From 61cbccb6aa8df8669736033b97a51f7f5395f30a Mon Sep 17 00:00:00 2001
From: Josef Schlehofer <josef.schlehofer@nic.cz>
Date: Tue, 29 Nov 2022 11:22:01 +0100
Subject: [PATCH] patches/openwrt: 5.15: generic patches: rebase

---
 .../0001-Backport-kernel-5.15-generic.patch   | 87651 ++++++++++++----
 1 file changed, 66690 insertions(+), 20961 deletions(-)

diff --git a/patches/openwrt/a-new-kernel-5.15/0001-Backport-kernel-5.15-generic.patch b/patches/openwrt/a-new-kernel-5.15/0001-Backport-kernel-5.15-generic.patch
index a0029975c..4fcefcf42 100644
--- a/patches/openwrt/a-new-kernel-5.15/0001-Backport-kernel-5.15-generic.patch
+++ b/patches/openwrt/a-new-kernel-5.15/0001-Backport-kernel-5.15-generic.patch
@@ -1,43 +1,85 @@
-From 20c1f9271d0ca07239c8676d0f098d424230535e Mon Sep 17 00:00:00 2001
+From b04c93ec08ec46f1cc70119f1eca1da578e141b1 Mon Sep 17 00:00:00 2001
 From: Josef Schlehofer <pepe.schlehofer@gmail.com>
-Date: Mon, 4 Jul 2022 14:19:08 +0200
+Date: Tue, 29 Nov 2022 11:11:39 +0100
 Subject: [PATCH] Generic backport patches 5.15
 
 ---
- .../011-kbuild-export-SUBARCH.patch           |   21 +
+ ...ild-use-Wdeclaration-after-statement.patch |   73 +
+ ...05-v5.17-02-Kbuild-move-to-std-gnu11.patch |   60 +
+ ...-use-std-gnu11-for-KBUILD_USERCFLAGS.patch |   43 +
+ ...-x86-arm64-add-arch_has_hw_pte_young.patch |  169 +
+ ...dd-CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG.patch |  111 +
+ ...-03-mm-vmscan.c-refactor-shrink_node.patch |  224 +
+ ...-mm-multigenerational-lru-groundwork.patch |  996 +++
+ ...multigenerational-lru-mm_struct-list.patch |  760 ++
+ ....1-06-mm-multigenerational-lru-aging.patch | 1176 +++
+ ...07-mm-multigenerational-lru-eviction.patch | 1002 +++
+ ...multigenerational-lru-user-interface.patch |  496 ++
+ ...-09-mm-multigenerational-lru-Kconfig.patch |   80 +
+ ...-multigenerational-lru-documentation.patch |  161 +
+ ...don-t-sync-disk-for-each-aging-cycle.patch |   32 +
  ...-uasm-Enable-muhu-opcode-for-MIPS-R6.patch |   65 +
  ...rkaround-for-Loongson-2F-nop-CPU-err.patch |   31 +
- ...ips-bpf-Add-eBPF-JIT-for-32-bit-MIPS.patch | 3078 +++++++++++++++++
- ...bpf-Add-new-eBPF-JIT-for-64-bit-MIPS.patch | 1005 ++++++
+ ...ips-bpf-Add-eBPF-JIT-for-32-bit-MIPS.patch | 3078 +++++++
+ ...bpf-Add-new-eBPF-JIT-for-64-bit-MIPS.patch | 1005 +++
  ...f-Add-JIT-workarounds-for-CPU-errata.patch |  120 +
  ...0-v5.16-05-mips-bpf-Enable-eBPF-JITs.patch |   61 +
- ...f-Remove-old-BPF-JIT-implementations.patch |  387 +++
+ ...f-Remove-old-BPF-JIT-implementations.patch |  387 +
  ...to-define-reg_update_bits-for-no-bus.patch |   52 +
  ...63xx-use-more-precise-Kconfig-symbol.patch |   37 +
  ...resolve_btfids-Build-with-host-flags.patch |   49 +
+ ...te-to-upstream-version-v1.6.1-19-g0a.patch |  997 +++
  ...or-setting-affinity-if-no-IRQ-parent.patch |   48 +
- ...ow_offload-handle-netdevice-events-f.patch |  106 +
+ ...-Support-reserved-memory-description.patch |  166 +
+ ...kernel-proc-add-CPU-option-reporting.patch |  162 +
+ ...mp_processor_id-in-preemptible-in-sh.patch |   62 +
+ ...spinand-Add-support-for-XTX-XT26G0xA.patch |  186 +
+ ...mvebu-a3700-comphy-Remove-port-from-.patch |  219 +
+ ...mvebu-a3700-comphy-Add-native-kernel.patch | 1552 ++++
+ ...l-armada-37xx-Add-xtal-clock-to-comp.patch |   32 +
+ ...mvebu-Make-SATA-PHY-optional-for-Arm.patch |   64 +
+ ...xhci-mvebu-make-USB-3.0-PHY-optional.patch |  166 +
+ ...ark-Fix-initialization-with-old-Marv.patch |   39 +
+ ...mvebu-a3700-comphy-Remove-broken-res.patch |  194 +
  ...platform_populate-for-MTD-partitions.patch |   72 +
- ...s-add-support-for-Sercomm-partitions.patch |  302 ++
+ ...s-add-support-for-Sercomm-partitions.patch |  302 +
  ...ce-of-support-for-dynamic-partitions.patch |  106 +
+ ...g-MTD-device-associated-with-a-speci.patch |   72 +
+ ...e-check-partition-before-dereference.patch |   30 +
+ ...sing-of_node_get-in-dynamic-partitio.patch |  101 +
+ ...y-a-bit-code-find-partition-matching.patch |   65 +
+ ...find-OF-node-for-every-MTD-partition.patch |   84 +
+ ...-Don-t-print-error-message-on-EPROBE.patch |   32 +
+ ...T_DEV-for-partitions-marked-as-rootf.patch |   47 +
  ...x-allow-to-use-on-MediaTek-MIPS-SoCs.patch |   33 +
  ...device-add-support-for-GD5FxGQ4xExxG.patch |   58 +
  ...device-add-support-for-GD5F1GQ5RExxG.patch |   33 +
  ...device-add-support-for-GD5F-2-4-GQ5x.patch |   84 +
  ...device-add-support-for-GD5FxGM7xExxG.patch |   91 +
- ...uce-tagger-owned-storage-for-private.patch |  279 ++
- ...ocols-connect-to-individual-switches.patch |  274 ++
- ..._eth_soc-add-support-for-coherent-DM.patch |  327 ++
+ ...TP-Link-SafeLoader-partitions-table-.patch |  229 +
+ ...v5.18-page_pool-Add-allocation-stats.patch |  165 +
+ ...01-v5.18-page_pool-Add-recycle-stats.patch |  140 +
+ ...d-function-to-batch-and-return-stats.patch |   77 +
+ ...cycle-stats-to-page_pool_put_page_bu.patch |   55 +
+ ...et-page_pool-introduce-ethtool-stats.patch |  147 +
+ ...ce-flags-field-in-xdp_buff-xdp_frame.patch |   99 +
+ ...gs-support-to-xdp_return_-buff-frame.patch |  137 +
+ ...ize-metadata-to-skb_shared_info-for-.patch |   31 +
+ ...-total-xdp_frame-len-running-ndo_xdp.patch |   65 +
+ ...-veth-Allow-jumbo-frames-in-xdp-mode.patch |   40 +
+ ...uce-tagger-owned-storage-for-private.patch |  279 +
+ ...ocols-connect-to-individual-switches.patch |  274 +
+ ..._eth_soc-add-support-for-coherent-DM.patch |  327 +
  ...ek-mt7622-add-support-for-coherent-D.patch |   30 +
- ..._eth_soc-add-support-for-Wireless-Et.patch | 1679 +++++++++
- ..._eth_soc-implement-flow-offloading-t.patch |  269 ++
+ ..._eth_soc-add-support-for-Wireless-Et.patch | 1679 ++++
+ ..._eth_soc-implement-flow-offloading-t.patch |  269 +
  ...ek-mt7622-introduce-nodes-for-Wirele.patch |   62 +
  ..._eth_soc-add-ipv6-flow-offload-suppo.patch |   79 +
  ..._eth_soc-support-TC_SETUP_BLOCK-for-.patch |   29 +
  ..._eth_soc-allocate-struct-mtk_ppe-sep.patch |  159 +
- ..._eth_soc-rework-hardware-flow-table-.patch |  424 +++
+ ..._eth_soc-rework-hardware-flow-table-.patch |  424 +
  ..._eth_soc-remove-bridge-flow-offload-.patch |   44 +
- ..._eth_soc-support-creating-mac-addres.patch |  553 +++
+ ..._eth_soc-support-creating-mac-addres.patch |  553 ++
  ..._eth_soc-wed-fix-sparse-endian-warni.patch |   56 +
  ..._eth_soc-fix-return-value-check-in-m.patch |   25 +
  ..._eth_soc-use-standard-property-for-c.patch |   35 +
@@ -45,7 +87,7 @@ Subject: [PATCH] Generic backport patches 5.15
  ..._eth_soc-add-check-for-allocation-fa.patch |   22 +
  ...silence-the-GCC-12-array-bounds-warn.patch |   26 +
  ..._eth_soc-rely-on-GFP_KERNEL-for-dma_.patch |   52 +
- ..._eth_soc-move-tx-dma-desc-configurat.patch |  206 ++
+ ..._eth_soc-move-tx-dma-desc-configurat.patch |  206 +
  ..._eth_soc-add-txd_size-to-mtk_soc_dat.patch |  167 +
  ..._eth_soc-rely-on-txd_size-in-mtk_tx_.patch |   78 +
  ..._eth_soc-rely-on-txd_size-in-mtk_des.patch |  109 +
@@ -53,21 +95,105 @@ Subject: [PATCH] Generic backport patches 5.15
  ..._eth_soc-add-rxd_size-to-mtk_soc_dat.patch |  102 +
  ..._eth_soc-rely-on-txd_size-field-in-m.patch |   46 +
  ..._eth_soc-rely-on-rxd_size-field-in-m.patch |   68 +
- ..._eth_soc-introduce-device-register-m.patch |  814 +++++
- ..._eth_soc-introduce-MTK_NETSYS_V2-sup.patch |  917 +++++
+ ..._eth_soc-introduce-device-register-m.patch |  814 ++
+ ..._eth_soc-introduce-MTK_NETSYS_V2-sup.patch |  917 ++
  ..._eth_soc-convert-ring-dma-pointer-to.patch |  135 +
  ..._eth_soc-convert-scratch_ring-pointe.patch |   33 +
  ..._eth_soc-introduce-support-for-mt798.patch |  138 +
  ..._eth_soc-fix-error-code-in-mtk_flow_.patch |   25 +
  ..._eth_soc-enable-rx-cksum-offload-for.patch |   47 +
  ...19-34-eth-mtk_ppe-fix-up-after-merge.patch |   28 +
+ ...-users-of-bitmap_foo-to-linkmode_foo.patch |  948 +++
+ ...ylink-add-MAC-phy_interface_t-bitmap.patch |   24 +
+ ...supported_interfaces-for-phylink-val.patch |   98 +
+ ...populate-supported_interfaces-member.patch |   63 +
+ ...net-dsa-consolidate-phylink-creation.patch |  149 +
+ ...phylink_get_interfaces-with-phylink_.patch |   51 +
+ ...d-support-for-phylink-mac_select_pcs.patch |   59 +
+ ...y-add-phy_interface_t-bitmap-support.patch |   61 +
+ ...mac_select_pcs-method-to-phylink_mac.patch |  197 +
+ ...-add-generic-validate-implementation.patch |  341 +
+ ...e-helpers-for-iterating-through-port.patch |   68 +
+ ...-net-phylink-add-pcs_validate-method.patch |  106 +
+ ...k-add-legacy_pre_march2020-indicator.patch |   43 +
+ ...-DSA-phylink-as-legacy_pre_march2020.patch |   36 +
+ ...net-phylink-use-legacy_pre_march2020.patch |  115 +
+ ...-fix-DSA-mac_select_pcs-introduction.patch |   88 +
+ ...populate-supported_interfaces-member.patch |   48 +
+ ...e-interface-checks-in-mvneta_validat.patch |   35 +
+ ...op-use-of-phylink_helper_basex_speed.patch |   55 +
+ ...-mvneta-use-phylink_generic_validate.patch |   72 +
+ ...ark-as-a-legacy_pre_march2020-driver.patch |   29 +
+ ...populate-supported_interfaces-member.patch |   43 +
+ ...remove-interface-checks-in-mtk_valid.patch |   75 +
+ ...drop-use-of-phylink_helper_basex_spe.patch |   42 +
+ ...eth_soc-use-phylink_generic_validate.patch |   84 +
+ ...mark-as-a-legacy_pre_march2020-drive.patch |   29 +
+ ...remove-a-copy-of-the-NAPI_POLL_WEIGH.patch |   40 +
+ ...9-mtk_eth_soc-remove-unused-mac-mode.patch |   35 +
+ ...tk_eth_soc-remove-unused-sgmii-flags.patch |   40 +
+ ...add-mask-and-update-PCS-speed-defini.patch |   40 +
+ ...eth_soc-correct-802.3z-speed-setting.patch |   60 +
+ ...th_soc-correct-802.3z-duplex-setting.patch |  101 +
+ ...stop-passing-phylink-state-to-sgmii-.patch |   60 +
+ ...mtk_eth_soc-provide-mtk_sgmii_config.patch |   89 +
+ ...add-fixme-comment-for-state-speed-us.patch |   38 +
+ ...c-move-MAC_MCR-setting-to-mac_finish.patch |   79 +
+ ...move-restoration-of-SYSCFG0-to-mac_f.patch |   57 +
+ ...convert-code-structure-to-suit-split.patch |  254 +
+ ...soc-partially-convert-to-phylink_pcs.patch |  262 +
+ ...terate-using-dsa_switch_for_each_use.patch |  106 +
+ ...opulate-supported_interfaces-and-mac.patch |  166 +
+ ...t-dsa-mt7530-remove-interface-checks.patch |  172 +
+ ...rop-use-of-phylink_helper_basex_spee.patch |   34 +
+ ...nly-indicate-linkmodes-that-can-be-s.patch |   86 +
+ ...-switch-to-use-phylink_get_linkmodes.patch |  131 +
+ ...530-partially-convert-to-phylink_pcs.patch |  385 +
+ ...ove-autoneg-handling-to-PCS-validati.patch |   80 +
+ ...19-net-dsa-mt7530-mark-as-non-legacy.patch |   34 +
+ ...mt753x-fix-pcs-conversion-regression.patch |  116 +
+ ...t7530-rework-mt7530_hw_vlan_-add-del.patch |   87 +
+ ...et-dsa-mt7530-rework-mt753-01-_setup.patch |   75 +
+ ...et-cpu-port-via-dp-cpu_dp-instead-of.patch |  117 +
+ ..._eth_soc-rely-on-page_pool-for-singl.patch |  330 +
+ ...et-mtk_eth_soc-add-basic-XDP-support.patch |  291 +
+ ..._eth_soc-introduce-xdp-ethtool-count.patch |  110 +
+ ...net-mtk_eth_soc-add-xmit-XDP-support.patch |  340 +
+ ..._eth_soc-add-support-for-page_pool_g.patch |   95 +
+ ..._eth_soc-introduce-mtk_xdp_frame_map.patch |  113 +
+ ..._eth_soc-introduce-xdp-multi-frag-su.patch |  218 +
+ ..._eth_soc-fix-hw-hash-reporting-for-M.patch |   74 +
+ ..._eth_soc-fix-off-by-one-check-of-ARR.patch |   31 +
+ ..._ppe-fix-possible-NULL-pointer-deref.patch |   27 +
+ ...-ppe-fix-traffic-offload-with-bridge.patch |   64 +
+ ..._eth_soc-remove-mtk_foe_entry_timest.patch |   33 +
+ ..._eth_soc-enable-XDP-support-just-for.patch |   35 +
+ ..._eth_soc-move-gdma_to_ppe-and-ppe_ba.patch |  127 +
+ ..._eth_soc-move-ppe-table-hash-offset-.patch |  199 +
+ ..._eth_soc-add-the-capability-to-run-m.patch |  317 +
+ ..._eth_soc-move-wdma_base-definitions-.patch |   80 +
+ ..._eth_soc-add-foe_entry_size-to-mtk_e.patch |  251 +
+ ..._eth_soc-fix-typo-in-__mtk_foe_entry.patch |   27 +
+ ..._eth_soc-check-max-allowed-value-in-.patch |   28 +
+ ..._eth_wed-add-mtk_wed_configure_irq-a.patch |  189 +
+ ..._eth_wed-add-wed-support-for-mt7986-.patch |  942 +++
+ ...rnet-mtk_eth_wed-add-axi-bus-support.patch |  237 +
+ ..._eth_soc-introduce-flow-offloading-s.patch |  882 ++
+ ..._eth_soc-enable-flow-offloading-supp.patch |   26 +
+ ..._eth_soc-fix-wrong-use-of-new-helper.patch |   46 +
+ ..._eth_soc-fix-usage-of-foe_entry_size.patch |   49 +
+ ..._eth_soc-fix-mask-of-RX_DMA_GET_SPOR.patch |   32 +
+ ..._eth_soc-fix-state-in-__mtk_foe_entr.patch |   37 +
+ ..._eth_soc-fix-possible-memory-leak-in.patch |   73 +
+ ..._eth_wed-add-missing-put_device-in-m.patch |   47 +
+ ...-mtk_eth_wed-add-missing-of_node_put.patch |   43 +
  ...-0001-net-bgmac-improve-handling-PHY.patch |   84 +
  ...t-bgmac-support-MDIO-described-in-DT.patch |   54 +
  ...dd-support-for-qca-8327-internal-phy.patch |   48 +
  ...3-Include-all-ports-in-enabled_ports.patch |  131 +
  ...-BCM5301x-workaround-for-a-wrong-CPU.patch |   42 +
  ...prove-flow-control-setup-on-BCM5301x.patch |   32 +
- ...t-dsa-b53-Drop-unused-cpu_port-field.patch |  205 ++
+ ...t-dsa-b53-Drop-unused-cpu_port-field.patch |  205 +
  ...x-add-support-for-qca-8327-A-variant.patch |   65 +
  ...dd-resume-suspend-function-to-qca83x.patch |   45 +
  ...ix-spacing-and-improve-name-for-83xx.patch |   95 +
@@ -80,7 +206,7 @@ Subject: [PATCH] Generic backport patches 5.15
  ...k-add-support-for-sgmii-falling-edge.patch |  127 +
  ...dsa-qca8k-Document-support-for-CPU-p.patch |   29 +
  ...dsa-qca8k-add-support-for-cpu-port-6.patch |  153 +
- ...work-rgmii-delay-logic-and-scan-for-.patch |  295 ++
+ ...work-rgmii-delay-logic-and-scan-for-.patch |  295 +
  ...dsa-qca8k-Document-qca-sgmii-enable-.patch |   33 +
  ...-qca8k-add-explicit-SGMII-PLL-enable.patch |   65 +
  ...dsa-qca8k-Document-qca-led-open-drai.patch |   37 +
@@ -90,23 +216,23 @@ Subject: [PATCH] Generic backport patches 5.15
  ...8k-set-internal-delay-also-for-sgmii.patch |  159 +
  ...move-port-config-to-dedicated-struct.patch |  124 +
  ...ipq8064-mdio-fix-warning-with-new-qc.patch |   26 +
- ...net-dsa-qca8k-convert-to-YAML-schema.patch |  631 ++++
+ ...net-dsa-qca8k-convert-to-YAML-schema.patch |  631 ++
  ...elay-applied-to-wrong-cpu-in-parse-p.patch |   28 +
  ...for-loop-in-setup-and-add-cpu-port-c.patch |  151 +
  ...sure-pad0-mac06-exchange-is-disabled.patch |   47 +
  ...ernal-delay-applied-to-the-wrong-PAD.patch |   48 +
  ...16-net-dsa-qca8k-fix-MTU-calculation.patch |   46 +
  ...redundant-check-in-parse_port_config.patch |   29 +
- ...vert-to-GENMASK_FIELD_PREP_FIELD_GET.patch |  507 +++
+ ...vert-to-GENMASK_FIELD_PREP_FIELD_GET.patch |  507 ++
  ...move-extra-mutex_init-in-qca8k_setup.patch |   25 +
  ...move-regmap-init-in-probe-and-set-it.patch |   46 +
- ...k-initial-conversion-to-regmap-heper.patch |  249 ++
+ ...k-initial-conversion-to-regmap-heper.patch |  249 +
  ...ca8k-add-additional-MIB-counter-and-.patch |  120 +
  ...ca8k-add-support-for-port-fast-aging.patch |   53 +
  ...sa-qca8k-add-set_ageing_time-support.patch |   78 +
  ...sa-qca8k-add-support-for-mdb_add-del.patch |  142 +
  ...sa-qca8k-add-support-for-mirror-mode.patch |  155 +
- ...t-next-net-dsa-qca8k-add-LAG-support.patch |  288 ++
+ ...t-next-net-dsa-qca8k-add-LAG-support.patch |  288 +
  ...dsa-qca8k-fix-warning-in-LAG-feature.patch |   40 +
  ...PHY-initialization-with-MTU-setup-in.patch |   52 +
  ...nl_lock-sections-in-dsa_slave_create.patch |   34 +
@@ -114,7 +240,7 @@ Subject: [PATCH] Generic backport patches 5.15
  ...l_mutex-when-calling-dsa_master_-set.patch |   78 +
  ...t-up-shared-ports-then-non-shared-po.patch |  118 +
  ...xt-net-dsa-setup-master-before-ports.patch |  115 +
- ...switch-operations-for-tracking-the-m.patch |  254 ++
+ ...switch-operations-for-tracking-the-m.patch |  254 +
  ...aster-state-events-in-dsa_tree_-setu.patch |   89 +
  ...t-dsa-tag_qca-convert-to-FIELD-macro.patch |   86 +
  ...qca-move-define-to-include-linux-dsa.patch |   71 +
@@ -123,35 +249,101 @@ Subject: [PATCH] Generic backport patches 5.15
  ...a-add-define-for-handling-MIB-packet.patch |   45 +
  ...add-support-for-handling-mgmt-and-MI.patch |  116 +
  ...8k-add-tracking-state-of-master-port.patch |   67 +
- ...d-support-for-mgmt-read-write-in-Eth.patch |  363 ++
- ...d-support-for-mib-autocast-in-Ethern.patch |  226 ++
- ...d-support-for-phy-read-write-with-mg.patch |  287 ++
- ...qca8k-move-page-cache-to-driver-priv.patch |  208 ++
+ ...d-support-for-mgmt-read-write-in-Eth.patch |  363 +
+ ...d-support-for-mib-autocast-in-Ethern.patch |  226 +
+ ...d-support-for-phy-read-write-with-mg.patch |  287 +
+ ...qca8k-move-page-cache-to-driver-priv.patch |  208 +
  ...qca8k-cache-lo-and-hi-for-mdio-write.patch |  164 +
- ...d-support-for-larger-read-write-size.patch |  206 ++
+ ...d-support-for-larger-read-write-size.patch |  206 +
  ...troduce-qca8k_bulk_read-write-functi.patch |  104 +
+ ...eck-correct-variable-in-qca8k_phy_et.patch |   28 +
+ ...dsa-qca8k-fix-noderef.cocci-warnings.patch |   34 +
+ ...8k-drop-MTU-tracking-from-qca8k_priv.patch |   79 +
+ ...-qca8k-drop-port_sts-from-qca8k_priv.patch |  116 +
+ ...8k-rework-and-simplify-mdiobus-logic.patch |  173 +
+ ...-drop-dsa_switch_ops-from-qca8k_priv.patch |   39 +
+ ...a8k-correctly-handle-mdio-read-error.patch |   33 +
+ ...ify-bus-id-naming-with-legacy-and-OF.patch |   44 +
+ ...net-dsa-qca8k-move-driver-to-qca-dir.patch | 7389 +++++++++++++++++
+ ...-cache-match-data-to-speed-up-access.patch |  157 +
+ ...k-make-mib-autocast-feature-optional.patch |   77 +
+ ...qca8k-move-mib-struct-to-common-code.patch | 6532 +++++++++++++++
+ ...ve-qca8k-read-write-rmw-and-reg-tabl.patch |  135 +
+ ...ve-qca8k-bulk-read-write-helper-to-c.patch |  145 +
+ ...ove-mib-init-function-to-common-code.patch |  137 +
+ ...ve-port-set-status-eee-ethtool-stats.patch |  281 +
+ ...move-bridge-functions-to-common-code.patch |  237 +
+ ...ve-set-age-MTU-port-enable-disable-f.patch |  227 +
+ ...ve-port-FDB-MDB-function-to-common-c.patch |  704 ++
+ ...ve-port-mirror-functions-to-common-c.patch |  232 +
+ ...ve-port-VLAN-functions-to-common-cod.patch |  448 +
+ ...ve-port-LAG-functions-to-common-code.patch |  384 +
+ ...ve-read_switch_id-function-to-common.patch |  102 +
+ ...x-NULL-pointer-dereference-for-of_de.patch |   29 +
  ...N-filtering-syncing-out-of-dsa_switc.patch |   77 +
  ...cross-chip-syncing-of-VLAN-filtering.patch |   52 +
  ...-rtl8366rb-Support-bridge-offloading.patch |  141 +
  ...-dsa-rtl8366-Drop-custom-VLAN-set-up.patch |  118 +
- ...b-Rewrite-weird-VLAN-filering-enable.patch |  270 ++
+ ...b-Rewrite-weird-VLAN-filering-enable.patch |  270 +
  ...-Drop-and-depromote-pointless-prints.patch |   51 +
  ...tl8366rb-Use-core-filtering-tracking.patch |   61 +
  ...rtl8366rb-Support-disabling-learning.patch |  115 +
  ...net-dsa-rtl8366rb-Support-fast-aging.patch |   57 +
  ...-rtl8366rb-Support-setting-STP-state.patch |  107 +
+ ...icro-stmmac-move-queue-reset-to-dedi.patch |  142 +
+ ...icro-stmmac-first-disable-all-queues.patch |   37 +
+ ...icro-stmmac-move-dma-conf-to-dedicat.patch | 1289 +++
+ ...icro-stmmac-generate-stmmac-dma-conf.patch | 1161 +++
+ ...icro-stmmac-permit-MTU-change-with-i.patch |   73 +
+ ...x-inband-mgmt-for-big-endian-systems.patch |  156 +
+ ...x-ethtool-autocast-mib-for-big-endia.patch |   81 +
+ ...eric-Introduce-Sierra-EM919X-support.patch |   80 +
+ ...ost-always-print-detected-modem-name.patch |   37 +
+ ...-add-support-for-in-band-link-status.patch |  130 +
+ ...e-implement-soft-state-polling-setup.patch |   98 +
+ ...t-sfp-move-quirk-handling-into-sfp.c.patch |  291 +
+ ...move-Alcatel-Lucent-3FE46541AA-fixup.patch |   69 +
+ ....1-net-sfp-move-Huawei-MA5671A-fixup.patch |   48 +
+ ...t-sfp-add-support-for-HALNy-GPON-SFP.patch |   79 +
+ ...net-usb-ax88179_178a-add-TSO-feature.patch |   68 +
  ...s-add-Broadcom-s-BCM63138-controller.patch |  125 +
- ...-add-support-for-BCM63138-controller.patch |  356 ++
+ ...-add-support-for-BCM63138-controller.patch |  356 +
  ...-leds-bcm63138-unify-full-stops-in-d.patch |   30 +
  ...help-info-about-BCM63138-module-name.patch |   28 +
  ...eds-leds-bcm63138-get-rid-of-LED_OFF.patch |   30 +
+ ...-handling-U-Boot-environment-variabl.patch |  278 +
+ ...-find-Device-Tree-nodes-for-NVMEM-ce.patch |   29 +
+ ...em-u-boot-env-fix-crc32-casting-type.patch |   30 +
+ ...support-passing-DT-node-in-cell-info.patch |   41 +
+ ...-fix-crc32_data_offset-on-redundant-.patch |   56 +
+ ...env-align-endianness-of-crc32-values.patch |   47 +
+ ...boot-env-add-Broadcom-format-support.patch |   65 +
+ ..._wwan-add-ZTE-MF286D-modem-19d2-1485.patch |   59 +
+ ...Support-public-address-configuration.patch |   51 +
+ ...Fix-application-of-sizeof-to-pointer.patch |   29 +
+ ...Add-a-new-PID-VID-13d3-3567-for-MT79.patch |   70 +
+ ...Add-a-new-PID-VID-0489-e0c8-for-MT79.patch |   68 +
+ ...Add-a-new-VID-PID-0e8d-0608-for-MT79.patch |   66 +
  ...PS-ath79-drop-_machine_restart-again.patch |   49 +
- ...ext-hwmon-lm70-Add-ti-tmp125-support.patch |   71 +
+ ....18-hwmon-lm70-Add-ti-tmp125-support.patch |   71 +
  ...ether-export-usbnet_cdc_zte_rx_fixup.patch |   58 +
  ...e-the-bogus-MAC-fixup-for-ZTE-device.patch |  118 +
  ...-scope-of-bogus-MAC-address-detectio.patch |   63 +
- 146 files changed, 23356 insertions(+)
- create mode 100644 target/linux/generic/backport-5.15/011-kbuild-export-SUBARCH.patch
+ 325 files changed, 67653 insertions(+)
+ create mode 100644 target/linux/generic/backport-5.15/005-v5.17-01-Kbuild-use-Wdeclaration-after-statement.patch
+ create mode 100644 target/linux/generic/backport-5.15/005-v5.17-02-Kbuild-move-to-std-gnu11.patch
+ create mode 100644 target/linux/generic/backport-5.15/005-v5.17-03-Kbuild-use-std-gnu11-for-KBUILD_USERCFLAGS.patch
+ create mode 100644 target/linux/generic/backport-5.15/020-v6.1-01-mm-x86-arm64-add-arch_has_hw_pte_young.patch
+ create mode 100644 target/linux/generic/backport-5.15/020-v6.1-02-mm-x86-add-CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG.patch
+ create mode 100644 target/linux/generic/backport-5.15/020-v6.1-03-mm-vmscan.c-refactor-shrink_node.patch
+ create mode 100644 target/linux/generic/backport-5.15/020-v6.1-04-mm-multigenerational-lru-groundwork.patch
+ create mode 100644 target/linux/generic/backport-5.15/020-v6.1-05-mm-multigenerational-lru-mm_struct-list.patch
+ create mode 100644 target/linux/generic/backport-5.15/020-v6.1-06-mm-multigenerational-lru-aging.patch
+ create mode 100644 target/linux/generic/backport-5.15/020-v6.1-07-mm-multigenerational-lru-eviction.patch
+ create mode 100644 target/linux/generic/backport-5.15/020-v6.1-08-mm-multigenerational-lru-user-interface.patch
+ create mode 100644 target/linux/generic/backport-5.15/020-v6.1-09-mm-multigenerational-lru-Kconfig.patch
+ create mode 100644 target/linux/generic/backport-5.15/020-v6.1-10-mm-multigenerational-lru-documentation.patch
+ create mode 100644 target/linux/generic/backport-5.15/021-v6.1-mm-mglru-don-t-sync-disk-for-each-aging-cycle.patch
  create mode 100644 target/linux/generic/backport-5.15/050-v5.16-00-MIPS-uasm-Enable-muhu-opcode-for-MIPS-R6.patch
  create mode 100644 target/linux/generic/backport-5.15/050-v5.16-01-mips-uasm-Add-workaround-for-Loongson-2F-nop-CPU-err.patch
  create mode 100644 target/linux/generic/backport-5.15/050-v5.16-02-mips-bpf-Add-eBPF-JIT-for-32-bit-MIPS.patch
@@ -159,21 +351,50 @@ Subject: [PATCH] Generic backport patches 5.15
  create mode 100644 target/linux/generic/backport-5.15/050-v5.16-04-mips-bpf-Add-JIT-workarounds-for-CPU-errata.patch
  create mode 100644 target/linux/generic/backport-5.15/050-v5.16-05-mips-bpf-Enable-eBPF-JITs.patch
  create mode 100644 target/linux/generic/backport-5.15/050-v5.16-06-mips-bpf-Remove-old-BPF-JIT-implementations.patch
- create mode 100644 target/linux/generic/backport-5.15/081-net-next-regmap-allow-to-define-reg_update_bits-for-no-bus.patch
+ create mode 100644 target/linux/generic/backport-5.15/081-v5.17-regmap-allow-to-define-reg_update_bits-for-no-bus.patch
  create mode 100644 target/linux/generic/backport-5.15/100-v5.18-tty-serial-bcm63xx-use-more-precise-Kconfig-symbol.patch
  create mode 100644 target/linux/generic/backport-5.15/200-v5.18-tools-resolve_btfids-Build-with-host-flags.patch
+ create mode 100644 target/linux/generic/backport-5.15/201-v5.16-scripts-dtc-Update-to-upstream-version-v1.6.1-19-g0a.patch
  create mode 100644 target/linux/generic/backport-5.15/300-v5.18-pinctrl-qcom-Return--EINVAL-for-setting-affinity-if-no-IRQ-parent.patch
- create mode 100644 target/linux/generic/backport-5.15/343-netfilter-nft_flow_offload-handle-netdevice-events-f.patch
+ create mode 100644 target/linux/generic/backport-5.15/301-v5.16-soc-qcom-smem-Support-reserved-memory-description.patch
+ create mode 100644 target/linux/generic/backport-5.15/330-v5.16-01-MIPS-kernel-proc-add-CPU-option-reporting.patch
+ create mode 100644 target/linux/generic/backport-5.15/330-v5.16-02-MIPS-Fix-using-smp_processor_id-in-preemptible-in-sh.patch
+ create mode 100644 target/linux/generic/backport-5.15/331-v5.19-mtd-spinand-Add-support-for-XTX-XT26G0xA.patch
+ create mode 100644 target/linux/generic/backport-5.15/344-v5.18-01-phy-marvell-phy-mvebu-a3700-comphy-Remove-port-from-.patch
+ create mode 100644 target/linux/generic/backport-5.15/344-v5.18-02-phy-marvell-phy-mvebu-a3700-comphy-Add-native-kernel.patch
+ create mode 100644 target/linux/generic/backport-5.15/345-v5.17-arm64-dts-marvell-armada-37xx-Add-xtal-clock-to-comp.patch
+ create mode 100644 target/linux/generic/backport-5.15/346-v5.18-01-Revert-ata-ahci-mvebu-Make-SATA-PHY-optional-for-Arm.patch
+ create mode 100644 target/linux/generic/backport-5.15/346-v5.18-02-Revert-usb-host-xhci-mvebu-make-USB-3.0-PHY-optional.patch
+ create mode 100644 target/linux/generic/backport-5.15/346-v5.18-03-Revert-PCI-aardvark-Fix-initialization-with-old-Marv.patch
+ create mode 100644 target/linux/generic/backport-5.15/347-v6.0-phy-marvell-phy-mvebu-a3700-comphy-Remove-broken-res.patch
  create mode 100644 target/linux/generic/backport-5.15/400-v5.19-mtd-call-of_platform_populate-for-MTD-partitions.patch
- create mode 100644 target/linux/generic/backport-5.15/401-v5.20-mtd-parsers-add-support-for-Sercomm-partitions.patch
- create mode 100644 target/linux/generic/backport-5.15/402-v5.20-mtd-next-mtd-core-introduce-of-support-for-dynamic-partitions.patch
- create mode 100644 target/linux/generic/backport-5.15/410-mtd-next-mtd-parsers-trx-allow-to-use-on-MediaTek-MIPS-SoCs.patch
+ create mode 100644 target/linux/generic/backport-5.15/401-v6.0-mtd-parsers-add-support-for-Sercomm-partitions.patch
+ create mode 100644 target/linux/generic/backport-5.15/402-v6.0-mtd-next-mtd-core-introduce-of-support-for-dynamic-partitions.patch
+ create mode 100644 target/linux/generic/backport-5.15/403-v6.1-mtd-allow-getting-MTD-device-associated-with-a-speci.patch
+ create mode 100644 target/linux/generic/backport-5.15/404-v6.0-mtd-core-check-partition-before-dereference.patch
+ create mode 100644 target/linux/generic/backport-5.15/405-v6.1-mtd-core-add-missing-of_node_get-in-dynamic-partitio.patch
+ create mode 100644 target/linux/generic/backport-5.15/406-v6.2-0001-mtd-core-simplify-a-bit-code-find-partition-matching.patch
+ create mode 100644 target/linux/generic/backport-5.15/406-v6.2-0002-mtd-core-try-to-find-OF-node-for-every-MTD-partition.patch
+ create mode 100644 target/linux/generic/backport-5.15/407-v5.17-mtd-parsers-qcom-Don-t-print-error-message-on-EPROBE.patch
+ create mode 100644 target/linux/generic/backport-5.15/408-v6.2-mtd-core-set-ROOT_DEV-for-partitions-marked-as-rootf.patch
+ create mode 100644 target/linux/generic/backport-5.15/410-v5.18-mtd-parsers-trx-allow-to-use-on-MediaTek-MIPS-SoCs.patch
  create mode 100644 target/linux/generic/backport-5.15/420-v5.19-02-mtd-spinand-gigadevice-add-support-for-GD5FxGQ4xExxG.patch
  create mode 100644 target/linux/generic/backport-5.15/420-v5.19-03-mtd-spinand-gigadevice-add-support-for-GD5F1GQ5RExxG.patch
  create mode 100644 target/linux/generic/backport-5.15/420-v5.19-04-mtd-spinand-gigadevice-add-support-for-GD5F-2-4-GQ5x.patch
  create mode 100644 target/linux/generic/backport-5.15/420-v5.19-05-mtd-spinand-gigadevice-add-support-for-GD5FxGM7xExxG.patch
- create mode 100644 target/linux/generic/backport-5.15/700-net-next-net-dsa-introduce-tagger-owned-storage-for-private.patch
- create mode 100644 target/linux/generic/backport-5.15/701-net-dsa-make-tagging-protocols-connect-to-individual-switches.patch
+ create mode 100644 target/linux/generic/backport-5.15/421-v6.2-mtd-parsers-add-TP-Link-SafeLoader-partitions-table-.patch
+ create mode 100644 target/linux/generic/backport-5.15/600-v5.18-page_pool-Add-allocation-stats.patch
+ create mode 100644 target/linux/generic/backport-5.15/601-v5.18-page_pool-Add-recycle-stats.patch
+ create mode 100644 target/linux/generic/backport-5.15/602-v5.18-page_pool-Add-function-to-batch-and-return-stats.patch
+ create mode 100644 target/linux/generic/backport-5.15/603-v5.19-page_pool-Add-recycle-stats-to-page_pool_put_page_bu.patch
+ create mode 100644 target/linux/generic/backport-5.15/604-v5.19-net-page_pool-introduce-ethtool-stats.patch
+ create mode 100644 target/linux/generic/backport-5.15/605-v5.18-xdp-introduce-flags-field-in-xdp_buff-xdp_frame.patch
+ create mode 100644 target/linux/generic/backport-5.15/606-v5.18-xdp-add-frags-support-to-xdp_return_-buff-frame.patch
+ create mode 100644 target/linux/generic/backport-5.15/607-v5.18-net-skbuff-add-size-metadata-to-skb_shared_info-for-.patch
+ create mode 100644 target/linux/generic/backport-5.15/608-v5.18-net-veth-Account-total-xdp_frame-len-running-ndo_xdp.patch
+ create mode 100644 target/linux/generic/backport-5.15/609-v5.18-veth-Allow-jumbo-frames-in-xdp-mode.patch
+ create mode 100644 target/linux/generic/backport-5.15/700-v5.17-net-dsa-introduce-tagger-owned-storage-for-private.patch
+ create mode 100644 target/linux/generic/backport-5.15/701-v5.17-dsa-make-tagging-protocols-connect-to-individual-switches.patch
  create mode 100644 target/linux/generic/backport-5.15/702-v5.19-00-net-ethernet-mtk_eth_soc-add-support-for-coherent-DM.patch
  create mode 100644 target/linux/generic/backport-5.15/702-v5.19-01-arm64-dts-mediatek-mt7622-add-support-for-coherent-D.patch
  create mode 100644 target/linux/generic/backport-5.15/702-v5.19-02-net-ethernet-mtk_eth_soc-add-support-for-Wireless-Et.patch
@@ -208,6 +429,90 @@ Subject: [PATCH] Generic backport patches 5.15
  create mode 100644 target/linux/generic/backport-5.15/702-v5.19-31-net-ethernet-mtk_eth_soc-fix-error-code-in-mtk_flow_.patch
  create mode 100644 target/linux/generic/backport-5.15/702-v5.19-33-net-ethernet-mtk_eth_soc-enable-rx-cksum-offload-for.patch
  create mode 100644 target/linux/generic/backport-5.15/702-v5.19-34-eth-mtk_ppe-fix-up-after-merge.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-00-v5.16-net-convert-users-of-bitmap_foo-to-linkmode_foo.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-01-v5.16-net-phylink-add-MAC-phy_interface_t-bitmap.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-02-v5.16-net-phylink-use-supported_interfaces-for-phylink-val.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-03-v5.16-net-dsa-populate-supported_interfaces-member.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-04-v5.17-net-dsa-consolidate-phylink-creation.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-05-v5.17-net-dsa-replace-phylink_get_interfaces-with-phylink_.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-06-v5.18-net-dsa-add-support-for-phylink-mac_select_pcs.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-07-v5.16-net-phy-add-phy_interface_t-bitmap-support.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-08-v5.17-net-phylink-add-mac_select_pcs-method-to-phylink_mac.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-09-v5.17-net-phylink-add-generic-validate-implementation.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-10-v5.16-net-dsa-introduce-helpers-for-iterating-through-port.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-11-v5.17-net-phylink-add-pcs_validate-method.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-12-v5.17-net-phylink-add-legacy_pre_march2020-indicator.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-13-v5.17-net-dsa-mark-DSA-phylink-as-legacy_pre_march2020.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-14-v5.17-net-phylink-use-legacy_pre_march2020.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-15-v5.18-net-phy-phylink-fix-DSA-mac_select_pcs-introduction.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-16-v5.16-net-mvneta-populate-supported_interfaces-member.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-17-v5.16-net-mvneta-remove-interface-checks-in-mvneta_validat.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-18-v5.16-net-mvneta-drop-use-of-phylink_helper_basex_speed.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-19-v5.17-net-mvneta-use-phylink_generic_validate.patch
+ create mode 100644 target/linux/generic/backport-5.15/703-20-v5.17-net-mvneta-mark-as-a-legacy_pre_march2020-driver.patch
+ create mode 100644 target/linux/generic/backport-5.15/704-01-v5.17-net-mtk_eth_soc-populate-supported_interfaces-member.patch
+ create mode 100644 target/linux/generic/backport-5.15/704-02-v5.17-net-mtk_eth_soc-remove-interface-checks-in-mtk_valid.patch
+ create mode 100644 target/linux/generic/backport-5.15/704-03-v5.17-net-mtk_eth_soc-drop-use-of-phylink_helper_basex_spe.patch
+ create mode 100644 target/linux/generic/backport-5.15/704-04-v5.17-net-mtk_eth_soc-use-phylink_generic_validate.patch
+ create mode 100644 target/linux/generic/backport-5.15/704-05-v5.17-net-mtk_eth_soc-mark-as-a-legacy_pre_march2020-drive.patch
+ create mode 100644 target/linux/generic/backport-5.15/704-06-v5.19-eth-mtk_eth_soc-remove-a-copy-of-the-NAPI_POLL_WEIGH.patch
+ create mode 100644 target/linux/generic/backport-5.15/704-07-v5.19-mtk_eth_soc-remove-unused-mac-mode.patch
+ create mode 100644 target/linux/generic/backport-5.15/704-08-v5.19-net-mtk_eth_soc-remove-unused-sgmii-flags.patch
+ create mode 100644 target/linux/generic/backport-5.15/704-09-v5.19-net-mtk_eth_soc-add-mask-and-update-PCS-speed-defini.patch
+ create mode 100644 target/linux/generic/backport-5.15/704-10-v5.19-net-mtk_eth_soc-correct-802.3z-speed-setting.patch
+ create mode 100644 target/linux/generic/backport-5.15/704-11-v5.19-net-mtk_eth_soc-correct-802.3z-duplex-setting.patch
+ create mode 100644 target/linux/generic/backport-5.15/704-12-v5.19-net-mtk_eth_soc-stop-passing-phylink-state-to-sgmii-.patch
+ create mode 100644 target/linux/generic/backport-5.15/704-13-v5.19-net-mtk_eth_soc-provide-mtk_sgmii_config.patch
+ create mode 100644 target/linux/generic/backport-5.15/704-14-v5.19-net-mtk_eth_soc-add-fixme-comment-for-state-speed-us.patch
+ create mode 100644 target/linux/generic/backport-5.15/704-15-v5.19-net-mtk_eth_soc-move-MAC_MCR-setting-to-mac_finish.patch
+ create mode 100644 target/linux/generic/backport-5.15/704-16-v5.19-net-mtk_eth_soc-move-restoration-of-SYSCFG0-to-mac_f.patch
+ create mode 100644 target/linux/generic/backport-5.15/704-17-v5.19-net-mtk_eth_soc-convert-code-structure-to-suit-split.patch
+ create mode 100644 target/linux/generic/backport-5.15/704-18-v5.19-net-mtk_eth_soc-partially-convert-to-phylink_pcs.patch
+ create mode 100644 target/linux/generic/backport-5.15/705-01-v5.17-net-dsa-mt7530-iterate-using-dsa_switch_for_each_use.patch
+ create mode 100644 target/linux/generic/backport-5.15/705-02-v5.19-net-dsa-mt7530-populate-supported_interfaces-and-mac.patch
+ create mode 100644 target/linux/generic/backport-5.15/705-03-v5.19-net-dsa-mt7530-remove-interface-checks.patch
+ create mode 100644 target/linux/generic/backport-5.15/705-04-v5.19-net-dsa-mt7530-drop-use-of-phylink_helper_basex_spee.patch
+ create mode 100644 target/linux/generic/backport-5.15/705-05-v5.19-net-dsa-mt7530-only-indicate-linkmodes-that-can-be-s.patch
+ create mode 100644 target/linux/generic/backport-5.15/705-06-v5.19-net-dsa-mt7530-switch-to-use-phylink_get_linkmodes.patch
+ create mode 100644 target/linux/generic/backport-5.15/705-07-v5.19-net-dsa-mt7530-partially-convert-to-phylink_pcs.patch
+ create mode 100644 target/linux/generic/backport-5.15/705-08-v5.19-net-dsa-mt7530-move-autoneg-handling-to-PCS-validati.patch
+ create mode 100644 target/linux/generic/backport-5.15/705-09-v5.19-net-dsa-mt7530-mark-as-non-legacy.patch
+ create mode 100644 target/linux/generic/backport-5.15/705-10-v5.19-net-dsa-mt753x-fix-pcs-conversion-regression.patch
+ create mode 100644 target/linux/generic/backport-5.15/705-11-v6.0-net-dsa-mt7530-rework-mt7530_hw_vlan_-add-del.patch
+ create mode 100644 target/linux/generic/backport-5.15/705-12-v6.0-net-dsa-mt7530-rework-mt753-01-_setup.patch
+ create mode 100644 target/linux/generic/backport-5.15/705-13-v6.0-net-dsa-mt7530-get-cpu-port-via-dp-cpu_dp-instead-of.patch
+ create mode 100644 target/linux/generic/backport-5.15/706-00-v6.0-net-ethernet-mtk_eth_soc-rely-on-page_pool-for-singl.patch
+ create mode 100644 target/linux/generic/backport-5.15/706-01-v6.0-net-ethernet-mtk_eth_soc-add-basic-XDP-support.patch
+ create mode 100644 target/linux/generic/backport-5.15/706-02-v6.0-net-ethernet-mtk_eth_soc-introduce-xdp-ethtool-count.patch
+ create mode 100644 target/linux/generic/backport-5.15/706-03-v6.0-net-ethernet-mtk_eth_soc-add-xmit-XDP-support.patch
+ create mode 100644 target/linux/generic/backport-5.15/706-04-v6.0-net-ethernet-mtk_eth_soc-add-support-for-page_pool_g.patch
+ create mode 100644 target/linux/generic/backport-5.15/706-05-v6.0-net-ethernet-mtk_eth_soc-introduce-mtk_xdp_frame_map.patch
+ create mode 100644 target/linux/generic/backport-5.15/706-06-v6.0-net-ethernet-mtk_eth_soc-introduce-xdp-multi-frag-su.patch
+ create mode 100644 target/linux/generic/backport-5.15/710-v6.0-net-ethernet-mtk_eth_soc-fix-hw-hash-reporting-for-M.patch
+ create mode 100644 target/linux/generic/backport-5.15/711-v6.0-01-net-ethernet-mtk_eth_soc-fix-off-by-one-check-of-ARR.patch
+ create mode 100644 target/linux/generic/backport-5.15/711-v6.0-02-net-ethernet-mtk_ppe-fix-possible-NULL-pointer-deref.patch
+ create mode 100644 target/linux/generic/backport-5.15/711-v6.0-03-net-ethernet-mtk-ppe-fix-traffic-offload-with-bridge.patch
+ create mode 100644 target/linux/generic/backport-5.15/711-v6.0-04-net-ethernet-mtk_eth_soc-remove-mtk_foe_entry_timest.patch
+ create mode 100644 target/linux/generic/backport-5.15/712-v6.0-net-ethernet-mtk_eth_soc-enable-XDP-support-just-for.patch
+ create mode 100644 target/linux/generic/backport-5.15/713-v6.0-net-ethernet-mtk_eth_soc-move-gdma_to_ppe-and-ppe_ba.patch
+ create mode 100644 target/linux/generic/backport-5.15/714-v6.0-net-ethernet-mtk_eth_soc-move-ppe-table-hash-offset-.patch
+ create mode 100644 target/linux/generic/backport-5.15/715-v6.0-net-ethernet-mtk_eth_soc-add-the-capability-to-run-m.patch
+ create mode 100644 target/linux/generic/backport-5.15/716-v6.0-net-ethernet-mtk_eth_soc-move-wdma_base-definitions-.patch
+ create mode 100644 target/linux/generic/backport-5.15/717-v6.0-net-ethernet-mtk_eth_soc-add-foe_entry_size-to-mtk_e.patch
+ create mode 100644 target/linux/generic/backport-5.15/718-v6.0-net-ethernet-mtk_eth_soc-fix-typo-in-__mtk_foe_entry.patch
+ create mode 100644 target/linux/generic/backport-5.15/719-v6.0-net-ethernet-mtk_eth_soc-check-max-allowed-value-in-.patch
+ create mode 100644 target/linux/generic/backport-5.15/720-v6.0-net-ethernet-mtk_eth_wed-add-mtk_wed_configure_irq-a.patch
+ create mode 100644 target/linux/generic/backport-5.15/721-v6.0-net-ethernet-mtk_eth_wed-add-wed-support-for-mt7986-.patch
+ create mode 100644 target/linux/generic/backport-5.15/722-v6.0-net-ethernet-mtk_eth_wed-add-axi-bus-support.patch
+ create mode 100644 target/linux/generic/backport-5.15/723-v6.0-net-ethernet-mtk_eth_soc-introduce-flow-offloading-s.patch
+ create mode 100644 target/linux/generic/backport-5.15/724-v6.0-net-ethernet-mtk_eth_soc-enable-flow-offloading-supp.patch
+ create mode 100644 target/linux/generic/backport-5.15/724-v6.0-net-ethernet-mtk_eth_soc-fix-wrong-use-of-new-helper.patch
+ create mode 100644 target/linux/generic/backport-5.15/725-v6.0-net-ethernet-mtk_eth_soc-fix-usage-of-foe_entry_size.patch
+ create mode 100644 target/linux/generic/backport-5.15/726-v6.0-net-ethernet-mtk_eth_soc-fix-mask-of-RX_DMA_GET_SPOR.patch
+ create mode 100644 target/linux/generic/backport-5.15/727-v6.1-net-ethernet-mtk_eth_soc-fix-state-in-__mtk_foe_entr.patch
+ create mode 100644 target/linux/generic/backport-5.15/728-v6.1-01-net-ethernet-mtk_eth_soc-fix-possible-memory-leak-in.patch
+ create mode 100644 target/linux/generic/backport-5.15/728-v6.1-02-net-ethernet-mtk_eth_wed-add-missing-put_device-in-m.patch
+ create mode 100644 target/linux/generic/backport-5.15/728-v6.1-03-net-ethernet-mtk_eth_wed-add-missing-of_node_put.patch
  create mode 100644 target/linux/generic/backport-5.15/734-v5.16-0001-net-bgmac-improve-handling-PHY.patch
  create mode 100644 target/linux/generic/backport-5.15/734-v5.16-0002-net-bgmac-support-MDIO-described-in-DT.patch
  create mode 100644 target/linux/generic/backport-5.15/742-v5.16-net-phy-at803x-add-support-for-qca-8327-internal-phy.patch
@@ -243,40 +548,64 @@ Subject: [PATCH] Generic backport patches 5.15
  create mode 100644 target/linux/generic/backport-5.15/750-v5.16-net-dsa-qca8k-make-sure-pad0-mac06-exchange-is-disabled.patch
  create mode 100644 target/linux/generic/backport-5.15/751-v5.16-net-dsa-qca8k-fix-internal-delay-applied-to-the-wrong-PAD.patch
  create mode 100644 target/linux/generic/backport-5.15/752-v5.16-net-dsa-qca8k-fix-MTU-calculation.patch
- create mode 100644 target/linux/generic/backport-5.15/753-net-next-net-dsa-qca8k-remove-redundant-check-in-parse_port_config.patch
- create mode 100644 target/linux/generic/backport-5.15/754-net-next-net-dsa-qca8k-convert-to-GENMASK_FIELD_PREP_FIELD_GET.patch
- create mode 100644 target/linux/generic/backport-5.15/755-net-next-net-dsa-qca8k-remove-extra-mutex_init-in-qca8k_setup.patch
- create mode 100644 target/linux/generic/backport-5.15/756-net-next-net-dsa-qca8k-move-regmap-init-in-probe-and-set-it.patch
- create mode 100644 target/linux/generic/backport-5.15/757-net-next-net-dsa-qca8k-initial-conversion-to-regmap-heper.patch
- create mode 100644 target/linux/generic/backport-5.15/758-net-next-net-dsa-qca8k-add-additional-MIB-counter-and-.patch
- create mode 100644 target/linux/generic/backport-5.15/759-net-next-net-dsa-qca8k-add-support-for-port-fast-aging.patch
- create mode 100644 target/linux/generic/backport-5.15/760-net-next-net-dsa-qca8k-add-set_ageing_time-support.patch
- create mode 100644 target/linux/generic/backport-5.15/761-net-next-net-dsa-qca8k-add-support-for-mdb_add-del.patch
- create mode 100644 target/linux/generic/backport-5.15/762-net-next-net-dsa-qca8k-add-support-for-mirror-mode.patch
- create mode 100644 target/linux/generic/backport-5.15/763-net-next-net-dsa-qca8k-add-LAG-support.patch
- create mode 100644 target/linux/generic/backport-5.15/764-net-next-net-dsa-qca8k-fix-warning-in-LAG-feature.patch
- create mode 100644 target/linux/generic/backport-5.15/765-1-net-next-net-dsa-reorder-PHY-initialization-with-MTU-setup-in.patch
- create mode 100644 target/linux/generic/backport-5.15/765-2-net-next-net-dsa-merge-rtnl_lock-sections-in-dsa_slave_create.patch
- create mode 100644 target/linux/generic/backport-5.15/765-3-net-next-net-dsa-stop-updating-master-MTU-from-master.c.patch
- create mode 100644 target/linux/generic/backport-5.15/765-4-net-next-net-dsa-hold-rtnl_mutex-when-calling-dsa_master_-set.patch
- create mode 100644 target/linux/generic/backport-5.15/765-5-net-next-net-dsa-first-set-up-shared-ports-then-non-shared-po.patch
- create mode 100644 target/linux/generic/backport-5.15/765-6-net-next-net-dsa-setup-master-before-ports.patch
- create mode 100644 target/linux/generic/backport-5.15/766-01-net-dsa-provide-switch-operations-for-tracking-the-m.patch
- create mode 100644 target/linux/generic/backport-5.15/766-02-net-dsa-replay-master-state-events-in-dsa_tree_-setu.patch
- create mode 100644 target/linux/generic/backport-5.15/766-03-net-dsa-tag_qca-convert-to-FIELD-macro.patch
- create mode 100644 target/linux/generic/backport-5.15/766-04-net-dsa-tag_qca-move-define-to-include-linux-dsa.patch
- create mode 100644 target/linux/generic/backport-5.15/766-05-net-dsa-tag_qca-enable-promisc_on_master-flag.patch
- create mode 100644 target/linux/generic/backport-5.15/766-06-net-dsa-tag_qca-add-define-for-handling-mgmt-Etherne.patch
- create mode 100644 target/linux/generic/backport-5.15/766-07-net-dsa-tag_qca-add-define-for-handling-MIB-packet.patch
- create mode 100644 target/linux/generic/backport-5.15/766-08-net-dsa-tag_qca-add-support-for-handling-mgmt-and-MI.patch
- create mode 100644 target/linux/generic/backport-5.15/766-09-net-dsa-qca8k-add-tracking-state-of-master-port.patch
- create mode 100644 target/linux/generic/backport-5.15/766-10-net-dsa-qca8k-add-support-for-mgmt-read-write-in-Eth.patch
- create mode 100644 target/linux/generic/backport-5.15/766-11-net-dsa-qca8k-add-support-for-mib-autocast-in-Ethern.patch
- create mode 100644 target/linux/generic/backport-5.15/766-12-net-dsa-qca8k-add-support-for-phy-read-write-with-mg.patch
- create mode 100644 target/linux/generic/backport-5.15/766-13-net-dsa-qca8k-move-page-cache-to-driver-priv.patch
- create mode 100644 target/linux/generic/backport-5.15/766-14-net-dsa-qca8k-cache-lo-and-hi-for-mdio-write.patch
- create mode 100644 target/linux/generic/backport-5.15/766-15-net-dsa-qca8k-add-support-for-larger-read-write-size.patch
- create mode 100644 target/linux/generic/backport-5.15/766-16-net-dsa-qca8k-introduce-qca8k_bulk_read-write-functi.patch
+ create mode 100644 target/linux/generic/backport-5.15/753-v5.17-net-next-net-dsa-qca8k-remove-redundant-check-in-parse_port_config.patch
+ create mode 100644 target/linux/generic/backport-5.15/754-v5.17-net-next-net-dsa-qca8k-convert-to-GENMASK_FIELD_PREP_FIELD_GET.patch
+ create mode 100644 target/linux/generic/backport-5.15/755-v5.17-net-next-net-dsa-qca8k-remove-extra-mutex_init-in-qca8k_setup.patch
+ create mode 100644 target/linux/generic/backport-5.15/756-v5.17-net-next-net-dsa-qca8k-move-regmap-init-in-probe-and-set-it.patch
+ create mode 100644 target/linux/generic/backport-5.15/757-v5.17-net-next-net-dsa-qca8k-initial-conversion-to-regmap-heper.patch
+ create mode 100644 target/linux/generic/backport-5.15/758-v5.17-net-next-net-dsa-qca8k-add-additional-MIB-counter-and-.patch
+ create mode 100644 target/linux/generic/backport-5.15/759-v5.17-net-next-net-dsa-qca8k-add-support-for-port-fast-aging.patch
+ create mode 100644 target/linux/generic/backport-5.15/760-v5.17-net-next-net-dsa-qca8k-add-set_ageing_time-support.patch
+ create mode 100644 target/linux/generic/backport-5.15/761-v5.17-net-next-net-dsa-qca8k-add-support-for-mdb_add-del.patch
+ create mode 100644 target/linux/generic/backport-5.15/762-v5.17-net-next-net-dsa-qca8k-add-support-for-mirror-mode.patch
+ create mode 100644 target/linux/generic/backport-5.15/763-v5.17-net-next-net-dsa-qca8k-add-LAG-support.patch
+ create mode 100644 target/linux/generic/backport-5.15/764-v5.17-net-next-net-dsa-qca8k-fix-warning-in-LAG-feature.patch
+ create mode 100644 target/linux/generic/backport-5.15/765-v5.17-01-net-next-net-dsa-reorder-PHY-initialization-with-MTU-setup-in.patch
+ create mode 100644 target/linux/generic/backport-5.15/765-v5.17-02-net-next-net-dsa-merge-rtnl_lock-sections-in-dsa_slave_create.patch
+ create mode 100644 target/linux/generic/backport-5.15/765-v5.17-03-net-next-net-dsa-stop-updating-master-MTU-from-master.c.patch
+ create mode 100644 target/linux/generic/backport-5.15/765-v5.17-04-net-next-net-dsa-hold-rtnl_mutex-when-calling-dsa_master_-set.patch
+ create mode 100644 target/linux/generic/backport-5.15/765-v5.17-05-net-next-net-dsa-first-set-up-shared-ports-then-non-shared-po.patch
+ create mode 100644 target/linux/generic/backport-5.15/765-v5.17-06-net-next-net-dsa-setup-master-before-ports.patch
+ create mode 100644 target/linux/generic/backport-5.15/766-v5.18-01-net-dsa-provide-switch-operations-for-tracking-the-m.patch
+ create mode 100644 target/linux/generic/backport-5.15/766-v5.18-02-net-dsa-replay-master-state-events-in-dsa_tree_-setu.patch
+ create mode 100644 target/linux/generic/backport-5.15/766-v5.18-03-net-dsa-tag_qca-convert-to-FIELD-macro.patch
+ create mode 100644 target/linux/generic/backport-5.15/766-v5.18-04-net-dsa-tag_qca-move-define-to-include-linux-dsa.patch
+ create mode 100644 target/linux/generic/backport-5.15/766-v5.18-05-net-dsa-tag_qca-enable-promisc_on_master-flag.patch
+ create mode 100644 target/linux/generic/backport-5.15/766-v5.18-06-net-dsa-tag_qca-add-define-for-handling-mgmt-Etherne.patch
+ create mode 100644 target/linux/generic/backport-5.15/766-v5.18-07-net-dsa-tag_qca-add-define-for-handling-MIB-packet.patch
+ create mode 100644 target/linux/generic/backport-5.15/766-v5.18-08-net-dsa-tag_qca-add-support-for-handling-mgmt-and-MI.patch
+ create mode 100644 target/linux/generic/backport-5.15/766-v5.18-09-net-dsa-qca8k-add-tracking-state-of-master-port.patch
+ create mode 100644 target/linux/generic/backport-5.15/766-v5.18-10-net-dsa-qca8k-add-support-for-mgmt-read-write-in-Eth.patch
+ create mode 100644 target/linux/generic/backport-5.15/766-v5.18-11-net-dsa-qca8k-add-support-for-mib-autocast-in-Ethern.patch
+ create mode 100644 target/linux/generic/backport-5.15/766-v5.18-12-net-dsa-qca8k-add-support-for-phy-read-write-with-mg.patch
+ create mode 100644 target/linux/generic/backport-5.15/766-v5.18-13-net-dsa-qca8k-move-page-cache-to-driver-priv.patch
+ create mode 100644 target/linux/generic/backport-5.15/766-v5.18-14-net-dsa-qca8k-cache-lo-and-hi-for-mdio-write.patch
+ create mode 100644 target/linux/generic/backport-5.15/766-v5.18-15-net-dsa-qca8k-add-support-for-larger-read-write-size.patch
+ create mode 100644 target/linux/generic/backport-5.15/766-v5.18-16-net-dsa-qca8k-introduce-qca8k_bulk_read-write-functi.patch
+ create mode 100644 target/linux/generic/backport-5.15/767-v5.18-net-dsa-qca8k-check-correct-variable-in-qca8k_phy_et.patch
+ create mode 100644 target/linux/generic/backport-5.15/768-v5.18-net-dsa-qca8k-fix-noderef.cocci-warnings.patch
+ create mode 100644 target/linux/generic/backport-5.15/769-v5.19-01-net-dsa-qca8k-drop-MTU-tracking-from-qca8k_priv.patch
+ create mode 100644 target/linux/generic/backport-5.15/769-v5.19-02-net-dsa-qca8k-drop-port_sts-from-qca8k_priv.patch
+ create mode 100644 target/linux/generic/backport-5.15/769-v5.19-03-net-dsa-qca8k-rework-and-simplify-mdiobus-logic.patch
+ create mode 100644 target/linux/generic/backport-5.15/769-v5.19-04-net-dsa-qca8k-drop-dsa_switch_ops-from-qca8k_priv.patch
+ create mode 100644 target/linux/generic/backport-5.15/769-v5.19-05-net-dsa-qca8k-correctly-handle-mdio-read-error.patch
+ create mode 100644 target/linux/generic/backport-5.15/769-v5.19-06-net-dsa-qca8k-unify-bus-id-naming-with-legacy-and-OF.patch
+ create mode 100644 target/linux/generic/backport-5.15/770-v6.0-net-dsa-qca8k-move-driver-to-qca-dir.patch
+ create mode 100644 target/linux/generic/backport-5.15/771-v6.0-01-net-dsa-qca8k-cache-match-data-to-speed-up-access.patch
+ create mode 100644 target/linux/generic/backport-5.15/771-v6.0-02-net-dsa-qca8k-make-mib-autocast-feature-optional.patch
+ create mode 100644 target/linux/generic/backport-5.15/771-v6.0-03-net-dsa-qca8k-move-mib-struct-to-common-code.patch
+ create mode 100644 target/linux/generic/backport-5.15/771-v6.0-04-net-dsa-qca8k-move-qca8k-read-write-rmw-and-reg-tabl.patch
+ create mode 100644 target/linux/generic/backport-5.15/771-v6.0-05-net-dsa-qca8k-move-qca8k-bulk-read-write-helper-to-c.patch
+ create mode 100644 target/linux/generic/backport-5.15/771-v6.0-06-net-dsa-qca8k-move-mib-init-function-to-common-code.patch
+ create mode 100644 target/linux/generic/backport-5.15/771-v6.0-07-net-dsa-qca8k-move-port-set-status-eee-ethtool-stats.patch
+ create mode 100644 target/linux/generic/backport-5.15/771-v6.0-08-net-dsa-qca8k-move-bridge-functions-to-common-code.patch
+ create mode 100644 target/linux/generic/backport-5.15/771-v6.0-09-net-dsa-qca8k-move-set-age-MTU-port-enable-disable-f.patch
+ create mode 100644 target/linux/generic/backport-5.15/771-v6.0-10-net-dsa-qca8k-move-port-FDB-MDB-function-to-common-c.patch
+ create mode 100644 target/linux/generic/backport-5.15/771-v6.0-11-net-dsa-qca8k-move-port-mirror-functions-to-common-c.patch
+ create mode 100644 target/linux/generic/backport-5.15/771-v6.0-12-net-dsa-qca8k-move-port-VLAN-functions-to-common-cod.patch
+ create mode 100644 target/linux/generic/backport-5.15/771-v6.0-13-net-dsa-qca8k-move-port-LAG-functions-to-common-code.patch
+ create mode 100644 target/linux/generic/backport-5.15/771-v6.0-14-net-dsa-qca8k-move-read_switch_id-function-to-common.patch
+ create mode 100644 target/linux/generic/backport-5.15/772-v6.0-net-dsa-qca8k-fix-NULL-pointer-dereference-for-of_de.patch
  create mode 100644 target/linux/generic/backport-5.15/773-v5.18-1-net-dsa-Move-VLAN-filtering-syncing-out-of-dsa_switc.patch
  create mode 100644 target/linux/generic/backport-5.15/773-v5.18-2-net-dsa-Avoid-cross-chip-syncing-of-VLAN-filtering.patch
  create mode 100644 target/linux/generic/backport-5.15/774-v5.16-01-net-dsa-rtl8366rb-Support-bridge-offloading.patch
@@ -287,17925 +616,59288 @@ Subject: [PATCH] Generic backport patches 5.15
  create mode 100644 target/linux/generic/backport-5.15/774-v5.16-08-net-dsa-rtl8366rb-Support-disabling-learning.patch
  create mode 100644 target/linux/generic/backport-5.15/774-v5.16-09-net-dsa-rtl8366rb-Support-fast-aging.patch
  create mode 100644 target/linux/generic/backport-5.15/774-v5.16-10-net-dsa-rtl8366rb-Support-setting-STP-state.patch
- create mode 100644 target/linux/generic/backport-5.15/800-v5.20-0001-dt-bindings-leds-add-Broadcom-s-BCM63138-controller.patch
- create mode 100644 target/linux/generic/backport-5.15/800-v5.20-0002-leds-bcm63138-add-support-for-BCM63138-controller.patch
- create mode 100644 target/linux/generic/backport-5.15/801-v5.20-0001-dt-bindings-leds-leds-bcm63138-unify-full-stops-in-d.patch
- create mode 100644 target/linux/generic/backport-5.15/801-v5.20-0002-leds-add-help-info-about-BCM63138-module-name.patch
- create mode 100644 target/linux/generic/backport-5.15/801-v5.20-0003-leds-leds-bcm63138-get-rid-of-LED_OFF.patch
+ create mode 100644 target/linux/generic/backport-5.15/775-v6.0-01-net-ethernet-stmicro-stmmac-move-queue-reset-to-dedi.patch
+ create mode 100644 target/linux/generic/backport-5.15/775-v6.0-02-net-ethernet-stmicro-stmmac-first-disable-all-queues.patch
+ create mode 100644 target/linux/generic/backport-5.15/775-v6.0-03-net-ethernet-stmicro-stmmac-move-dma-conf-to-dedicat.patch
+ create mode 100644 target/linux/generic/backport-5.15/775-v6.0-04-net-ethernet-stmicro-stmmac-generate-stmmac-dma-conf.patch
+ create mode 100644 target/linux/generic/backport-5.15/775-v6.0-05-net-ethernet-stmicro-stmmac-permit-MTU-change-with-i.patch
+ create mode 100644 target/linux/generic/backport-5.15/776-v6.1-01-net-dsa-qca8k-fix-inband-mgmt-for-big-endian-systems.patch
+ create mode 100644 target/linux/generic/backport-5.15/776-v6.1-02-net-dsa-qca8k-fix-ethtool-autocast-mib-for-big-endia.patch
+ create mode 100644 target/linux/generic/backport-5.15/780-v5.16-bus-mhi-pci_generic-Introduce-Sierra-EM919X-support.patch
+ create mode 100644 target/linux/generic/backport-5.15/781-v6.1-bus-mhi-host-always-print-detected-modem-name.patch
+ create mode 100644 target/linux/generic/backport-5.15/782-v6.1-net-dsa-mt7530-add-support-for-in-band-link-status.patch
+ create mode 100644 target/linux/generic/backport-5.15/783-v6.1-net-sfp-re-implement-soft-state-polling-setup.patch
+ create mode 100644 target/linux/generic/backport-5.15/784-v6.1-net-sfp-move-quirk-handling-into-sfp.c.patch
+ create mode 100644 target/linux/generic/backport-5.15/785-v6.1-net-sfp-move-Alcatel-Lucent-3FE46541AA-fixup.patch
+ create mode 100644 target/linux/generic/backport-5.15/786-v6.1-net-sfp-move-Huawei-MA5671A-fixup.patch
+ create mode 100644 target/linux/generic/backport-5.15/787-v6.1-net-sfp-add-support-for-HALNy-GPON-SFP.patch
+ create mode 100644 target/linux/generic/backport-5.15/797-v5.17-net-usb-ax88179_178a-add-TSO-feature.patch
+ create mode 100644 target/linux/generic/backport-5.15/800-v6.0-0001-dt-bindings-leds-add-Broadcom-s-BCM63138-controller.patch
+ create mode 100644 target/linux/generic/backport-5.15/800-v6.0-0002-leds-bcm63138-add-support-for-BCM63138-controller.patch
+ create mode 100644 target/linux/generic/backport-5.15/801-v6.0-0001-dt-bindings-leds-leds-bcm63138-unify-full-stops-in-d.patch
+ create mode 100644 target/linux/generic/backport-5.15/801-v6.0-0002-leds-add-help-info-about-BCM63138-module-name.patch
+ create mode 100644 target/linux/generic/backport-5.15/801-v6.0-0003-leds-leds-bcm63138-get-rid-of-LED_OFF.patch
+ create mode 100644 target/linux/generic/backport-5.15/802-v6.1-0001-nvmem-add-driver-handling-U-Boot-environment-variabl.patch
+ create mode 100644 target/linux/generic/backport-5.15/802-v6.1-0002-nvmem-u-boot-env-find-Device-Tree-nodes-for-NVMEM-ce.patch
+ create mode 100644 target/linux/generic/backport-5.15/802-v6.1-0003-nvmem-u-boot-env-fix-crc32-casting-type.patch
+ create mode 100644 target/linux/generic/backport-5.15/803-v5.19-nvmem-core-support-passing-DT-node-in-cell-info.patch
+ create mode 100644 target/linux/generic/backport-5.15/804-v6.2-0001-nvmem-u-boot-env-fix-crc32_data_offset-on-redundant-.patch
+ create mode 100644 target/linux/generic/backport-5.15/804-v6.2-0002-nvmem-u-boot-env-align-endianness-of-crc32-values.patch
+ create mode 100644 target/linux/generic/backport-5.15/804-v6.2-0003-nvmem-u-boot-env-add-Broadcom-format-support.patch
+ create mode 100644 target/linux/generic/backport-5.15/810-v5.17-net-qmi_wwan-add-ZTE-MF286D-modem-19d2-1485.patch
+ create mode 100644 target/linux/generic/backport-5.15/821-v5.16-Bluetooth-btusb-Support-public-address-configuration.patch
+ create mode 100644 target/linux/generic/backport-5.15/822-v5.17-Bluetooth-btusb-Fix-application-of-sizeof-to-pointer.patch
+ create mode 100644 target/linux/generic/backport-5.15/823-v5.18-Bluetooth-btusb-Add-a-new-PID-VID-13d3-3567-for-MT79.patch
+ create mode 100644 target/linux/generic/backport-5.15/824-v5.19-Bluetooth-btusb-Add-a-new-PID-VID-0489-e0c8-for-MT79.patch
+ create mode 100644 target/linux/generic/backport-5.15/825-v6.1-Bluetooth-btusb-Add-a-new-VID-PID-0e8d-0608-for-MT79.patch
  create mode 100644 target/linux/generic/backport-5.15/860-v5.17-MIPS-ath79-drop-_machine_restart-again.patch
- create mode 100644 target/linux/generic/backport-5.15/870-hwmon-next-hwmon-lm70-Add-ti-tmp125-support.patch
+ create mode 100644 target/linux/generic/backport-5.15/870-v5.18-hwmon-lm70-Add-ti-tmp125-support.patch
  create mode 100644 target/linux/generic/backport-5.15/880-v5.19-cdc_ether-export-usbnet_cdc_zte_rx_fixup.patch
  create mode 100644 target/linux/generic/backport-5.15/881-v5.19-rndis_host-enable-the-bogus-MAC-fixup-for-ZTE-device.patch
  create mode 100644 target/linux/generic/backport-5.15/882-v5.19-rndis_host-limit-scope-of-bogus-MAC-address-detectio.patch
 
-diff --git a/target/linux/generic/backport-5.15/011-kbuild-export-SUBARCH.patch b/target/linux/generic/backport-5.15/011-kbuild-export-SUBARCH.patch
+diff --git a/target/linux/generic/backport-5.15/005-v5.17-01-Kbuild-use-Wdeclaration-after-statement.patch b/target/linux/generic/backport-5.15/005-v5.17-01-Kbuild-use-Wdeclaration-after-statement.patch
 new file mode 100644
-index 0000000000..0aedad4bfd
+index 0000000000..ff4c6d7041
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/011-kbuild-export-SUBARCH.patch
-@@ -0,0 +1,21 @@
-+From 173019b66dcc9d68ad9333aa744dad1e369b5aa8 Mon Sep 17 00:00:00 2001
-+From: Felix Fietkau <nbd@nbd.name>
-+Date: Sun, 9 Jul 2017 00:26:53 +0200
-+Subject: [PATCH 34/34] kernel: add compile fix for linux 4.9 on x86
-+
-+Signed-off-by: Felix Fietkau <nbd@nbd.name>
++++ b/target/linux/generic/backport-5.15/005-v5.17-01-Kbuild-use-Wdeclaration-after-statement.patch
+@@ -0,0 +1,73 @@
++From 2fd7e7f9317d3048a14026816d081b08ba98ea8e Mon Sep 17 00:00:00 2001
++From: Mark Rutland <mark.rutland@arm.com>
++Date: Tue, 8 Mar 2022 22:56:13 +0100
++Subject: [PATCH 1/3] Kbuild: use -Wdeclaration-after-statement
++
++The kernel is moving from using `-std=gnu89` to `-std=gnu11`, permitting
++the use of additional C11 features such as for-loop initial declarations.
++
++One contentious aspect of C99 is that it permits mixed declarations and
++code, and for now at least, it seems preferable to enforce that
++declarations must come first.
++
++These warnings were already enabled in the kernel itself, but not
++for KBUILD_USERCFLAGS or the compat VDSO on arch/arm64, which uses
++a separate set of CFLAGS.
++
++This patch fixes an existing violation in modpost.c, which is not
++reported because of the missing flag in KBUILD_USERCFLAGS:
++
++| scripts/mod/modpost.c: In function ‘match’:
++| scripts/mod/modpost.c:837:3: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]
++|   837 |   const char *endp = p + strlen(p) - 1;
++|       |   ^~~~~
++
++Signed-off-by: Mark Rutland <mark.rutland@arm.com>
++[arnd: don't add a duplicate flag to the default set, update changelog]
++Signed-off-by: Arnd Bergmann <arnd@arndb.de>
++Reviewed-by: Nathan Chancellor <nathan@kernel.org>
++Reviewed-by: Nick Desaulniers <ndesaulniers@google.com>
++Tested-by: Sedat Dilek <sedat.dilek@gmail.com> # LLVM/Clang v13.0.0 (x86-64)
++Signed-off-by: Masahiro Yamada <masahiroy@kernel.org>
 +---
-+ Makefile | 4 ++--
-+ 1 file changed, 2 insertions(+), 2 deletions(-)
++ Makefile                          | 3 ++-
++ arch/arm64/kernel/vdso32/Makefile | 1 +
++ scripts/mod/modpost.c             | 4 +++-
++ 3 files changed, 6 insertions(+), 2 deletions(-)
 +
 +--- a/Makefile
 ++++ b/Makefile
-+@@ -523,7 +523,7 @@ KBUILD_LDFLAGS_MODULE :=
-+ KBUILD_LDFLAGS :=
-+ CLANG_FLAGS :=
-+ 
-+-export ARCH SRCARCH CONFIG_SHELL BASH HOSTCC KBUILD_HOSTCFLAGS CROSS_COMPILE LD CC
-++export ARCH SRCARCH SUBARCH CONFIG_SHELL BASH HOSTCC KBUILD_HOSTCFLAGS CROSS_COMPILE LD CC
-+ export CPP AR NM STRIP OBJCOPY OBJDUMP READELF PAHOLE RESOLVE_BTFIDS LEX YACC AWK INSTALLKERNEL
-+ export PERL PYTHON3 CHECK CHECKFLAGS MAKE UTS_MACHINE HOSTCXX
-+ export KGZIP KBZIP2 KLZOP LZMA LZ4 XZ ZSTD
-diff --git a/target/linux/generic/backport-5.15/050-v5.16-00-MIPS-uasm-Enable-muhu-opcode-for-MIPS-R6.patch b/target/linux/generic/backport-5.15/050-v5.16-00-MIPS-uasm-Enable-muhu-opcode-for-MIPS-R6.patch
++@@ -432,7 +432,8 @@ HOSTCXX	= g++
++ endif
++ 
++ export KBUILD_USERCFLAGS := -Wall -Wmissing-prototypes -Wstrict-prototypes \
++-			      -O2 -fomit-frame-pointer -std=gnu89
+++			      -O2 -fomit-frame-pointer -std=gnu89 \
+++			      -Wdeclaration-after-statement
++ export KBUILD_USERLDFLAGS :=
++ 
++ KBUILD_HOSTCFLAGS   := $(KBUILD_USERCFLAGS) $(HOST_LFS_CFLAGS) $(HOSTCFLAGS)
++--- a/arch/arm64/kernel/vdso32/Makefile
+++++ b/arch/arm64/kernel/vdso32/Makefile
++@@ -76,6 +76,7 @@ VDSO_CFLAGS += -Wall -Wundef -Wstrict-pr
++                -fno-strict-aliasing -fno-common \
++                -Werror-implicit-function-declaration \
++                -Wno-format-security \
+++               -Wdeclaration-after-statement \
++                -std=gnu89
++ VDSO_CFLAGS  += -O2
++ # Some useful compiler-dependent flags from top-level Makefile
++--- a/scripts/mod/modpost.c
+++++ b/scripts/mod/modpost.c
++@@ -833,8 +833,10 @@ static int match(const char *sym, const
++ {
++ 	const char *p;
++ 	while (*pat) {
+++		const char *endp;
+++
++ 		p = *pat++;
++-		const char *endp = p + strlen(p) - 1;
+++		endp = p + strlen(p) - 1;
++ 
++ 		/* "*foo*" */
++ 		if (*p == '*' && *endp == '*') {
+diff --git a/target/linux/generic/backport-5.15/005-v5.17-02-Kbuild-move-to-std-gnu11.patch b/target/linux/generic/backport-5.15/005-v5.17-02-Kbuild-move-to-std-gnu11.patch
 new file mode 100644
-index 0000000000..82feb7421d
+index 0000000000..d22eefb773
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/050-v5.16-00-MIPS-uasm-Enable-muhu-opcode-for-MIPS-R6.patch
-@@ -0,0 +1,65 @@
-+From: Johan Almbladh <johan.almbladh@anyfinetworks.com>
-+Date: Tue, 5 Oct 2021 18:54:02 +0200
-+Subject: [PATCH] MIPS: uasm: Enable muhu opcode for MIPS R6
-+
-+Enable the 'muhu' instruction, complementing the existing 'mulu', needed
-+to implement a MIPS32 BPF JIT.
-+
-+Also fix a typo in the existing definition of 'dmulu'.
-+
-+Signed-off-by: Tony Ambardar <Tony.Ambardar@gmail.com>
-+
-+This patch is a dependency for my 32-bit MIPS eBPF JIT.
-+
-+Signed-off-by: Johan Almbladh <johan.almbladh@anyfinetworks.com>
++++ b/target/linux/generic/backport-5.15/005-v5.17-02-Kbuild-move-to-std-gnu11.patch
+@@ -0,0 +1,60 @@
++From b810c8e719ea082e47c7a8f7cf878bc84fa2455d Mon Sep 17 00:00:00 2001
++From: Arnd Bergmann <arnd@arndb.de>
++Date: Tue, 8 Mar 2022 22:56:14 +0100
++Subject: [PATCH 2/3] Kbuild: move to -std=gnu11
++
++During a patch discussion, Linus brought up the option of changing
++the C standard version from gnu89 to gnu99, which allows using variable
++declaration inside of a for() loop. While the C99, C11 and later standards
++introduce many other features, most of these are already available in
++gnu89 as GNU extensions as well.
++
++An earlier attempt to do this when gcc-5 started defaulting to
++-std=gnu11 failed because at the time that caused warnings about
++designated initializers with older compilers. Now that gcc-5.1 is
++the minimum compiler version used for building kernels, that is no
++longer a concern. Similarly, the behavior of 'inline' functions changes
++between gnu89 using gnu_inline behavior and gnu11 using standard c99+
++behavior, but this was taken care of by defining 'inline' to include
++__attribute__((gnu_inline)) in order to allow building with clang a
++while ago.
++
++Nathan Chancellor reported a new -Wdeclaration-after-statement
++warning that appears in a system header on arm, this still needs a
++workaround.
++
++The differences between gnu99, gnu11, gnu1x and gnu17 are fairly
++minimal and mainly impact warnings at the -Wpedantic level that the
++kernel never enables. Between these, gnu11 is the newest version
++that is supported by all supported compiler versions, though it is
++only the default on gcc-5, while all other supported versions of
++gcc or clang default to gnu1x/gnu17.
++
++Link: https://lore.kernel.org/lkml/CAHk-=wiyCH7xeHcmiFJ-YgXUy2Jaj7pnkdKpcovt8fYbVFW3TA@mail.gmail.com/
++Link: https://github.com/ClangBuiltLinux/linux/issues/1603
++Suggested-by: Linus Torvalds <torvalds@linux-foundation.org>
++Acked-by: Marco Elver <elver@google.com>
++Acked-by: Jani Nikula <jani.nikula@intel.com>
++Acked-by: David Sterba <dsterba@suse.com>
++Tested-by: Sedat Dilek <sedat.dilek@gmail.com>
++Reviewed-by: Alex Shi <alexs@kernel.org>
++Reviewed-by: Nick Desaulniers <ndesaulniers@google.com>
++Reviewed-by: Miguel Ojeda <ojeda@kernel.org>
++Signed-off-by: Arnd Bergmann <arnd@arndb.de>
++Reviewed-by: Nathan Chancellor <nathan@kernel.org>
++Signed-off-by: Masahiro Yamada <masahiroy@kernel.org>
 +---
++ Makefile | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
 +
-+--- a/arch/mips/include/asm/uasm.h
-++++ b/arch/mips/include/asm/uasm.h
-+@@ -145,6 +145,7 @@ Ip_u1(_mtlo);
-+ Ip_u3u1u2(_mul);
-+ Ip_u1u2(_multu);
-+ Ip_u3u1u2(_mulu);
-++Ip_u3u1u2(_muhu);
-+ Ip_u3u1u2(_nor);
-+ Ip_u3u1u2(_or);
-+ Ip_u2u1u3(_ori);
-+--- a/arch/mips/mm/uasm-mips.c
-++++ b/arch/mips/mm/uasm-mips.c
-+@@ -90,7 +90,7 @@ static const struct insn insn_table[insn
-+ 				RS | RT | RD},
-+ 	[insn_dmtc0]	= {M(cop0_op, dmtc_op, 0, 0, 0, 0), RT | RD | SET},
-+ 	[insn_dmultu]	= {M(spec_op, 0, 0, 0, 0, dmultu_op), RS | RT},
-+-	[insn_dmulu]	= {M(spec_op, 0, 0, 0, dmult_dmul_op, dmultu_op),
-++	[insn_dmulu]	= {M(spec_op, 0, 0, 0, dmultu_dmulu_op, dmultu_op),
-+ 				RS | RT | RD},
-+ 	[insn_drotr]	= {M(spec_op, 1, 0, 0, 0, dsrl_op), RT | RD | RE},
-+ 	[insn_drotr32]	= {M(spec_op, 1, 0, 0, 0, dsrl32_op), RT | RD | RE},
-+@@ -150,6 +150,8 @@ static const struct insn insn_table[insn
-+ 	[insn_mtlo]	= {M(spec_op, 0, 0, 0, 0, mtlo_op), RS},
-+ 	[insn_mulu]	= {M(spec_op, 0, 0, 0, multu_mulu_op, multu_op),
-+ 				RS | RT | RD},
-++	[insn_muhu]	= {M(spec_op, 0, 0, 0, multu_muhu_op, multu_op),
-++				RS | RT | RD},
-+ #ifndef CONFIG_CPU_MIPSR6
-+ 	[insn_mul]	= {M(spec2_op, 0, 0, 0, 0, mul_op), RS | RT | RD},
-+ #else
-+--- a/arch/mips/mm/uasm.c
-++++ b/arch/mips/mm/uasm.c
-+@@ -59,7 +59,7 @@ enum opcode {
-+ 	insn_lddir, insn_ldpte, insn_ldx, insn_lh, insn_lhu, insn_ll, insn_lld,
-+ 	insn_lui, insn_lw, insn_lwu, insn_lwx, insn_mfc0, insn_mfhc0, insn_mfhi,
-+ 	insn_mflo, insn_modu, insn_movn, insn_movz, insn_mtc0, insn_mthc0,
-+-	insn_mthi, insn_mtlo, insn_mul, insn_multu, insn_mulu, insn_nor,
-++	insn_mthi, insn_mtlo, insn_mul, insn_multu, insn_mulu, insn_muhu, insn_nor,
-+ 	insn_or, insn_ori, insn_pref, insn_rfe, insn_rotr, insn_sb, insn_sc,
-+ 	insn_scd, insn_seleqz, insn_selnez, insn_sd, insn_sh, insn_sll,
-+ 	insn_sllv, insn_slt, insn_slti, insn_sltiu, insn_sltu, insn_sra,
-+@@ -344,6 +344,7 @@ I_u1(_mtlo)
-+ I_u3u1u2(_mul)
-+ I_u1u2(_multu)
-+ I_u3u1u2(_mulu)
-++I_u3u1u2(_muhu)
-+ I_u3u1u2(_nor)
-+ I_u3u1u2(_or)
-+ I_u2u1u3(_ori)
-diff --git a/target/linux/generic/backport-5.15/050-v5.16-01-mips-uasm-Add-workaround-for-Loongson-2F-nop-CPU-err.patch b/target/linux/generic/backport-5.15/050-v5.16-01-mips-uasm-Add-workaround-for-Loongson-2F-nop-CPU-err.patch
++--- a/Makefile
+++++ b/Makefile
++@@ -516,7 +516,7 @@ KBUILD_CFLAGS   := -Wall -Wundef -Werror
++ 		   -fno-strict-aliasing -fno-common -fshort-wchar -fno-PIE \
++ 		   -Werror=implicit-function-declaration -Werror=implicit-int \
++ 		   -Werror=return-type -Wno-format-security \
++-		   -std=gnu89
+++		   -std=gnu11
++ KBUILD_CPPFLAGS := -D__KERNEL__
++ KBUILD_AFLAGS_KERNEL :=
++ KBUILD_CFLAGS_KERNEL :=
+diff --git a/target/linux/generic/backport-5.15/005-v5.17-03-Kbuild-use-std-gnu11-for-KBUILD_USERCFLAGS.patch b/target/linux/generic/backport-5.15/005-v5.17-03-Kbuild-use-std-gnu11-for-KBUILD_USERCFLAGS.patch
 new file mode 100644
-index 0000000000..3a4d573f80
+index 0000000000..198c9e671a
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/050-v5.16-01-mips-uasm-Add-workaround-for-Loongson-2F-nop-CPU-err.patch
-@@ -0,0 +1,31 @@
-+From: Johan Almbladh <johan.almbladh@anyfinetworks.com>
-+Date: Tue, 5 Oct 2021 18:54:03 +0200
-+Subject: [PATCH] mips: uasm: Add workaround for Loongson-2F nop CPU errata
-+
-+This patch implements a workaround for the Loongson-2F nop in generated,
-+code, if the existing option CONFIG_CPU_NOP_WORKAROUND is set. Before,
-+the binutils option -mfix-loongson2f-nop was enabled, but no workaround
-+was done when emitting MIPS code. Now, the nop pseudo instruction is
-+emitted as "or ax,ax,zero" instead of the default "sll zero,zero,0". This
-+is consistent with the workaround implemented by binutils.
-+
-+Link: https://sourceware.org/legacy-ml/binutils/2009-11/msg00387.html
-+
-+Signed-off-by: Johan Almbladh <johan.almbladh@anyfinetworks.com>
-+Reviewed-by: Jiaxun Yang <jiaxun.yang@flygoat.com>
++++ b/target/linux/generic/backport-5.15/005-v5.17-03-Kbuild-use-std-gnu11-for-KBUILD_USERCFLAGS.patch
+@@ -0,0 +1,43 @@
++From 40337d6f3d677aee7ad3052ae662d3f53dd4d5cb Mon Sep 17 00:00:00 2001
++From: Arnd Bergmann <arnd@arndb.de>
++Date: Tue, 8 Mar 2022 22:56:15 +0100
++Subject: [PATCH 3/3] Kbuild: use -std=gnu11 for KBUILD_USERCFLAGS
++
++As we change the C language standard for the kernel from gnu89 to
++gnu11, it makes sense to also update the version for user space
++compilation.
++
++Some users have older native compilers than what they use for
++kernel builds, so I considered using gnu99 as the default version
++for wider compatibility with gcc-4.6 and earlier.
++
++However, testing with older compilers showed that we already require
++HOSTCC version 5.1 as well because a lot of host tools include
++linux/compiler.h that uses __has_attribute():
++
++  CC      tools/objtool/exec-cmd.o
++In file included from tools/include/linux/compiler_types.h:36:0,
++                 from tools/include/linux/compiler.h:5,
++                 from exec-cmd.c:2:
++tools/include/linux/compiler-gcc.h:19:5: error: "__has_attribute" is not defined [-Werror=undef]
++
++Signed-off-by: Arnd Bergmann <arnd@arndb.de>
++Reviewed-by: Nathan Chancellor <nathan@kernel.org>
++Reviewed-by: Nick Desaulniers <ndesaulniers@google.com>
++Tested-by: Sedat Dilek <sedat.dilek@gmail.com>
++Signed-off-by: Masahiro Yamada <masahiroy@kernel.org>
 +---
++ Makefile | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
 +
-+--- a/arch/mips/include/asm/uasm.h
-++++ b/arch/mips/include/asm/uasm.h
-+@@ -249,7 +249,11 @@ static inline void uasm_l##lb(struct uas
-+ #define uasm_i_bnezl(buf, rs, off) uasm_i_bnel(buf, rs, 0, off)
-+ #define uasm_i_ehb(buf) uasm_i_sll(buf, 0, 0, 3)
-+ #define uasm_i_move(buf, a, b) UASM_i_ADDU(buf, a, 0, b)
-++#ifdef CONFIG_CPU_NOP_WORKAROUNDS
-++#define uasm_i_nop(buf) uasm_i_or(buf, 1, 1, 0)
-++#else
-+ #define uasm_i_nop(buf) uasm_i_sll(buf, 0, 0, 0)
-++#endif
-+ #define uasm_i_ssnop(buf) uasm_i_sll(buf, 0, 0, 1)
++--- a/Makefile
+++++ b/Makefile
++@@ -432,7 +432,7 @@ HOSTCXX	= g++
++ endif
 + 
-+ static inline void uasm_i_drotr_safe(u32 **p, unsigned int a1,
-diff --git a/target/linux/generic/backport-5.15/050-v5.16-02-mips-bpf-Add-eBPF-JIT-for-32-bit-MIPS.patch b/target/linux/generic/backport-5.15/050-v5.16-02-mips-bpf-Add-eBPF-JIT-for-32-bit-MIPS.patch
++ export KBUILD_USERCFLAGS := -Wall -Wmissing-prototypes -Wstrict-prototypes \
++-			      -O2 -fomit-frame-pointer -std=gnu89 \
+++			      -O2 -fomit-frame-pointer -std=gnu11 \
++ 			      -Wdeclaration-after-statement
++ export KBUILD_USERLDFLAGS :=
++ 
+diff --git a/target/linux/generic/backport-5.15/020-v6.1-01-mm-x86-arm64-add-arch_has_hw_pte_young.patch b/target/linux/generic/backport-5.15/020-v6.1-01-mm-x86-arm64-add-arch_has_hw_pte_young.patch
 new file mode 100644
-index 0000000000..7980659961
+index 0000000000..2a4207c3b5
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/050-v5.16-02-mips-bpf-Add-eBPF-JIT-for-32-bit-MIPS.patch
-@@ -0,0 +1,3078 @@
-+From: Johan Almbladh <johan.almbladh@anyfinetworks.com>
-+Date: Tue, 5 Oct 2021 18:54:04 +0200
-+Subject: [PATCH] mips: bpf: Add eBPF JIT for 32-bit MIPS
-+
-+This is an implementation of an eBPF JIT for 32-bit MIPS I-V and MIPS32.
-+The implementation supports all 32-bit and 64-bit ALU and JMP operations,
-+including the recently-added atomics. 64-bit div/mod and 64-bit atomics
-+are implemented using function calls to math64 and atomic64 functions,
-+respectively. All 32-bit operations are implemented natively by the JIT,
-+except if the CPU lacks ll/sc instructions.
-+
-+Register mapping
-+================
-+All 64-bit eBPF registers are mapped to native 32-bit MIPS register pairs,
-+and does not use any stack scratch space for register swapping. This means
-+that all eBPF register data is kept in CPU registers all the time, and
-+this simplifies the register management a lot. It also reduces the JIT's
-+pressure on temporary registers since we do not have to move data around.
-+
-+Native register pairs are ordered according to CPU endiannes, following
-+the O32 calling convention for passing 64-bit arguments and return values.
-+The eBPF return value, arguments and callee-saved registers are mapped to
-+their native MIPS equivalents.
-+
-+Since the 32 highest bits in the eBPF FP (frame pointer) register are
-+always zero, only one general-purpose register is actually needed for the
-+mapping. The MIPS fp register is used for this purpose. The high bits are
-+mapped to MIPS register r0. This saves us one CPU register, which is much
-+needed for temporaries, while still allowing us to treat the R10 (FP)
-+register just like any other eBPF register in the JIT.
-+
-+The MIPS gp (global pointer) and at (assembler temporary) registers are
-+used as internal temporary registers for constant blinding. CPU registers
-+t6-t9 are used internally by the JIT when constructing more complex 64-bit
-+operations. This is precisely what is needed - two registers to store an
-+operand value, and two more as scratch registers when performing the
-+operation.
-+
-+The register mapping is shown below.
-+
-+    R0 - $v1, $v0   return value
-+    R1 - $a1, $a0   argument 1, passed in registers
-+    R2 - $a3, $a2   argument 2, passed in registers
-+    R3 - $t1, $t0   argument 3, passed on stack
-+    R4 - $t3, $t2   argument 4, passed on stack
-+    R5 - $t4, $t3   argument 5, passed on stack
-+    R6 - $s1, $s0   callee-saved
-+    R7 - $s3, $s2   callee-saved
-+    R8 - $s5, $s4   callee-saved
-+    R9 - $s7, $s6   callee-saved
-+    FP - $r0, $fp   32-bit frame pointer
-+    AX - $gp, $at   constant-blinding
-+         $t6 - $t9  unallocated, JIT temporaries
-+
-+Jump offsets
-+============
-+The JIT tries to map all conditional JMP operations to MIPS conditional
-+PC-relative branches. The MIPS branch offset field is 18 bits, in bytes,
-+which is equivalent to the eBPF 16-bit instruction offset. However, since
-+the JIT may emit more than one CPU instruction per eBPF instruction, the
-+field width may overflow. If that happens, the JIT converts the long
-+conditional jump to a short PC-relative branch with the condition
-+inverted, jumping over a long unconditional absolute jmp (j).
-+
-+This conversion will change the instruction offset mapping used for jumps,
-+and may in turn result in more branch offset overflows. The JIT therefore
-+dry-runs the translation until no more branches are converted and the
-+offsets do not change anymore. There is an upper bound on this of course,
-+and if the JIT hits that limit, the last two iterations are run with all
-+branches being converted.
-+
-+Tail call count
-+===============
-+The current tail call count is stored in the 16-byte area of the caller's
-+stack frame that is reserved for the callee in the o32 ABI. The value is
-+initialized in the prologue, and propagated to the tail-callee by skipping
-+the initialization instructions when emitting the tail call.
-+
-+Signed-off-by: Johan Almbladh <johan.almbladh@anyfinetworks.com>
++++ b/target/linux/generic/backport-5.15/020-v6.1-01-mm-x86-arm64-add-arch_has_hw_pte_young.patch
+@@ -0,0 +1,169 @@
++From a8e6015d9534f39abc08e6804566af059e498a60 Mon Sep 17 00:00:00 2001
++From: Yu Zhao <yuzhao@google.com>
++Date: Wed, 4 Aug 2021 01:31:34 -0600
++Subject: [PATCH 01/10] mm: x86, arm64: add arch_has_hw_pte_young()
++
++Some architectures automatically set the accessed bit in PTEs, e.g.,
++x86 and arm64 v8.2. On architectures that do not have this capability,
++clearing the accessed bit in a PTE triggers a page fault following the
++TLB miss of this PTE.
++
++Being aware of this capability can help make better decisions, i.e.,
++whether to limit the size of each batch of PTEs and the burst of
++batches when clearing the accessed bit.
++
++Signed-off-by: Yu Zhao <yuzhao@google.com>
++Change-Id: Ib49b44fb56df3333a2ff1fcc496fb1980b976e7a
 +---
-+ create mode 100644 arch/mips/net/bpf_jit_comp.c
-+ create mode 100644 arch/mips/net/bpf_jit_comp.h
-+ create mode 100644 arch/mips/net/bpf_jit_comp32.c
-+
-+--- a/arch/mips/net/Makefile
-++++ b/arch/mips/net/Makefile
-+@@ -2,4 +2,9 @@
-+ # MIPS networking code
++ arch/arm64/include/asm/cpufeature.h |  5 +++++
++ arch/arm64/include/asm/pgtable.h    | 13 ++++++++-----
++ arch/arm64/kernel/cpufeature.c      | 10 ++++++++++
++ arch/arm64/tools/cpucaps            |  1 +
++ arch/x86/include/asm/pgtable.h      |  6 +++---
++ include/linux/pgtable.h             | 13 +++++++++++++
++ mm/memory.c                         | 14 +-------------
++ 7 files changed, 41 insertions(+), 21 deletions(-)
++
++--- a/arch/arm64/include/asm/cpufeature.h
+++++ b/arch/arm64/include/asm/cpufeature.h
++@@ -808,6 +808,11 @@ static inline bool system_supports_tlb_r
++ 		cpus_have_const_cap(ARM64_HAS_TLB_RANGE);
++ }
 + 
-+ obj-$(CONFIG_MIPS_CBPF_JIT) += bpf_jit.o bpf_jit_asm.o
-+-obj-$(CONFIG_MIPS_EBPF_JIT) += ebpf_jit.o
+++static inline bool system_has_hw_af(void)
+++{
+++	return IS_ENABLED(CONFIG_ARM64_HW_AFDBM) && cpus_have_const_cap(ARM64_HW_AF);
+++}
 ++
-++ifeq ($(CONFIG_32BIT),y)
-++        obj-$(CONFIG_MIPS_EBPF_JIT) += bpf_jit_comp.o bpf_jit_comp32.o
-++else
-++        obj-$(CONFIG_MIPS_EBPF_JIT) += ebpf_jit.o
-++endif
-+--- /dev/null
-++++ b/arch/mips/net/bpf_jit_comp.c
-+@@ -0,0 +1,1020 @@
-++// SPDX-License-Identifier: GPL-2.0-only
++ extern int do_emulate_mrs(struct pt_regs *regs, u32 sys_reg, u32 rt);
++ 
++ static inline u32 id_aa64mmfr0_parange_to_phys_shift(int parange)
++--- a/arch/arm64/include/asm/pgtable.h
+++++ b/arch/arm64/include/asm/pgtable.h
++@@ -999,13 +999,16 @@ static inline void update_mmu_cache(stru
++  * page after fork() + CoW for pfn mappings. We don't always have a
++  * hardware-managed access flag on arm64.
++  */
++-static inline bool arch_faults_on_old_pte(void)
+++static inline bool arch_has_hw_pte_young(bool local)
++ {
++-	WARN_ON(preemptible());
+++	if (local) {
+++		WARN_ON(preemptible());
+++		return cpu_has_hw_af();
+++	}
++ 
++-	return !cpu_has_hw_af();
+++	return system_has_hw_af();
++ }
++-#define arch_faults_on_old_pte		arch_faults_on_old_pte
+++#define arch_has_hw_pte_young		arch_has_hw_pte_young
++ 
++ /*
++  * Experimentally, it's cheap to set the access flag in hardware and we
++@@ -1013,7 +1016,7 @@ static inline bool arch_faults_on_old_pt
++  */
++ static inline bool arch_wants_old_prefaulted_pte(void)
++ {
++-	return !arch_faults_on_old_pte();
+++	return arch_has_hw_pte_young(true);
++ }
++ #define arch_wants_old_prefaulted_pte	arch_wants_old_prefaulted_pte
++ 
++--- a/arch/arm64/kernel/cpufeature.c
+++++ b/arch/arm64/kernel/cpufeature.c
++@@ -2197,6 +2197,16 @@ static const struct arm64_cpu_capabiliti
++ 		.matches = has_hw_dbm,
++ 		.cpu_enable = cpu_enable_hw_dbm,
++ 	},
+++	{
+++		.desc = "Hardware update of the Access flag",
+++		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
+++		.capability = ARM64_HW_AF,
+++		.sys_reg = SYS_ID_AA64MMFR1_EL1,
+++		.sign = FTR_UNSIGNED,
+++		.field_pos = ID_AA64MMFR1_HADBS_SHIFT,
+++		.min_field_value = 1,
+++		.matches = has_cpuid_feature,
+++	},
++ #endif
++ 	{
++ 		.desc = "CRC32 instructions",
++--- a/arch/arm64/tools/cpucaps
+++++ b/arch/arm64/tools/cpucaps
++@@ -35,6 +35,7 @@ HAS_STAGE2_FWB
++ HAS_SYSREG_GIC_CPUIF
++ HAS_TLB_RANGE
++ HAS_VIRT_HOST_EXTN
+++HW_AF
++ HW_DBM
++ KVM_PROTECTED_MODE
++ MISMATCHED_CACHE_TYPE
++--- a/arch/x86/include/asm/pgtable.h
+++++ b/arch/x86/include/asm/pgtable.h
++@@ -1397,10 +1397,10 @@ static inline bool arch_has_pfn_modify_c
++ 	return boot_cpu_has_bug(X86_BUG_L1TF);
++ }
++ 
++-#define arch_faults_on_old_pte arch_faults_on_old_pte
++-static inline bool arch_faults_on_old_pte(void)
+++#define arch_has_hw_pte_young arch_has_hw_pte_young
+++static inline bool arch_has_hw_pte_young(bool local)
++ {
++-	return false;
+++	return true;
++ }
++ 
++ #endif	/* __ASSEMBLY__ */
++--- a/include/linux/pgtable.h
+++++ b/include/linux/pgtable.h
++@@ -259,6 +259,19 @@ static inline int pmdp_clear_flush_young
++ #endif /* CONFIG_TRANSPARENT_HUGEPAGE */
++ #endif
++ 
+++#ifndef arch_has_hw_pte_young
 ++/*
-++ * Just-In-Time compiler for eBPF bytecode on MIPS.
-++ * Implementation of JIT functions common to 32-bit and 64-bit CPUs.
+++ * Return whether the accessed bit is supported by the local CPU or all CPUs.
 ++ *
-++ * Copyright (c) 2021 Anyfi Networks AB.
-++ * Author: Johan Almbladh <johan.almbladh@gmail.com>
-++ *
-++ * Based on code and ideas from
-++ * Copyright (c) 2017 Cavium, Inc.
-++ * Copyright (c) 2017 Shubham Bansal <illusionist.neo@gmail.com>
-++ * Copyright (c) 2011 Mircea Gherzan <mgherzan@gmail.com>
+++ * Those arches which have hw access flag feature need to implement their own
+++ * helper. By default, "false" means pagefault will be hit on old pte.
 ++ */
+++static inline bool arch_has_hw_pte_young(bool local)
+++{
+++	return false;
+++}
+++#endif
 ++
-++/*
-++ * Code overview
-++ * =============
-++ *
-++ * - bpf_jit_comp.h
-++ *   Common definitions and utilities.
-++ *
-++ * - bpf_jit_comp.c
-++ *   Implementation of JIT top-level logic and exported JIT API functions.
-++ *   Implementation of internal operations shared by 32-bit and 64-bit code.
-++ *   JMP and ALU JIT control code, register control code, shared ALU and
-++ *   JMP/JMP32 JIT operations.
-++ *
-++ * - bpf_jit_comp32.c
-++ *   Implementation of functions to JIT prologue, epilogue and a single eBPF
-++ *   instruction for 32-bit MIPS CPUs. The functions use shared operations
-++ *   where possible, and implement the rest for 32-bit MIPS such as ALU64
-++ *   operations.
-++ *
-++ * - bpf_jit_comp64.c
-++ *   Ditto, for 64-bit MIPS CPUs.
-++ *
-++ * Zero and sign extension
-++ * ========================
-++ * 32-bit MIPS instructions on 64-bit MIPS registers use sign extension,
-++ * but the eBPF instruction set mandates zero extension. We let the verifier
-++ * insert explicit zero-extensions after 32-bit ALU operations, both for
-++ * 32-bit and 64-bit MIPS JITs. Conditional JMP32 operations on 64-bit MIPs
-++ * are JITed with sign extensions inserted when so expected.
-++ *
-++ * ALU operations
-++ * ==============
-++ * ALU operations on 32/64-bit MIPS and ALU64 operations on 64-bit MIPS are
-++ * JITed in the following steps. ALU64 operations on 32-bit MIPS are more
-++ * complicated and therefore only processed by special implementations in
-++ * step (3).
-++ *
-++ * 1) valid_alu_i:
-++ *    Determine if an immediate operation can be emitted as such, or if
-++ *    we must fall back to the register version.
-++ *
-++ * 2) rewrite_alu_i:
-++ *    Convert BPF operation and immediate value to a canonical form for
-++ *    JITing. In some degenerate cases this form may be a no-op.
-++ *
-++ * 3) emit_alu_{i,i64,r,64}:
-++ *    Emit instructions for an ALU or ALU64 immediate or register operation.
-++ *
-++ * JMP operations
-++ * ==============
-++ * JMP and JMP32 operations require an JIT instruction offset table for
-++ * translating the jump offset. This table is computed by dry-running the
-++ * JIT without actually emitting anything. However, the computed PC-relative
-++ * offset may overflow the 18-bit offset field width of the native MIPS
-++ * branch instruction. In such cases, the long jump is converted into the
-++ * following sequence.
-++ *
-++ *    <branch> !<cond> +2    Inverted PC-relative branch
-++ *    nop                    Delay slot
-++ *    j <offset>             Unconditional absolute long jump
-++ *    nop                    Delay slot
-++ *
-++ * Since this converted sequence alters the offset table, all offsets must
-++ * be re-calculated. This may in turn trigger new branch conversions, so
-++ * the process is repeated until no further changes are made. Normally it
-++ * completes in 1-2 iterations. If JIT_MAX_ITERATIONS should reached, we
-++ * fall back to converting every remaining jump operation. The branch
-++ * conversion is independent of how the JMP or JMP32 condition is JITed.
-++ *
-++ * JMP32 and JMP operations are JITed as follows.
-++ *
-++ * 1) setup_jmp_{i,r}:
-++ *    Convert jump conditional and offset into a form that can be JITed.
-++ *    This form may be a no-op, a canonical form, or an inverted PC-relative
-++ *    jump if branch conversion is necessary.
-++ *
-++ * 2) valid_jmp_i:
-++ *    Determine if an immediate operations can be emitted as such, or if
-++ *    we must fall back to the register version. Applies to JMP32 for 32-bit
-++ *    MIPS, and both JMP and JMP32 for 64-bit MIPS.
-++ *
-++ * 3) emit_jmp_{i,i64,r,r64}:
-++ *    Emit instructions for an JMP or JMP32 immediate or register operation.
-++ *
-++ * 4) finish_jmp_{i,r}:
-++ *    Emit any instructions needed to finish the jump. This includes a nop
-++ *    for the delay slot if a branch was emitted, and a long absolute jump
-++ *    if the branch was converted.
-++ */
++ #ifndef __HAVE_ARCH_PTEP_GET_AND_CLEAR
++ static inline pte_t ptep_get_and_clear(struct mm_struct *mm,
++ 				       unsigned long address,
++--- a/mm/memory.c
+++++ b/mm/memory.c
++@@ -121,18 +121,6 @@ int randomize_va_space __read_mostly =
++ 					2;
++ #endif
++ 
++-#ifndef arch_faults_on_old_pte
++-static inline bool arch_faults_on_old_pte(void)
++-{
++-	/*
++-	 * Those arches which don't have hw access flag feature need to
++-	 * implement their own helper. By default, "true" means pagefault
++-	 * will be hit on old pte.
++-	 */
++-	return true;
++-}
++-#endif
++-
++ #ifndef arch_wants_old_prefaulted_pte
++ static inline bool arch_wants_old_prefaulted_pte(void)
++ {
++@@ -2782,7 +2770,7 @@ static inline bool cow_user_page(struct
++ 	 * On architectures with software "accessed" bits, we would
++ 	 * take a double page fault, so mark it accessed here.
++ 	 */
++-	if (arch_faults_on_old_pte() && !pte_young(vmf->orig_pte)) {
+++	if (!arch_has_hw_pte_young(true) && !pte_young(vmf->orig_pte)) {
++ 		pte_t entry;
++ 
++ 		vmf->pte = pte_offset_map_lock(mm, vmf->pmd, addr, &vmf->ptl);
+diff --git a/target/linux/generic/backport-5.15/020-v6.1-02-mm-x86-add-CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG.patch b/target/linux/generic/backport-5.15/020-v6.1-02-mm-x86-add-CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG.patch
+new file mode 100644
+index 0000000000..785af275f5
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/020-v6.1-02-mm-x86-add-CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG.patch
+@@ -0,0 +1,111 @@
++From f8b663bbfa30af5515e222fd74df20ea4e8393a2 Mon Sep 17 00:00:00 2001
++From: Yu Zhao <yuzhao@google.com>
++Date: Sat, 26 Sep 2020 21:17:18 -0600
++Subject: [PATCH 02/10] mm: x86: add CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG
++
++Some architectures support the accessed bit on non-leaf PMD entries,
++e.g., x86_64 sets the accessed bit on a non-leaf PMD entry when using
++it as part of linear address translation [1]. As an optimization, page
++table walkers who are interested in the accessed bit can skip the PTEs
++under a non-leaf PMD entry if the accessed bit is cleared on this PMD
++entry.
++
++Although an inline function may be preferable, this capability is
++added as a configuration option to look consistent when used with the
++existing macros.
++
++[1]: Intel 64 and IA-32 Architectures Software Developer's Manual
++     Volume 3 (June 2021), section 4.8
++
++Signed-off-by: Yu Zhao <yuzhao@google.com>
++Tested-by: Konstantin Kharlamov <Hi-Angel@yandex.ru>
++Change-Id: I1a17be3ae926f721f7b17ea1539e5c39e8c4f9a8
++---
++ arch/Kconfig                   | 9 +++++++++
++ arch/x86/Kconfig               | 1 +
++ arch/x86/include/asm/pgtable.h | 3 ++-
++ arch/x86/mm/pgtable.c          | 5 ++++-
++ include/linux/pgtable.h        | 4 ++--
++ 5 files changed, 18 insertions(+), 4 deletions(-)
++
++--- a/arch/Kconfig
+++++ b/arch/Kconfig
++@@ -1295,6 +1295,15 @@ config ARCH_HAS_ELFCORE_COMPAT
++ config ARCH_HAS_PARANOID_L1D_FLUSH
++ 	bool
++ 
+++config ARCH_HAS_NONLEAF_PMD_YOUNG
+++	bool
+++	depends on PGTABLE_LEVELS > 2
+++	help
+++	  Architectures that select this are able to set the accessed bit on
+++	  non-leaf PMD entries in addition to leaf PTE entries where pages are
+++	  mapped. For them, page table walkers that clear the accessed bit may
+++	  stop at non-leaf PMD entries if they do not see the accessed bit.
+++
++ source "kernel/gcov/Kconfig"
++ 
++ source "scripts/gcc-plugins/Kconfig"
++--- a/arch/x86/Kconfig
+++++ b/arch/x86/Kconfig
++@@ -84,6 +84,7 @@ config X86
++ 	select ARCH_HAS_PMEM_API		if X86_64
++ 	select ARCH_HAS_PTE_DEVMAP		if X86_64
++ 	select ARCH_HAS_PTE_SPECIAL
+++	select ARCH_HAS_NONLEAF_PMD_YOUNG	if X86_64
++ 	select ARCH_HAS_UACCESS_FLUSHCACHE	if X86_64
++ 	select ARCH_HAS_COPY_MC			if X86_64
++ 	select ARCH_HAS_SET_MEMORY
++--- a/arch/x86/include/asm/pgtable.h
+++++ b/arch/x86/include/asm/pgtable.h
++@@ -817,7 +817,8 @@ static inline unsigned long pmd_page_vad
++ 
++ static inline int pmd_bad(pmd_t pmd)
++ {
++-	return (pmd_flags(pmd) & ~_PAGE_USER) != _KERNPG_TABLE;
+++	return (pmd_flags(pmd) & ~(_PAGE_USER | _PAGE_ACCESSED)) !=
+++	       (_KERNPG_TABLE & ~_PAGE_ACCESSED);
++ }
++ 
++ static inline unsigned long pages_to_mb(unsigned long npg)
++--- a/arch/x86/mm/pgtable.c
+++++ b/arch/x86/mm/pgtable.c
++@@ -550,7 +550,7 @@ int ptep_test_and_clear_young(struct vm_
++ 	return ret;
++ }
++ 
++-#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+++#if defined(CONFIG_TRANSPARENT_HUGEPAGE) || defined(CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG)
++ int pmdp_test_and_clear_young(struct vm_area_struct *vma,
++ 			      unsigned long addr, pmd_t *pmdp)
++ {
++@@ -562,6 +562,9 @@ int pmdp_test_and_clear_young(struct vm_
++ 
++ 	return ret;
++ }
+++#endif
 ++
-++#include <linux/limits.h>
-++#include <linux/bitops.h>
-++#include <linux/errno.h>
-++#include <linux/filter.h>
-++#include <linux/bpf.h>
-++#include <linux/slab.h>
-++#include <asm/bitops.h>
-++#include <asm/cacheflush.h>
-++#include <asm/cpu-features.h>
-++#include <asm/isa-rev.h>
-++#include <asm/uasm.h>
+++#ifdef CONFIG_TRANSPARENT_HUGEPAGE
++ int pudp_test_and_clear_young(struct vm_area_struct *vma,
++ 			      unsigned long addr, pud_t *pudp)
++ {
++--- a/include/linux/pgtable.h
+++++ b/include/linux/pgtable.h
++@@ -212,7 +212,7 @@ static inline int ptep_test_and_clear_yo
++ #endif
++ 
++ #ifndef __HAVE_ARCH_PMDP_TEST_AND_CLEAR_YOUNG
++-#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+++#if defined(CONFIG_TRANSPARENT_HUGEPAGE) || defined(CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG)
++ static inline int pmdp_test_and_clear_young(struct vm_area_struct *vma,
++ 					    unsigned long address,
++ 					    pmd_t *pmdp)
++@@ -233,7 +233,7 @@ static inline int pmdp_test_and_clear_yo
++ 	BUILD_BUG();
++ 	return 0;
++ }
++-#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
+++#endif /* CONFIG_TRANSPARENT_HUGEPAGE || CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG */
++ #endif
++ 
++ #ifndef __HAVE_ARCH_PTEP_CLEAR_YOUNG_FLUSH
+diff --git a/target/linux/generic/backport-5.15/020-v6.1-03-mm-vmscan.c-refactor-shrink_node.patch b/target/linux/generic/backport-5.15/020-v6.1-03-mm-vmscan.c-refactor-shrink_node.patch
+new file mode 100644
+index 0000000000..f466f1105c
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/020-v6.1-03-mm-vmscan.c-refactor-shrink_node.patch
+@@ -0,0 +1,224 @@
++From a810f8e2f1bdd0707eaf05c8b4ba84a3ff2801bd Mon Sep 17 00:00:00 2001
++From: Yu Zhao <yuzhao@google.com>
++Date: Sun, 27 Sep 2020 20:49:08 -0600
++Subject: [PATCH 03/10] mm/vmscan.c: refactor shrink_node()
++
++This patch refactors shrink_node(). This will make the upcoming
++changes to mm/vmscan.c more readable.
++
++Signed-off-by: Yu Zhao <yuzhao@google.com>
++Tested-by: Konstantin Kharlamov <Hi-Angel@yandex.ru>
++Change-Id: Iae734b5b4030205b7db6e8c841f747b6f6ae1a04
++---
++ mm/vmscan.c | 186 +++++++++++++++++++++++++++-------------------------
++ 1 file changed, 98 insertions(+), 88 deletions(-)
++
++--- a/mm/vmscan.c
+++++ b/mm/vmscan.c
++@@ -2562,6 +2562,103 @@ enum scan_balance {
++ 	SCAN_FILE,
++ };
++ 
+++static void prepare_scan_count(pg_data_t *pgdat, struct scan_control *sc)
+++{
+++	unsigned long file;
+++	struct lruvec *target_lruvec;
 ++
-++#include "bpf_jit_comp.h"
+++	target_lruvec = mem_cgroup_lruvec(sc->target_mem_cgroup, pgdat);
 ++
-++/* Convenience macros for descriptor access */
-++#define CONVERTED(desc)	((desc) & JIT_DESC_CONVERT)
-++#define INDEX(desc)	((desc) & ~JIT_DESC_CONVERT)
+++	/*
+++	 * Determine the scan balance between anon and file LRUs.
+++	 */
+++	spin_lock_irq(&target_lruvec->lru_lock);
+++	sc->anon_cost = target_lruvec->anon_cost;
+++	sc->file_cost = target_lruvec->file_cost;
+++	spin_unlock_irq(&target_lruvec->lru_lock);
 ++
-++/*
-++ * Push registers on the stack, starting at a given depth from the stack
-++ * pointer and increasing. The next depth to be written is returned.
-++ */
-++int push_regs(struct jit_context *ctx, u32 mask, u32 excl, int depth)
-++{
-++	int reg;
+++	/*
+++	 * Target desirable inactive:active list ratios for the anon
+++	 * and file LRU lists.
+++	 */
+++	if (!sc->force_deactivate) {
+++		unsigned long refaults;
+++
+++		refaults = lruvec_page_state(target_lruvec,
+++				WORKINGSET_ACTIVATE_ANON);
+++		if (refaults != target_lruvec->refaults[0] ||
+++			inactive_is_low(target_lruvec, LRU_INACTIVE_ANON))
+++			sc->may_deactivate |= DEACTIVATE_ANON;
+++		else
+++			sc->may_deactivate &= ~DEACTIVATE_ANON;
 ++
-++	for (reg = 0; reg < BITS_PER_BYTE * sizeof(mask); reg++)
-++		if (mask & BIT(reg)) {
-++			if ((excl & BIT(reg)) == 0) {
-++				if (sizeof(long) == 4)
-++					emit(ctx, sw, reg, depth, MIPS_R_SP);
-++				else /* sizeof(long) == 8 */
-++					emit(ctx, sd, reg, depth, MIPS_R_SP);
-++			}
-++			depth += sizeof(long);
-++		}
+++		/*
+++		 * When refaults are being observed, it means a new
+++		 * workingset is being established. Deactivate to get
+++		 * rid of any stale active pages quickly.
+++		 */
+++		refaults = lruvec_page_state(target_lruvec,
+++				WORKINGSET_ACTIVATE_FILE);
+++		if (refaults != target_lruvec->refaults[1] ||
+++		    inactive_is_low(target_lruvec, LRU_INACTIVE_FILE))
+++			sc->may_deactivate |= DEACTIVATE_FILE;
+++		else
+++			sc->may_deactivate &= ~DEACTIVATE_FILE;
+++	} else
+++		sc->may_deactivate = DEACTIVATE_ANON | DEACTIVATE_FILE;
 ++
-++	ctx->stack_used = max((int)ctx->stack_used, depth);
-++	return depth;
-++}
+++	/*
+++	 * If we have plenty of inactive file pages that aren't
+++	 * thrashing, try to reclaim those first before touching
+++	 * anonymous pages.
+++	 */
+++	file = lruvec_page_state(target_lruvec, NR_INACTIVE_FILE);
+++	if (file >> sc->priority && !(sc->may_deactivate & DEACTIVATE_FILE))
+++		sc->cache_trim_mode = 1;
+++	else
+++		sc->cache_trim_mode = 0;
 ++
-++/*
-++ * Pop registers from the stack, starting at a given depth from the stack
-++ * pointer and increasing. The next depth to be read is returned.
-++ */
-++int pop_regs(struct jit_context *ctx, u32 mask, u32 excl, int depth)
-++{
-++	int reg;
+++	/*
+++	 * Prevent the reclaimer from falling into the cache trap: as
+++	 * cache pages start out inactive, every cache fault will tip
+++	 * the scan balance towards the file LRU.  And as the file LRU
+++	 * shrinks, so does the window for rotation from references.
+++	 * This means we have a runaway feedback loop where a tiny
+++	 * thrashing file LRU becomes infinitely more attractive than
+++	 * anon pages.  Try to detect this based on file LRU size.
+++	 */
+++	if (!cgroup_reclaim(sc)) {
+++		unsigned long total_high_wmark = 0;
+++		unsigned long free, anon;
+++		int z;
 ++
-++	for (reg = 0; reg < BITS_PER_BYTE * sizeof(mask); reg++)
-++		if (mask & BIT(reg)) {
-++			if ((excl & BIT(reg)) == 0) {
-++				if (sizeof(long) == 4)
-++					emit(ctx, lw, reg, depth, MIPS_R_SP);
-++				else /* sizeof(long) == 8 */
-++					emit(ctx, ld, reg, depth, MIPS_R_SP);
-++			}
-++			depth += sizeof(long);
-++		}
+++		free = sum_zone_node_page_state(pgdat->node_id, NR_FREE_PAGES);
+++		file = node_page_state(pgdat, NR_ACTIVE_FILE) +
+++			   node_page_state(pgdat, NR_INACTIVE_FILE);
 ++
-++	return depth;
-++}
+++		for (z = 0; z < MAX_NR_ZONES; z++) {
+++			struct zone *zone = &pgdat->node_zones[z];
 ++
-++/* Compute the 28-bit jump target address from a BPF program location */
-++int get_target(struct jit_context *ctx, u32 loc)
-++{
-++	u32 index = INDEX(ctx->descriptors[loc]);
-++	unsigned long pc = (unsigned long)&ctx->target[ctx->jit_index];
-++	unsigned long addr = (unsigned long)&ctx->target[index];
+++			if (!managed_zone(zone))
+++				continue;
 ++
-++	if (!ctx->target)
-++		return 0;
+++			total_high_wmark += high_wmark_pages(zone);
+++		}
 ++
-++	if ((addr ^ pc) & ~MIPS_JMP_MASK)
-++		return -1;
+++		/*
+++		 * Consider anon: if that's low too, this isn't a
+++		 * runaway file reclaim problem, but rather just
+++		 * extreme pressure. Reclaim as per usual then.
+++		 */
+++		anon = node_page_state(pgdat, NR_INACTIVE_ANON);
 ++
-++	return addr & MIPS_JMP_MASK;
+++		sc->file_is_tiny =
+++			file + free <= total_high_wmark &&
+++			!(sc->may_deactivate & DEACTIVATE_ANON) &&
+++			anon >> sc->priority;
+++	}
 ++}
 ++
-++/* Compute the PC-relative offset to relative BPF program offset */
-++int get_offset(const struct jit_context *ctx, int off)
++ /*
++  * Determine how aggressively the anon and file LRU lists should be
++  * scanned.  The relative value of each set of LRU lists is determined
++@@ -3032,7 +3129,6 @@ static void shrink_node(pg_data_t *pgdat
++ 	unsigned long nr_reclaimed, nr_scanned;
++ 	struct lruvec *target_lruvec;
++ 	bool reclaimable = false;
++-	unsigned long file;
++ 
++ 	target_lruvec = mem_cgroup_lruvec(sc->target_mem_cgroup, pgdat);
++ 
++@@ -3048,93 +3144,7 @@ again:
++ 	nr_reclaimed = sc->nr_reclaimed;
++ 	nr_scanned = sc->nr_scanned;
++ 
++-	/*
++-	 * Determine the scan balance between anon and file LRUs.
++-	 */
++-	spin_lock_irq(&target_lruvec->lru_lock);
++-	sc->anon_cost = target_lruvec->anon_cost;
++-	sc->file_cost = target_lruvec->file_cost;
++-	spin_unlock_irq(&target_lruvec->lru_lock);
++-
++-	/*
++-	 * Target desirable inactive:active list ratios for the anon
++-	 * and file LRU lists.
++-	 */
++-	if (!sc->force_deactivate) {
++-		unsigned long refaults;
++-
++-		refaults = lruvec_page_state(target_lruvec,
++-				WORKINGSET_ACTIVATE_ANON);
++-		if (refaults != target_lruvec->refaults[0] ||
++-			inactive_is_low(target_lruvec, LRU_INACTIVE_ANON))
++-			sc->may_deactivate |= DEACTIVATE_ANON;
++-		else
++-			sc->may_deactivate &= ~DEACTIVATE_ANON;
++-
++-		/*
++-		 * When refaults are being observed, it means a new
++-		 * workingset is being established. Deactivate to get
++-		 * rid of any stale active pages quickly.
++-		 */
++-		refaults = lruvec_page_state(target_lruvec,
++-				WORKINGSET_ACTIVATE_FILE);
++-		if (refaults != target_lruvec->refaults[1] ||
++-		    inactive_is_low(target_lruvec, LRU_INACTIVE_FILE))
++-			sc->may_deactivate |= DEACTIVATE_FILE;
++-		else
++-			sc->may_deactivate &= ~DEACTIVATE_FILE;
++-	} else
++-		sc->may_deactivate = DEACTIVATE_ANON | DEACTIVATE_FILE;
++-
++-	/*
++-	 * If we have plenty of inactive file pages that aren't
++-	 * thrashing, try to reclaim those first before touching
++-	 * anonymous pages.
++-	 */
++-	file = lruvec_page_state(target_lruvec, NR_INACTIVE_FILE);
++-	if (file >> sc->priority && !(sc->may_deactivate & DEACTIVATE_FILE))
++-		sc->cache_trim_mode = 1;
++-	else
++-		sc->cache_trim_mode = 0;
++-
++-	/*
++-	 * Prevent the reclaimer from falling into the cache trap: as
++-	 * cache pages start out inactive, every cache fault will tip
++-	 * the scan balance towards the file LRU.  And as the file LRU
++-	 * shrinks, so does the window for rotation from references.
++-	 * This means we have a runaway feedback loop where a tiny
++-	 * thrashing file LRU becomes infinitely more attractive than
++-	 * anon pages.  Try to detect this based on file LRU size.
++-	 */
++-	if (!cgroup_reclaim(sc)) {
++-		unsigned long total_high_wmark = 0;
++-		unsigned long free, anon;
++-		int z;
++-
++-		free = sum_zone_node_page_state(pgdat->node_id, NR_FREE_PAGES);
++-		file = node_page_state(pgdat, NR_ACTIVE_FILE) +
++-			   node_page_state(pgdat, NR_INACTIVE_FILE);
++-
++-		for (z = 0; z < MAX_NR_ZONES; z++) {
++-			struct zone *zone = &pgdat->node_zones[z];
++-			if (!managed_zone(zone))
++-				continue;
++-
++-			total_high_wmark += high_wmark_pages(zone);
++-		}
++-
++-		/*
++-		 * Consider anon: if that's low too, this isn't a
++-		 * runaway file reclaim problem, but rather just
++-		 * extreme pressure. Reclaim as per usual then.
++-		 */
++-		anon = node_page_state(pgdat, NR_INACTIVE_ANON);
++-
++-		sc->file_is_tiny =
++-			file + free <= total_high_wmark &&
++-			!(sc->may_deactivate & DEACTIVATE_ANON) &&
++-			anon >> sc->priority;
++-	}
+++	prepare_scan_count(pgdat, sc);
++ 
++ 	shrink_node_memcgs(pgdat, sc);
++ 
+diff --git a/target/linux/generic/backport-5.15/020-v6.1-04-mm-multigenerational-lru-groundwork.patch b/target/linux/generic/backport-5.15/020-v6.1-04-mm-multigenerational-lru-groundwork.patch
+new file mode 100644
+index 0000000000..fbc15d8c91
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/020-v6.1-04-mm-multigenerational-lru-groundwork.patch
+@@ -0,0 +1,996 @@
++From 05f366c941ae2bb8ba21c79fafcb747a5a6b967b Mon Sep 17 00:00:00 2001
++From: Yu Zhao <yuzhao@google.com>
++Date: Mon, 25 Jan 2021 21:12:33 -0700
++Subject: [PATCH 04/10] mm: multigenerational lru: groundwork
++
++For each lruvec, evictable pages are divided into multiple
++generations. The youngest generation number is stored in
++lrugen->max_seq for both anon and file types as they are aged on an
++equal footing. The oldest generation numbers are stored in
++lrugen->min_seq[] separately for anon and file types as clean file
++pages can be evicted regardless of swap constraints. These three
++variables are monotonically increasing. Generation numbers are
++truncated into order_base_2(MAX_NR_GENS+1) bits in order to fit into
++page->flags. The sliding window technique is used to prevent truncated
++generation numbers from overlapping. Each truncated generation number
++is an index to
++lrugen->lists[MAX_NR_GENS][ANON_AND_FILE][MAX_NR_ZONES].
++
++The framework comprises two conceptually independent components: the
++aging, which produces young generations, and the eviction, which
++consumes old generations. Both can be invoked independently from user
++space for the purpose of working set estimation and proactive reclaim.
++
++The protection of hot pages and the selection of cold pages are based
++on page access types and patterns. There are two access types: one via
++page tables and the other via file descriptors. The protection of the
++former type is by design stronger because:
++  1) The uncertainty in determining the access patterns of the former
++  type is higher due to the coalesced nature of the accessed bit.
++  2) The cost of evicting the former type is higher due to the TLB
++  flushes required and the likelihood of involving I/O.
++  3) The penalty of under-protecting the former type is higher because
++  applications usually do not prepare themselves for major faults like
++  they do for blocked I/O. For example, client applications commonly
++  dedicate blocked I/O to separate threads to avoid UI janks that
++  negatively affect user experience.
++
++There are also two access patterns: one with temporal locality and the
++other without. The latter pattern, e.g., random and sequential, needs
++to be explicitly excluded to avoid weakening the protection of the
++former pattern. Generally the former type follows the former pattern
++unless MADV_SEQUENTIAL is specified and the latter type follows the
++latter pattern unless outlying refaults have been observed.
++
++Upon faulting, a page is added to the youngest generation, which
++provides the strongest protection as the eviction will not consider
++this page before the aging has scanned it at least twice. The first
++scan clears the accessed bit set during the initial fault. And the
++second scan makes sure this page has not been used since the first
++scan. A page from any other generations is brought back to the
++youngest generation whenever the aging finds the accessed bit set on
++any of the PTEs mapping this page.
++
++Unmapped pages are initially added to the oldest generation and then
++conditionally protected by tiers. This is done later [PATCH 07/10].
++
++Signed-off-by: Yu Zhao <yuzhao@google.com>
++Tested-by: Konstantin Kharlamov <Hi-Angel@yandex.ru>
++Change-Id: I71de7cd15b8dfa6f9fdd838023474693c4fee0a7
++---
++ fs/fuse/dev.c                     |   3 +-
++ include/linux/cgroup.h            |  15 +-
++ include/linux/mm.h                |  36 ++++
++ include/linux/mm_inline.h         | 182 ++++++++++++++++++++
++ include/linux/mmzone.h            |  70 ++++++++
++ include/linux/page-flags-layout.h |  19 ++-
++ include/linux/page-flags.h        |   4 +-
++ include/linux/sched.h             |   3 +
++ kernel/bounds.c                   |   3 +
++ kernel/cgroup/cgroup-internal.h   |   1 -
++ mm/huge_memory.c                  |   3 +-
++ mm/memcontrol.c                   |   1 +
++ mm/memory.c                       |   7 +
++ mm/mm_init.c                      |   6 +-
++ mm/page_alloc.c                   |   1 +
++ mm/swap.c                         |   9 +-
++ mm/swapfile.c                     |   2 +
++ mm/vmscan.c                       | 268 ++++++++++++++++++++++++++++++
++ 18 files changed, 618 insertions(+), 15 deletions(-)
++
++--- a/fs/fuse/dev.c
+++++ b/fs/fuse/dev.c
++@@ -785,7 +785,8 @@ static int fuse_check_page(struct page *
++ 	       1 << PG_active |
++ 	       1 << PG_workingset |
++ 	       1 << PG_reclaim |
++-	       1 << PG_waiters))) {
+++	       1 << PG_waiters |
+++	       LRU_GEN_MASK | LRU_REFS_MASK))) {
++ 		dump_page(page, "fuse: trying to steal weird page");
++ 		return 1;
++ 	}
++--- a/include/linux/cgroup.h
+++++ b/include/linux/cgroup.h
++@@ -432,6 +432,18 @@ static inline void cgroup_put(struct cgr
++ 	css_put(&cgrp->self);
++ }
++ 
+++extern struct mutex cgroup_mutex;
+++
+++static inline void cgroup_lock(void)
 ++{
-++	return (INDEX(ctx->descriptors[ctx->bpf_index + off]) -
-++		ctx->jit_index - 1) * sizeof(u32);
+++	mutex_lock(&cgroup_mutex);
 ++}
 ++
-++/* dst = imm (register width) */
-++void emit_mov_i(struct jit_context *ctx, u8 dst, s32 imm)
+++static inline void cgroup_unlock(void)
 ++{
-++	if (imm >= -0x8000 && imm <= 0x7fff) {
-++		emit(ctx, addiu, dst, MIPS_R_ZERO, imm);
-++	} else {
-++		emit(ctx, lui, dst, (s16)((u32)imm >> 16));
-++		emit(ctx, ori, dst, dst, (u16)(imm & 0xffff));
-++	}
-++	clobber_reg(ctx, dst);
+++	mutex_unlock(&cgroup_mutex);
 ++}
 ++
-++/* dst = src (register width) */
-++void emit_mov_r(struct jit_context *ctx, u8 dst, u8 src)
++ /**
++  * task_css_set_check - obtain a task's css_set with extra access conditions
++  * @task: the task to obtain css_set for
++@@ -446,7 +458,6 @@ static inline void cgroup_put(struct cgr
++  * as locks used during the cgroup_subsys::attach() methods.
++  */
++ #ifdef CONFIG_PROVE_RCU
++-extern struct mutex cgroup_mutex;
++ extern spinlock_t css_set_lock;
++ #define task_css_set_check(task, __c)					\
++ 	rcu_dereference_check((task)->cgroups,				\
++@@ -707,6 +718,8 @@ struct cgroup;
++ static inline u64 cgroup_id(const struct cgroup *cgrp) { return 1; }
++ static inline void css_get(struct cgroup_subsys_state *css) {}
++ static inline void css_put(struct cgroup_subsys_state *css) {}
+++static inline void cgroup_lock(void) {}
+++static inline void cgroup_unlock(void) {}
++ static inline int cgroup_attach_task_all(struct task_struct *from,
++ 					 struct task_struct *t) { return 0; }
++ static inline int cgroupstats_build(struct cgroupstats *stats,
++--- a/include/linux/mm.h
+++++ b/include/linux/mm.h
++@@ -1093,6 +1093,8 @@ vm_fault_t finish_mkwrite_fault(struct v
++ #define ZONES_PGOFF		(NODES_PGOFF - ZONES_WIDTH)
++ #define LAST_CPUPID_PGOFF	(ZONES_PGOFF - LAST_CPUPID_WIDTH)
++ #define KASAN_TAG_PGOFF		(LAST_CPUPID_PGOFF - KASAN_TAG_WIDTH)
+++#define LRU_GEN_PGOFF		(KASAN_TAG_PGOFF - LRU_GEN_WIDTH)
+++#define LRU_REFS_PGOFF		(LRU_GEN_PGOFF - LRU_REFS_WIDTH)
++ 
++ /*
++  * Define the bit shifts to access each section.  For non-existent
++@@ -1807,6 +1809,40 @@ static inline void unmap_mapping_range(s
++ 		loff_t const holebegin, loff_t const holelen, int even_cows) { }
++ #endif
++ 
+++#ifdef CONFIG_LRU_GEN
+++static inline void task_enter_nonseq_fault(void)
 ++{
-++	emit(ctx, ori, dst, src, 0);
-++	clobber_reg(ctx, dst);
+++	WARN_ON(current->in_nonseq_fault);
+++
+++	current->in_nonseq_fault = 1;
 ++}
 ++
-++/* Validate ALU immediate range */
-++bool valid_alu_i(u8 op, s32 imm)
+++static inline void task_exit_nonseq_fault(void)
+++{
+++	WARN_ON(!current->in_nonseq_fault);
+++
+++	current->in_nonseq_fault = 0;
+++}
+++
+++static inline bool task_in_nonseq_fault(void)
+++{
+++	return current->in_nonseq_fault;
+++}
+++#else
+++static inline void task_enter_nonseq_fault(void)
+++{
+++}
+++
+++static inline void task_exit_nonseq_fault(void)
+++{
+++}
+++
+++static inline bool task_in_nonseq_fault(void)
 ++{
-++	switch (BPF_OP(op)) {
-++	case BPF_NEG:
-++	case BPF_LSH:
-++	case BPF_RSH:
-++	case BPF_ARSH:
-++		/* All legal eBPF values are valid */
-++		return true;
-++	case BPF_ADD:
-++		/* imm must be 16 bits */
-++		return imm >= -0x8000 && imm <= 0x7fff;
-++	case BPF_SUB:
-++		/* -imm must be 16 bits */
-++		return imm >= -0x7fff && imm <= 0x8000;
-++	case BPF_AND:
-++	case BPF_OR:
-++	case BPF_XOR:
-++		/* imm must be 16 bits unsigned */
-++		return imm >= 0 && imm <= 0xffff;
-++	case BPF_MUL:
-++		/* imm must be zero or a positive power of two */
-++		return imm == 0 || (imm > 0 && is_power_of_2(imm));
-++	case BPF_DIV:
-++	case BPF_MOD:
-++		/* imm must be an 17-bit power of two */
-++		return (u32)imm <= 0x10000 && is_power_of_2((u32)imm);
-++	}
 ++	return false;
 ++}
+++#endif /* CONFIG_LRU_GEN */
 ++
-++/* Rewrite ALU immediate operation */
-++bool rewrite_alu_i(u8 op, s32 imm, u8 *alu, s32 *val)
++ static inline void unmap_shared_mapping_range(struct address_space *mapping,
++ 		loff_t const holebegin, loff_t const holelen)
++ {
++--- a/include/linux/mm_inline.h
+++++ b/include/linux/mm_inline.h
++@@ -79,11 +79,187 @@ static __always_inline enum lru_list pag
++ 	return lru;
++ }
++ 
+++#ifdef CONFIG_LRU_GEN
+++
+++static inline bool lru_gen_enabled(void)
 ++{
-++	bool act = true;
+++#ifdef CONFIG_LRU_GEN_ENABLED
+++	DECLARE_STATIC_KEY_TRUE(lru_gen_static_key);
 ++
-++	switch (BPF_OP(op)) {
-++	case BPF_LSH:
-++	case BPF_RSH:
-++	case BPF_ARSH:
-++	case BPF_ADD:
-++	case BPF_SUB:
-++	case BPF_OR:
-++	case BPF_XOR:
-++		/* imm == 0 is a no-op */
-++		act = imm != 0;
-++		break;
-++	case BPF_MUL:
-++		if (imm == 1) {
-++			/* dst * 1 is a no-op */
-++			act = false;
-++		} else if (imm == 0) {
-++			/* dst * 0 is dst & 0 */
-++			op = BPF_AND;
-++		} else {
-++			/* dst * (1 << n) is dst << n */
-++			op = BPF_LSH;
-++			imm = ilog2(abs(imm));
-++		}
-++		break;
-++	case BPF_DIV:
-++		if (imm == 1) {
-++			/* dst / 1 is a no-op */
-++			act = false;
-++		} else {
-++			/* dst / (1 << n) is dst >> n */
-++			op = BPF_RSH;
-++			imm = ilog2(imm);
-++		}
-++		break;
-++	case BPF_MOD:
-++		/* dst % (1 << n) is dst & ((1 << n) - 1) */
-++		op = BPF_AND;
-++		imm--;
-++		break;
-++	}
+++	return static_branch_likely(&lru_gen_static_key);
+++#else
+++	DECLARE_STATIC_KEY_FALSE(lru_gen_static_key);
 ++
-++	*alu = op;
-++	*val = imm;
-++	return act;
+++	return static_branch_unlikely(&lru_gen_static_key);
+++#endif
 ++}
 ++
-++/* ALU immediate operation (32-bit) */
-++void emit_alu_i(struct jit_context *ctx, u8 dst, s32 imm, u8 op)
+++/* Return an index within the sliding window that tracks MAX_NR_GENS generations. */
+++static inline int lru_gen_from_seq(unsigned long seq)
 ++{
-++	switch (BPF_OP(op)) {
-++	/* dst = -dst */
-++	case BPF_NEG:
-++		emit(ctx, subu, dst, MIPS_R_ZERO, dst);
-++		break;
-++	/* dst = dst & imm */
-++	case BPF_AND:
-++		emit(ctx, andi, dst, dst, (u16)imm);
-++		break;
-++	/* dst = dst | imm */
-++	case BPF_OR:
-++		emit(ctx, ori, dst, dst, (u16)imm);
-++		break;
-++	/* dst = dst ^ imm */
-++	case BPF_XOR:
-++		emit(ctx, xori, dst, dst, (u16)imm);
-++		break;
-++	/* dst = dst << imm */
-++	case BPF_LSH:
-++		emit(ctx, sll, dst, dst, imm);
-++		break;
-++	/* dst = dst >> imm */
-++	case BPF_RSH:
-++		emit(ctx, srl, dst, dst, imm);
-++		break;
-++	/* dst = dst >> imm (arithmetic) */
-++	case BPF_ARSH:
-++		emit(ctx, sra, dst, dst, imm);
-++		break;
-++	/* dst = dst + imm */
-++	case BPF_ADD:
-++		emit(ctx, addiu, dst, dst, imm);
-++		break;
-++	/* dst = dst - imm */
-++	case BPF_SUB:
-++		emit(ctx, addiu, dst, dst, -imm);
-++		break;
-++	}
-++	clobber_reg(ctx, dst);
+++	return seq % MAX_NR_GENS;
 ++}
 ++
-++/* ALU register operation (32-bit) */
-++void emit_alu_r(struct jit_context *ctx, u8 dst, u8 src, u8 op)
+++/* The youngest and the second youngest generations are counted as active. */
+++static inline bool lru_gen_is_active(struct lruvec *lruvec, int gen)
 ++{
-++	switch (BPF_OP(op)) {
-++	/* dst = dst & src */
-++	case BPF_AND:
-++		emit(ctx, and, dst, dst, src);
-++		break;
-++	/* dst = dst | src */
-++	case BPF_OR:
-++		emit(ctx, or, dst, dst, src);
-++		break;
-++	/* dst = dst ^ src */
-++	case BPF_XOR:
-++		emit(ctx, xor, dst, dst, src);
-++		break;
-++	/* dst = dst << src */
-++	case BPF_LSH:
-++		emit(ctx, sllv, dst, dst, src);
-++		break;
-++	/* dst = dst >> src */
-++	case BPF_RSH:
-++		emit(ctx, srlv, dst, dst, src);
-++		break;
-++	/* dst = dst >> src (arithmetic) */
-++	case BPF_ARSH:
-++		emit(ctx, srav, dst, dst, src);
-++		break;
-++	/* dst = dst + src */
-++	case BPF_ADD:
-++		emit(ctx, addu, dst, dst, src);
-++		break;
-++	/* dst = dst - src */
-++	case BPF_SUB:
-++		emit(ctx, subu, dst, dst, src);
-++		break;
-++	/* dst = dst * src */
-++	case BPF_MUL:
-++		if (cpu_has_mips32r1 || cpu_has_mips32r6) {
-++			emit(ctx, mul, dst, dst, src);
-++		} else {
-++			emit(ctx, multu, dst, src);
-++			emit(ctx, mflo, dst);
-++		}
-++		break;
-++	/* dst = dst / src */
-++	case BPF_DIV:
-++		if (cpu_has_mips32r6) {
-++			emit(ctx, divu_r6, dst, dst, src);
-++		} else {
-++			emit(ctx, divu, dst, src);
-++			emit(ctx, mflo, dst);
-++		}
-++		break;
-++	/* dst = dst % src */
-++	case BPF_MOD:
-++		if (cpu_has_mips32r6) {
-++			emit(ctx, modu, dst, dst, src);
-++		} else {
-++			emit(ctx, divu, dst, src);
-++			emit(ctx, mfhi, dst);
-++		}
-++		break;
-++	}
-++	clobber_reg(ctx, dst);
+++	unsigned long max_seq = lruvec->evictable.max_seq;
+++
+++	VM_BUG_ON(gen >= MAX_NR_GENS);
+++
+++	return gen == lru_gen_from_seq(max_seq) || gen == lru_gen_from_seq(max_seq - 1);
 ++}
 ++
-++/* Atomic read-modify-write (32-bit) */
-++void emit_atomic_r(struct jit_context *ctx, u8 dst, u8 src, s16 off, u8 code)
+++/* Update the sizes of the multigenerational lru lists. */
+++static inline void lru_gen_update_size(struct page *page, struct lruvec *lruvec,
+++				       int old_gen, int new_gen)
 ++{
-++	emit(ctx, ll, MIPS_R_T9, off, dst);
-++	switch (code) {
-++	case BPF_ADD:
-++		emit(ctx, addu, MIPS_R_T8, MIPS_R_T9, src);
-++		break;
-++	case BPF_AND:
-++		emit(ctx, and, MIPS_R_T8, MIPS_R_T9, src);
-++		break;
-++	case BPF_OR:
-++		emit(ctx, or, MIPS_R_T8, MIPS_R_T9, src);
-++		break;
-++	case BPF_XOR:
-++		emit(ctx, xor, MIPS_R_T8, MIPS_R_T9, src);
-++		break;
+++	int type = page_is_file_lru(page);
+++	int zone = page_zonenum(page);
+++	int delta = thp_nr_pages(page);
+++	enum lru_list lru = type * LRU_FILE;
+++	struct lrugen *lrugen = &lruvec->evictable;
+++
+++	lockdep_assert_held(&lruvec->lru_lock);
+++	VM_BUG_ON(old_gen != -1 && old_gen >= MAX_NR_GENS);
+++	VM_BUG_ON(new_gen != -1 && new_gen >= MAX_NR_GENS);
+++	VM_BUG_ON(old_gen == -1 && new_gen == -1);
+++
+++	if (old_gen >= 0)
+++		WRITE_ONCE(lrugen->sizes[old_gen][type][zone],
+++			   lrugen->sizes[old_gen][type][zone] - delta);
+++	if (new_gen >= 0)
+++		WRITE_ONCE(lrugen->sizes[new_gen][type][zone],
+++			   lrugen->sizes[new_gen][type][zone] + delta);
+++
+++	if (old_gen < 0) {
+++		if (lru_gen_is_active(lruvec, new_gen))
+++			lru += LRU_ACTIVE;
+++		update_lru_size(lruvec, lru, zone, delta);
+++		return;
 ++	}
-++	emit(ctx, sc, MIPS_R_T8, off, dst);
-++	emit(ctx, beqz, MIPS_R_T8, -16);
-++	emit(ctx, nop); /* Delay slot */
+++
+++	if (new_gen < 0) {
+++		if (lru_gen_is_active(lruvec, old_gen))
+++			lru += LRU_ACTIVE;
+++		update_lru_size(lruvec, lru, zone, -delta);
+++		return;
+++	}
+++
+++	if (!lru_gen_is_active(lruvec, old_gen) && lru_gen_is_active(lruvec, new_gen)) {
+++		update_lru_size(lruvec, lru, zone, -delta);
+++		update_lru_size(lruvec, lru + LRU_ACTIVE, zone, delta);
+++	}
+++
+++	VM_BUG_ON(lru_gen_is_active(lruvec, old_gen) && !lru_gen_is_active(lruvec, new_gen));
 ++}
 ++
-++/* Atomic compare-and-exchange (32-bit) */
-++void emit_cmpxchg_r(struct jit_context *ctx, u8 dst, u8 src, u8 res, s16 off)
+++/* Add a page to one of the multigenerational lru lists. Return true on success. */
+++static inline bool lru_gen_add_page(struct page *page, struct lruvec *lruvec, bool reclaiming)
 ++{
-++	emit(ctx, ll, MIPS_R_T9, off, dst);
-++	emit(ctx, bne, MIPS_R_T9, res, 12);
-++	emit(ctx, move, MIPS_R_T8, src);     /* Delay slot */
-++	emit(ctx, sc, MIPS_R_T8, off, dst);
-++	emit(ctx, beqz, MIPS_R_T8, -20);
-++	emit(ctx, move, res, MIPS_R_T9);     /* Delay slot */
-++	clobber_reg(ctx, res);
+++	int gen;
+++	unsigned long old_flags, new_flags;
+++	int type = page_is_file_lru(page);
+++	int zone = page_zonenum(page);
+++	struct lrugen *lrugen = &lruvec->evictable;
+++
+++	if (PageUnevictable(page) || !lrugen->enabled[type])
+++		return false;
+++	/*
+++	 * If a page shouldn't be considered for eviction, i.e., a page mapped
+++	 * upon fault during which the accessed bit is set, add it to the
+++	 * youngest generation.
+++	 *
+++	 * If a page can't be evicted immediately, i.e., an anon page not in
+++	 * swap cache or a dirty page pending writeback, add it to the second
+++	 * oldest generation.
+++	 *
+++	 * If a page could be evicted immediately, e.g., a clean page, add it to
+++	 * the oldest generation.
+++	 */
+++	if (PageActive(page))
+++		gen = lru_gen_from_seq(lrugen->max_seq);
+++	else if ((!type && !PageSwapCache(page)) ||
+++		 (PageReclaim(page) && (PageDirty(page) || PageWriteback(page))))
+++		gen = lru_gen_from_seq(lrugen->min_seq[type] + 1);
+++	else
+++		gen = lru_gen_from_seq(lrugen->min_seq[type]);
+++
+++	do {
+++		new_flags = old_flags = READ_ONCE(page->flags);
+++		VM_BUG_ON_PAGE(new_flags & LRU_GEN_MASK, page);
+++
+++		new_flags &= ~(LRU_GEN_MASK | BIT(PG_active));
+++		new_flags |= (gen + 1UL) << LRU_GEN_PGOFF;
+++	} while (cmpxchg(&page->flags, old_flags, new_flags) != old_flags);
+++
+++	lru_gen_update_size(page, lruvec, -1, gen);
+++	/* for rotate_reclaimable_page() */
+++	if (reclaiming)
+++		list_add_tail(&page->lru, &lrugen->lists[gen][type][zone]);
+++	else
+++		list_add(&page->lru, &lrugen->lists[gen][type][zone]);
+++
+++	return true;
 ++}
 ++
-++/* Swap bytes and truncate a register word or half word */
-++void emit_bswap_r(struct jit_context *ctx, u8 dst, u32 width)
+++/* Delete a page from one of the multigenerational lru lists. Return true on success. */
+++static inline bool lru_gen_del_page(struct page *page, struct lruvec *lruvec, bool reclaiming)
 ++{
-++	u8 tmp = MIPS_R_T8;
-++	u8 msk = MIPS_R_T9;
+++	int gen;
+++	unsigned long old_flags, new_flags;
 ++
-++	switch (width) {
-++	/* Swap bytes in a word */
-++	case 32:
-++		if (cpu_has_mips32r2 || cpu_has_mips32r6) {
-++			emit(ctx, wsbh, dst, dst);
-++			emit(ctx, rotr, dst, dst, 16);
-++		} else {
-++			emit(ctx, sll, tmp, dst, 16);    /* tmp  = dst << 16 */
-++			emit(ctx, srl, dst, dst, 16);    /* dst = dst >> 16  */
-++			emit(ctx, or, dst, dst, tmp);    /* dst = dst | tmp  */
+++	do {
+++		new_flags = old_flags = READ_ONCE(page->flags);
+++		if (!(new_flags & LRU_GEN_MASK))
+++			return false;
 ++
-++			emit(ctx, lui, msk, 0xff);       /* msk = 0x00ff0000 */
-++			emit(ctx, ori, msk, msk, 0xff);  /* msk = msk | 0xff */
+++		VM_BUG_ON_PAGE(PageActive(page), page);
+++		VM_BUG_ON_PAGE(PageUnevictable(page), page);
 ++
-++			emit(ctx, and, tmp, dst, msk);   /* tmp = dst & msk  */
-++			emit(ctx, sll, tmp, tmp, 8);     /* tmp = tmp << 8   */
-++			emit(ctx, srl, dst, dst, 8);     /* dst = dst >> 8   */
-++			emit(ctx, and, dst, dst, msk);   /* dst = dst & msk  */
-++			emit(ctx, or, dst, dst, tmp);    /* reg = dst | tmp  */
-++		}
-++		break;
-++	/* Swap bytes in a half word */
-++	case 16:
-++		if (cpu_has_mips32r2 || cpu_has_mips32r6) {
-++			emit(ctx, wsbh, dst, dst);
-++			emit(ctx, andi, dst, dst, 0xffff);
-++		} else {
-++			emit(ctx, andi, tmp, dst, 0xff00); /* t = d & 0xff00 */
-++			emit(ctx, srl, tmp, tmp, 8);       /* t = t >> 8     */
-++			emit(ctx, andi, dst, dst, 0x00ff); /* d = d & 0x00ff */
-++			emit(ctx, sll, dst, dst, 8);       /* d = d << 8     */
-++			emit(ctx, or,  dst, dst, tmp);     /* d = d | t      */
-++		}
-++		break;
-++	}
-++	clobber_reg(ctx, dst);
+++		gen = ((new_flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
+++
+++		new_flags &= ~LRU_GEN_MASK;
+++		/* for shrink_page_list() */
+++		if (reclaiming)
+++			new_flags &= ~(BIT(PG_referenced) | BIT(PG_reclaim));
+++		else if (lru_gen_is_active(lruvec, gen))
+++			new_flags |= BIT(PG_active);
+++	} while (cmpxchg(&page->flags, old_flags, new_flags) != old_flags);
+++
+++	lru_gen_update_size(page, lruvec, gen, -1);
+++	list_del(&page->lru);
+++
+++	return true;
 ++}
 ++
-++/* Validate jump immediate range */
-++bool valid_jmp_i(u8 op, s32 imm)
+++#else
+++
+++static inline bool lru_gen_enabled(void)
 ++{
-++	switch (op) {
-++	case JIT_JNOP:
-++		/* Immediate value not used */
-++		return true;
-++	case BPF_JEQ:
-++	case BPF_JNE:
-++		/* No immediate operation */
-++		return false;
-++	case BPF_JSET:
-++	case JIT_JNSET:
-++		/* imm must be 16 bits unsigned */
-++		return imm >= 0 && imm <= 0xffff;
-++	case BPF_JGE:
-++	case BPF_JLT:
-++	case BPF_JSGE:
-++	case BPF_JSLT:
-++		/* imm must be 16 bits */
-++		return imm >= -0x8000 && imm <= 0x7fff;
-++	case BPF_JGT:
-++	case BPF_JLE:
-++	case BPF_JSGT:
-++	case BPF_JSLE:
-++		/* imm + 1 must be 16 bits */
-++		return imm >= -0x8001 && imm <= 0x7ffe;
-++	}
 ++	return false;
 ++}
 ++
-++/* Invert a conditional jump operation */
-++static u8 invert_jmp(u8 op)
+++static inline bool lru_gen_add_page(struct page *page, struct lruvec *lruvec, bool reclaiming)
 ++{
-++	switch (op) {
-++	case BPF_JA: return JIT_JNOP;
-++	case BPF_JEQ: return BPF_JNE;
-++	case BPF_JNE: return BPF_JEQ;
-++	case BPF_JSET: return JIT_JNSET;
-++	case BPF_JGT: return BPF_JLE;
-++	case BPF_JGE: return BPF_JLT;
-++	case BPF_JLT: return BPF_JGE;
-++	case BPF_JLE: return BPF_JGT;
-++	case BPF_JSGT: return BPF_JSLE;
-++	case BPF_JSGE: return BPF_JSLT;
-++	case BPF_JSLT: return BPF_JSGE;
-++	case BPF_JSLE: return BPF_JSGT;
-++	}
-++	return 0;
+++	return false;
 ++}
 ++
-++/* Prepare a PC-relative jump operation */
-++static void setup_jmp(struct jit_context *ctx, u8 bpf_op,
-++		      s16 bpf_off, u8 *jit_op, s32 *jit_off)
+++static inline bool lru_gen_del_page(struct page *page, struct lruvec *lruvec, bool reclaiming)
 ++{
-++	u32 *descp = &ctx->descriptors[ctx->bpf_index];
-++	int op = bpf_op;
-++	int offset = 0;
+++	return false;
+++}
 ++
-++	/* Do not compute offsets on the first pass */
-++	if (INDEX(*descp) == 0)
-++		goto done;
+++#endif /* CONFIG_LRU_GEN */
 ++
-++	/* Skip jumps never taken */
-++	if (bpf_op == JIT_JNOP)
-++		goto done;
++ static __always_inline void add_page_to_lru_list(struct page *page,
++ 				struct lruvec *lruvec)
++ {
++ 	enum lru_list lru = page_lru(page);
++ 
+++	if (lru_gen_add_page(page, lruvec, false))
+++		return;
 ++
-++	/* Convert jumps always taken */
-++	if (bpf_op == BPF_JA)
-++		*descp |= JIT_DESC_CONVERT;
++ 	update_lru_size(lruvec, lru, page_zonenum(page), thp_nr_pages(page));
++ 	list_add(&page->lru, &lruvec->lists[lru]);
++ }
++@@ -93,6 +269,9 @@ static __always_inline void add_page_to_
++ {
++ 	enum lru_list lru = page_lru(page);
++ 
+++	if (lru_gen_add_page(page, lruvec, true))
+++		return;
 ++
-++	/*
-++	 * Current ctx->jit_index points to the start of the branch preamble.
-++	 * Since the preamble differs among different branch conditionals,
-++	 * the current index cannot be used to compute the branch offset.
-++	 * Instead, we use the offset table value for the next instruction,
-++	 * which gives the index immediately after the branch delay slot.
-++	 */
-++	if (!CONVERTED(*descp)) {
-++		int target = ctx->bpf_index + bpf_off + 1;
-++		int origin = ctx->bpf_index + 1;
++ 	update_lru_size(lruvec, lru, page_zonenum(page), thp_nr_pages(page));
++ 	list_add_tail(&page->lru, &lruvec->lists[lru]);
++ }
++@@ -100,6 +279,9 @@ static __always_inline void add_page_to_
++ static __always_inline void del_page_from_lru_list(struct page *page,
++ 				struct lruvec *lruvec)
++ {
+++	if (lru_gen_del_page(page, lruvec, false))
+++		return;
 ++
-++		offset = (INDEX(ctx->descriptors[target]) -
-++			  INDEX(ctx->descriptors[origin]) + 1) * sizeof(u32);
-++	}
++ 	list_del(&page->lru);
++ 	update_lru_size(lruvec, page_lru(page), page_zonenum(page),
++ 			-thp_nr_pages(page));
++--- a/include/linux/mmzone.h
+++++ b/include/linux/mmzone.h
++@@ -294,6 +294,72 @@ enum lruvec_flags {
++ 					 */
++ };
++ 
+++struct lruvec;
 ++
-++	/*
-++	 * The PC-relative branch offset field on MIPS is 18 bits signed,
-++	 * so if the computed offset is larger than this we generate a an
-++	 * absolute jump that we skip with an inverted conditional branch.
-++	 */
-++	if (CONVERTED(*descp) || offset < -0x20000 || offset > 0x1ffff) {
-++		offset = 3 * sizeof(u32);
-++		op = invert_jmp(bpf_op);
-++		ctx->changes += !CONVERTED(*descp);
-++		*descp |= JIT_DESC_CONVERT;
-++	}
+++#define LRU_GEN_MASK		((BIT(LRU_GEN_WIDTH) - 1) << LRU_GEN_PGOFF)
+++#define LRU_REFS_MASK		((BIT(LRU_REFS_WIDTH) - 1) << LRU_REFS_PGOFF)
 ++
-++done:
-++	*jit_off = offset;
-++	*jit_op = op;
-++}
+++#ifdef CONFIG_LRU_GEN
 ++
-++/* Prepare a PC-relative jump operation with immediate conditional */
-++void setup_jmp_i(struct jit_context *ctx, s32 imm, u8 width,
-++		 u8 bpf_op, s16 bpf_off, u8 *jit_op, s32 *jit_off)
-++{
-++	bool always = false;
-++	bool never = false;
+++/*
+++ * For each lruvec, evictable pages are divided into multiple generations. The
+++ * youngest and the oldest generation numbers, AKA max_seq and min_seq, are
+++ * monotonically increasing. The sliding window technique is used to track at
+++ * least MIN_NR_GENS and at most MAX_NR_GENS generations. An offset within the
+++ * window, AKA gen, indexes an array of per-type and per-zone lists for the
+++ * corresponding generation. The counter in page->flags stores gen+1 while a
+++ * page is on one of the multigenerational lru lists. Otherwise, it stores 0.
+++ *
+++ * After a page is faulted in, the aging must check the accessed bit at least
+++ * twice before the eviction would consider it. The first check clears the
+++ * accessed bit set during the initial fault. The second check makes sure this
+++ * page hasn't been used since then.
+++ */
+++#define MIN_NR_GENS		2
+++#define MAX_NR_GENS		((unsigned int)CONFIG_NR_LRU_GENS)
+++
+++struct lrugen {
+++	/* the aging increments the max generation number */
+++	unsigned long max_seq;
+++	/* the eviction increments the min generation numbers */
+++	unsigned long min_seq[ANON_AND_FILE];
+++	/* the birth time of each generation in jiffies */
+++	unsigned long timestamps[MAX_NR_GENS];
+++	/* the multigenerational lru lists */
+++	struct list_head lists[MAX_NR_GENS][ANON_AND_FILE][MAX_NR_ZONES];
+++	/* the sizes of the multigenerational lru lists in pages */
+++	unsigned long sizes[MAX_NR_GENS][ANON_AND_FILE][MAX_NR_ZONES];
+++	/* whether the multigenerational lru is enabled */
+++	bool enabled[ANON_AND_FILE];
+++};
 ++
-++	switch (bpf_op) {
-++	case BPF_JEQ:
-++	case BPF_JNE:
-++		break;
-++	case BPF_JSET:
-++	case BPF_JLT:
-++		never = imm == 0;
-++		break;
-++	case BPF_JGE:
-++		always = imm == 0;
-++		break;
-++	case BPF_JGT:
-++		never = (u32)imm == U32_MAX;
-++		break;
-++	case BPF_JLE:
-++		always = (u32)imm == U32_MAX;
-++		break;
-++	case BPF_JSGT:
-++		never = imm == S32_MAX && width == 32;
-++		break;
-++	case BPF_JSGE:
-++		always = imm == S32_MIN && width == 32;
-++		break;
-++	case BPF_JSLT:
-++		never = imm == S32_MIN && width == 32;
-++		break;
-++	case BPF_JSLE:
-++		always = imm == S32_MAX && width == 32;
-++		break;
-++	}
+++#define MAX_BATCH_SIZE		8192
 ++
-++	if (never)
-++		bpf_op = JIT_JNOP;
-++	if (always)
-++		bpf_op = BPF_JA;
-++	setup_jmp(ctx, bpf_op, bpf_off, jit_op, jit_off);
-++}
+++void lru_gen_init_state(struct mem_cgroup *memcg, struct lruvec *lruvec);
+++void lru_gen_change_state(bool enable, bool main, bool swap);
 ++
-++/* Prepare a PC-relative jump operation with register conditional */
-++void setup_jmp_r(struct jit_context *ctx, bool same_reg,
-++		 u8 bpf_op, s16 bpf_off, u8 *jit_op, s32 *jit_off)
+++#ifdef CONFIG_MEMCG
+++void lru_gen_init_memcg(struct mem_cgroup *memcg);
+++#endif
+++
+++#else /* !CONFIG_LRU_GEN */
+++
+++static inline void lru_gen_init_state(struct mem_cgroup *memcg, struct lruvec *lruvec)
 ++{
-++	switch (bpf_op) {
-++	case BPF_JSET:
-++		break;
-++	case BPF_JEQ:
-++	case BPF_JGE:
-++	case BPF_JLE:
-++	case BPF_JSGE:
-++	case BPF_JSLE:
-++		if (same_reg)
-++			bpf_op = BPF_JA;
-++		break;
-++	case BPF_JNE:
-++	case BPF_JLT:
-++	case BPF_JGT:
-++	case BPF_JSGT:
-++	case BPF_JSLT:
-++		if (same_reg)
-++			bpf_op = JIT_JNOP;
-++		break;
-++	}
-++	setup_jmp(ctx, bpf_op, bpf_off, jit_op, jit_off);
 ++}
 ++
-++/* Finish a PC-relative jump operation */
-++int finish_jmp(struct jit_context *ctx, u8 jit_op, s16 bpf_off)
+++static inline void lru_gen_change_state(bool enable, bool main, bool swap)
 ++{
-++	/* Emit conditional branch delay slot */
-++	if (jit_op != JIT_JNOP)
-++		emit(ctx, nop);
-++	/*
-++	 * Emit an absolute long jump with delay slot,
-++	 * if the PC-relative branch was converted.
-++	 */
-++	if (CONVERTED(ctx->descriptors[ctx->bpf_index])) {
-++		int target = get_target(ctx, ctx->bpf_index + bpf_off + 1);
-++
-++		if (target < 0)
-++			return -1;
-++		emit(ctx, j, target);
-++		emit(ctx, nop);
-++	}
-++	return 0;
 ++}
 ++
-++/* Jump immediate (32-bit) */
-++void emit_jmp_i(struct jit_context *ctx, u8 dst, s32 imm, s32 off, u8 op)
+++#ifdef CONFIG_MEMCG
+++static inline void lru_gen_init_memcg(struct mem_cgroup *memcg)
 ++{
-++	switch (op) {
-++	/* No-op, used internally for branch optimization */
-++	case JIT_JNOP:
-++		break;
-++	/* PC += off if dst & imm */
-++	case BPF_JSET:
-++		emit(ctx, andi, MIPS_R_T9, dst, (u16)imm);
-++		emit(ctx, bnez, MIPS_R_T9, off);
-++		break;
-++	/* PC += off if (dst & imm) == 0 (not in BPF, used for long jumps) */
-++	case JIT_JNSET:
-++		emit(ctx, andi, MIPS_R_T9, dst, (u16)imm);
-++		emit(ctx, beqz, MIPS_R_T9, off);
-++		break;
-++	/* PC += off if dst > imm */
-++	case BPF_JGT:
-++		emit(ctx, sltiu, MIPS_R_T9, dst, imm + 1);
-++		emit(ctx, beqz, MIPS_R_T9, off);
-++		break;
-++	/* PC += off if dst >= imm */
-++	case BPF_JGE:
-++		emit(ctx, sltiu, MIPS_R_T9, dst, imm);
-++		emit(ctx, beqz, MIPS_R_T9, off);
-++		break;
-++	/* PC += off if dst < imm */
-++	case BPF_JLT:
-++		emit(ctx, sltiu, MIPS_R_T9, dst, imm);
-++		emit(ctx, bnez, MIPS_R_T9, off);
-++		break;
-++	/* PC += off if dst <= imm */
-++	case BPF_JLE:
-++		emit(ctx, sltiu, MIPS_R_T9, dst, imm + 1);
-++		emit(ctx, bnez, MIPS_R_T9, off);
-++		break;
-++	/* PC += off if dst > imm (signed) */
-++	case BPF_JSGT:
-++		emit(ctx, slti, MIPS_R_T9, dst, imm + 1);
-++		emit(ctx, beqz, MIPS_R_T9, off);
-++		break;
-++	/* PC += off if dst >= imm (signed) */
-++	case BPF_JSGE:
-++		emit(ctx, slti, MIPS_R_T9, dst, imm);
-++		emit(ctx, beqz, MIPS_R_T9, off);
-++		break;
-++	/* PC += off if dst < imm (signed) */
-++	case BPF_JSLT:
-++		emit(ctx, slti, MIPS_R_T9, dst, imm);
-++		emit(ctx, bnez, MIPS_R_T9, off);
-++		break;
-++	/* PC += off if dst <= imm (signed) */
-++	case BPF_JSLE:
-++		emit(ctx, slti, MIPS_R_T9, dst, imm + 1);
-++		emit(ctx, bnez, MIPS_R_T9, off);
-++		break;
-++	}
 ++}
+++#endif
 ++
-++/* Jump register (32-bit) */
-++void emit_jmp_r(struct jit_context *ctx, u8 dst, u8 src, s32 off, u8 op)
-++{
-++	switch (op) {
-++	/* No-op, used internally for branch optimization */
-++	case JIT_JNOP:
-++		break;
-++	/* PC += off if dst == src */
-++	case BPF_JEQ:
-++		emit(ctx, beq, dst, src, off);
-++		break;
-++	/* PC += off if dst != src */
-++	case BPF_JNE:
-++		emit(ctx, bne, dst, src, off);
-++		break;
-++	/* PC += off if dst & src */
-++	case BPF_JSET:
-++		emit(ctx, and, MIPS_R_T9, dst, src);
-++		emit(ctx, bnez, MIPS_R_T9, off);
-++		break;
-++	/* PC += off if (dst & imm) == 0 (not in BPF, used for long jumps) */
-++	case JIT_JNSET:
-++		emit(ctx, and, MIPS_R_T9, dst, src);
-++		emit(ctx, beqz, MIPS_R_T9, off);
-++		break;
-++	/* PC += off if dst > src */
-++	case BPF_JGT:
-++		emit(ctx, sltu, MIPS_R_T9, src, dst);
-++		emit(ctx, bnez, MIPS_R_T9, off);
-++		break;
-++	/* PC += off if dst >= src */
-++	case BPF_JGE:
-++		emit(ctx, sltu, MIPS_R_T9, dst, src);
-++		emit(ctx, beqz, MIPS_R_T9, off);
-++		break;
-++	/* PC += off if dst < src */
-++	case BPF_JLT:
-++		emit(ctx, sltu, MIPS_R_T9, dst, src);
-++		emit(ctx, bnez, MIPS_R_T9, off);
-++		break;
-++	/* PC += off if dst <= src */
-++	case BPF_JLE:
-++		emit(ctx, sltu, MIPS_R_T9, src, dst);
-++		emit(ctx, beqz, MIPS_R_T9, off);
-++		break;
-++	/* PC += off if dst > src (signed) */
-++	case BPF_JSGT:
-++		emit(ctx, slt, MIPS_R_T9, src, dst);
-++		emit(ctx, bnez, MIPS_R_T9, off);
-++		break;
-++	/* PC += off if dst >= src (signed) */
-++	case BPF_JSGE:
-++		emit(ctx, slt, MIPS_R_T9, dst, src);
-++		emit(ctx, beqz, MIPS_R_T9, off);
-++		break;
-++	/* PC += off if dst < src (signed) */
-++	case BPF_JSLT:
-++		emit(ctx, slt, MIPS_R_T9, dst, src);
-++		emit(ctx, bnez, MIPS_R_T9, off);
-++		break;
-++	/* PC += off if dst <= src (signed) */
-++	case BPF_JSLE:
-++		emit(ctx, slt, MIPS_R_T9, src, dst);
-++		emit(ctx, beqz, MIPS_R_T9, off);
-++		break;
-++	}
-++}
+++#endif /* CONFIG_LRU_GEN */
+++
++ struct lruvec {
++ 	struct list_head		lists[NR_LRU_LISTS];
++ 	/* per lruvec lru_lock for memcg */
++@@ -311,6 +377,10 @@ struct lruvec {
++ 	unsigned long			refaults[ANON_AND_FILE];
++ 	/* Various lruvec state flags (enum lruvec_flags) */
++ 	unsigned long			flags;
+++#ifdef CONFIG_LRU_GEN
+++	/* unevictable pages are on LRU_UNEVICTABLE */
+++	struct lrugen			evictable;
+++#endif
++ #ifdef CONFIG_MEMCG
++ 	struct pglist_data *pgdat;
++ #endif
++--- a/include/linux/page-flags-layout.h
+++++ b/include/linux/page-flags-layout.h
++@@ -26,6 +26,14 @@
++ 
++ #define ZONES_WIDTH		ZONES_SHIFT
++ 
+++#ifdef CONFIG_LRU_GEN
+++/* LRU_GEN_WIDTH is generated from order_base_2(CONFIG_NR_LRU_GENS + 1). */
+++#define LRU_REFS_WIDTH		(CONFIG_TIERS_PER_GEN - 2)
+++#else
+++#define LRU_GEN_WIDTH		0
+++#define LRU_REFS_WIDTH		0
+++#endif /* CONFIG_LRU_GEN */
+++
++ #ifdef CONFIG_SPARSEMEM
++ #include <asm/sparsemem.h>
++ #define SECTIONS_SHIFT	(MAX_PHYSMEM_BITS - SECTION_SIZE_BITS)
++@@ -55,7 +63,8 @@
++ #define SECTIONS_WIDTH		0
++ #endif
++ 
++-#if ZONES_WIDTH + SECTIONS_WIDTH + NODES_SHIFT <= BITS_PER_LONG - NR_PAGEFLAGS
+++#if ZONES_WIDTH + LRU_GEN_WIDTH + LRU_REFS_WIDTH + SECTIONS_WIDTH + NODES_SHIFT \
+++	<= BITS_PER_LONG - NR_PAGEFLAGS
++ #define NODES_WIDTH		NODES_SHIFT
++ #elif defined(CONFIG_SPARSEMEM_VMEMMAP)
++ #error "Vmemmap: No space for nodes field in page flags"
++@@ -89,8 +98,8 @@
++ #define LAST_CPUPID_SHIFT 0
++ #endif
++ 
++-#if ZONES_WIDTH + SECTIONS_WIDTH + NODES_WIDTH + KASAN_TAG_WIDTH + LAST_CPUPID_SHIFT \
++-	<= BITS_PER_LONG - NR_PAGEFLAGS
+++#if ZONES_WIDTH + LRU_GEN_WIDTH + LRU_REFS_WIDTH + SECTIONS_WIDTH + NODES_WIDTH + \
+++	KASAN_TAG_WIDTH + LAST_CPUPID_SHIFT <= BITS_PER_LONG - NR_PAGEFLAGS
++ #define LAST_CPUPID_WIDTH LAST_CPUPID_SHIFT
++ #else
++ #define LAST_CPUPID_WIDTH 0
++@@ -100,8 +109,8 @@
++ #define LAST_CPUPID_NOT_IN_PAGE_FLAGS
++ #endif
++ 
++-#if ZONES_WIDTH + SECTIONS_WIDTH + NODES_WIDTH + KASAN_TAG_WIDTH + LAST_CPUPID_WIDTH \
++-	> BITS_PER_LONG - NR_PAGEFLAGS
+++#if ZONES_WIDTH + LRU_GEN_WIDTH + LRU_REFS_WIDTH + SECTIONS_WIDTH + NODES_WIDTH + \
+++	KASAN_TAG_WIDTH + LAST_CPUPID_WIDTH > BITS_PER_LONG - NR_PAGEFLAGS
++ #error "Not enough bits in page flags"
++ #endif
++ 
++--- a/include/linux/page-flags.h
+++++ b/include/linux/page-flags.h
++@@ -845,7 +845,7 @@ static inline void ClearPageSlabPfmemall
++ 	 1UL << PG_private	| 1UL << PG_private_2	|	\
++ 	 1UL << PG_writeback	| 1UL << PG_reserved	|	\
++ 	 1UL << PG_slab		| 1UL << PG_active 	|	\
++-	 1UL << PG_unevictable	| __PG_MLOCKED)
+++	 1UL << PG_unevictable	| __PG_MLOCKED | LRU_GEN_MASK)
++ 
++ /*
++  * Flags checked when a page is prepped for return by the page allocator.
++@@ -856,7 +856,7 @@ static inline void ClearPageSlabPfmemall
++  * alloc-free cycle to prevent from reusing the page.
++  */
++ #define PAGE_FLAGS_CHECK_AT_PREP	\
++-	(PAGEFLAGS_MASK & ~__PG_HWPOISON)
+++	((PAGEFLAGS_MASK & ~__PG_HWPOISON) | LRU_GEN_MASK | LRU_REFS_MASK)
++ 
++ #define PAGE_FLAGS_PRIVATE				\
++ 	(1UL << PG_private | 1UL << PG_private_2)
++--- a/include/linux/sched.h
+++++ b/include/linux/sched.h
++@@ -911,6 +911,9 @@ struct task_struct {
++ #ifdef CONFIG_MEMCG
++ 	unsigned			in_user_fault:1;
++ #endif
+++#ifdef CONFIG_LRU_GEN
+++	unsigned			in_nonseq_fault:1;
+++#endif
++ #ifdef CONFIG_COMPAT_BRK
++ 	unsigned			brk_randomized:1;
++ #endif
++--- a/kernel/bounds.c
+++++ b/kernel/bounds.c
++@@ -22,6 +22,9 @@ int main(void)
++ 	DEFINE(NR_CPUS_BITS, ilog2(CONFIG_NR_CPUS));
++ #endif
++ 	DEFINE(SPINLOCK_SIZE, sizeof(spinlock_t));
+++#ifdef CONFIG_LRU_GEN
+++	DEFINE(LRU_GEN_WIDTH, order_base_2(CONFIG_NR_LRU_GENS + 1));
+++#endif
++ 	/* End of constants */
++ 
++ 	return 0;
++--- a/kernel/cgroup/cgroup-internal.h
+++++ b/kernel/cgroup/cgroup-internal.h
++@@ -165,7 +165,6 @@ struct cgroup_mgctx {
++ #define DEFINE_CGROUP_MGCTX(name)						\
++ 	struct cgroup_mgctx name = CGROUP_MGCTX_INIT(name)
++ 
++-extern struct mutex cgroup_mutex;
++ extern spinlock_t css_set_lock;
++ extern struct cgroup_subsys *cgroup_subsys[];
++ extern struct list_head cgroup_roots;
++--- a/mm/huge_memory.c
+++++ b/mm/huge_memory.c
++@@ -2364,7 +2364,8 @@ static void __split_huge_page_tail(struc
++ #ifdef CONFIG_64BIT
++ 			 (1L << PG_arch_2) |
++ #endif
++-			 (1L << PG_dirty)));
+++			 (1L << PG_dirty) |
+++			 LRU_GEN_MASK | LRU_REFS_MASK));
++ 
++ 	/* ->mapping in first tail page is compound_mapcount */
++ 	VM_BUG_ON_PAGE(tail > 2 && page_tail->mapping != TAIL_MAPPING,
++--- a/mm/memcontrol.c
+++++ b/mm/memcontrol.c
++@@ -5226,6 +5226,7 @@ static struct mem_cgroup *mem_cgroup_all
++ 	memcg->deferred_split_queue.split_queue_len = 0;
++ #endif
++ 	idr_replace(&mem_cgroup_idr, memcg, memcg->id.id);
+++	lru_gen_init_memcg(memcg);
++ 	return memcg;
++ fail:
++ 	mem_cgroup_id_remove(memcg);
++--- a/mm/memory.c
+++++ b/mm/memory.c
++@@ -4788,6 +4788,7 @@ vm_fault_t handle_mm_fault(struct vm_are
++ 			   unsigned int flags, struct pt_regs *regs)
++ {
++ 	vm_fault_t ret;
+++	bool nonseq_fault = !(vma->vm_flags & VM_SEQ_READ);
++ 
++ 	__set_current_state(TASK_RUNNING);
++ 
++@@ -4809,11 +4810,17 @@ vm_fault_t handle_mm_fault(struct vm_are
++ 	if (flags & FAULT_FLAG_USER)
++ 		mem_cgroup_enter_user_fault();
++ 
+++	if (nonseq_fault)
+++		task_enter_nonseq_fault();
 ++
-++/* Jump always */
-++int emit_ja(struct jit_context *ctx, s16 off)
-++{
-++	int target = get_target(ctx, ctx->bpf_index + off + 1);
++ 	if (unlikely(is_vm_hugetlb_page(vma)))
++ 		ret = hugetlb_fault(vma->vm_mm, vma, address, flags);
++ 	else
++ 		ret = __handle_mm_fault(vma, address, flags);
++ 
+++	if (nonseq_fault)
+++		task_exit_nonseq_fault();
+++
++ 	if (flags & FAULT_FLAG_USER) {
++ 		mem_cgroup_exit_user_fault();
++ 		/*
++--- a/mm/mm_init.c
+++++ b/mm/mm_init.c
++@@ -65,14 +65,16 @@ void __init mminit_verify_pageflags_layo
++ 
++ 	shift = 8 * sizeof(unsigned long);
++ 	width = shift - SECTIONS_WIDTH - NODES_WIDTH - ZONES_WIDTH
++-		- LAST_CPUPID_SHIFT - KASAN_TAG_WIDTH;
+++		- LAST_CPUPID_SHIFT - KASAN_TAG_WIDTH - LRU_GEN_WIDTH - LRU_REFS_WIDTH;
++ 	mminit_dprintk(MMINIT_TRACE, "pageflags_layout_widths",
++-		"Section %d Node %d Zone %d Lastcpupid %d Kasantag %d Flags %d\n",
+++		"Section %d Node %d Zone %d Lastcpupid %d Kasantag %d Gen %d Tier %d Flags %d\n",
++ 		SECTIONS_WIDTH,
++ 		NODES_WIDTH,
++ 		ZONES_WIDTH,
++ 		LAST_CPUPID_WIDTH,
++ 		KASAN_TAG_WIDTH,
+++		LRU_GEN_WIDTH,
+++		LRU_REFS_WIDTH,
++ 		NR_PAGEFLAGS);
++ 	mminit_dprintk(MMINIT_TRACE, "pageflags_layout_shifts",
++ 		"Section %d Node %d Zone %d Lastcpupid %d Kasantag %d\n",
++--- a/mm/page_alloc.c
+++++ b/mm/page_alloc.c
++@@ -7456,6 +7456,7 @@ static void __meminit pgdat_init_interna
++ 
++ 	pgdat_page_ext_init(pgdat);
++ 	lruvec_init(&pgdat->__lruvec);
+++	lru_gen_init_state(NULL, &pgdat->__lruvec);
++ }
++ 
++ static void __meminit zone_init_internals(struct zone *zone, enum zone_type idx, int nid,
++--- a/mm/swap.c
+++++ b/mm/swap.c
++@@ -446,6 +446,11 @@ void lru_cache_add(struct page *page)
++ 	VM_BUG_ON_PAGE(PageActive(page) && PageUnevictable(page), page);
++ 	VM_BUG_ON_PAGE(PageLRU(page), page);
++ 
+++	/* see the comment in lru_gen_add_page() */
+++	if (lru_gen_enabled() && !PageUnevictable(page) &&
+++	    task_in_nonseq_fault() && !(current->flags & PF_MEMALLOC))
+++		SetPageActive(page);
 ++
-++	if (target < 0)
-++		return -1;
-++	emit(ctx, j, target);
-++	emit(ctx, nop);
-++	return 0;
-++}
++ 	get_page(page);
++ 	local_lock(&lru_pvecs.lock);
++ 	pvec = this_cpu_ptr(&lru_pvecs.lru_add);
++@@ -547,7 +552,7 @@ static void lru_deactivate_file_fn(struc
++ 
++ static void lru_deactivate_fn(struct page *page, struct lruvec *lruvec)
++ {
++-	if (PageActive(page) && !PageUnevictable(page)) {
+++	if (!PageUnevictable(page) && (PageActive(page) || lru_gen_enabled())) {
++ 		int nr_pages = thp_nr_pages(page);
++ 
++ 		del_page_from_lru_list(page, lruvec);
++@@ -661,7 +666,7 @@ void deactivate_file_page(struct page *p
++  */
++ void deactivate_page(struct page *page)
++ {
++-	if (PageLRU(page) && PageActive(page) && !PageUnevictable(page)) {
+++	if (PageLRU(page) && !PageUnevictable(page) && (PageActive(page) || lru_gen_enabled())) {
++ 		struct pagevec *pvec;
++ 
++ 		local_lock(&lru_pvecs.lock);
++--- a/mm/swapfile.c
+++++ b/mm/swapfile.c
++@@ -2688,6 +2688,7 @@ SYSCALL_DEFINE1(swapoff, const char __us
++ 	err = 0;
++ 	atomic_inc(&proc_poll_event);
++ 	wake_up_interruptible(&proc_poll_wait);
+++	lru_gen_change_state(false, false, true);
++ 
++ out_dput:
++ 	filp_close(victim, NULL);
++@@ -3349,6 +3350,7 @@ SYSCALL_DEFINE2(swapon, const char __use
++ 	mutex_unlock(&swapon_mutex);
++ 	atomic_inc(&proc_poll_event);
++ 	wake_up_interruptible(&proc_poll_wait);
+++	lru_gen_change_state(true, false, true);
++ 
++ 	error = 0;
++ 	goto out;
++--- a/mm/vmscan.c
+++++ b/mm/vmscan.c
++@@ -50,6 +50,7 @@
++ #include <linux/printk.h>
++ #include <linux/dax.h>
++ #include <linux/psi.h>
+++#include <linux/memory.h>
++ 
++ #include <asm/tlbflush.h>
++ #include <asm/div64.h>
++@@ -2880,6 +2881,273 @@ static bool can_age_anon_pages(struct pg
++ 	return can_demote(pgdat->node_id, sc);
++ }
++ 
+++#ifdef CONFIG_LRU_GEN
 ++
-++/* Jump to epilogue */
-++int emit_exit(struct jit_context *ctx)
-++{
-++	int target = get_target(ctx, ctx->program->len);
+++/******************************************************************************
+++ *                          shorthand helpers
+++ ******************************************************************************/
 ++
-++	if (target < 0)
-++		return -1;
-++	emit(ctx, j, target);
-++	emit(ctx, nop);
-++	return 0;
-++}
+++#define for_each_gen_type_zone(gen, type, zone)				\
+++	for ((gen) = 0; (gen) < MAX_NR_GENS; (gen)++)			\
+++		for ((type) = 0; (type) < ANON_AND_FILE; (type)++)	\
+++			for ((zone) = 0; (zone) < MAX_NR_ZONES; (zone)++)
 ++
-++/* Build the program body from eBPF bytecode */
-++static int build_body(struct jit_context *ctx)
+++static int page_lru_gen(struct page *page)
 ++{
-++	const struct bpf_prog *prog = ctx->program;
-++	unsigned int i;
+++	unsigned long flags = READ_ONCE(page->flags);
 ++
-++	ctx->stack_used = 0;
-++	for (i = 0; i < prog->len; i++) {
-++		const struct bpf_insn *insn = &prog->insnsi[i];
-++		u32 *descp = &ctx->descriptors[i];
-++		int ret;
+++	return ((flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
+++}
 ++
-++		access_reg(ctx, insn->src_reg);
-++		access_reg(ctx, insn->dst_reg);
+++static struct lruvec *get_lruvec(int nid, struct mem_cgroup *memcg)
+++{
+++	struct pglist_data *pgdat = NODE_DATA(nid);
 ++
-++		ctx->bpf_index = i;
-++		if (ctx->target == NULL) {
-++			ctx->changes += INDEX(*descp) != ctx->jit_index;
-++			*descp &= JIT_DESC_CONVERT;
-++			*descp |= ctx->jit_index;
-++		}
+++#ifdef CONFIG_MEMCG
+++	if (memcg) {
+++		struct lruvec *lruvec = &memcg->nodeinfo[nid]->lruvec;
 ++
-++		ret = build_insn(insn, ctx);
-++		if (ret < 0)
-++			return ret;
+++		if (lruvec->pgdat != pgdat)
+++			lruvec->pgdat = pgdat;
 ++
-++		if (ret > 0) {
-++			i++;
-++			if (ctx->target == NULL)
-++				descp[1] = ctx->jit_index;
-++		}
+++		return lruvec;
 ++	}
-++
-++	/* Store the end offset, where the epilogue begins */
-++	ctx->descriptors[prog->len] = ctx->jit_index;
-++	return 0;
+++#endif
+++	return pgdat ? &pgdat->__lruvec : NULL;
 ++}
 ++
-++/* Set the branch conversion flag on all instructions */
-++static void set_convert_flag(struct jit_context *ctx, bool enable)
+++static int get_nr_gens(struct lruvec *lruvec, int type)
 ++{
-++	const struct bpf_prog *prog = ctx->program;
-++	u32 flag = enable ? JIT_DESC_CONVERT : 0;
-++	unsigned int i;
-++
-++	for (i = 0; i <= prog->len; i++)
-++		ctx->descriptors[i] = INDEX(ctx->descriptors[i]) | flag;
+++	return lruvec->evictable.max_seq - lruvec->evictable.min_seq[type] + 1;
 ++}
 ++
-++static void jit_fill_hole(void *area, unsigned int size)
+++static bool __maybe_unused seq_is_valid(struct lruvec *lruvec)
 ++{
-++	u32 *p;
-++
-++	/* We are guaranteed to have aligned memory. */
-++	for (p = area; size >= sizeof(u32); size -= sizeof(u32))
-++		uasm_i_break(&p, BRK_BUG); /* Increments p */
+++	return get_nr_gens(lruvec, 1) >= MIN_NR_GENS &&
+++	       get_nr_gens(lruvec, 1) <= get_nr_gens(lruvec, 0) &&
+++	       get_nr_gens(lruvec, 0) <= MAX_NR_GENS;
 ++}
 ++
-++bool bpf_jit_needs_zext(void)
-++{
-++	return true;
-++}
+++/******************************************************************************
+++ *                          state change
+++ ******************************************************************************/
 ++
-++struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
-++{
-++	struct bpf_prog *tmp, *orig_prog = prog;
-++	struct bpf_binary_header *header = NULL;
-++	struct jit_context ctx;
-++	bool tmp_blinded = false;
-++	unsigned int tmp_idx;
-++	unsigned int image_size;
-++	u8 *image_ptr;
-++	int tries;
+++#ifdef CONFIG_LRU_GEN_ENABLED
+++DEFINE_STATIC_KEY_TRUE(lru_gen_static_key);
+++#else
+++DEFINE_STATIC_KEY_FALSE(lru_gen_static_key);
+++#endif
 ++
-++	/*
-++	 * If BPF JIT was not enabled then we must fall back to
-++	 * the interpreter.
-++	 */
-++	if (!prog->jit_requested)
-++		return orig_prog;
-++	/*
-++	 * If constant blinding was enabled and we failed during blinding
-++	 * then we must fall back to the interpreter. Otherwise, we save
-++	 * the new JITed code.
-++	 */
-++	tmp = bpf_jit_blind_constants(prog);
-++	if (IS_ERR(tmp))
-++		return orig_prog;
-++	if (tmp != prog) {
-++		tmp_blinded = true;
-++		prog = tmp;
-++	}
+++static int lru_gen_nr_swapfiles;
 ++
-++	memset(&ctx, 0, sizeof(ctx));
-++	ctx.program = prog;
+++static bool __maybe_unused state_is_valid(struct lruvec *lruvec)
+++{
+++	int gen, type, zone;
+++	enum lru_list lru;
+++	struct lrugen *lrugen = &lruvec->evictable;
 ++
-++	/*
-++	 * Not able to allocate memory for descriptors[], then
-++	 * we must fall back to the interpreter
-++	 */
-++	ctx.descriptors = kcalloc(prog->len + 1, sizeof(*ctx.descriptors),
-++				  GFP_KERNEL);
-++	if (ctx.descriptors == NULL)
-++		goto out_err;
+++	for_each_evictable_lru(lru) {
+++		type = is_file_lru(lru);
 ++
-++	/* First pass discovers used resources */
-++	if (build_body(&ctx) < 0)
-++		goto out_err;
-++	/*
-++	 * Second pass computes instruction offsets.
-++	 * If any PC-relative branches are out of range, a sequence of
-++	 * a PC-relative branch + a jump is generated, and we have to
-++	 * try again from the beginning to generate the new offsets.
-++	 * This is done until no additional conversions are necessary.
-++	 * The last two iterations are done with all branches being
-++	 * converted, to guarantee offset table convergence within a
-++	 * fixed number of iterations.
-++	 */
-++	ctx.jit_index = 0;
-++	build_prologue(&ctx);
-++	tmp_idx = ctx.jit_index;
+++		if (lrugen->enabled[type] && !list_empty(&lruvec->lists[lru]))
+++			return false;
+++	}
 ++
-++	tries = JIT_MAX_ITERATIONS;
-++	do {
-++		ctx.jit_index = tmp_idx;
-++		ctx.changes = 0;
-++		if (tries == 2)
-++			set_convert_flag(&ctx, true);
-++		if (build_body(&ctx) < 0)
-++			goto out_err;
-++	} while (ctx.changes > 0 && --tries > 0);
+++	for_each_gen_type_zone(gen, type, zone) {
+++		if (!lrugen->enabled[type] && !list_empty(&lrugen->lists[gen][type][zone]))
+++			return false;
 ++
-++	if (WARN_ONCE(ctx.changes > 0, "JIT offsets failed to converge"))
-++		goto out_err;
+++		/* unlikely but not a bug when reset_batch_size() is pending */
+++		VM_WARN_ON(!lrugen->enabled[type] && lrugen->sizes[gen][type][zone]);
+++	}
 ++
-++	build_epilogue(&ctx, MIPS_R_RA);
+++	return true;
+++}
 ++
-++	/* Now we know the size of the structure to make */
-++	image_size = sizeof(u32) * ctx.jit_index;
-++	header = bpf_jit_binary_alloc(image_size, &image_ptr,
-++				      sizeof(u32), jit_fill_hole);
-++	/*
-++	 * Not able to allocate memory for the structure then
-++	 * we must fall back to the interpretation
-++	 */
-++	if (header == NULL)
-++		goto out_err;
+++static bool fill_lists(struct lruvec *lruvec)
+++{
+++	enum lru_list lru;
+++	int remaining = MAX_BATCH_SIZE;
 ++
-++	/* Actual pass to generate final JIT code */
-++	ctx.target = (u32 *)image_ptr;
-++	ctx.jit_index = 0;
+++	for_each_evictable_lru(lru) {
+++		int type = is_file_lru(lru);
+++		bool active = is_active_lru(lru);
+++		struct list_head *head = &lruvec->lists[lru];
 ++
-++	/*
-++	 * If building the JITed code fails somehow,
-++	 * we fall back to the interpretation.
-++	 */
-++	build_prologue(&ctx);
-++	if (build_body(&ctx) < 0)
-++		goto out_err;
-++	build_epilogue(&ctx, MIPS_R_RA);
+++		if (!lruvec->evictable.enabled[type])
+++			continue;
 ++
-++	/* Populate line info meta data */
-++	set_convert_flag(&ctx, false);
-++	bpf_prog_fill_jited_linfo(prog, &ctx.descriptors[1]);
+++		while (!list_empty(head)) {
+++			bool success;
+++			struct page *page = lru_to_page(head);
 ++
-++	/* Set as read-only exec and flush instruction cache */
-++	bpf_jit_binary_lock_ro(header);
-++	flush_icache_range((unsigned long)header,
-++			   (unsigned long)&ctx.target[ctx.jit_index]);
+++			VM_BUG_ON_PAGE(PageTail(page), page);
+++			VM_BUG_ON_PAGE(PageUnevictable(page), page);
+++			VM_BUG_ON_PAGE(PageActive(page) != active, page);
+++			VM_BUG_ON_PAGE(page_is_file_lru(page) != type, page);
+++			VM_BUG_ON_PAGE(page_lru_gen(page) < MAX_NR_GENS, page);
 ++
-++	if (bpf_jit_enable > 1)
-++		bpf_jit_dump(prog->len, image_size, 2, ctx.target);
+++			prefetchw_prev_lru_page(page, head, flags);
 ++
-++	prog->bpf_func = (void *)ctx.target;
-++	prog->jited = 1;
-++	prog->jited_len = image_size;
+++			del_page_from_lru_list(page, lruvec);
+++			success = lru_gen_add_page(page, lruvec, false);
+++			VM_BUG_ON(!success);
 ++
-++out:
-++	if (tmp_blinded)
-++		bpf_jit_prog_release_other(prog, prog == orig_prog ?
-++					   tmp : orig_prog);
-++	kfree(ctx.descriptors);
-++	return prog;
+++			if (!--remaining)
+++				return false;
+++		}
+++	}
 ++
-++out_err:
-++	prog = orig_prog;
-++	if (header)
-++		bpf_jit_binary_free(header);
-++	goto out;
+++	return true;
 ++}
-+--- /dev/null
-++++ b/arch/mips/net/bpf_jit_comp.h
-+@@ -0,0 +1,211 @@
-++/* SPDX-License-Identifier: GPL-2.0-only */
-++/*
-++ * Just-In-Time compiler for eBPF bytecode on 32-bit and 64-bit MIPS.
-++ *
-++ * Copyright (c) 2021 Anyfi Networks AB.
-++ * Author: Johan Almbladh <johan.almbladh@gmail.com>
-++ *
-++ * Based on code and ideas from
-++ * Copyright (c) 2017 Cavium, Inc.
-++ * Copyright (c) 2017 Shubham Bansal <illusionist.neo@gmail.com>
-++ * Copyright (c) 2011 Mircea Gherzan <mgherzan@gmail.com>
-++ */
 ++
-++#ifndef _BPF_JIT_COMP_H
-++#define _BPF_JIT_COMP_H
+++static bool drain_lists(struct lruvec *lruvec)
+++{
+++	int gen, type, zone;
+++	int remaining = MAX_BATCH_SIZE;
 ++
-++/* MIPS registers */
-++#define MIPS_R_ZERO	0   /* Const zero */
-++#define MIPS_R_AT	1   /* Asm temp   */
-++#define MIPS_R_V0	2   /* Result     */
-++#define MIPS_R_V1	3   /* Result     */
-++#define MIPS_R_A0	4   /* Argument   */
-++#define MIPS_R_A1	5   /* Argument   */
-++#define MIPS_R_A2	6   /* Argument   */
-++#define MIPS_R_A3	7   /* Argument   */
-++#define MIPS_R_A4	8   /* Arg (n64)  */
-++#define MIPS_R_A5	9   /* Arg (n64)  */
-++#define MIPS_R_A6	10  /* Arg (n64)  */
-++#define MIPS_R_A7	11  /* Arg (n64)  */
-++#define MIPS_R_T0	8   /* Temp (o32) */
-++#define MIPS_R_T1	9   /* Temp (o32) */
-++#define MIPS_R_T2	10  /* Temp (o32) */
-++#define MIPS_R_T3	11  /* Temp (o32) */
-++#define MIPS_R_T4	12  /* Temporary  */
-++#define MIPS_R_T5	13  /* Temporary  */
-++#define MIPS_R_T6	14  /* Temporary  */
-++#define MIPS_R_T7	15  /* Temporary  */
-++#define MIPS_R_S0	16  /* Saved      */
-++#define MIPS_R_S1	17  /* Saved      */
-++#define MIPS_R_S2	18  /* Saved      */
-++#define MIPS_R_S3	19  /* Saved      */
-++#define MIPS_R_S4	20  /* Saved      */
-++#define MIPS_R_S5	21  /* Saved      */
-++#define MIPS_R_S6	22  /* Saved      */
-++#define MIPS_R_S7	23  /* Saved      */
-++#define MIPS_R_T8	24  /* Temporary  */
-++#define MIPS_R_T9	25  /* Temporary  */
-++/*      MIPS_R_K0	26     Reserved   */
-++/*      MIPS_R_K1	27     Reserved   */
-++#define MIPS_R_GP	28  /* Global ptr */
-++#define MIPS_R_SP	29  /* Stack ptr  */
-++#define MIPS_R_FP	30  /* Frame ptr  */
-++#define MIPS_R_RA	31  /* Return     */
+++	for_each_gen_type_zone(gen, type, zone) {
+++		struct list_head *head = &lruvec->evictable.lists[gen][type][zone];
 ++
-++/*
-++ * Jump address mask for immediate jumps. The four most significant bits
-++ * must be equal to PC.
-++ */
-++#define MIPS_JMP_MASK	0x0fffffffUL
+++		if (lruvec->evictable.enabled[type])
+++			continue;
 ++
-++/* Maximum number of iterations in offset table computation */
-++#define JIT_MAX_ITERATIONS	8
+++		while (!list_empty(head)) {
+++			bool success;
+++			struct page *page = lru_to_page(head);
 ++
-++/*
-++ * Jump pseudo-instructions used internally
-++ * for branch conversion and branch optimization.
-++ */
-++#define JIT_JNSET	0xe0
-++#define JIT_JNOP	0xf0
+++			VM_BUG_ON_PAGE(PageTail(page), page);
+++			VM_BUG_ON_PAGE(PageUnevictable(page), page);
+++			VM_BUG_ON_PAGE(PageActive(page), page);
+++			VM_BUG_ON_PAGE(page_is_file_lru(page) != type, page);
+++			VM_BUG_ON_PAGE(page_zonenum(page) != zone, page);
 ++
-++/* Descriptor flag for PC-relative branch conversion */
-++#define JIT_DESC_CONVERT	BIT(31)
+++			prefetchw_prev_lru_page(page, head, flags);
 ++
-++/* JIT context for an eBPF program */
-++struct jit_context {
-++	struct bpf_prog *program;     /* The eBPF program being JITed        */
-++	u32 *descriptors;             /* eBPF to JITed CPU insn descriptors  */
-++	u32 *target;                  /* JITed code buffer                   */
-++	u32 bpf_index;                /* Index of current BPF program insn   */
-++	u32 jit_index;                /* Index of current JIT target insn    */
-++	u32 changes;                  /* Number of PC-relative branch conv   */
-++	u32 accessed;                 /* Bit mask of read eBPF registers     */
-++	u32 clobbered;                /* Bit mask of modified CPU registers  */
-++	u32 stack_size;               /* Total allocated stack size in bytes */
-++	u32 saved_size;               /* Size of callee-saved registers      */
-++	u32 stack_used;               /* Stack size used for function calls  */
-++};
+++			success = lru_gen_del_page(page, lruvec, false);
+++			VM_BUG_ON(!success);
+++			add_page_to_lru_list(page, lruvec);
 ++
-++/* Emit the instruction if the JIT memory space has been allocated */
-++#define emit(ctx, func, ...)					\
-++do {								\
-++	if ((ctx)->target != NULL) {				\
-++		u32 *p = &(ctx)->target[ctx->jit_index];	\
-++		uasm_i_##func(&p, ##__VA_ARGS__);		\
-++	}							\
-++	(ctx)->jit_index++;					\
-++} while (0)
+++			if (!--remaining)
+++				return false;
+++		}
+++	}
 ++
-++/*
-++ * Mark a BPF register as accessed, it needs to be
-++ * initialized by the program if expected, e.g. FP.
-++ */
-++static inline void access_reg(struct jit_context *ctx, u8 reg)
-++{
-++	ctx->accessed |= BIT(reg);
+++	return true;
 ++}
 ++
 ++/*
-++ * Mark a CPU register as clobbered, it needs to be
-++ * saved/restored by the program if callee-saved.
+++ * For file page tracking, we enable/disable it according to the main switch.
+++ * For anon page tracking, we only enabled it when the main switch is on and
+++ * there is at least one swapfile; we disable it when there are no swapfiles
+++ * regardless of the value of the main switch. Otherwise, we will eventually
+++ * reach the max size of the sliding window and have to call inc_min_seq().
 ++ */
-++static inline void clobber_reg(struct jit_context *ctx, u8 reg)
+++void lru_gen_change_state(bool enable, bool main, bool swap)
 ++{
-++	ctx->clobbered |= BIT(reg);
-++}
+++	static DEFINE_MUTEX(state_mutex);
 ++
-++/*
-++ * Push registers on the stack, starting at a given depth from the stack
-++ * pointer and increasing. The next depth to be written is returned.
-++ */
-++int push_regs(struct jit_context *ctx, u32 mask, u32 excl, int depth);
+++	struct mem_cgroup *memcg;
 ++
-++/*
-++ * Pop registers from the stack, starting at a given depth from the stack
-++ * pointer and increasing. The next depth to be read is returned.
-++ */
-++int pop_regs(struct jit_context *ctx, u32 mask, u32 excl, int depth);
+++	mem_hotplug_begin();
+++	cgroup_lock();
+++	mutex_lock(&state_mutex);
 ++
-++/* Compute the 28-bit jump target address from a BPF program location */
-++int get_target(struct jit_context *ctx, u32 loc);
+++	if (swap) {
+++		if (enable)
+++			swap = !lru_gen_nr_swapfiles++;
+++		else
+++			swap = !--lru_gen_nr_swapfiles;
+++	}
 ++
-++/* Compute the PC-relative offset to relative BPF program offset */
-++int get_offset(const struct jit_context *ctx, int off);
+++	if (main && enable != lru_gen_enabled()) {
+++		if (enable)
+++			static_branch_enable(&lru_gen_static_key);
+++		else
+++			static_branch_disable(&lru_gen_static_key);
+++	} else if (!swap || !lru_gen_enabled())
+++		goto unlock;
 ++
-++/* dst = imm (32-bit) */
-++void emit_mov_i(struct jit_context *ctx, u8 dst, s32 imm);
+++	memcg = mem_cgroup_iter(NULL, NULL, NULL);
+++	do {
+++		int nid;
 ++
-++/* dst = src (32-bit) */
-++void emit_mov_r(struct jit_context *ctx, u8 dst, u8 src);
+++		for_each_node(nid) {
+++			struct lruvec *lruvec = get_lruvec(nid, memcg);
 ++
-++/* Validate ALU/ALU64 immediate range */
-++bool valid_alu_i(u8 op, s32 imm);
+++			if (!lruvec)
+++				continue;
 ++
-++/* Rewrite ALU/ALU64 immediate operation */
-++bool rewrite_alu_i(u8 op, s32 imm, u8 *alu, s32 *val);
+++			spin_lock_irq(&lruvec->lru_lock);
 ++
-++/* ALU immediate operation (32-bit) */
-++void emit_alu_i(struct jit_context *ctx, u8 dst, s32 imm, u8 op);
+++			VM_BUG_ON(!seq_is_valid(lruvec));
+++			VM_BUG_ON(!state_is_valid(lruvec));
 ++
-++/* ALU register operation (32-bit) */
-++void emit_alu_r(struct jit_context *ctx, u8 dst, u8 src, u8 op);
+++			lruvec->evictable.enabled[0] = lru_gen_enabled() && lru_gen_nr_swapfiles;
+++			lruvec->evictable.enabled[1] = lru_gen_enabled();
 ++
-++/* Atomic read-modify-write (32-bit) */
-++void emit_atomic_r(struct jit_context *ctx, u8 dst, u8 src, s16 off, u8 code);
+++			while (!(enable ? fill_lists(lruvec) : drain_lists(lruvec))) {
+++				spin_unlock_irq(&lruvec->lru_lock);
+++				cond_resched();
+++				spin_lock_irq(&lruvec->lru_lock);
+++			}
 ++
-++/* Atomic compare-and-exchange (32-bit) */
-++void emit_cmpxchg_r(struct jit_context *ctx, u8 dst, u8 src, u8 res, s16 off);
+++			spin_unlock_irq(&lruvec->lru_lock);
+++		}
 ++
-++/* Swap bytes and truncate a register word or half word */
-++void emit_bswap_r(struct jit_context *ctx, u8 dst, u32 width);
+++		cond_resched();
+++	} while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)));
+++unlock:
+++	mutex_unlock(&state_mutex);
+++	cgroup_unlock();
+++	mem_hotplug_done();
+++}
 ++
-++/* Validate JMP/JMP32 immediate range */
-++bool valid_jmp_i(u8 op, s32 imm);
+++/******************************************************************************
+++ *                          initialization
+++ ******************************************************************************/
 ++
-++/* Prepare a PC-relative jump operation with immediate conditional */
-++void setup_jmp_i(struct jit_context *ctx, s32 imm, u8 width,
-++		 u8 bpf_op, s16 bpf_off, u8 *jit_op, s32 *jit_off);
+++void lru_gen_init_state(struct mem_cgroup *memcg, struct lruvec *lruvec)
+++{
+++	int i;
+++	int gen, type, zone;
+++	struct lrugen *lrugen = &lruvec->evictable;
 ++
-++/* Prepare a PC-relative jump operation with register conditional */
-++void setup_jmp_r(struct jit_context *ctx, bool same_reg,
-++		 u8 bpf_op, s16 bpf_off, u8 *jit_op, s32 *jit_off);
+++	lrugen->max_seq = MIN_NR_GENS + 1;
+++	lrugen->enabled[0] = lru_gen_enabled() && lru_gen_nr_swapfiles;
+++	lrugen->enabled[1] = lru_gen_enabled();
 ++
-++/* Finish a PC-relative jump operation */
-++int finish_jmp(struct jit_context *ctx, u8 jit_op, s16 bpf_off);
+++	for (i = 0; i <= MIN_NR_GENS + 1; i++)
+++		lrugen->timestamps[i] = jiffies;
 ++
-++/* Conditional JMP/JMP32 immediate */
-++void emit_jmp_i(struct jit_context *ctx, u8 dst, s32 imm, s32 off, u8 op);
+++	for_each_gen_type_zone(gen, type, zone)
+++		INIT_LIST_HEAD(&lrugen->lists[gen][type][zone]);
+++}
 ++
-++/* Conditional JMP/JMP32 register */
-++void emit_jmp_r(struct jit_context *ctx, u8 dst, u8 src, s32 off, u8 op);
+++#ifdef CONFIG_MEMCG
+++void lru_gen_init_memcg(struct mem_cgroup *memcg)
+++{
+++	int nid;
 ++
-++/* Jump always */
-++int emit_ja(struct jit_context *ctx, s16 off);
+++	for_each_node(nid) {
+++		struct lruvec *lruvec = get_lruvec(nid, memcg);
 ++
-++/* Jump to epilogue */
-++int emit_exit(struct jit_context *ctx);
+++		lru_gen_init_state(memcg, lruvec);
+++	}
+++}
+++#endif
 ++
-++/*
-++ * Build program prologue to set up the stack and registers.
-++ * This function is implemented separately for 32-bit and 64-bit JITs.
-++ */
-++void build_prologue(struct jit_context *ctx);
+++static int __init init_lru_gen(void)
+++{
+++	BUILD_BUG_ON(MIN_NR_GENS + 1 >= MAX_NR_GENS);
+++	BUILD_BUG_ON(BIT(LRU_GEN_WIDTH) <= MAX_NR_GENS);
 ++
-++/*
-++ * Build the program epilogue to restore the stack and registers.
-++ * This function is implemented separately for 32-bit and 64-bit JITs.
-++ */
-++void build_epilogue(struct jit_context *ctx, int dest_reg);
+++	return 0;
+++};
+++late_initcall(init_lru_gen);
 ++
-++/*
-++ * Convert an eBPF instruction to native instruction, i.e
-++ * JITs an eBPF instruction.
-++ * Returns :
-++ *	0  - Successfully JITed an 8-byte eBPF instruction
-++ *	>0 - Successfully JITed a 16-byte eBPF instruction
-++ *	<0 - Failed to JIT.
-++ * This function is implemented separately for 32-bit and 64-bit JITs.
-++ */
-++int build_insn(const struct bpf_insn *insn, struct jit_context *ctx);
+++#endif /* CONFIG_LRU_GEN */
 ++
-++#endif /* _BPF_JIT_COMP_H */
-+--- /dev/null
-++++ b/arch/mips/net/bpf_jit_comp32.c
-+@@ -0,0 +1,1741 @@
-++// SPDX-License-Identifier: GPL-2.0-only
-++/*
-++ * Just-In-Time compiler for eBPF bytecode on MIPS.
-++ * Implementation of JIT functions for 32-bit CPUs.
-++ *
-++ * Copyright (c) 2021 Anyfi Networks AB.
-++ * Author: Johan Almbladh <johan.almbladh@gmail.com>
-++ *
-++ * Based on code and ideas from
-++ * Copyright (c) 2017 Cavium, Inc.
-++ * Copyright (c) 2017 Shubham Bansal <illusionist.neo@gmail.com>
-++ * Copyright (c) 2011 Mircea Gherzan <mgherzan@gmail.com>
-++ */
++ static void shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
++ {
++ 	unsigned long nr[NR_LRU_LISTS];
+diff --git a/target/linux/generic/backport-5.15/020-v6.1-05-mm-multigenerational-lru-mm_struct-list.patch b/target/linux/generic/backport-5.15/020-v6.1-05-mm-multigenerational-lru-mm_struct-list.patch
+new file mode 100644
+index 0000000000..2592f18e06
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/020-v6.1-05-mm-multigenerational-lru-mm_struct-list.patch
+@@ -0,0 +1,760 @@
++From 534bcc4a0bb5b24600891ce793f0295a142e9dae Mon Sep 17 00:00:00 2001
++From: Yu Zhao <yuzhao@google.com>
++Date: Mon, 5 Apr 2021 04:17:41 -0600
++Subject: [PATCH 05/10] mm: multigenerational lru: mm_struct list
++
++To scan PTEs for accessed pages, a mm_struct list is maintained for
++each memcg. When multiple threads traverse the same memcg->mm_list,
++each of them gets a unique mm_struct and therefore they can run
++walk_page_range() concurrently to reach page tables of all processes
++of this memcg.
++
++This infrastructure also provides the following optimizations:
++  1) it allows walkers to skip processes that have been sleeping since
++  the last walk by tracking the usage of mm_struct between context
++  switches.
++  2) it allows walkers to add interesting items they find during a
++  walk to a Bloom filter so that they can skip uninteresting items
++  during the next walk by testing whether an item is in this Bloom
++  filter.
++
++Signed-off-by: Yu Zhao <yuzhao@google.com>
++Tested-by: Konstantin Kharlamov <Hi-Angel@yandex.ru>
++Change-Id: I25d9eda8c6bdc7c3653b9f210a159d6c247c81e8
++---
++ fs/exec.c                  |   2 +
++ include/linux/memcontrol.h |   4 +
++ include/linux/mm_inline.h  |   6 +
++ include/linux/mm_types.h   |  75 +++++++++
++ include/linux/mmzone.h     |  63 +++++++
++ kernel/exit.c              |   1 +
++ kernel/fork.c              |   9 +
++ kernel/sched/core.c        |   1 +
++ mm/memcontrol.c            |  25 +++
++ mm/vmscan.c                | 331 +++++++++++++++++++++++++++++++++++++
++ 10 files changed, 517 insertions(+)
++
++--- a/fs/exec.c
+++++ b/fs/exec.c
++@@ -1013,6 +1013,7 @@ static int exec_mmap(struct mm_struct *m
++ 	active_mm = tsk->active_mm;
++ 	tsk->active_mm = mm;
++ 	tsk->mm = mm;
+++	lru_gen_add_mm(mm);
++ 	/*
++ 	 * This prevents preemption while active_mm is being loaded and
++ 	 * it and mm are being updated, which could cause problems for
++@@ -1023,6 +1024,7 @@ static int exec_mmap(struct mm_struct *m
++ 	if (!IS_ENABLED(CONFIG_ARCH_WANT_IRQS_OFF_ACTIVATE_MM))
++ 		local_irq_enable();
++ 	activate_mm(active_mm, mm);
+++	lru_gen_activate_mm(mm);
++ 	if (IS_ENABLED(CONFIG_ARCH_WANT_IRQS_OFF_ACTIVATE_MM))
++ 		local_irq_enable();
++ 	tsk->mm->vmacache_seqnum = 0;
++--- a/include/linux/memcontrol.h
+++++ b/include/linux/memcontrol.h
++@@ -348,6 +348,10 @@ struct mem_cgroup {
++ 	struct deferred_split deferred_split_queue;
++ #endif
++ 
+++#ifdef CONFIG_LRU_GEN
+++	struct lru_gen_mm_list mm_list;
+++#endif
 ++
-++#include <linux/math64.h>
-++#include <linux/errno.h>
-++#include <linux/filter.h>
-++#include <linux/bpf.h>
-++#include <asm/cpu-features.h>
-++#include <asm/isa-rev.h>
-++#include <asm/uasm.h>
++ 	struct mem_cgroup_per_node *nodeinfo[];
++ };
++ 
++--- a/include/linux/mm_inline.h
+++++ b/include/linux/mm_inline.h
++@@ -100,6 +100,12 @@ static inline int lru_gen_from_seq(unsig
++ 	return seq % MAX_NR_GENS;
++ }
++ 
+++/* Return a proper index regardless whether we keep stats for historical generations. */
+++static inline int lru_hist_from_seq(unsigned long seq)
+++{
+++	return seq % NR_HIST_GENS;
+++}
 ++
-++#include "bpf_jit_comp.h"
++ /* The youngest and the second youngest generations are counted as active. */
++ static inline bool lru_gen_is_active(struct lruvec *lruvec, int gen)
++ {
++--- a/include/linux/mm_types.h
+++++ b/include/linux/mm_types.h
++@@ -3,6 +3,7 @@
++ #define _LINUX_MM_TYPES_H
++ 
++ #include <linux/mm_types_task.h>
+++#include <linux/sched.h>
++ 
++ #include <linux/auxvec.h>
++ #include <linux/list.h>
++@@ -15,6 +16,8 @@
++ #include <linux/page-flags-layout.h>
++ #include <linux/workqueue.h>
++ #include <linux/seqlock.h>
+++#include <linux/nodemask.h>
+++#include <linux/mmdebug.h>
++ 
++ #include <asm/mmu.h>
++ 
++@@ -580,6 +583,18 @@ struct mm_struct {
++ #ifdef CONFIG_IOMMU_SUPPORT
++ 		u32 pasid;
++ #endif
+++#ifdef CONFIG_LRU_GEN
+++		struct {
+++			/* the node of a global or per-memcg mm_struct list */
+++			struct list_head list;
+++#ifdef CONFIG_MEMCG
+++			/* points to the memcg of the owner task above */
+++			struct mem_cgroup *memcg;
+++#endif
+++			/* whether this mm_struct has been used since the last walk */
+++			nodemask_t nodes;
+++		} lrugen;
+++#endif /* CONFIG_LRU_GEN */
++ 	} __randomize_layout;
++ 
++ 	/*
++@@ -606,6 +621,66 @@ static inline cpumask_t *mm_cpumask(stru
++ 	return (struct cpumask *)&mm->cpu_bitmap;
++ }
++ 
+++#ifdef CONFIG_LRU_GEN
 ++
-++/* MIPS a4-a7 are not available in the o32 ABI */
-++#undef MIPS_R_A4
-++#undef MIPS_R_A5
-++#undef MIPS_R_A6
-++#undef MIPS_R_A7
+++struct lru_gen_mm_list {
+++	/* a global or per-memcg mm_struct list */
+++	struct list_head fifo;
+++	/* protects the list above */
+++	spinlock_t lock;
+++};
 ++
-++/* Stack is 8-byte aligned in o32 ABI */
-++#define MIPS_STACK_ALIGNMENT 8
+++void lru_gen_add_mm(struct mm_struct *mm);
+++void lru_gen_del_mm(struct mm_struct *mm);
+++#ifdef CONFIG_MEMCG
+++void lru_gen_migrate_mm(struct mm_struct *mm);
+++#endif
 ++
-++/*
-++ * The top 16 bytes of a stack frame is reserved for the callee in O32 ABI.
-++ * This corresponds to stack space for register arguments a0-a3.
-++ */
-++#define JIT_RESERVED_STACK 16
+++static inline void lru_gen_init_mm(struct mm_struct *mm)
+++{
+++	INIT_LIST_HEAD(&mm->lrugen.list);
+++#ifdef CONFIG_MEMCG
+++	mm->lrugen.memcg = NULL;
+++#endif
+++	nodes_clear(mm->lrugen.nodes);
+++}
 ++
-++/* Temporary 64-bit register used by JIT */
-++#define JIT_REG_TMP MAX_BPF_JIT_REG
+++/* Track the usage of each mm_struct so that we can skip inactive ones. */
+++static inline void lru_gen_activate_mm(struct mm_struct *mm)
+++{
+++	/* unlikely but not a bug when racing with lru_gen_migrate_mm() */
+++	VM_WARN_ON(list_empty(&mm->lrugen.list));
 ++
-++/*
-++ * Number of prologue bytes to skip when doing a tail call.
-++ * Tail call count (TCC) initialization (8 bytes) always, plus
-++ * R0-to-v0 assignment (4 bytes) if big endian.
-++ */
-++#ifdef __BIG_ENDIAN
-++#define JIT_TCALL_SKIP 12
-++#else
-++#define JIT_TCALL_SKIP 8
-++#endif
+++	if (!(current->flags & PF_KTHREAD) && !nodes_full(mm->lrugen.nodes))
+++		nodes_setall(mm->lrugen.nodes);
+++}
 ++
-++/* CPU registers holding the callee return value */
-++#define JIT_RETURN_REGS	  \
-++	(BIT(MIPS_R_V0) | \
-++	 BIT(MIPS_R_V1))
+++#else /* !CONFIG_LRU_GEN */
 ++
-++/* CPU registers arguments passed to callee directly */
-++#define JIT_ARG_REGS      \
-++	(BIT(MIPS_R_A0) | \
-++	 BIT(MIPS_R_A1) | \
-++	 BIT(MIPS_R_A2) | \
-++	 BIT(MIPS_R_A3))
+++static inline void lru_gen_add_mm(struct mm_struct *mm)
+++{
+++}
 ++
-++/* CPU register arguments passed to callee on stack */
-++#define JIT_STACK_REGS    \
-++	(BIT(MIPS_R_T0) | \
-++	 BIT(MIPS_R_T1) | \
-++	 BIT(MIPS_R_T2) | \
-++	 BIT(MIPS_R_T3) | \
-++	 BIT(MIPS_R_T4) | \
-++	 BIT(MIPS_R_T5))
+++static inline void lru_gen_del_mm(struct mm_struct *mm)
+++{
+++}
 ++
-++/* Caller-saved CPU registers */
-++#define JIT_CALLER_REGS    \
-++	(JIT_RETURN_REGS | \
-++	 JIT_ARG_REGS    | \
-++	 JIT_STACK_REGS)
+++#ifdef CONFIG_MEMCG
+++static inline void lru_gen_migrate_mm(struct mm_struct *mm)
+++{
+++}
+++#endif
 ++
-++/* Callee-saved CPU registers */
-++#define JIT_CALLEE_REGS   \
-++	(BIT(MIPS_R_S0) | \
-++	 BIT(MIPS_R_S1) | \
-++	 BIT(MIPS_R_S2) | \
-++	 BIT(MIPS_R_S3) | \
-++	 BIT(MIPS_R_S4) | \
-++	 BIT(MIPS_R_S5) | \
-++	 BIT(MIPS_R_S6) | \
-++	 BIT(MIPS_R_S7) | \
-++	 BIT(MIPS_R_GP) | \
-++	 BIT(MIPS_R_FP) | \
-++	 BIT(MIPS_R_RA))
+++static inline void lru_gen_init_mm(struct mm_struct *mm)
+++{
+++}
 ++
-++/*
-++ * Mapping of 64-bit eBPF registers to 32-bit native MIPS registers.
-++ *
-++ * 1) Native register pairs are ordered according to CPU endiannes, following
-++ *    the MIPS convention for passing 64-bit arguments and return values.
-++ * 2) The eBPF return value, arguments and callee-saved registers are mapped
-++ *    to their native MIPS equivalents.
-++ * 3) Since the 32 highest bits in the eBPF FP register are always zero,
-++ *    only one general-purpose register is actually needed for the mapping.
-++ *    We use the fp register for this purpose, and map the highest bits to
-++ *    the MIPS register r0 (zero).
-++ * 4) We use the MIPS gp and at registers as internal temporary registers
-++ *    for constant blinding. The gp register is callee-saved.
-++ * 5) One 64-bit temporary register is mapped for use when sign-extending
-++ *    immediate operands. MIPS registers t6-t9 are available to the JIT
-++ *    for as temporaries when implementing complex 64-bit operations.
-++ *
-++ * With this scheme all eBPF registers are being mapped to native MIPS
-++ * registers without having to use any stack scratch space. The direct
-++ * register mapping (2) simplifies the handling of function calls.
-++ */
-++static const u8 bpf2mips32[][2] = {
-++	/* Return value from in-kernel function, and exit value from eBPF */
-++	[BPF_REG_0] = {MIPS_R_V1, MIPS_R_V0},
-++	/* Arguments from eBPF program to in-kernel function */
-++	[BPF_REG_1] = {MIPS_R_A1, MIPS_R_A0},
-++	[BPF_REG_2] = {MIPS_R_A3, MIPS_R_A2},
-++	/* Remaining arguments, to be passed on the stack per O32 ABI */
-++	[BPF_REG_3] = {MIPS_R_T1, MIPS_R_T0},
-++	[BPF_REG_4] = {MIPS_R_T3, MIPS_R_T2},
-++	[BPF_REG_5] = {MIPS_R_T5, MIPS_R_T4},
-++	/* Callee-saved registers that in-kernel function will preserve */
-++	[BPF_REG_6] = {MIPS_R_S1, MIPS_R_S0},
-++	[BPF_REG_7] = {MIPS_R_S3, MIPS_R_S2},
-++	[BPF_REG_8] = {MIPS_R_S5, MIPS_R_S4},
-++	[BPF_REG_9] = {MIPS_R_S7, MIPS_R_S6},
-++	/* Read-only frame pointer to access the eBPF stack */
-++#ifdef __BIG_ENDIAN
-++	[BPF_REG_FP] = {MIPS_R_FP, MIPS_R_ZERO},
+++static inline void lru_gen_activate_mm(struct mm_struct *mm)
+++{
+++}
+++
+++#endif /* CONFIG_LRU_GEN */
+++
++ struct mmu_gather;
++ extern void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm);
++ extern void tlb_gather_mmu_fullmm(struct mmu_gather *tlb, struct mm_struct *mm);
++--- a/include/linux/mmzone.h
+++++ b/include/linux/mmzone.h
++@@ -318,6 +318,13 @@ struct lruvec;
++ #define MIN_NR_GENS		2
++ #define MAX_NR_GENS		((unsigned int)CONFIG_NR_LRU_GENS)
++ 
+++/* Whether to keep stats for historical generations. */
+++#ifdef CONFIG_LRU_GEN_STATS
+++#define NR_HIST_GENS		((unsigned int)CONFIG_NR_LRU_GENS)
 ++#else
-++	[BPF_REG_FP] = {MIPS_R_ZERO, MIPS_R_FP},
+++#define NR_HIST_GENS		1U
 ++#endif
-++	/* Temporary register for blinding constants */
-++	[BPF_REG_AX] = {MIPS_R_GP, MIPS_R_AT},
-++	/* Temporary register for internal JIT use */
-++	[JIT_REG_TMP] = {MIPS_R_T7, MIPS_R_T6},
+++
++ struct lrugen {
++ 	/* the aging increments the max generation number */
++ 	unsigned long max_seq;
++@@ -333,13 +340,63 @@ struct lrugen {
++ 	bool enabled[ANON_AND_FILE];
++ };
++ 
+++enum {
+++	MM_LEAF_TOTAL,		/* total leaf entries */
+++	MM_LEAF_OLD,		/* old leaf entries */
+++	MM_LEAF_YOUNG,		/* young leaf entries */
+++	MM_NONLEAF_TOTAL,	/* total non-leaf entries */
+++	MM_NONLEAF_PREV,	/* previously worthy non-leaf entries */
+++	MM_NONLEAF_CUR,		/* currently worthy non-leaf entries */
+++	NR_MM_STATS
 ++};
 ++
-++/* Get low CPU register for a 64-bit eBPF register mapping */
-++static inline u8 lo(const u8 reg[])
+++/* mnemonic codes for the stats above */
+++#define MM_STAT_CODES		"toydpc"
+++
+++/* double buffering bloom filters */
+++#define NR_BLOOM_FILTERS	2
+++
+++struct lru_gen_mm_walk {
+++	/* set to max_seq after each round of walk */
+++	unsigned long seq;
+++	/* the next mm_struct on the list to walk */
+++	struct list_head *head;
+++	/* the first mm_struct never walked before */
+++	struct list_head *tail;
+++	/* to wait for the last walker to finish */
+++	struct wait_queue_head wait;
+++	/* bloom filters flip after each round of walk */
+++	unsigned long *filters[NR_BLOOM_FILTERS];
+++	/* page table stats for debugging */
+++	unsigned long stats[NR_HIST_GENS][NR_MM_STATS];
+++	/* the number of concurrent walkers */
+++	int nr_walkers;
+++};
+++
+++#define MIN_BATCH_SIZE		64
++ #define MAX_BATCH_SIZE		8192
++ 
+++struct mm_walk_args {
+++	struct mem_cgroup *memcg;
+++	unsigned long max_seq;
+++	unsigned long start_pfn;
+++	unsigned long end_pfn;
+++	unsigned long next_addr;
+++	unsigned long bitmap[BITS_TO_LONGS(MIN_BATCH_SIZE)];
+++	int node_id;
+++	int swappiness;
+++	int batch_size;
+++	int nr_pages[MAX_NR_GENS][ANON_AND_FILE][MAX_NR_ZONES];
+++	int mm_stats[NR_MM_STATS];
+++	bool use_filter;
+++};
+++
++ void lru_gen_init_state(struct mem_cgroup *memcg, struct lruvec *lruvec);
++ void lru_gen_change_state(bool enable, bool main, bool swap);
++ 
++ #ifdef CONFIG_MEMCG
++ void lru_gen_init_memcg(struct mem_cgroup *memcg);
+++void lru_gen_free_memcg(struct mem_cgroup *memcg);
++ #endif
++ 
++ #else /* !CONFIG_LRU_GEN */
++@@ -356,6 +413,10 @@ static inline void lru_gen_change_state(
++ static inline void lru_gen_init_memcg(struct mem_cgroup *memcg)
++ {
++ }
+++
+++static inline void lru_gen_free_memcg(struct mem_cgroup *memcg)
 ++{
-++#ifdef __BIG_ENDIAN
-++	return reg[0];
-++#else
-++	return reg[1];
-++#endif
 ++}
++ #endif
++ 
++ #endif /* CONFIG_LRU_GEN */
++@@ -380,6 +441,8 @@ struct lruvec {
++ #ifdef CONFIG_LRU_GEN
++ 	/* unevictable pages are on LRU_UNEVICTABLE */
++ 	struct lrugen			evictable;
+++	/* state for mm list and page table walks */
+++	struct lru_gen_mm_walk		mm_walk;
++ #endif
++ #ifdef CONFIG_MEMCG
++ 	struct pglist_data *pgdat;
++--- a/kernel/exit.c
+++++ b/kernel/exit.c
++@@ -422,6 +422,7 @@ assign_new_owner:
++ 		goto retry;
++ 	}
++ 	WRITE_ONCE(mm->owner, c);
+++	lru_gen_migrate_mm(mm);
++ 	task_unlock(c);
++ 	put_task_struct(c);
++ }
++--- a/kernel/fork.c
+++++ b/kernel/fork.c
++@@ -1080,6 +1080,7 @@ static struct mm_struct *mm_init(struct
++ 		goto fail_nocontext;
++ 
++ 	mm->user_ns = get_user_ns(user_ns);
+++	lru_gen_init_mm(mm);
++ 	return mm;
++ 
++ fail_nocontext:
++@@ -1122,6 +1123,7 @@ static inline void __mmput(struct mm_str
++ 	}
++ 	if (mm->binfmt)
++ 		module_put(mm->binfmt->module);
+++	lru_gen_del_mm(mm);
++ 	mmdrop(mm);
++ }
++ 
++@@ -2617,6 +2619,13 @@ pid_t kernel_clone(struct kernel_clone_a
++ 		get_task_struct(p);
++ 	}
++ 
+++	if (IS_ENABLED(CONFIG_LRU_GEN) && !(clone_flags & CLONE_VM)) {
+++		/* lock the task to synchronize with memcg migration */
+++		task_lock(p);
+++		lru_gen_add_mm(p->mm);
+++		task_unlock(p);
+++	}
 ++
-++/* Get high CPU register for a 64-bit eBPF register mapping */
-++static inline u8 hi(const u8 reg[])
++ 	wake_up_new_task(p);
++ 
++ 	/* forking complete and child started to run, tell ptracer */
++--- a/kernel/sched/core.c
+++++ b/kernel/sched/core.c
++@@ -4978,6 +4978,7 @@ context_switch(struct rq *rq, struct tas
++ 		 * finish_task_switch()'s mmdrop().
++ 		 */
++ 		switch_mm_irqs_off(prev->active_mm, next->mm, next);
+++		lru_gen_activate_mm(next->mm);
++ 
++ 		if (!prev->mm) {                        // from kernel
++ 			/* will mmdrop() in finish_task_switch(). */
++--- a/mm/memcontrol.c
+++++ b/mm/memcontrol.c
++@@ -5163,6 +5163,7 @@ static void __mem_cgroup_free(struct mem
++ 
++ static void mem_cgroup_free(struct mem_cgroup *memcg)
++ {
+++	lru_gen_free_memcg(memcg);
++ 	memcg_wb_domain_exit(memcg);
++ 	__mem_cgroup_free(memcg);
++ }
++@@ -6195,6 +6196,29 @@ static void mem_cgroup_move_task(void)
++ }
++ #endif
++ 
+++#ifdef CONFIG_LRU_GEN
+++static void mem_cgroup_attach(struct cgroup_taskset *tset)
 ++{
-++#ifdef __BIG_ENDIAN
-++	return reg[1];
+++	struct cgroup_subsys_state *css;
+++	struct task_struct *task = NULL;
+++
+++	cgroup_taskset_for_each_leader(task, css, tset)
+++		break;
+++
+++	if (!task)
+++		return;
+++
+++	task_lock(task);
+++	if (task->mm && task->mm->owner == task)
+++		lru_gen_migrate_mm(task->mm);
+++	task_unlock(task);
+++}
 ++#else
-++	return reg[0];
-++#endif
+++static void mem_cgroup_attach(struct cgroup_taskset *tset)
+++{
 ++}
+++#endif /* CONFIG_LRU_GEN */
 ++
-++/*
-++ * Mark a 64-bit CPU register pair as clobbered, it needs to be
-++ * saved/restored by the program if callee-saved.
-++ */
-++static void clobber_reg64(struct jit_context *ctx, const u8 reg[])
++ static int seq_puts_memcg_tunable(struct seq_file *m, unsigned long value)
++ {
++ 	if (value == PAGE_COUNTER_MAX)
++@@ -6538,6 +6562,7 @@ struct cgroup_subsys memory_cgrp_subsys
++ 	.css_reset = mem_cgroup_css_reset,
++ 	.css_rstat_flush = mem_cgroup_css_rstat_flush,
++ 	.can_attach = mem_cgroup_can_attach,
+++	.attach = mem_cgroup_attach,
++ 	.cancel_attach = mem_cgroup_cancel_attach,
++ 	.post_attach = mem_cgroup_move_task,
++ 	.dfl_cftypes = memory_files,
++--- a/mm/vmscan.c
+++++ b/mm/vmscan.c
++@@ -2929,6 +2929,306 @@ static bool __maybe_unused seq_is_valid(
++ }
++ 
++ /******************************************************************************
+++ *                          mm_struct list
+++ ******************************************************************************/
+++
+++static struct lru_gen_mm_list *get_mm_list(struct mem_cgroup *memcg)
 ++{
-++	clobber_reg(ctx, reg[0]);
-++	clobber_reg(ctx, reg[1]);
+++	static struct lru_gen_mm_list mm_list = {
+++		.fifo = LIST_HEAD_INIT(mm_list.fifo),
+++		.lock = __SPIN_LOCK_UNLOCKED(mm_list.lock),
+++	};
+++
+++#ifdef CONFIG_MEMCG
+++	if (memcg)
+++		return &memcg->mm_list;
+++#endif
+++	return &mm_list;
 ++}
 ++
-++/* dst = imm (sign-extended) */
-++static void emit_mov_se_i64(struct jit_context *ctx, const u8 dst[], s32 imm)
+++void lru_gen_add_mm(struct mm_struct *mm)
 ++{
-++	emit_mov_i(ctx, lo(dst), imm);
-++	if (imm < 0)
-++		emit(ctx, addiu, hi(dst), MIPS_R_ZERO, -1);
-++	else
-++		emit(ctx, move, hi(dst), MIPS_R_ZERO);
-++	clobber_reg64(ctx, dst);
+++	int nid;
+++	struct mem_cgroup *memcg = get_mem_cgroup_from_mm(mm);
+++	struct lru_gen_mm_list *mm_list = get_mm_list(memcg);
+++
+++	VM_BUG_ON_MM(!list_empty(&mm->lrugen.list), mm);
+++#ifdef CONFIG_MEMCG
+++	VM_BUG_ON_MM(mm->lrugen.memcg, mm);
+++	mm->lrugen.memcg = memcg;
+++#endif
+++	spin_lock(&mm_list->lock);
+++
+++	list_add_tail(&mm->lrugen.list, &mm_list->fifo);
+++
+++	for_each_node(nid) {
+++		struct lruvec *lruvec = get_lruvec(nid, memcg);
+++
+++		if (!lruvec)
+++			continue;
+++
+++		if (lruvec->mm_walk.tail == &mm_list->fifo)
+++			lruvec->mm_walk.tail = lruvec->mm_walk.tail->prev;
+++	}
+++
+++	spin_unlock(&mm_list->lock);
 ++}
 ++
-++/* Zero extension, if verifier does not do it for us  */
-++static void emit_zext_ver(struct jit_context *ctx, const u8 dst[])
+++void lru_gen_del_mm(struct mm_struct *mm)
 ++{
-++	if (!ctx->program->aux->verifier_zext) {
-++		emit(ctx, move, hi(dst), MIPS_R_ZERO);
-++		clobber_reg(ctx, hi(dst));
+++	int nid;
+++	struct lru_gen_mm_list *mm_list;
+++	struct mem_cgroup *memcg = NULL;
+++
+++	if (list_empty(&mm->lrugen.list))
+++		return;
+++
+++#ifdef CONFIG_MEMCG
+++	memcg = mm->lrugen.memcg;
+++#endif
+++	mm_list = get_mm_list(memcg);
+++
+++	spin_lock(&mm_list->lock);
+++
+++	for_each_node(nid) {
+++		struct lruvec *lruvec = get_lruvec(nid, memcg);
+++
+++		if (!lruvec)
+++			continue;
+++
+++		if (lruvec->mm_walk.tail == &mm->lrugen.list)
+++			lruvec->mm_walk.tail = lruvec->mm_walk.tail->next;
+++
+++		if (lruvec->mm_walk.head != &mm->lrugen.list)
+++			continue;
+++
+++		lruvec->mm_walk.head = lruvec->mm_walk.head->next;
+++		if (lruvec->mm_walk.head == &mm_list->fifo)
+++			WRITE_ONCE(lruvec->mm_walk.seq, lruvec->mm_walk.seq + 1);
 ++	}
+++
+++	list_del_init(&mm->lrugen.list);
+++
+++	spin_unlock(&mm_list->lock);
+++
+++#ifdef CONFIG_MEMCG
+++	mem_cgroup_put(mm->lrugen.memcg);
+++	mm->lrugen.memcg = NULL;
+++#endif
 ++}
 ++
-++/* Load delay slot, if ISA mandates it */
-++static void emit_load_delay(struct jit_context *ctx)
+++#ifdef CONFIG_MEMCG
+++void lru_gen_migrate_mm(struct mm_struct *mm)
 ++{
-++	if (!cpu_has_mips_2_3_4_5_r)
-++		emit(ctx, nop);
+++	struct mem_cgroup *memcg;
+++
+++	lockdep_assert_held(&mm->owner->alloc_lock);
+++
+++	if (mem_cgroup_disabled())
+++		return;
+++
+++	rcu_read_lock();
+++	memcg = mem_cgroup_from_task(mm->owner);
+++	rcu_read_unlock();
+++	if (memcg == mm->lrugen.memcg)
+++		return;
+++
+++	VM_BUG_ON_MM(!mm->lrugen.memcg, mm);
+++	VM_BUG_ON_MM(list_empty(&mm->lrugen.list), mm);
+++
+++	lru_gen_del_mm(mm);
+++	lru_gen_add_mm(mm);
 ++}
+++#endif
 ++
-++/* ALU immediate operation (64-bit) */
-++static void emit_alu_i64(struct jit_context *ctx,
-++			 const u8 dst[], s32 imm, u8 op)
+++#define BLOOM_FILTER_SHIFT	15
+++
+++static inline int filter_gen_from_seq(unsigned long seq)
 ++{
-++	u8 src = MIPS_R_T6;
+++	return seq % NR_BLOOM_FILTERS;
+++}
 ++
-++	/*
-++	 * ADD/SUB with all but the max negative imm can be handled by
-++	 * inverting the operation and the imm value, saving one insn.
-++	 */
-++	if (imm > S32_MIN && imm < 0)
-++		switch (op) {
-++		case BPF_ADD:
-++			op = BPF_SUB;
-++			imm = -imm;
-++			break;
-++		case BPF_SUB:
-++			op = BPF_ADD;
-++			imm = -imm;
-++			break;
-++		}
+++static void get_item_key(void *item, int *key)
+++{
+++	u32 hash = hash_ptr(item, BLOOM_FILTER_SHIFT * 2);
 ++
-++	/* Move immediate to temporary register */
-++	emit_mov_i(ctx, src, imm);
+++	BUILD_BUG_ON(BLOOM_FILTER_SHIFT * 2 > BITS_PER_TYPE(u32));
 ++
-++	switch (op) {
-++	/* dst = dst + imm */
-++	case BPF_ADD:
-++		emit(ctx, addu, lo(dst), lo(dst), src);
-++		emit(ctx, sltu, MIPS_R_T9, lo(dst), src);
-++		emit(ctx, addu, hi(dst), hi(dst), MIPS_R_T9);
-++		if (imm < 0)
-++			emit(ctx, addiu, hi(dst), hi(dst), -1);
-++		break;
-++	/* dst = dst - imm */
-++	case BPF_SUB:
-++		emit(ctx, sltu, MIPS_R_T9, lo(dst), src);
-++		emit(ctx, subu, lo(dst), lo(dst), src);
-++		emit(ctx, subu, hi(dst), hi(dst), MIPS_R_T9);
-++		if (imm < 0)
-++			emit(ctx, addiu, hi(dst), hi(dst), 1);
-++		break;
-++	/* dst = dst | imm */
-++	case BPF_OR:
-++		emit(ctx, or, lo(dst), lo(dst), src);
-++		if (imm < 0)
-++			emit(ctx, addiu, hi(dst), MIPS_R_ZERO, -1);
-++		break;
-++	/* dst = dst & imm */
-++	case BPF_AND:
-++		emit(ctx, and, lo(dst), lo(dst), src);
-++		if (imm >= 0)
-++			emit(ctx, move, hi(dst), MIPS_R_ZERO);
-++		break;
-++	/* dst = dst ^ imm */
-++	case BPF_XOR:
-++		emit(ctx, xor, lo(dst), lo(dst), src);
-++		if (imm < 0) {
-++			emit(ctx, subu, hi(dst), MIPS_R_ZERO, hi(dst));
-++			emit(ctx, addiu, hi(dst), hi(dst), -1);
-++		}
-++		break;
-++	}
-++	clobber_reg64(ctx, dst);
+++	key[0] = hash & (BIT(BLOOM_FILTER_SHIFT) - 1);
+++	key[1] = hash >> BLOOM_FILTER_SHIFT;
 ++}
 ++
-++/* ALU register operation (64-bit) */
-++static void emit_alu_r64(struct jit_context *ctx,
-++			 const u8 dst[], const u8 src[], u8 op)
+++static void clear_bloom_filter(struct lruvec *lruvec, unsigned long seq)
 ++{
-++	switch (BPF_OP(op)) {
-++	/* dst = dst + src */
-++	case BPF_ADD:
-++		if (src == dst) {
-++			emit(ctx, srl, MIPS_R_T9, lo(dst), 31);
-++			emit(ctx, addu, lo(dst), lo(dst), lo(dst));
-++		} else {
-++			emit(ctx, addu, lo(dst), lo(dst), lo(src));
-++			emit(ctx, sltu, MIPS_R_T9, lo(dst), lo(src));
-++		}
-++		emit(ctx, addu, hi(dst), hi(dst), hi(src));
-++		emit(ctx, addu, hi(dst), hi(dst), MIPS_R_T9);
-++		break;
-++	/* dst = dst - src */
-++	case BPF_SUB:
-++		emit(ctx, sltu, MIPS_R_T9, lo(dst), lo(src));
-++		emit(ctx, subu, lo(dst), lo(dst), lo(src));
-++		emit(ctx, subu, hi(dst), hi(dst), hi(src));
-++		emit(ctx, subu, hi(dst), hi(dst), MIPS_R_T9);
-++		break;
-++	/* dst = dst | src */
-++	case BPF_OR:
-++		emit(ctx, or, lo(dst), lo(dst), lo(src));
-++		emit(ctx, or, hi(dst), hi(dst), hi(src));
-++		break;
-++	/* dst = dst & src */
-++	case BPF_AND:
-++		emit(ctx, and, lo(dst), lo(dst), lo(src));
-++		emit(ctx, and, hi(dst), hi(dst), hi(src));
-++		break;
-++	/* dst = dst ^ src */
-++	case BPF_XOR:
-++		emit(ctx, xor, lo(dst), lo(dst), lo(src));
-++		emit(ctx, xor, hi(dst), hi(dst), hi(src));
-++		break;
+++	unsigned long *filter;
+++	int gen = filter_gen_from_seq(seq);
+++
+++	lockdep_assert_held(&get_mm_list(lruvec_memcg(lruvec))->lock);
+++
+++	filter = lruvec->mm_walk.filters[gen];
+++	if (filter) {
+++		bitmap_clear(filter, 0, BIT(BLOOM_FILTER_SHIFT));
+++		return;
 ++	}
-++	clobber_reg64(ctx, dst);
+++
+++	filter = bitmap_zalloc(BIT(BLOOM_FILTER_SHIFT), GFP_ATOMIC);
+++	WRITE_ONCE(lruvec->mm_walk.filters[gen], filter);
 ++}
 ++
-++/* ALU invert (64-bit) */
-++static void emit_neg_i64(struct jit_context *ctx, const u8 dst[])
+++static void set_bloom_filter(struct lruvec *lruvec, unsigned long seq, void *item)
 ++{
-++	emit(ctx, sltu, MIPS_R_T9, MIPS_R_ZERO, lo(dst));
-++	emit(ctx, subu, lo(dst), MIPS_R_ZERO, lo(dst));
-++	emit(ctx, subu, hi(dst), MIPS_R_ZERO, hi(dst));
-++	emit(ctx, subu, hi(dst), hi(dst), MIPS_R_T9);
+++	int key[2];
+++	unsigned long *filter;
+++	int gen = filter_gen_from_seq(seq);
 ++
-++	clobber_reg64(ctx, dst);
+++	filter = READ_ONCE(lruvec->mm_walk.filters[gen]);
+++	if (!filter)
+++		return;
+++
+++	get_item_key(item, key);
+++
+++	if (!test_bit(key[0], filter))
+++		set_bit(key[0], filter);
+++	if (!test_bit(key[1], filter))
+++		set_bit(key[1], filter);
 ++}
 ++
-++/* ALU shift immediate (64-bit) */
-++static void emit_shift_i64(struct jit_context *ctx,
-++			   const u8 dst[], u32 imm, u8 op)
+++static bool test_bloom_filter(struct lruvec *lruvec, unsigned long seq, void *item)
 ++{
-++	switch (BPF_OP(op)) {
-++	/* dst = dst << imm */
-++	case BPF_LSH:
-++		if (imm < 32) {
-++			emit(ctx, srl, MIPS_R_T9, lo(dst), 32 - imm);
-++			emit(ctx, sll, lo(dst), lo(dst), imm);
-++			emit(ctx, sll, hi(dst), hi(dst), imm);
-++			emit(ctx, or, hi(dst), hi(dst), MIPS_R_T9);
-++		} else {
-++			emit(ctx, sll, hi(dst), lo(dst), imm - 32);
-++			emit(ctx, move, lo(dst), MIPS_R_ZERO);
-++		}
-++		break;
-++	/* dst = dst >> imm */
-++	case BPF_RSH:
-++		if (imm < 32) {
-++			emit(ctx, sll, MIPS_R_T9, hi(dst), 32 - imm);
-++			emit(ctx, srl, lo(dst), lo(dst), imm);
-++			emit(ctx, srl, hi(dst), hi(dst), imm);
-++			emit(ctx, or, lo(dst), lo(dst), MIPS_R_T9);
-++		} else {
-++			emit(ctx, srl, lo(dst), hi(dst), imm - 32);
-++			emit(ctx, move, hi(dst), MIPS_R_ZERO);
-++		}
-++		break;
-++	/* dst = dst >> imm (arithmetic) */
-++	case BPF_ARSH:
-++		if (imm < 32) {
-++			emit(ctx, sll, MIPS_R_T9, hi(dst), 32 - imm);
-++			emit(ctx, srl, lo(dst), lo(dst), imm);
-++			emit(ctx, sra, hi(dst), hi(dst), imm);
-++			emit(ctx, or, lo(dst), lo(dst), MIPS_R_T9);
-++		} else {
-++			emit(ctx, sra, lo(dst), hi(dst), imm - 32);
-++			emit(ctx, sra, hi(dst), hi(dst), 31);
-++		}
-++		break;
+++	int key[2];
+++	unsigned long *filter;
+++	int gen = filter_gen_from_seq(seq);
+++
+++	filter = READ_ONCE(lruvec->mm_walk.filters[gen]);
+++	if (!filter)
+++		return false;
+++
+++	get_item_key(item, key);
+++
+++	return test_bit(key[0], filter) && test_bit(key[1], filter);
+++}
+++
+++static void reset_mm_stats(struct lruvec *lruvec, bool last, struct mm_walk_args *args)
+++{
+++	int i;
+++	int hist = lru_hist_from_seq(args->max_seq);
+++
+++	lockdep_assert_held(&get_mm_list(lruvec_memcg(lruvec))->lock);
+++
+++	for (i = 0; i < NR_MM_STATS; i++) {
+++		WRITE_ONCE(lruvec->mm_walk.stats[hist][i],
+++			   lruvec->mm_walk.stats[hist][i] + args->mm_stats[i]);
+++		args->mm_stats[i] = 0;
 ++	}
-++	clobber_reg64(ctx, dst);
+++
+++	if (!last || NR_HIST_GENS == 1)
+++		return;
+++
+++	hist = lru_hist_from_seq(args->max_seq + 1);
+++	for (i = 0; i < NR_MM_STATS; i++)
+++		WRITE_ONCE(lruvec->mm_walk.stats[hist][i], 0);
 ++}
 ++
-++/* ALU shift register (64-bit) */
-++static void emit_shift_r64(struct jit_context *ctx,
-++			   const u8 dst[], u8 src, u8 op)
+++static bool should_skip_mm(struct mm_struct *mm, struct mm_walk_args *args)
 ++{
-++	u8 t1 = MIPS_R_T8;
-++	u8 t2 = MIPS_R_T9;
+++	int type;
+++	unsigned long size = 0;
 ++
-++	emit(ctx, andi, t1, src, 32);              /* t1 = src & 32          */
-++	emit(ctx, beqz, t1, 16);                   /* PC += 16 if t1 == 0    */
-++	emit(ctx, nor, t2, src, MIPS_R_ZERO);      /* t2 = ~src (delay slot) */
+++	if (cpumask_empty(mm_cpumask(mm)) && !node_isset(args->node_id, mm->lrugen.nodes))
+++		return true;
 ++
-++	switch (BPF_OP(op)) {
-++	/* dst = dst << src */
-++	case BPF_LSH:
-++		/* Next: shift >= 32 */
-++		emit(ctx, sllv, hi(dst), lo(dst), src);    /* dh = dl << src */
-++		emit(ctx, move, lo(dst), MIPS_R_ZERO);     /* dl = 0         */
-++		emit(ctx, b, 20);                          /* PC += 20       */
-++		/* +16: shift < 32 */
-++		emit(ctx, srl, t1, lo(dst), 1);            /* t1 = dl >> 1   */
-++		emit(ctx, srlv, t1, t1, t2);               /* t1 = t1 >> t2  */
-++		emit(ctx, sllv, lo(dst), lo(dst), src);    /* dl = dl << src */
-++		emit(ctx, sllv, hi(dst), hi(dst), src);    /* dh = dh << src */
-++		emit(ctx, or, hi(dst), hi(dst), t1);       /* dh = dh | t1   */
-++		break;
-++	/* dst = dst >> src */
-++	case BPF_RSH:
-++		/* Next: shift >= 32 */
-++		emit(ctx, srlv, lo(dst), hi(dst), src);    /* dl = dh >> src */
-++		emit(ctx, move, hi(dst), MIPS_R_ZERO);     /* dh = 0         */
-++		emit(ctx, b, 20);                          /* PC += 20       */
-++		/* +16: shift < 32 */
-++		emit(ctx, sll, t1, hi(dst), 1);            /* t1 = dl << 1   */
-++		emit(ctx, sllv, t1, t1, t2);               /* t1 = t1 << t2  */
-++		emit(ctx, srlv, lo(dst), lo(dst), src);    /* dl = dl >> src */
-++		emit(ctx, srlv, hi(dst), hi(dst), src);    /* dh = dh >> src */
-++		emit(ctx, or, lo(dst), lo(dst), t1);       /* dl = dl | t1   */
-++		break;
-++	/* dst = dst >> src (arithmetic) */
-++	case BPF_ARSH:
-++		/* Next: shift >= 32 */
-++		emit(ctx, srav, lo(dst), hi(dst), src);   /* dl = dh >>a src */
-++		emit(ctx, sra, hi(dst), hi(dst), 31);     /* dh = dh >>a 31  */
-++		emit(ctx, b, 20);                         /* PC += 20        */
-++		/* +16: shift < 32 */
-++		emit(ctx, sll, t1, hi(dst), 1);           /* t1 = dl << 1    */
-++		emit(ctx, sllv, t1, t1, t2);              /* t1 = t1 << t2   */
-++		emit(ctx, srlv, lo(dst), lo(dst), src);   /* dl = dl >>a src */
-++		emit(ctx, srav, hi(dst), hi(dst), src);   /* dh = dh >> src  */
-++		emit(ctx, or, lo(dst), lo(dst), t1);      /* dl = dl | t1    */
-++		break;
+++	if (mm_is_oom_victim(mm))
+++		return true;
+++
+++	for (type = !args->swappiness; type < ANON_AND_FILE; type++) {
+++		size += type ? get_mm_counter(mm, MM_FILEPAGES) :
+++			       get_mm_counter(mm, MM_ANONPAGES) +
+++			       get_mm_counter(mm, MM_SHMEMPAGES);
 ++	}
 ++
-++	/* +20: Done */
-++	clobber_reg64(ctx, dst);
+++	if (size < MIN_BATCH_SIZE)
+++		return true;
+++
+++	if (!mmget_not_zero(mm))
+++		return true;
+++
+++	node_clear(args->node_id, mm->lrugen.nodes);
+++
+++	return false;
 ++}
 ++
-++/* ALU mul immediate (64x32-bit) */
-++static void emit_mul_i64(struct jit_context *ctx, const u8 dst[], s32 imm)
+++/* To support multiple walkers that concurrently walk an mm_struct list. */
+++static bool get_next_mm(struct lruvec *lruvec, struct mm_walk_args *args,
+++			struct mm_struct **iter)
 ++{
-++	u8 src = MIPS_R_T6;
-++	u8 tmp = MIPS_R_T9;
+++	bool first = false;
+++	bool last = true;
+++	struct mm_struct *mm = NULL;
+++	struct lru_gen_mm_walk *mm_walk = &lruvec->mm_walk;
+++	struct lru_gen_mm_list *mm_list = get_mm_list(args->memcg);
+++
+++	if (*iter)
+++		mmput_async(*iter);
+++	else if (args->max_seq <= READ_ONCE(mm_walk->seq))
+++		return false;
 ++
-++	switch (imm) {
-++	/* dst = dst * 1 is a no-op */
-++	case 1:
-++		break;
-++	/* dst = dst * -1 */
-++	case -1:
-++		emit_neg_i64(ctx, dst);
-++		break;
-++	case 0:
-++		emit_mov_r(ctx, lo(dst), MIPS_R_ZERO);
-++		emit_mov_r(ctx, hi(dst), MIPS_R_ZERO);
-++		break;
-++	/* Full 64x32 multiply */
-++	default:
-++		/* hi(dst) = hi(dst) * src(imm) */
-++		emit_mov_i(ctx, src, imm);
-++		if (cpu_has_mips32r1 || cpu_has_mips32r6) {
-++			emit(ctx, mul, hi(dst), hi(dst), src);
-++		} else {
-++			emit(ctx, multu, hi(dst), src);
-++			emit(ctx, mflo, hi(dst));
-++		}
+++	spin_lock(&mm_list->lock);
 ++
-++		/* hi(dst) = hi(dst) - lo(dst) */
-++		if (imm < 0)
-++			emit(ctx, subu, hi(dst), hi(dst), lo(dst));
+++	VM_BUG_ON(args->max_seq > mm_walk->seq + 1);
+++	VM_BUG_ON(*iter && args->max_seq < mm_walk->seq);
+++	VM_BUG_ON(*iter && !mm_walk->nr_walkers);
 ++
-++		/* tmp = lo(dst) * src(imm) >> 32 */
-++		/* lo(dst) = lo(dst) * src(imm) */
-++		if (cpu_has_mips32r6) {
-++			emit(ctx, muhu, tmp, lo(dst), src);
-++			emit(ctx, mulu, lo(dst), lo(dst), src);
-++		} else {
-++			emit(ctx, multu, lo(dst), src);
-++			emit(ctx, mflo, lo(dst));
-++			emit(ctx, mfhi, tmp);
-++		}
+++	if (args->max_seq <= mm_walk->seq) {
+++		if (!*iter)
+++			last = false;
+++		goto done;
+++	}
 ++
-++		/* hi(dst) += tmp */
-++		emit(ctx, addu, hi(dst), hi(dst), tmp);
-++		clobber_reg64(ctx, dst);
-++		break;
+++	if (mm_walk->head == &mm_list->fifo) {
+++		VM_BUG_ON(mm_walk->nr_walkers);
+++		mm_walk->head = mm_walk->head->next;
+++		first = true;
 ++	}
-++}
 ++
-++/* ALU mul register (64x64-bit) */
-++static void emit_mul_r64(struct jit_context *ctx,
-++			 const u8 dst[], const u8 src[])
-++{
-++	u8 acc = MIPS_R_T8;
-++	u8 tmp = MIPS_R_T9;
+++	while (!mm && mm_walk->head != &mm_list->fifo) {
+++		mm = list_entry(mm_walk->head, struct mm_struct, lrugen.list);
 ++
-++	/* acc = hi(dst) * lo(src) */
-++	if (cpu_has_mips32r1 || cpu_has_mips32r6) {
-++		emit(ctx, mul, acc, hi(dst), lo(src));
-++	} else {
-++		emit(ctx, multu, hi(dst), lo(src));
-++		emit(ctx, mflo, acc);
-++	}
+++		mm_walk->head = mm_walk->head->next;
 ++
-++	/* tmp = lo(dst) * hi(src) */
-++	if (cpu_has_mips32r1 || cpu_has_mips32r6) {
-++		emit(ctx, mul, tmp, lo(dst), hi(src));
-++	} else {
-++		emit(ctx, multu, lo(dst), hi(src));
-++		emit(ctx, mflo, tmp);
+++		if (mm_walk->tail == &mm->lrugen.list) {
+++			mm_walk->tail = mm_walk->tail->next;
+++			args->use_filter = false;
+++		}
+++
+++		if (should_skip_mm(mm, args))
+++			mm = NULL;
 ++	}
 ++
-++	/* acc += tmp */
-++	emit(ctx, addu, acc, acc, tmp);
+++	if (mm_walk->head == &mm_list->fifo)
+++		WRITE_ONCE(mm_walk->seq, mm_walk->seq + 1);
+++done:
+++	if (*iter && !mm)
+++		mm_walk->nr_walkers--;
+++	if (!*iter && mm)
+++		mm_walk->nr_walkers++;
 ++
-++	/* tmp = lo(dst) * lo(src) >> 32 */
-++	/* lo(dst) = lo(dst) * lo(src) */
-++	if (cpu_has_mips32r6) {
-++		emit(ctx, muhu, tmp, lo(dst), lo(src));
-++		emit(ctx, mulu, lo(dst), lo(dst), lo(src));
-++	} else {
-++		emit(ctx, multu, lo(dst), lo(src));
-++		emit(ctx, mflo, lo(dst));
-++		emit(ctx, mfhi, tmp);
-++	}
+++	if (mm_walk->nr_walkers)
+++		last = false;
 ++
-++	/* hi(dst) = acc + tmp */
-++	emit(ctx, addu, hi(dst), acc, tmp);
-++	clobber_reg64(ctx, dst);
+++	if (mm && first)
+++		clear_bloom_filter(lruvec, args->max_seq + 1);
+++
+++	if (*iter || last)
+++		reset_mm_stats(lruvec, last, args);
+++
+++	spin_unlock(&mm_list->lock);
+++
+++	*iter = mm;
+++
+++	return last;
 ++}
 ++
-++/* Helper function for 64-bit modulo */
-++static u64 jit_mod64(u64 a, u64 b)
+++/******************************************************************************
++  *                          state change
++  ******************************************************************************/
++ 
++@@ -3112,6 +3412,7 @@ void lru_gen_init_state(struct mem_cgrou
++ 	int i;
++ 	int gen, type, zone;
++ 	struct lrugen *lrugen = &lruvec->evictable;
+++	struct lru_gen_mm_list *mm_list = get_mm_list(memcg);
++ 
++ 	lrugen->max_seq = MIN_NR_GENS + 1;
++ 	lrugen->enabled[0] = lru_gen_enabled() && lru_gen_nr_swapfiles;
++@@ -3122,6 +3423,17 @@ void lru_gen_init_state(struct mem_cgrou
++ 
++ 	for_each_gen_type_zone(gen, type, zone)
++ 		INIT_LIST_HEAD(&lrugen->lists[gen][type][zone]);
+++
+++	if (IS_ENABLED(CONFIG_MEMORY_HOTPLUG) && !memcg)
+++		spin_lock(&mm_list->lock);
+++
+++	lruvec->mm_walk.seq = MIN_NR_GENS;
+++	lruvec->mm_walk.head = &mm_list->fifo;
+++	lruvec->mm_walk.tail = &mm_list->fifo;
+++	init_waitqueue_head(&lruvec->mm_walk.wait);
+++
+++	if (IS_ENABLED(CONFIG_MEMORY_HOTPLUG) && !memcg)
+++		spin_unlock(&mm_list->lock);
++ }
++ 
++ #ifdef CONFIG_MEMCG
++@@ -3129,18 +3441,37 @@ void lru_gen_init_memcg(struct mem_cgrou
++ {
++ 	int nid;
++ 
+++	INIT_LIST_HEAD(&memcg->mm_list.fifo);
+++	spin_lock_init(&memcg->mm_list.lock);
+++
++ 	for_each_node(nid) {
++ 		struct lruvec *lruvec = get_lruvec(nid, memcg);
++ 
++ 		lru_gen_init_state(memcg, lruvec);
++ 	}
++ }
+++
+++void lru_gen_free_memcg(struct mem_cgroup *memcg)
 ++{
-++	u64 rem;
+++	int nid;
 ++
-++	div64_u64_rem(a, b, &rem);
-++	return rem;
+++	for_each_node(nid) {
+++		int i;
+++		struct lruvec *lruvec = get_lruvec(nid, memcg);
+++
+++		for (i = 0; i < NR_BLOOM_FILTERS; i++) {
+++			bitmap_free(lruvec->mm_walk.filters[i]);
+++			lruvec->mm_walk.filters[i] = NULL;
+++		}
+++	}
+++}
++ #endif
++ 
++ static int __init init_lru_gen(void)
++ {
++ 	BUILD_BUG_ON(MIN_NR_GENS + 1 >= MAX_NR_GENS);
++ 	BUILD_BUG_ON(BIT(LRU_GEN_WIDTH) <= MAX_NR_GENS);
+++	BUILD_BUG_ON(sizeof(MM_STAT_CODES) != NR_MM_STATS + 1);
++ 
++ 	return 0;
++ };
+diff --git a/target/linux/generic/backport-5.15/020-v6.1-06-mm-multigenerational-lru-aging.patch b/target/linux/generic/backport-5.15/020-v6.1-06-mm-multigenerational-lru-aging.patch
+new file mode 100644
+index 0000000000..6fc93d9422
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/020-v6.1-06-mm-multigenerational-lru-aging.patch
+@@ -0,0 +1,1176 @@
++From 8217cd2238c40cf77208aa27a7cc09879e685890 Mon Sep 17 00:00:00 2001
++From: Yu Zhao <yuzhao@google.com>
++Date: Mon, 5 Apr 2021 04:35:07 -0600
++Subject: [PATCH 06/10] mm: multigenerational lru: aging
++
++The aging produces young generations. Given an lruvec, the aging
++traverses lruvec_memcg()->mm_list and calls walk_page_range() to scan
++PTEs for accessed pages. Upon finding one, the aging updates its
++generation number to max_seq (modulo MAX_NR_GENS). After each round of
++traversal, the aging increments max_seq. The aging is due when
++min_seq[] reaches max_seq-1.
++
++The aging uses the following optimizations when walking page tables:
++  1) It skips non-leaf PMD entries that have the accessed bit cleared
++  when CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG=y.
++  2) It does not zigzag between a PGD table and the same PMD or PTE
++  table spanning multiple VMAs. In other words, it finishes all the
++  VMAs within the range of the same PMD or PTE table before it returns
++  to this PGD table. This optimizes workloads that have large numbers
++  of tiny VMAs, especially when CONFIG_PGTABLE_LEVELS=5.
++
++Signed-off-by: Yu Zhao <yuzhao@google.com>
++Tested-by: Konstantin Kharlamov <Hi-Angel@yandex.ru>
++Change-Id: I3ae8abc3100d023cecb3a699d86020ae6fc10a45
++---
++ include/linux/memcontrol.h |   3 +
++ include/linux/mmzone.h     |   9 +
++ include/linux/oom.h        |  16 +
++ include/linux/swap.h       |   3 +
++ mm/memcontrol.c            |   5 +
++ mm/oom_kill.c              |   4 +-
++ mm/rmap.c                  |   8 +
++ mm/vmscan.c                | 948 +++++++++++++++++++++++++++++++++++++
++ 8 files changed, 994 insertions(+), 2 deletions(-)
++
++--- a/include/linux/memcontrol.h
+++++ b/include/linux/memcontrol.h
++@@ -1367,10 +1367,13 @@ mem_cgroup_print_oom_meminfo(struct mem_
++ 
++ static inline void lock_page_memcg(struct page *page)
++ {
+++	/* to match page_memcg_rcu() */
+++	rcu_read_lock();
++ }
++ 
++ static inline void unlock_page_memcg(struct page *page)
++ {
+++	rcu_read_unlock();
++ }
++ 
++ static inline void mem_cgroup_handle_over_high(void)
++--- a/include/linux/mmzone.h
+++++ b/include/linux/mmzone.h
++@@ -295,6 +295,7 @@ enum lruvec_flags {
++ };
++ 
++ struct lruvec;
+++struct page_vma_mapped_walk;
++ 
++ #define LRU_GEN_MASK		((BIT(LRU_GEN_WIDTH) - 1) << LRU_GEN_PGOFF)
++ #define LRU_REFS_MASK		((BIT(LRU_REFS_WIDTH) - 1) << LRU_REFS_PGOFF)
++@@ -393,6 +394,7 @@ struct mm_walk_args {
++ 
++ void lru_gen_init_state(struct mem_cgroup *memcg, struct lruvec *lruvec);
++ void lru_gen_change_state(bool enable, bool main, bool swap);
+++void lru_gen_look_around(struct page_vma_mapped_walk *pvmw);
++ 
++ #ifdef CONFIG_MEMCG
++ void lru_gen_init_memcg(struct mem_cgroup *memcg);
++@@ -409,6 +411,10 @@ static inline void lru_gen_change_state(
++ {
++ }
++ 
+++static inline void lru_gen_look_around(struct page_vma_mapped_walk *pvmw)
+++{
 ++}
 ++
-++/* ALU div/mod register (64-bit) */
-++static void emit_divmod_r64(struct jit_context *ctx,
-++			    const u8 dst[], const u8 src[], u8 op)
++ #ifdef CONFIG_MEMCG
++ static inline void lru_gen_init_memcg(struct mem_cgroup *memcg)
++ {
++@@ -1028,6 +1034,9 @@ typedef struct pglist_data {
++ 
++ 	unsigned long		flags;
++ 
+++#ifdef CONFIG_LRU_GEN
+++	struct mm_walk_args	mm_walk_args;
+++#endif
++ 	ZONE_PADDING(_pad2_)
++ 
++ 	/* Per-node vmstats */
++--- a/include/linux/oom.h
+++++ b/include/linux/oom.h
++@@ -57,6 +57,22 @@ struct oom_control {
++ extern struct mutex oom_lock;
++ extern struct mutex oom_adj_mutex;
++ 
+++#ifdef CONFIG_MMU
+++extern struct task_struct *oom_reaper_list;
+++extern struct wait_queue_head oom_reaper_wait;
+++
+++static inline bool oom_reaping_in_progress(void)
 ++{
-++	const u8 *r0 = bpf2mips32[BPF_REG_0]; /* Mapped to v0-v1 */
-++	const u8 *r1 = bpf2mips32[BPF_REG_1]; /* Mapped to a0-a1 */
-++	const u8 *r2 = bpf2mips32[BPF_REG_2]; /* Mapped to a2-a3 */
-++	int exclude, k;
-++	u32 addr = 0;
+++	/* racy check to see if oom reaping could be in progress */
+++	return READ_ONCE(oom_reaper_list) || !waitqueue_active(&oom_reaper_wait);
+++}
+++#else
+++static inline bool oom_reaping_in_progress(void)
+++{
+++	return false;
+++}
+++#endif
 ++
-++	/* Push caller-saved registers on stack */
-++	push_regs(ctx, ctx->clobbered & JIT_CALLER_REGS,
-++		  0, JIT_RESERVED_STACK);
++ static inline void set_current_oom_origin(void)
++ {
++ 	current->signal->oom_flag_origin = true;
++--- a/include/linux/swap.h
+++++ b/include/linux/swap.h
++@@ -137,6 +137,9 @@ union swap_header {
++  */
++ struct reclaim_state {
++ 	unsigned long reclaimed_slab;
+++#ifdef CONFIG_LRU_GEN
+++	struct mm_walk_args *mm_walk_args;
+++#endif
++ };
++ 
++ #ifdef __KERNEL__
++--- a/mm/memcontrol.c
+++++ b/mm/memcontrol.c
++@@ -1304,12 +1304,17 @@ void mem_cgroup_update_lru_size(struct l
++ 		*lru_size += nr_pages;
++ 
++ 	size = *lru_size;
+++#ifdef CONFIG_LRU_GEN
+++	/* unlikely but not a bug when reset_batch_size() is pending */
+++	VM_WARN_ON(size + MAX_BATCH_SIZE < 0);
+++#else
++ 	if (WARN_ONCE(size < 0,
++ 		"%s(%p, %d, %d): lru_size %ld\n",
++ 		__func__, lruvec, lru, nr_pages, size)) {
++ 		VM_BUG_ON(1);
++ 		*lru_size = 0;
++ 	}
+++#endif
++ 
++ 	if (nr_pages > 0)
++ 		*lru_size += nr_pages;
++--- a/mm/oom_kill.c
+++++ b/mm/oom_kill.c
++@@ -508,8 +508,8 @@ bool process_shares_mm(struct task_struc
++  * victim (if that is possible) to help the OOM killer to move on.
++  */
++ static struct task_struct *oom_reaper_th;
++-static DECLARE_WAIT_QUEUE_HEAD(oom_reaper_wait);
++-static struct task_struct *oom_reaper_list;
+++DECLARE_WAIT_QUEUE_HEAD(oom_reaper_wait);
+++struct task_struct *oom_reaper_list;
++ static DEFINE_SPINLOCK(oom_reaper_lock);
++ 
++ bool __oom_reap_task_mm(struct mm_struct *mm)
++--- a/mm/rmap.c
+++++ b/mm/rmap.c
++@@ -73,6 +73,7 @@
++ #include <linux/page_idle.h>
++ #include <linux/memremap.h>
++ #include <linux/userfaultfd_k.h>
+++#include <linux/mm_inline.h>
++ 
++ #include <asm/tlbflush.h>
++ 
++@@ -793,6 +794,13 @@ static bool page_referenced_one(struct p
++ 		}
++ 
++ 		if (pvmw.pte) {
+++			/* the multigenerational lru exploits the spatial locality */
+++			if (lru_gen_enabled() && pte_young(*pvmw.pte) &&
+++			    !(vma->vm_flags & VM_SEQ_READ)) {
+++				lru_gen_look_around(&pvmw);
+++				referenced++;
+++			}
 ++
-++	/* Put 64-bit arguments 1 and 2 in registers a0-a3 */
-++	for (k = 0; k < 2; k++) {
-++		emit(ctx, move, MIPS_R_T9, src[k]);
-++		emit(ctx, move, r1[k], dst[k]);
-++		emit(ctx, move, r2[k], MIPS_R_T9);
++ 			if (ptep_clear_flush_young_notify(vma, address,
++ 						pvmw.pte)) {
++ 				/*
++--- a/mm/vmscan.c
+++++ b/mm/vmscan.c
++@@ -51,6 +51,8 @@
++ #include <linux/dax.h>
++ #include <linux/psi.h>
++ #include <linux/memory.h>
+++#include <linux/pagewalk.h>
+++#include <linux/shmem_fs.h>
++ 
++ #include <asm/tlbflush.h>
++ #include <asm/div64.h>
++@@ -2887,6 +2889,15 @@ static bool can_age_anon_pages(struct pg
++  *                          shorthand helpers
++  ******************************************************************************/
++ 
+++#define DEFINE_MAX_SEQ(lruvec)						\
+++	unsigned long max_seq = READ_ONCE((lruvec)->evictable.max_seq)
+++
+++#define DEFINE_MIN_SEQ(lruvec)						\
+++	unsigned long min_seq[ANON_AND_FILE] = {			\
+++		READ_ONCE((lruvec)->evictable.min_seq[0]),		\
+++		READ_ONCE((lruvec)->evictable.min_seq[1]),		\
 ++	}
 ++
-++	/* Emit function call */
-++	switch (BPF_OP(op)) {
-++	/* dst = dst / src */
-++	case BPF_DIV:
-++		addr = (u32)&div64_u64;
-++		break;
-++	/* dst = dst % src */
-++	case BPF_MOD:
-++		addr = (u32)&jit_mod64;
-++		break;
-++	}
-++	emit_mov_i(ctx, MIPS_R_T9, addr);
-++	emit(ctx, jalr, MIPS_R_RA, MIPS_R_T9);
-++	emit(ctx, nop); /* Delay slot */
++ #define for_each_gen_type_zone(gen, type, zone)				\
++ 	for ((gen) = 0; (gen) < MAX_NR_GENS; (gen)++)			\
++ 		for ((type) = 0; (type) < ANON_AND_FILE; (type)++)	\
++@@ -2899,6 +2910,12 @@ static int page_lru_gen(struct page *pag
++ 	return ((flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
++ }
++ 
+++static int get_swappiness(struct mem_cgroup *memcg)
+++{
+++	return mem_cgroup_get_nr_swap_pages(memcg) >= MIN_BATCH_SIZE ?
+++	       mem_cgroup_swappiness(memcg) : 0;
+++}
 ++
-++	/* Store the 64-bit result in dst */
-++	emit(ctx, move, dst[0], r0[0]);
-++	emit(ctx, move, dst[1], r0[1]);
++ static struct lruvec *get_lruvec(int nid, struct mem_cgroup *memcg)
++ {
++ 	struct pglist_data *pgdat = NODE_DATA(nid);
++@@ -3229,6 +3246,926 @@ done:
++ }
++ 
++ /******************************************************************************
+++ *                          the aging
+++ ******************************************************************************/
 ++
-++	/* Restore caller-saved registers, excluding the computed result */
-++	exclude = BIT(lo(dst)) | BIT(hi(dst));
-++	pop_regs(ctx, ctx->clobbered & JIT_CALLER_REGS,
-++		 exclude, JIT_RESERVED_STACK);
-++	emit_load_delay(ctx);
+++static int page_update_gen(struct page *page, int gen)
+++{
+++	unsigned long old_flags, new_flags;
 ++
-++	clobber_reg64(ctx, dst);
-++	clobber_reg(ctx, MIPS_R_V0);
-++	clobber_reg(ctx, MIPS_R_V1);
-++	clobber_reg(ctx, MIPS_R_RA);
+++	VM_BUG_ON(gen >= MAX_NR_GENS);
+++
+++	do {
+++		new_flags = old_flags = READ_ONCE(page->flags);
+++
+++		if (!(new_flags & LRU_GEN_MASK)) {
+++			new_flags |= BIT(PG_referenced);
+++			continue;
+++		}
+++
+++		new_flags &= ~LRU_GEN_MASK;
+++		new_flags |= (gen + 1UL) << LRU_GEN_PGOFF;
+++	} while (new_flags != old_flags &&
+++		 cmpxchg(&page->flags, old_flags, new_flags) != old_flags);
+++
+++	return ((old_flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
 ++}
 ++
-++/* Swap bytes in a register word */
-++static void emit_swap8_r(struct jit_context *ctx, u8 dst, u8 src, u8 mask)
+++static void page_inc_gen(struct page *page, struct lruvec *lruvec, bool reclaiming)
 ++{
-++	u8 tmp = MIPS_R_T9;
+++	int old_gen, new_gen;
+++	unsigned long old_flags, new_flags;
+++	int type = page_is_file_lru(page);
+++	int zone = page_zonenum(page);
+++	struct lrugen *lrugen = &lruvec->evictable;
 ++
-++	emit(ctx, and, tmp, src, mask); /* tmp = src & 0x00ff00ff */
-++	emit(ctx, sll, tmp, tmp, 8);    /* tmp = tmp << 8         */
-++	emit(ctx, srl, dst, src, 8);    /* dst = src >> 8         */
-++	emit(ctx, and, dst, dst, mask); /* dst = dst & 0x00ff00ff */
-++	emit(ctx, or,  dst, dst, tmp);  /* dst = dst | tmp        */
+++	old_gen = lru_gen_from_seq(lrugen->min_seq[type]);
+++
+++	do {
+++		new_flags = old_flags = READ_ONCE(page->flags);
+++		VM_BUG_ON_PAGE(!(new_flags & LRU_GEN_MASK), page);
+++
+++		new_gen = ((new_flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
+++		/* page_update_gen() has updated this page? */
+++		if (new_gen >= 0 && new_gen != old_gen) {
+++			list_move(&page->lru, &lrugen->lists[new_gen][type][zone]);
+++			return;
+++		}
+++
+++		new_gen = (old_gen + 1) % MAX_NR_GENS;
+++
+++		new_flags &= ~LRU_GEN_MASK;
+++		new_flags |= (new_gen + 1UL) << LRU_GEN_PGOFF;
+++		/* for end_page_writeback() */
+++		if (reclaiming)
+++			new_flags |= BIT(PG_reclaim);
+++	} while (cmpxchg(&page->flags, old_flags, new_flags) != old_flags);
+++
+++	lru_gen_update_size(page, lruvec, old_gen, new_gen);
+++	if (reclaiming)
+++		list_move(&page->lru, &lrugen->lists[new_gen][type][zone]);
+++	else
+++		list_move_tail(&page->lru, &lrugen->lists[new_gen][type][zone]);
 ++}
 ++
-++/* Swap half words in a register word */
-++static void emit_swap16_r(struct jit_context *ctx, u8 dst, u8 src)
+++static void update_batch_size(struct page *page, int old_gen, int new_gen,
+++			      struct mm_walk_args *args)
 ++{
-++	u8 tmp = MIPS_R_T9;
+++	int type = page_is_file_lru(page);
+++	int zone = page_zonenum(page);
+++	int delta = thp_nr_pages(page);
 ++
-++	emit(ctx, sll, tmp, src, 16);  /* tmp = src << 16 */
-++	emit(ctx, srl, dst, src, 16);  /* dst = src >> 16 */
-++	emit(ctx, or,  dst, dst, tmp); /* dst = dst | tmp */
+++	VM_BUG_ON(old_gen >= MAX_NR_GENS);
+++	VM_BUG_ON(new_gen >= MAX_NR_GENS);
+++
+++	args->batch_size++;
+++
+++	args->nr_pages[old_gen][type][zone] -= delta;
+++	args->nr_pages[new_gen][type][zone] += delta;
 ++}
 ++
-++/* Swap bytes and truncate a register double word, word or half word */
-++static void emit_bswap_r64(struct jit_context *ctx, const u8 dst[], u32 width)
+++static void reset_batch_size(struct lruvec *lruvec, struct mm_walk_args *args)
 ++{
-++	u8 tmp = MIPS_R_T8;
+++	int gen, type, zone;
+++	struct lrugen *lrugen = &lruvec->evictable;
 ++
-++	switch (width) {
-++	/* Swap bytes in a double word */
-++	case 64:
-++		if (cpu_has_mips32r2 || cpu_has_mips32r6) {
-++			emit(ctx, rotr, tmp, hi(dst), 16);
-++			emit(ctx, rotr, hi(dst), lo(dst), 16);
-++			emit(ctx, wsbh, lo(dst), tmp);
-++			emit(ctx, wsbh, hi(dst), hi(dst));
-++		} else {
-++			emit_swap16_r(ctx, tmp, lo(dst));
-++			emit_swap16_r(ctx, lo(dst), hi(dst));
-++			emit(ctx, move, hi(dst), tmp);
+++	args->batch_size = 0;
 ++
-++			emit(ctx, lui, tmp, 0xff);      /* tmp = 0x00ff0000 */
-++			emit(ctx, ori, tmp, tmp, 0xff); /* tmp = 0x00ff00ff */
-++			emit_swap8_r(ctx, lo(dst), lo(dst), tmp);
-++			emit_swap8_r(ctx, hi(dst), hi(dst), tmp);
-++		}
-++		break;
-++	/* Swap bytes in a word */
-++	/* Swap bytes in a half word */
-++	case 32:
-++	case 16:
-++		emit_bswap_r(ctx, lo(dst), width);
-++		emit(ctx, move, hi(dst), MIPS_R_ZERO);
-++		break;
+++	for_each_gen_type_zone(gen, type, zone) {
+++		enum lru_list lru = type * LRU_FILE;
+++		int delta = args->nr_pages[gen][type][zone];
+++
+++		if (!delta)
+++			continue;
+++
+++		args->nr_pages[gen][type][zone] = 0;
+++		WRITE_ONCE(lrugen->sizes[gen][type][zone],
+++			   lrugen->sizes[gen][type][zone] + delta);
+++
+++		if (lru_gen_is_active(lruvec, gen))
+++			lru += LRU_ACTIVE;
+++		update_lru_size(lruvec, lru, zone, delta);
 ++	}
-++	clobber_reg64(ctx, dst);
 ++}
 ++
-++/* Truncate a register double word, word or half word */
-++static void emit_trunc_r64(struct jit_context *ctx, const u8 dst[], u32 width)
+++static int should_skip_vma(unsigned long start, unsigned long end, struct mm_walk *walk)
 ++{
-++	switch (width) {
-++	case 64:
-++		break;
-++	/* Zero-extend a word */
-++	case 32:
-++		emit(ctx, move, hi(dst), MIPS_R_ZERO);
-++		clobber_reg(ctx, hi(dst));
-++		break;
-++	/* Zero-extend a half word */
-++	case 16:
-++		emit(ctx, move, hi(dst), MIPS_R_ZERO);
-++		emit(ctx, andi, lo(dst), lo(dst), 0xffff);
-++		clobber_reg64(ctx, dst);
-++		break;
-++	}
+++	struct address_space *mapping;
+++	struct vm_area_struct *vma = walk->vma;
+++	struct mm_walk_args *args = walk->private;
+++
+++	if (!vma_is_accessible(vma) || is_vm_hugetlb_page(vma) ||
+++	    (vma->vm_flags & (VM_LOCKED | VM_SPECIAL | VM_SEQ_READ)))
+++		return true;
+++
+++	if (vma_is_anonymous(vma))
+++		return !args->swappiness;
+++
+++	if (WARN_ON_ONCE(!vma->vm_file || !vma->vm_file->f_mapping))
+++		return true;
+++
+++	mapping = vma->vm_file->f_mapping;
+++	if (!mapping->a_ops->writepage)
+++		return true;
+++
+++	return (shmem_mapping(mapping) && !args->swappiness) || mapping_unevictable(mapping);
 ++}
 ++
-++/* Load operation: dst = *(size*)(src + off) */
-++static void emit_ldx(struct jit_context *ctx,
-++		     const u8 dst[], u8 src, s16 off, u8 size)
+++/*
+++ * Some userspace memory allocators create many single-page VMAs. So instead of
+++ * returning back to the PGD table for each of such VMAs, we finish at least an
+++ * entire PMD table and therefore avoid many zigzags.
+++ */
+++static bool get_next_vma(struct mm_walk *walk, unsigned long mask, unsigned long size,
+++			 unsigned long *start, unsigned long *end)
 ++{
-++	switch (size) {
-++	/* Load a byte */
-++	case BPF_B:
-++		emit(ctx, lbu, lo(dst), off, src);
-++		emit(ctx, move, hi(dst), MIPS_R_ZERO);
-++		break;
-++	/* Load a half word */
-++	case BPF_H:
-++		emit(ctx, lhu, lo(dst), off, src);
-++		emit(ctx, move, hi(dst), MIPS_R_ZERO);
-++		break;
-++	/* Load a word */
-++	case BPF_W:
-++		emit(ctx, lw, lo(dst), off, src);
-++		emit(ctx, move, hi(dst), MIPS_R_ZERO);
-++		break;
-++	/* Load a double word */
-++	case BPF_DW:
-++		if (dst[1] == src) {
-++			emit(ctx, lw, dst[0], off + 4, src);
-++			emit(ctx, lw, dst[1], off, src);
-++		} else {
-++			emit(ctx, lw, dst[1], off, src);
-++			emit(ctx, lw, dst[0], off + 4, src);
+++	unsigned long next = round_up(*end, size);
+++
+++	VM_BUG_ON(mask & size);
+++	VM_BUG_ON(*start >= *end);
+++	VM_BUG_ON((next & mask) != (*start & mask));
+++
+++	while (walk->vma) {
+++		if (next >= walk->vma->vm_end) {
+++			walk->vma = walk->vma->vm_next;
+++			continue;
 ++		}
-++		emit_load_delay(ctx);
-++		break;
+++
+++		if ((next & mask) != (walk->vma->vm_start & mask))
+++			return false;
+++
+++		if (should_skip_vma(walk->vma->vm_start, walk->vma->vm_end, walk)) {
+++			walk->vma = walk->vma->vm_next;
+++			continue;
+++		}
+++
+++		*start = max(next, walk->vma->vm_start);
+++		next = (next | ~mask) + 1;
+++		/* rounded-up boundaries can wrap to 0 */
+++		*end = next && next < walk->vma->vm_end ? next : walk->vma->vm_end;
+++
+++		return true;
 ++	}
-++	clobber_reg64(ctx, dst);
+++
+++	return false;
 ++}
 ++
-++/* Store operation: *(size *)(dst + off) = src */
-++static void emit_stx(struct jit_context *ctx,
-++		     const u8 dst, const u8 src[], s16 off, u8 size)
+++static bool walk_pte_range(pmd_t *pmd, unsigned long start, unsigned long end,
+++			   struct mm_walk *walk)
 ++{
-++	switch (size) {
-++	/* Store a byte */
-++	case BPF_B:
-++		emit(ctx, sb, lo(src), off, dst);
-++		break;
-++	/* Store a half word */
-++	case BPF_H:
-++		emit(ctx, sh, lo(src), off, dst);
-++		break;
-++	/* Store a word */
-++	case BPF_W:
-++		emit(ctx, sw, lo(src), off, dst);
-++		break;
-++	/* Store a double word */
-++	case BPF_DW:
-++		emit(ctx, sw, src[1], off, dst);
-++		emit(ctx, sw, src[0], off + 4, dst);
-++		break;
+++	int i;
+++	pte_t *pte;
+++	spinlock_t *ptl;
+++	unsigned long addr;
+++	int worth = 0;
+++	struct mm_walk_args *args = walk->private;
+++	int old_gen, new_gen = lru_gen_from_seq(args->max_seq);
+++
+++	VM_BUG_ON(pmd_leaf(*pmd));
+++
+++	pte = pte_offset_map_lock(walk->mm, pmd, start & PMD_MASK, &ptl);
+++	arch_enter_lazy_mmu_mode();
+++restart:
+++	for (i = pte_index(start), addr = start; addr != end; i++, addr += PAGE_SIZE) {
+++		struct page *page;
+++		unsigned long pfn = pte_pfn(pte[i]);
+++
+++		args->mm_stats[MM_LEAF_TOTAL]++;
+++
+++		if (!pte_present(pte[i]) || is_zero_pfn(pfn))
+++			continue;
+++
+++		if (WARN_ON_ONCE(pte_devmap(pte[i]) || pte_special(pte[i])))
+++			continue;
+++
+++		if (!pte_young(pte[i])) {
+++			args->mm_stats[MM_LEAF_OLD]++;
+++			continue;
+++		}
+++
+++		VM_BUG_ON(!pfn_valid(pfn));
+++		if (pfn < args->start_pfn || pfn >= args->end_pfn)
+++			continue;
+++
+++		page = compound_head(pfn_to_page(pfn));
+++		if (page_to_nid(page) != args->node_id)
+++			continue;
+++
+++		if (page_memcg_rcu(page) != args->memcg)
+++			continue;
+++
+++		VM_BUG_ON(addr < walk->vma->vm_start || addr >= walk->vma->vm_end);
+++		if (!ptep_test_and_clear_young(walk->vma, addr, pte + i))
+++			continue;
+++
+++		args->mm_stats[MM_LEAF_YOUNG]++;
+++
+++		if (pte_dirty(pte[i]) && !PageDirty(page) &&
+++		    !(PageAnon(page) && PageSwapBacked(page) && !PageSwapCache(page)))
+++			set_page_dirty(page);
+++
+++		old_gen = page_update_gen(page, new_gen);
+++		if (old_gen >= 0 && old_gen != new_gen)
+++			update_batch_size(page, old_gen, new_gen, args);
+++
+++		worth++;
 ++	}
+++
+++	if (i < PTRS_PER_PTE && get_next_vma(walk, PMD_MASK, PAGE_SIZE, &start, &end))
+++		goto restart;
+++
+++	arch_leave_lazy_mmu_mode();
+++	pte_unmap_unlock(pte, ptl);
+++
+++	return worth >= MIN_BATCH_SIZE / 2;
 ++}
 ++
-++/* Atomic read-modify-write (32-bit, non-ll/sc fallback) */
-++static void emit_atomic_r32(struct jit_context *ctx,
-++			    u8 dst, u8 src, s16 off, u8 code)
+++/*
+++ * We scan PMD entries in two passes. The first pass reaches to PTE tables and
+++ * doesn't take the PMD lock. The second pass clears the accessed bit on PMD
+++ * entries and needs to take the PMD lock.
+++ */
+++#if defined(CONFIG_TRANSPARENT_HUGEPAGE) || defined(CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG)
+++static void walk_pmd_range_locked(pud_t *pud, unsigned long start, int offset,
+++				  struct vm_area_struct *vma, struct mm_walk *walk)
 ++{
-++	u32 exclude = 0;
-++	u32 addr = 0;
+++	int i;
+++	pmd_t *pmd;
+++	spinlock_t *ptl;
+++	struct mm_walk_args *args = walk->private;
+++	int old_gen, new_gen = lru_gen_from_seq(args->max_seq);
 ++
-++	/* Push caller-saved registers on stack */
-++	push_regs(ctx, ctx->clobbered & JIT_CALLER_REGS,
-++		  0, JIT_RESERVED_STACK);
-++	/*
-++	 * Argument 1: dst+off if xchg, otherwise src, passed in register a0
-++	 * Argument 2: src if xchg, othersize dst+off, passed in register a1
-++	 */
-++	emit(ctx, move, MIPS_R_T9, dst);
-++	emit(ctx, move, MIPS_R_A0, src);
-++	emit(ctx, addiu, MIPS_R_A1, MIPS_R_T9, off);
+++	VM_BUG_ON(pud_leaf(*pud));
 ++
-++	/* Emit function call */
-++	switch (code) {
-++	case BPF_ADD:
-++		addr = (u32)&atomic_add;
-++		break;
-++	case BPF_SUB:
-++		addr = (u32)&atomic_sub;
-++		break;
-++	case BPF_OR:
-++		addr = (u32)&atomic_or;
-++		break;
-++	case BPF_AND:
-++		addr = (u32)&atomic_and;
-++		break;
-++	case BPF_XOR:
-++		addr = (u32)&atomic_xor;
-++		break;
+++	start = (start & PUD_MASK) + offset * PMD_SIZE;
+++	pmd = pmd_offset(pud, start);
+++	ptl = pmd_lock(walk->mm, pmd);
+++	arch_enter_lazy_mmu_mode();
+++
+++	for_each_set_bit(i, args->bitmap, MIN_BATCH_SIZE) {
+++		struct page *page;
+++		unsigned long pfn = pmd_pfn(pmd[i]);
+++		unsigned long addr = start + i * PMD_SIZE;
+++
+++		if (!pmd_present(pmd[i]) || is_huge_zero_pmd(pmd[i]))
+++			continue;
+++
+++		if (WARN_ON_ONCE(pmd_devmap(pmd[i])))
+++			continue;
+++
+++		if (!pmd_trans_huge(pmd[i])) {
+++			if (IS_ENABLED(CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG))
+++				pmdp_test_and_clear_young(vma, addr, pmd + i);
+++			continue;
+++		}
+++
+++		VM_BUG_ON(!pfn_valid(pfn));
+++		if (pfn < args->start_pfn || pfn >= args->end_pfn)
+++			continue;
+++
+++		page = pfn_to_page(pfn);
+++		VM_BUG_ON_PAGE(PageTail(page), page);
+++		if (page_to_nid(page) != args->node_id)
+++			continue;
+++
+++		if (page_memcg_rcu(page) != args->memcg)
+++			continue;
+++
+++		VM_BUG_ON(addr < vma->vm_start || addr >= vma->vm_end);
+++		if (!pmdp_test_and_clear_young(vma, addr, pmd + i))
+++			continue;
+++
+++		args->mm_stats[MM_LEAF_YOUNG]++;
+++
+++		if (pmd_dirty(pmd[i]) && !PageDirty(page) &&
+++		    !(PageAnon(page) && PageSwapBacked(page) && !PageSwapCache(page)))
+++			set_page_dirty(page);
+++
+++		old_gen = page_update_gen(page, new_gen);
+++		if (old_gen >= 0 && old_gen != new_gen)
+++			update_batch_size(page, old_gen, new_gen, args);
 ++	}
-++	emit_mov_i(ctx, MIPS_R_T9, addr);
-++	emit(ctx, jalr, MIPS_R_RA, MIPS_R_T9);
-++	emit(ctx, nop); /* Delay slot */
 ++
-++	/* Restore caller-saved registers, except any fetched value */
-++	pop_regs(ctx, ctx->clobbered & JIT_CALLER_REGS,
-++		 exclude, JIT_RESERVED_STACK);
-++	emit_load_delay(ctx);
-++	clobber_reg(ctx, MIPS_R_RA);
+++	arch_leave_lazy_mmu_mode();
+++	spin_unlock(ptl);
+++
+++	bitmap_zero(args->bitmap, MIN_BATCH_SIZE);
+++}
+++#else
+++static void walk_pmd_range_locked(pud_t *pud, unsigned long start, int offset,
+++				  struct vm_area_struct *vma, struct mm_walk *walk)
+++{
 ++}
+++#endif
 ++
-++/* Atomic read-modify-write (64-bit) */
-++static void emit_atomic_r64(struct jit_context *ctx,
-++			    u8 dst, const u8 src[], s16 off, u8 code)
+++static void walk_pmd_range(pud_t *pud, unsigned long start, unsigned long end,
+++			   struct mm_walk *walk)
 ++{
-++	const u8 *r1 = bpf2mips32[BPF_REG_1]; /* Mapped to a0-a1 */
-++	u32 exclude = 0;
-++	u32 addr = 0;
+++	int i;
+++	pmd_t *pmd;
+++	unsigned long next;
+++	unsigned long addr;
+++	struct vm_area_struct *vma;
+++	int offset = -1;
+++	bool reset = false;
+++	struct mm_walk_args *args = walk->private;
+++	struct lruvec *lruvec = get_lruvec(args->node_id, args->memcg);
+++
+++	VM_BUG_ON(pud_leaf(*pud));
+++
+++	pmd = pmd_offset(pud, start & PUD_MASK);
+++restart:
+++	vma = walk->vma;
+++	for (i = pmd_index(start), addr = start; addr != end; i++, addr = next) {
+++		pmd_t val = pmd_read_atomic(pmd + i);
+++
+++		/* for pmd_read_atomic() */
+++		barrier();
+++
+++		next = pmd_addr_end(addr, end);
+++
+++		if (!pmd_present(val)) {
+++			args->mm_stats[MM_LEAF_TOTAL]++;
+++			continue;
+++		}
 ++
-++	/* Push caller-saved registers on stack */
-++	push_regs(ctx, ctx->clobbered & JIT_CALLER_REGS,
-++		  0, JIT_RESERVED_STACK);
-++	/*
-++	 * Argument 1: 64-bit src, passed in registers a0-a1
-++	 * Argument 2: 32-bit dst+off, passed in register a2
-++	 */
-++	emit(ctx, move, MIPS_R_T9, dst);
-++	emit(ctx, move, r1[0], src[0]);
-++	emit(ctx, move, r1[1], src[1]);
-++	emit(ctx, addiu, MIPS_R_A2, MIPS_R_T9, off);
+++#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+++		if (pmd_trans_huge(val)) {
+++			unsigned long pfn = pmd_pfn(val);
 ++
-++	/* Emit function call */
-++	switch (code) {
-++	case BPF_ADD:
-++		addr = (u32)&atomic64_add;
-++		break;
-++	case BPF_SUB:
-++		addr = (u32)&atomic64_sub;
-++		break;
-++	case BPF_OR:
-++		addr = (u32)&atomic64_or;
-++		break;
-++	case BPF_AND:
-++		addr = (u32)&atomic64_and;
-++		break;
-++	case BPF_XOR:
-++		addr = (u32)&atomic64_xor;
-++		break;
-++	}
-++	emit_mov_i(ctx, MIPS_R_T9, addr);
-++	emit(ctx, jalr, MIPS_R_RA, MIPS_R_T9);
-++	emit(ctx, nop); /* Delay slot */
+++			args->mm_stats[MM_LEAF_TOTAL]++;
 ++
-++	/* Restore caller-saved registers, except any fetched value */
-++	pop_regs(ctx, ctx->clobbered & JIT_CALLER_REGS,
-++		 exclude, JIT_RESERVED_STACK);
-++	emit_load_delay(ctx);
-++	clobber_reg(ctx, MIPS_R_RA);
-++}
+++			if (is_huge_zero_pmd(val))
+++				continue;
 ++
-++/*
-++ * Conditional movz or an emulated equivalent.
-++ * Note that the rs register may be modified.
-++ */
-++static void emit_movz_r(struct jit_context *ctx, u8 rd, u8 rs, u8 rt)
-++{
-++	if (cpu_has_mips_2) {
-++		emit(ctx, movz, rd, rs, rt);           /* rd = rt ? rd : rs  */
-++	} else if (cpu_has_mips32r6) {
-++		if (rs != MIPS_R_ZERO)
-++			emit(ctx, seleqz, rs, rs, rt); /* rs = 0 if rt == 0  */
-++		emit(ctx, selnez, rd, rd, rt);         /* rd = 0 if rt != 0  */
-++		if (rs != MIPS_R_ZERO)
-++			emit(ctx, or, rd, rd, rs);     /* rd = rd | rs       */
-++	} else {
-++		emit(ctx, bnez, rt, 8);                /* PC += 8 if rd != 0 */
-++		emit(ctx, nop);                        /* +0: delay slot     */
-++		emit(ctx, or, rd, rs, MIPS_R_ZERO);    /* +4: rd = rs        */
+++			if (!pmd_young(val)) {
+++				args->mm_stats[MM_LEAF_OLD]++;
+++				continue;
+++			}
+++
+++			if (pfn < args->start_pfn || pfn >= args->end_pfn)
+++				continue;
+++
+++			if (offset < 0)
+++				offset = i;
+++			else if (i - offset >= MIN_BATCH_SIZE) {
+++				walk_pmd_range_locked(pud, start, offset, vma, walk);
+++				offset = i;
+++			}
+++			__set_bit(i - offset, args->bitmap);
+++			reset = true;
+++			continue;
+++		}
+++#endif
+++		args->mm_stats[MM_NONLEAF_TOTAL]++;
+++
+++#ifdef CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG
+++		if (!pmd_young(val))
+++			continue;
+++
+++		if (offset < 0)
+++			offset = i;
+++		else if (i - offset >= MIN_BATCH_SIZE) {
+++			walk_pmd_range_locked(pud, start, offset, vma, walk);
+++			offset = i;
+++			reset = false;
+++		}
+++		__set_bit(i - offset, args->bitmap);
+++#endif
+++		if (args->use_filter && !test_bloom_filter(lruvec, args->max_seq, pmd + i))
+++			continue;
+++
+++		args->mm_stats[MM_NONLEAF_PREV]++;
+++
+++		if (!walk_pte_range(&val, addr, next, walk))
+++			continue;
+++
+++		args->mm_stats[MM_NONLEAF_CUR]++;
+++
+++		set_bloom_filter(lruvec, args->max_seq + 1, pmd + i);
 ++	}
-++	clobber_reg(ctx, rd);
-++	clobber_reg(ctx, rs);
+++
+++	if (reset) {
+++		walk_pmd_range_locked(pud, start, offset, vma, walk);
+++		offset = -1;
+++		reset = false;
+++	}
+++
+++	if (i < PTRS_PER_PMD && get_next_vma(walk, PUD_MASK, PMD_SIZE, &start, &end))
+++		goto restart;
+++
+++	if (offset >= 0)
+++		walk_pmd_range_locked(pud, start, offset, vma, walk);
 ++}
 ++
-++/*
-++ * Conditional movn or an emulated equivalent.
-++ * Note that the rs register may be modified.
-++ */
-++static void emit_movn_r(struct jit_context *ctx, u8 rd, u8 rs, u8 rt)
+++static int walk_pud_range(p4d_t *p4d, unsigned long start, unsigned long end,
+++			  struct mm_walk *walk)
 ++{
-++	if (cpu_has_mips_2) {
-++		emit(ctx, movn, rd, rs, rt);           /* rd = rt ? rs : rd  */
-++	} else if (cpu_has_mips32r6) {
-++		if (rs != MIPS_R_ZERO)
-++			emit(ctx, selnez, rs, rs, rt); /* rs = 0 if rt == 0  */
-++		emit(ctx, seleqz, rd, rd, rt);         /* rd = 0 if rt != 0  */
-++		if (rs != MIPS_R_ZERO)
-++			emit(ctx, or, rd, rd, rs);     /* rd = rd | rs       */
-++	} else {
-++		emit(ctx, beqz, rt, 8);                /* PC += 8 if rd == 0 */
-++		emit(ctx, nop);                        /* +0: delay slot     */
-++		emit(ctx, or, rd, rs, MIPS_R_ZERO);    /* +4: rd = rs        */
+++	int i;
+++	pud_t *pud;
+++	unsigned long addr;
+++	unsigned long next;
+++	struct mm_walk_args *args = walk->private;
+++
+++	VM_BUG_ON(p4d_leaf(*p4d));
+++
+++	pud = pud_offset(p4d, start & P4D_MASK);
+++restart:
+++	for (i = pud_index(start), addr = start; addr != end; i++, addr = next) {
+++		pud_t val = READ_ONCE(pud[i]);
+++
+++		next = pud_addr_end(addr, end);
+++
+++		if (!pud_present(val) || WARN_ON_ONCE(pud_leaf(val)))
+++			continue;
+++
+++		walk_pmd_range(&val, addr, next, walk);
+++
+++		if (args->batch_size >= MAX_BATCH_SIZE) {
+++			end = (addr | ~PUD_MASK) + 1;
+++			goto done;
+++		}
 ++	}
-++	clobber_reg(ctx, rd);
-++	clobber_reg(ctx, rs);
+++
+++	if (i < PTRS_PER_PUD && get_next_vma(walk, P4D_MASK, PUD_SIZE, &start, &end))
+++		goto restart;
+++
+++	end = round_up(end, P4D_SIZE);
+++done:
+++	/* rounded-up boundaries can wrap to 0 */
+++	args->next_addr = end && walk->vma ? max(end, walk->vma->vm_start) : 0;
+++
+++	return -EAGAIN;
 ++}
 ++
-++/* Emulation of 64-bit sltiu rd, rs, imm, where imm may be S32_MAX + 1 */
-++static void emit_sltiu_r64(struct jit_context *ctx, u8 rd,
-++			   const u8 rs[], s64 imm)
+++static void walk_mm(struct lruvec *lruvec, struct mm_struct *mm, struct mm_walk_args *args)
 ++{
-++	u8 tmp = MIPS_R_T9;
+++	static const struct mm_walk_ops mm_walk_ops = {
+++		.test_walk = should_skip_vma,
+++		.p4d_entry = walk_pud_range,
+++	};
 ++
-++	if (imm < 0) {
-++		emit_mov_i(ctx, rd, imm);                 /* rd = imm        */
-++		emit(ctx, sltu, rd, lo(rs), rd);          /* rd = rsl < rd   */
-++		emit(ctx, sltiu, tmp, hi(rs), -1);        /* tmp = rsh < ~0U */
-++		emit(ctx, or, rd, rd, tmp);               /* rd = rd | tmp   */
-++	} else { /* imm >= 0 */
-++		if (imm > 0x7fff) {
-++			emit_mov_i(ctx, rd, (s32)imm);     /* rd = imm       */
-++			emit(ctx, sltu, rd, lo(rs), rd);   /* rd = rsl < rd  */
-++		} else {
-++			emit(ctx, sltiu, rd, lo(rs), imm); /* rd = rsl < imm */
+++	int err;
+++
+++	args->next_addr = FIRST_USER_ADDRESS;
+++
+++	do {
+++		unsigned long start = args->next_addr;
+++		unsigned long end = mm->highest_vm_end;
+++
+++		err = -EBUSY;
+++
+++		rcu_read_lock();
+++#ifdef CONFIG_MEMCG
+++		if (args->memcg && atomic_read(&args->memcg->moving_account))
+++			goto contended;
+++#endif
+++		if (!mmap_read_trylock(mm))
+++			goto contended;
+++
+++		err = walk_page_range(mm, start, end, &mm_walk_ops, args);
+++
+++		mmap_read_unlock(mm);
+++
+++		if (args->batch_size) {
+++			spin_lock_irq(&lruvec->lru_lock);
+++			reset_batch_size(lruvec, args);
+++			spin_unlock_irq(&lruvec->lru_lock);
 ++		}
-++		emit_movn_r(ctx, rd, MIPS_R_ZERO, hi(rs)); /* rd = 0 if rsh  */
-++	}
+++contended:
+++		rcu_read_unlock();
+++
+++		cond_resched();
+++	} while (err == -EAGAIN && args->next_addr && !mm_is_oom_victim(mm));
 ++}
 ++
-++/* Emulation of 64-bit sltu rd, rs, rt */
-++static void emit_sltu_r64(struct jit_context *ctx, u8 rd,
-++			  const u8 rs[], const u8 rt[])
+++static struct mm_walk_args *alloc_mm_walk_args(void)
 ++{
-++	u8 tmp = MIPS_R_T9;
+++	if (!current->reclaim_state || !current->reclaim_state->mm_walk_args)
+++		return kvzalloc(sizeof(struct mm_walk_args), GFP_KERNEL);
 ++
-++	emit(ctx, sltu, rd, lo(rs), lo(rt));           /* rd = rsl < rtl     */
-++	emit(ctx, subu, tmp, hi(rs), hi(rt));          /* tmp = rsh - rth    */
-++	emit_movn_r(ctx, rd, MIPS_R_ZERO, tmp);        /* rd = 0 if tmp != 0 */
-++	emit(ctx, sltu, tmp, hi(rs), hi(rt));          /* tmp = rsh < rth    */
-++	emit(ctx, or, rd, rd, tmp);                    /* rd = rd | tmp      */
+++	return current->reclaim_state->mm_walk_args;
 ++}
 ++
-++/* Emulation of 64-bit slti rd, rs, imm, where imm may be S32_MAX + 1 */
-++static void emit_slti_r64(struct jit_context *ctx, u8 rd,
-++			  const u8 rs[], s64 imm)
+++static void free_mm_walk_args(struct mm_walk_args *args)
 ++{
-++	u8 t1 = MIPS_R_T8;
-++	u8 t2 = MIPS_R_T9;
-++	u8 cmp;
+++	if (!current->reclaim_state || !current->reclaim_state->mm_walk_args)
+++		kvfree(args);
+++}
 ++
-++	/*
-++	 * if ((rs < 0) ^ (imm < 0)) t1 = imm >u rsl
-++	 * else                      t1 = rsl <u imm
-++	 */
-++	emit_mov_i(ctx, rd, (s32)imm);
-++	emit(ctx, sltu, t1, lo(rs), rd);               /* t1 = rsl <u imm   */
-++	emit(ctx, sltu, t2, rd, lo(rs));               /* t2 = imm <u rsl   */
-++	emit(ctx, srl, rd, hi(rs), 31);                /* rd = rsh >> 31    */
-++	if (imm < 0)
-++		emit_movz_r(ctx, t1, t2, rd);          /* t1 = rd ? t1 : t2 */
-++	else
-++		emit_movn_r(ctx, t1, t2, rd);          /* t1 = rd ? t2 : t1 */
-++	/*
-++	 * if ((imm < 0 && rsh != 0xffffffff) ||
-++	 *     (imm >= 0 && rsh != 0))
-++	 *      t1 = 0
-++	 */
-++	if (imm < 0) {
-++		emit(ctx, addiu, rd, hi(rs), 1);       /* rd = rsh + 1 */
-++		cmp = rd;
-++	} else { /* imm >= 0 */
-++		cmp = hi(rs);
+++static bool inc_min_seq(struct lruvec *lruvec, int type)
+++{
+++	int gen, zone;
+++	int remaining = MAX_BATCH_SIZE;
+++	struct lrugen *lrugen = &lruvec->evictable;
+++
+++	VM_BUG_ON(!seq_is_valid(lruvec));
+++
+++	if (get_nr_gens(lruvec, type) != MAX_NR_GENS)
+++		return true;
+++
+++	gen = lru_gen_from_seq(lrugen->min_seq[type]);
+++
+++	for (zone = 0; zone < MAX_NR_ZONES; zone++) {
+++		struct list_head *head = &lrugen->lists[gen][type][zone];
+++
+++		while (!list_empty(head)) {
+++			struct page *page = lru_to_page(head);
+++
+++			VM_BUG_ON_PAGE(PageTail(page), page);
+++			VM_BUG_ON_PAGE(PageUnevictable(page), page);
+++			VM_BUG_ON_PAGE(PageActive(page), page);
+++			VM_BUG_ON_PAGE(page_is_file_lru(page) != type, page);
+++			VM_BUG_ON_PAGE(page_zonenum(page) != zone, page);
+++
+++			prefetchw_prev_lru_page(page, head, flags);
+++
+++			page_inc_gen(page, lruvec, false);
+++
+++			if (!--remaining)
+++				return false;
+++		}
 ++	}
-++	emit_movn_r(ctx, t1, MIPS_R_ZERO, cmp);        /* t1 = 0 if cmp != 0 */
 ++
-++	/*
-++	 * if (imm < 0) rd = rsh < -1
-++	 * else         rd = rsh != 0
-++	 * rd = rd | t1
-++	 */
-++	emit(ctx, slti, rd, hi(rs), imm < 0 ? -1 : 0); /* rd = rsh < hi(imm) */
-++	emit(ctx, or, rd, rd, t1);                     /* rd = rd | t1       */
+++	WRITE_ONCE(lrugen->min_seq[type], lrugen->min_seq[type] + 1);
+++
+++	return true;
 ++}
 ++
-++/* Emulation of 64-bit(slt rd, rs, rt) */
-++static void emit_slt_r64(struct jit_context *ctx, u8 rd,
-++			 const u8 rs[], const u8 rt[])
+++static bool try_to_inc_min_seq(struct lruvec *lruvec, int swappiness)
 ++{
-++	u8 t1 = MIPS_R_T7;
-++	u8 t2 = MIPS_R_T8;
-++	u8 t3 = MIPS_R_T9;
+++	int gen, type, zone;
+++	bool success = false;
+++	struct lrugen *lrugen = &lruvec->evictable;
+++	DEFINE_MIN_SEQ(lruvec);
 ++
-++	/*
-++	 * if ((rs < 0) ^ (rt < 0)) t1 = rtl <u rsl
-++	 * else                     t1 = rsl <u rtl
-++	 * if (rsh == rth)          t1 = 0
-++	 */
-++	emit(ctx, sltu, t1, lo(rs), lo(rt));           /* t1 = rsl <u rtl   */
-++	emit(ctx, sltu, t2, lo(rt), lo(rs));           /* t2 = rtl <u rsl   */
-++	emit(ctx, xor, t3, hi(rs), hi(rt));            /* t3 = rlh ^ rth    */
-++	emit(ctx, srl, rd, t3, 31);                    /* rd = t3 >> 31     */
-++	emit_movn_r(ctx, t1, t2, rd);                  /* t1 = rd ? t2 : t1 */
-++	emit_movn_r(ctx, t1, MIPS_R_ZERO, t3);         /* t1 = 0 if t3 != 0 */
+++	VM_BUG_ON(!seq_is_valid(lruvec));
 ++
-++	/* rd = (rsh < rth) | t1 */
-++	emit(ctx, slt, rd, hi(rs), hi(rt));            /* rd = rsh <s rth   */
-++	emit(ctx, or, rd, rd, t1);                     /* rd = rd | t1      */
+++	for (type = 0; type < ANON_AND_FILE; type++) {
+++		while (lrugen->max_seq - min_seq[type] >= MIN_NR_GENS) {
+++			gen = lru_gen_from_seq(min_seq[type]);
+++
+++			for (zone = 0; zone < MAX_NR_ZONES; zone++) {
+++				if (!list_empty(&lrugen->lists[gen][type][zone]))
+++					goto next;
+++			}
+++
+++			min_seq[type]++;
+++		}
+++next:
+++		;
+++	}
+++
+++	min_seq[0] = min(min_seq[0], min_seq[1]);
+++	if (swappiness)
+++		min_seq[1] = max(min_seq[0], lrugen->min_seq[1]);
+++
+++	for (type = 0; type < ANON_AND_FILE; type++) {
+++		if (min_seq[type] == lrugen->min_seq[type])
+++			continue;
+++
+++		WRITE_ONCE(lrugen->min_seq[type], min_seq[type]);
+++		success = true;
+++	}
+++
+++	return success;
 ++}
 ++
-++/* Jump immediate (64-bit) */
-++static void emit_jmp_i64(struct jit_context *ctx,
-++			 const u8 dst[], s32 imm, s32 off, u8 op)
+++static void inc_max_seq(struct lruvec *lruvec, unsigned long max_seq)
 ++{
-++	u8 tmp = MIPS_R_T6;
+++	int gen, type, zone;
+++	struct lrugen *lrugen = &lruvec->evictable;
 ++
-++	switch (op) {
-++	/* No-op, used internally for branch optimization */
-++	case JIT_JNOP:
-++		break;
-++	/* PC += off if dst == imm */
-++	/* PC += off if dst != imm */
-++	case BPF_JEQ:
-++	case BPF_JNE:
-++		if (imm >= -0x7fff && imm <= 0x8000) {
-++			emit(ctx, addiu, tmp, lo(dst), -imm);
-++		} else if ((u32)imm <= 0xffff) {
-++			emit(ctx, xori, tmp, lo(dst), imm);
-++		} else {       /* Register fallback */
-++			emit_mov_i(ctx, tmp, imm);
-++			emit(ctx, xor, tmp, lo(dst), tmp);
+++	spin_lock_irq(&lruvec->lru_lock);
+++
+++	VM_BUG_ON(!seq_is_valid(lruvec));
+++
+++	if (max_seq != lrugen->max_seq)
+++		goto unlock;
+++
+++	if (!try_to_inc_min_seq(lruvec, true)) {
+++		for (type = ANON_AND_FILE - 1; type >= 0; type--) {
+++			while (!inc_min_seq(lruvec, type)) {
+++				spin_unlock_irq(&lruvec->lru_lock);
+++				cond_resched();
+++				spin_lock_irq(&lruvec->lru_lock);
+++			}
 ++		}
-++		if (imm < 0) { /* Compare sign extension */
-++			emit(ctx, addu, MIPS_R_T9, hi(dst), 1);
-++			emit(ctx, or, tmp, tmp, MIPS_R_T9);
-++		} else {       /* Compare zero extension */
-++			emit(ctx, or, tmp, tmp, hi(dst));
+++	}
+++
+++	gen = lru_gen_from_seq(lrugen->max_seq - 1);
+++	for (type = 0; type < ANON_AND_FILE; type++) {
+++		for (zone = 0; zone < MAX_NR_ZONES; zone++) {
+++			enum lru_list lru = type * LRU_FILE;
+++			long delta = lrugen->sizes[gen][type][zone];
+++
+++			if (!delta)
+++				continue;
+++
+++			WARN_ON_ONCE(delta != (int)delta);
+++
+++			update_lru_size(lruvec, lru, zone, delta);
+++			update_lru_size(lruvec, lru + LRU_ACTIVE, zone, -delta);
 ++		}
-++		if (op == BPF_JEQ)
-++			emit(ctx, beqz, tmp, off);
-++		else   /* BPF_JNE */
-++			emit(ctx, bnez, tmp, off);
-++		break;
-++	/* PC += off if dst & imm */
-++	/* PC += off if (dst & imm) == 0 (not in BPF, used for long jumps) */
-++	case BPF_JSET:
-++	case JIT_JNSET:
-++		if ((u32)imm <= 0xffff) {
-++			emit(ctx, andi, tmp, lo(dst), imm);
-++		} else {     /* Register fallback */
-++			emit_mov_i(ctx, tmp, imm);
-++			emit(ctx, and, tmp, lo(dst), tmp);
+++	}
+++
+++	gen = lru_gen_from_seq(lrugen->max_seq + 1);
+++	for (type = 0; type < ANON_AND_FILE; type++) {
+++		for (zone = 0; zone < MAX_NR_ZONES; zone++) {
+++			enum lru_list lru = type * LRU_FILE;
+++			long delta = lrugen->sizes[gen][type][zone];
+++
+++			if (!delta)
+++				continue;
+++
+++			WARN_ON_ONCE(delta != (int)delta);
+++
+++			update_lru_size(lruvec, lru, zone, -delta);
+++			update_lru_size(lruvec, lru + LRU_ACTIVE, zone, delta);
 ++		}
-++		if (imm < 0) /* Sign-extension pulls in high word */
-++			emit(ctx, or, tmp, tmp, hi(dst));
-++		if (op == BPF_JSET)
-++			emit(ctx, bnez, tmp, off);
-++		else   /* JIT_JNSET */
-++			emit(ctx, beqz, tmp, off);
-++		break;
-++	/* PC += off if dst > imm */
-++	case BPF_JGT:
-++		emit_sltiu_r64(ctx, tmp, dst, (s64)imm + 1);
-++		emit(ctx, beqz, tmp, off);
-++		break;
-++	/* PC += off if dst >= imm */
-++	case BPF_JGE:
-++		emit_sltiu_r64(ctx, tmp, dst, imm);
-++		emit(ctx, beqz, tmp, off);
-++		break;
-++	/* PC += off if dst < imm */
-++	case BPF_JLT:
-++		emit_sltiu_r64(ctx, tmp, dst, imm);
-++		emit(ctx, bnez, tmp, off);
-++		break;
-++	/* PC += off if dst <= imm */
-++	case BPF_JLE:
-++		emit_sltiu_r64(ctx, tmp, dst, (s64)imm + 1);
-++		emit(ctx, bnez, tmp, off);
-++		break;
-++	/* PC += off if dst > imm (signed) */
-++	case BPF_JSGT:
-++		emit_slti_r64(ctx, tmp, dst, (s64)imm + 1);
-++		emit(ctx, beqz, tmp, off);
-++		break;
-++	/* PC += off if dst >= imm (signed) */
-++	case BPF_JSGE:
-++		emit_slti_r64(ctx, tmp, dst, imm);
-++		emit(ctx, beqz, tmp, off);
-++		break;
-++	/* PC += off if dst < imm (signed) */
-++	case BPF_JSLT:
-++		emit_slti_r64(ctx, tmp, dst, imm);
-++		emit(ctx, bnez, tmp, off);
-++		break;
-++	/* PC += off if dst <= imm (signed) */
-++	case BPF_JSLE:
-++		emit_slti_r64(ctx, tmp, dst, (s64)imm + 1);
-++		emit(ctx, bnez, tmp, off);
-++		break;
 ++	}
+++
+++	WRITE_ONCE(lrugen->timestamps[gen], jiffies);
+++	/* make sure all preceding modifications appear first */
+++	smp_store_release(&lrugen->max_seq, lrugen->max_seq + 1);
+++unlock:
+++	spin_unlock_irq(&lruvec->lru_lock);
 ++}
 ++
-++/* Jump register (64-bit) */
-++static void emit_jmp_r64(struct jit_context *ctx,
-++			 const u8 dst[], const u8 src[], s32 off, u8 op)
+++/* Main function used by the foreground, the background and the user-triggered aging. */
+++static bool try_to_inc_max_seq(struct lruvec *lruvec, struct scan_control *sc, int swappiness,
+++			       unsigned long max_seq, bool use_filter)
 ++{
-++	u8 t1 = MIPS_R_T6;
-++	u8 t2 = MIPS_R_T7;
+++	bool last;
+++	struct mm_walk_args *args;
+++	struct mm_struct *mm = NULL;
+++	struct lrugen *lrugen = &lruvec->evictable;
+++	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+++	struct pglist_data *pgdat = lruvec_pgdat(lruvec);
+++	int nid = pgdat->node_id;
 ++
-++	switch (op) {
-++	/* No-op, used internally for branch optimization */
-++	case JIT_JNOP:
-++		break;
-++	/* PC += off if dst == src */
-++	/* PC += off if dst != src */
-++	case BPF_JEQ:
-++	case BPF_JNE:
-++		emit(ctx, subu, t1, lo(dst), lo(src));
-++		emit(ctx, subu, t2, hi(dst), hi(src));
-++		emit(ctx, or, t1, t1, t2);
-++		if (op == BPF_JEQ)
-++			emit(ctx, beqz, t1, off);
-++		else   /* BPF_JNE */
-++			emit(ctx, bnez, t1, off);
-++		break;
-++	/* PC += off if dst & src */
-++	/* PC += off if (dst & imm) == 0 (not in BPF, used for long jumps) */
-++	case BPF_JSET:
-++	case JIT_JNSET:
-++		emit(ctx, and, t1, lo(dst), lo(src));
-++		emit(ctx, and, t2, hi(dst), hi(src));
-++		emit(ctx, or, t1, t1, t2);
-++		if (op == BPF_JSET)
-++			emit(ctx, bnez, t1, off);
-++		else   /* JIT_JNSET */
-++			emit(ctx, beqz, t1, off);
-++		break;
-++	/* PC += off if dst > src */
-++	case BPF_JGT:
-++		emit_sltu_r64(ctx, t1, src, dst);
-++		emit(ctx, bnez, t1, off);
-++		break;
-++	/* PC += off if dst >= src */
-++	case BPF_JGE:
-++		emit_sltu_r64(ctx, t1, dst, src);
-++		emit(ctx, beqz, t1, off);
-++		break;
-++	/* PC += off if dst < src */
-++	case BPF_JLT:
-++		emit_sltu_r64(ctx, t1, dst, src);
-++		emit(ctx, bnez, t1, off);
-++		break;
-++	/* PC += off if dst <= src */
-++	case BPF_JLE:
-++		emit_sltu_r64(ctx, t1, src, dst);
-++		emit(ctx, beqz, t1, off);
-++		break;
-++	/* PC += off if dst > src (signed) */
-++	case BPF_JSGT:
-++		emit_slt_r64(ctx, t1, src, dst);
-++		emit(ctx, bnez, t1, off);
-++		break;
-++	/* PC += off if dst >= src (signed) */
-++	case BPF_JSGE:
-++		emit_slt_r64(ctx, t1, dst, src);
-++		emit(ctx, beqz, t1, off);
-++		break;
-++	/* PC += off if dst < src (signed) */
-++	case BPF_JSLT:
-++		emit_slt_r64(ctx, t1, dst, src);
-++		emit(ctx, bnez, t1, off);
-++		break;
-++	/* PC += off if dst <= src (signed) */
-++	case BPF_JSLE:
-++		emit_slt_r64(ctx, t1, src, dst);
-++		emit(ctx, beqz, t1, off);
-++		break;
+++	VM_BUG_ON(max_seq > READ_ONCE(lrugen->max_seq));
+++
+++	/*
+++	 * If we are not from run_aging() and clearing the accessed bit may
+++	 * trigger page faults, then don't proceed to clearing all accessed
+++	 * PTEs. Instead, fallback to lru_gen_look_around(), which only clears a
+++	 * handful of accessed PTEs. This is less efficient but causes fewer
+++	 * page faults on CPUs that don't have the capability.
+++	 */
+++	if ((current->flags & PF_MEMALLOC) && !arch_has_hw_pte_young(false)) {
+++		inc_max_seq(lruvec, max_seq);
+++		return true;
 ++	}
-++}
 ++
-++/* Function call */
-++static int emit_call(struct jit_context *ctx, const struct bpf_insn *insn)
-++{
-++	bool fixed;
-++	u64 addr;
+++	args = alloc_mm_walk_args();
+++	if (!args)
+++		return false;
 ++
-++	/* Decode the call address */
-++	if (bpf_jit_get_func_addr(ctx->program, insn, false,
-++				  &addr, &fixed) < 0)
-++		return -1;
-++	if (!fixed)
-++		return -1;
+++	args->memcg = memcg;
+++	args->max_seq = max_seq;
+++	args->start_pfn = pgdat->node_start_pfn;
+++	args->end_pfn = pgdat_end_pfn(pgdat);
+++	args->node_id = nid;
+++	args->swappiness = swappiness;
+++	args->use_filter = use_filter;
 ++
-++	/* Push stack arguments */
-++	push_regs(ctx, JIT_STACK_REGS, 0, JIT_RESERVED_STACK);
+++	do {
+++		last = get_next_mm(lruvec, args, &mm);
+++		if (mm)
+++			walk_mm(lruvec, mm, args);
 ++
-++	/* Emit function call */
-++	emit_mov_i(ctx, MIPS_R_T9, addr);
-++	emit(ctx, jalr, MIPS_R_RA, MIPS_R_T9);
-++	emit(ctx, nop); /* Delay slot */
+++		cond_resched();
+++	} while (mm);
 ++
-++	clobber_reg(ctx, MIPS_R_RA);
-++	clobber_reg(ctx, MIPS_R_V0);
-++	clobber_reg(ctx, MIPS_R_V1);
-++	return 0;
+++	free_mm_walk_args(args);
+++
+++	if (!last) {
+++		/* don't wait unless we may have trouble reclaiming */
+++		if (!current_is_kswapd() && sc->priority < DEF_PRIORITY - 2)
+++			wait_event_killable(lruvec->mm_walk.wait,
+++					    max_seq < READ_ONCE(lrugen->max_seq));
+++
+++		return max_seq < READ_ONCE(lrugen->max_seq);
+++	}
+++
+++	VM_BUG_ON(max_seq != READ_ONCE(lrugen->max_seq));
+++
+++	inc_max_seq(lruvec, max_seq);
+++	/* either we see any waiters or they will see updated max_seq */
+++	if (wq_has_sleeper(&lruvec->mm_walk.wait))
+++		wake_up_all(&lruvec->mm_walk.wait);
+++
+++	wakeup_flusher_threads(WB_REASON_VMSCAN);
+++
+++	return true;
 ++}
 ++
-++/* Function tail call */
-++static int emit_tail_call(struct jit_context *ctx)
+++static long get_nr_evictable(struct lruvec *lruvec, struct scan_control *sc, int swappiness,
+++			     unsigned long max_seq, unsigned long *min_seq, bool *low)
 ++{
-++	u8 ary = lo(bpf2mips32[BPF_REG_2]);
-++	u8 ind = lo(bpf2mips32[BPF_REG_3]);
-++	u8 t1 = MIPS_R_T8;
-++	u8 t2 = MIPS_R_T9;
-++	int off;
+++	int gen, type, zone;
+++	long max = 0;
+++	long min = 0;
+++	struct lrugen *lrugen = &lruvec->evictable;
 ++
-++	/*
-++	 * Tail call:
-++	 * eBPF R1   - function argument (context ptr), passed in a0-a1
-++	 * eBPF R2   - ptr to object with array of function entry points
-++	 * eBPF R3   - array index of function to be called
-++	 * stack[sz] - remaining tail call count, initialized in prologue
-++	 */
+++	for (type = !swappiness; type < ANON_AND_FILE; type++) {
+++		unsigned long seq;
 ++
-++	/* if (ind >= ary->map.max_entries) goto out */
-++	off = offsetof(struct bpf_array, map.max_entries);
-++	if (off > 0x7fff)
-++		return -1;
-++	emit(ctx, lw, t1, off, ary);             /* t1 = ary->map.max_entries*/
-++	emit_load_delay(ctx);                    /* Load delay slot          */
-++	emit(ctx, sltu, t1, ind, t1);            /* t1 = ind < t1            */
-++	emit(ctx, beqz, t1, get_offset(ctx, 1)); /* PC += off(1) if t1 == 0  */
-++						 /* (next insn delay slot)   */
-++	/* if (TCC-- <= 0) goto out */
-++	emit(ctx, lw, t2, ctx->stack_size, MIPS_R_SP);  /* t2 = *(SP + size) */
-++	emit_load_delay(ctx);                     /* Load delay slot         */
-++	emit(ctx, blez, t2, get_offset(ctx, 1));  /* PC += off(1) if t2 < 0  */
-++	emit(ctx, addiu, t2, t2, -1);             /* t2-- (delay slot)       */
-++	emit(ctx, sw, t2, ctx->stack_size, MIPS_R_SP);  /* *(SP + size) = t2 */
+++		for (seq = min_seq[type]; seq <= max_seq; seq++) {
+++			long size = 0;
 ++
-++	/* prog = ary->ptrs[ind] */
-++	off = offsetof(struct bpf_array, ptrs);
-++	if (off > 0x7fff)
-++		return -1;
-++	emit(ctx, sll, t1, ind, 2);               /* t1 = ind << 2           */
-++	emit(ctx, addu, t1, t1, ary);             /* t1 += ary               */
-++	emit(ctx, lw, t2, off, t1);               /* t2 = *(t1 + off)        */
-++	emit_load_delay(ctx);                     /* Load delay slot         */
+++			gen = lru_gen_from_seq(seq);
 ++
-++	/* if (prog == 0) goto out */
-++	emit(ctx, beqz, t2, get_offset(ctx, 1));  /* PC += off(1) if t2 == 0 */
-++	emit(ctx, nop);                           /* Delay slot              */
+++			for (zone = 0; zone <= sc->reclaim_idx; zone++)
+++				size += READ_ONCE(lrugen->sizes[gen][type][zone]);
 ++
-++	/* func = prog->bpf_func + 8 (prologue skip offset) */
-++	off = offsetof(struct bpf_prog, bpf_func);
-++	if (off > 0x7fff)
-++		return -1;
-++	emit(ctx, lw, t1, off, t2);                /* t1 = *(t2 + off)       */
-++	emit_load_delay(ctx);                      /* Load delay slot        */
-++	emit(ctx, addiu, t1, t1, JIT_TCALL_SKIP);  /* t1 += skip (8 or 12)   */
+++			max += size;
+++			if (type && max_seq - seq >= MIN_NR_GENS)
+++				min += size;
+++		}
+++	}
 ++
-++	/* goto func */
-++	build_epilogue(ctx, t1);
-++	return 0;
+++	*low = max_seq - min_seq[1] <= MIN_NR_GENS && min < MIN_BATCH_SIZE;
+++
+++	return max > 0 ? max : 0;
 ++}
 ++
-++/*
-++ * Stack frame layout for a JITed program (stack grows down).
-++ *
-++ * Higher address  : Caller's stack frame       :
-++ *                 :----------------------------:
-++ *                 : 64-bit eBPF args r3-r5     :
-++ *                 :----------------------------:
-++ *                 : Reserved / tail call count :
-++ *                 +============================+  <--- MIPS sp before call
-++ *                 | Callee-saved registers,    |
-++ *                 | including RA and FP        |
-++ *                 +----------------------------+  <--- eBPF FP (MIPS zero,fp)
-++ *                 | Local eBPF variables       |
-++ *                 | allocated by program       |
-++ *                 +----------------------------+
-++ *                 | Reserved for caller-saved  |
-++ *                 | registers                  |
-++ *                 +----------------------------+
-++ *                 | Reserved for 64-bit eBPF   |
-++ *                 | args r3-r5 & args passed   |
-++ *                 | on stack in kernel calls   |
-++ * Lower address   +============================+  <--- MIPS sp
+++static bool age_lruvec(struct lruvec *lruvec, struct scan_control *sc,
+++		       unsigned long min_ttl)
+++{
+++	bool low;
+++	long nr_to_scan;
+++	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+++	int swappiness = get_swappiness(memcg);
+++	DEFINE_MAX_SEQ(lruvec);
+++	DEFINE_MIN_SEQ(lruvec);
+++
+++	if (mem_cgroup_below_min(memcg))
+++		return false;
+++
+++	if (min_ttl) {
+++		int gen = lru_gen_from_seq(min_seq[1]);
+++		unsigned long birth = READ_ONCE(lruvec->evictable.timestamps[gen]);
+++
+++		if (time_is_after_jiffies(birth + min_ttl))
+++			return false;
+++	}
+++
+++	nr_to_scan = get_nr_evictable(lruvec, sc, swappiness, max_seq, min_seq, &low);
+++	if (!nr_to_scan)
+++		return false;
+++
+++	nr_to_scan >>= sc->priority;
+++
+++	if (!mem_cgroup_online(memcg))
+++		nr_to_scan++;
+++
+++	if (nr_to_scan && low && (!mem_cgroup_below_low(memcg) || sc->memcg_low_reclaim))
+++		try_to_inc_max_seq(lruvec, sc, swappiness, max_seq, true);
+++
+++	return true;
+++}
+++
+++/* Protect the working set accessed within the last N milliseconds. */
+++static unsigned long lru_gen_min_ttl __read_mostly;
+++
+++static void lru_gen_age_node(struct pglist_data *pgdat, struct scan_control *sc)
+++{
+++	struct mem_cgroup *memcg;
+++	bool success = false;
+++	unsigned long min_ttl = READ_ONCE(lru_gen_min_ttl);
+++
+++	VM_BUG_ON(!current_is_kswapd());
+++
+++	if (!sc->force_deactivate) {
+++		sc->force_deactivate = 1;
+++		return;
+++	}
+++
+++	current->reclaim_state->mm_walk_args = &pgdat->mm_walk_args;
+++
+++	memcg = mem_cgroup_iter(NULL, NULL, NULL);
+++	do {
+++		struct lruvec *lruvec = mem_cgroup_lruvec(memcg, pgdat);
+++
+++		if (age_lruvec(lruvec, sc, min_ttl))
+++			success = true;
+++
+++		cond_resched();
+++	} while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)));
+++
+++	if (!success && mutex_trylock(&oom_lock)) {
+++		struct oom_control oc = {
+++			.gfp_mask = sc->gfp_mask,
+++			.order = sc->order,
+++		};
+++
+++		/* to avoid overkilling */
+++		if (!oom_reaping_in_progress())
+++			out_of_memory(&oc);
+++
+++		mutex_unlock(&oom_lock);
+++	}
+++
+++	current->reclaim_state->mm_walk_args = NULL;
+++}
+++
+++/* Scan the vicinity of an accessed PTE when shrink_page_list() uses the rmap. */
+++void lru_gen_look_around(struct page_vma_mapped_walk *pvmw)
+++{
+++	int i;
+++	pte_t *pte;
+++	struct page *page;
+++	int old_gen, new_gen;
+++	unsigned long start;
+++	unsigned long end;
+++	unsigned long addr;
+++	struct mm_walk_args *args;
+++	int worth = 0;
+++	struct mem_cgroup *memcg = page_memcg(pvmw->page);
+++	struct pglist_data *pgdat = page_pgdat(pvmw->page);
+++	struct lruvec *lruvec = mem_cgroup_lruvec(memcg, pgdat);
+++	DEFINE_MAX_SEQ(lruvec);
+++
+++	lockdep_assert_held(pvmw->ptl);
+++	VM_BUG_ON_PAGE(PageLRU(pvmw->page), pvmw->page);
+++
+++	args = current->reclaim_state ? current->reclaim_state->mm_walk_args : NULL;
+++	if (!args)
+++		return;
+++
+++	start = max(pvmw->address & PMD_MASK, pvmw->vma->vm_start);
+++	end = min(pvmw->address | ~PMD_MASK, pvmw->vma->vm_end - 1) + 1;
+++
+++	if (end - start > MIN_BATCH_SIZE * PAGE_SIZE) {
+++		if (pvmw->address - start < MIN_BATCH_SIZE * PAGE_SIZE / 2)
+++			end = start + MIN_BATCH_SIZE * PAGE_SIZE;
+++		else if (end - pvmw->address < MIN_BATCH_SIZE * PAGE_SIZE / 2)
+++			start = end - MIN_BATCH_SIZE * PAGE_SIZE;
+++		else {
+++			start = pvmw->address - MIN_BATCH_SIZE * PAGE_SIZE / 2;
+++			end = pvmw->address + MIN_BATCH_SIZE * PAGE_SIZE / 2;
+++		}
+++	}
+++
+++	pte = pvmw->pte - (pvmw->address - start) / PAGE_SIZE;
+++	new_gen = lru_gen_from_seq(max_seq);
+++
+++	lock_page_memcg(pvmw->page);
+++	arch_enter_lazy_mmu_mode();
+++
+++	for (i = 0, addr = start; addr != end; i++, addr += PAGE_SIZE) {
+++		unsigned long pfn = pte_pfn(pte[i]);
+++
+++		if (!pte_present(pte[i]) || is_zero_pfn(pfn))
+++			continue;
+++
+++		if (WARN_ON_ONCE(pte_devmap(pte[i]) || pte_special(pte[i])))
+++			continue;
+++
+++		VM_BUG_ON(!pfn_valid(pfn));
+++		if (pfn < pgdat->node_start_pfn || pfn >= pgdat_end_pfn(pgdat))
+++			continue;
+++
+++		worth++;
+++
+++		if (!pte_young(pte[i]))
+++			continue;
+++
+++		page = compound_head(pfn_to_page(pfn));
+++		if (page_to_nid(page) != pgdat->node_id)
+++			continue;
+++
+++		if (page_memcg_rcu(page) != memcg)
+++			continue;
+++
+++		VM_BUG_ON(addr < pvmw->vma->vm_start || addr >= pvmw->vma->vm_end);
+++		if (!ptep_test_and_clear_young(pvmw->vma, addr, pte + i))
+++			continue;
+++
+++		if (pte_dirty(pte[i]) && !PageDirty(page) &&
+++		    !(PageAnon(page) && PageSwapBacked(page) && !PageSwapCache(page)))
+++			__set_bit(i, args->bitmap);
+++
+++		old_gen = page_update_gen(page, new_gen);
+++		if (old_gen >= 0 && old_gen != new_gen)
+++			update_batch_size(page, old_gen, new_gen, args);
+++	}
+++
+++	arch_leave_lazy_mmu_mode();
+++	unlock_page_memcg(pvmw->page);
+++
+++	if (worth >= MIN_BATCH_SIZE / 2)
+++		set_bloom_filter(lruvec, max_seq, pvmw->pmd);
+++
+++	for_each_set_bit(i, args->bitmap, MIN_BATCH_SIZE)
+++		set_page_dirty(pte_page(pte[i]));
+++
+++	bitmap_zero(args->bitmap, MIN_BATCH_SIZE);
+++}
+++
+++/******************************************************************************
++  *                          state change
++  ******************************************************************************/
++ 
++@@ -3477,6 +4414,12 @@ static int __init init_lru_gen(void)
++ };
++ late_initcall(init_lru_gen);
++ 
+++#else
+++
+++static void lru_gen_age_node(struct pglist_data *pgdat, struct scan_control *sc)
+++{
+++}
+++
++ #endif /* CONFIG_LRU_GEN */
++ 
++ static void shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
++@@ -4333,6 +5276,11 @@ static void age_active_anon(struct pglis
++ 	struct mem_cgroup *memcg;
++ 	struct lruvec *lruvec;
++ 
+++	if (lru_gen_enabled()) {
+++		lru_gen_age_node(pgdat, sc);
+++		return;
+++	}
+++
++ 	if (!can_age_anon_pages(pgdat, sc))
++ 		return;
++ 
+diff --git a/target/linux/generic/backport-5.15/020-v6.1-07-mm-multigenerational-lru-eviction.patch b/target/linux/generic/backport-5.15/020-v6.1-07-mm-multigenerational-lru-eviction.patch
+new file mode 100644
+index 0000000000..09e4315dcc
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/020-v6.1-07-mm-multigenerational-lru-eviction.patch
+@@ -0,0 +1,1002 @@
++From f4b881ce07ccb2a519f664afaa2a68225b612ca3 Mon Sep 17 00:00:00 2001
++From: Yu Zhao <yuzhao@google.com>
++Date: Tue, 29 Jun 2021 20:46:47 -0600
++Subject: [PATCH 07/10] mm: multigenerational lru: eviction
++
++The eviction consumes old generations. Given an lruvec, the eviction
++scans pages on lrugen->lists indexed by anon and file min_seq[]
++(modulo MAX_NR_GENS). It first tries to select a type based on the
++values of min_seq[]. If they are equal, it selects the type that has
++a lower refaulted %. The eviction sorts a page according to its
++updated generation number if the aging has found this page accessed.
++It also moves a page to the next generation if this page is from an
++upper tier that has a higher refaulted % than the base tier. The
++eviction increments min_seq[] of a selected type when it finds
++lrugen->lists indexed by min_seq[] of this selected type are empty.
++
++Each generation is divided into multiple tiers. Tiers represent
++different ranges of numbers of accesses from file descriptors only.
++Pages accessed N times via file descriptors belong to tier
++order_base_2(N). Each generation contains at most MAX_NR_TIERS tiers,
++and they require additional MAX_NR_TIERS-2 bits in page->flags. In
++contrast to moving between generations which requires list operations,
++moving between tiers only involves operations on page->flags and
++therefore has a negligible cost. A feedback loop modeled after the PID
++controller monitors refaulted % across all tiers and decides when to
++protect pages from which tiers.
++
++Unmapped pages are initially added to the oldest generation and then
++conditionally protected by tiers. Each tier keeps track of how many
++pages from it have refaulted. Tier 0 is the base tier and pages from
++it are evicted unconditionally because there are no better candidates.
++Pages from an upper tier are either evicted or moved to the next
++generation, depending on whether this upper tier has a higher
++refaulted % than the base tier. This model has the following
++advantages:
++  1) It removes the cost in the buffered access path and reduces the
++  overall cost of protection because pages are conditionally protected
++  in the reclaim path.
++  2) It takes mapped pages into account and avoids overprotecting
++  pages accessed multiple times via file descriptors.
++  3 Additional tiers improve the protection of pages accessed more
++  than twice.
++
++Signed-off-by: Yu Zhao <yuzhao@google.com>
++Tested-by: Konstantin Kharlamov <Hi-Angel@yandex.ru>
++Change-Id: I64c06d8f2cdb83ac7d56c7e1d07f043483956cac
++---
++ include/linux/mm_inline.h |  10 +
++ include/linux/mmzone.h    |  33 +++
++ mm/swap.c                 |  42 +++
++ mm/vmscan.c               | 555 +++++++++++++++++++++++++++++++++++++-
++ mm/workingset.c           | 120 ++++++++-
++ 5 files changed, 757 insertions(+), 3 deletions(-)
++
++--- a/include/linux/mm_inline.h
+++++ b/include/linux/mm_inline.h
++@@ -106,6 +106,14 @@ static inline int lru_hist_from_seq(unsi
++ 	return seq % NR_HIST_GENS;
++ }
++ 
+++/* Convert the number of accesses to a tier. See the comment on MAX_NR_TIERS. */
+++static inline int lru_tier_from_refs(int refs)
+++{
+++	VM_BUG_ON(refs > BIT(LRU_REFS_WIDTH));
+++
+++	return order_base_2(refs + 1);
+++}
+++
++ /* The youngest and the second youngest generations are counted as active. */
++ static inline bool lru_gen_is_active(struct lruvec *lruvec, int gen)
++ {
++@@ -226,6 +234,8 @@ static inline bool lru_gen_del_page(stru
++ 		gen = ((new_flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
++ 
++ 		new_flags &= ~LRU_GEN_MASK;
+++		if ((new_flags & LRU_REFS_FLAGS) != LRU_REFS_FLAGS)
+++			new_flags &= ~(LRU_REFS_MASK | LRU_REFS_FLAGS);
++ 		/* for shrink_page_list() */
++ 		if (reclaiming)
++ 			new_flags &= ~(BIT(PG_referenced) | BIT(PG_reclaim));
++--- a/include/linux/mmzone.h
+++++ b/include/linux/mmzone.h
++@@ -319,6 +319,30 @@ struct page_vma_mapped_walk;
++ #define MIN_NR_GENS		2
++ #define MAX_NR_GENS		((unsigned int)CONFIG_NR_LRU_GENS)
++ 
+++/*
+++ * Each generation is divided into multiple tiers. Tiers represent different
+++ * ranges of numbers of accesses from file descriptors, i.e.,
+++ * mark_page_accessed(). In contrast to moving between generations which
+++ * requires the lru lock, moving between tiers only involves an atomic
+++ * operation on page->flags and therefore has a negligible cost.
+++ *
+++ * The purposes of tiers are to:
+++ *   1) estimate whether pages accessed multiple times via file descriptors are
+++ *   more active than pages accessed only via page tables by separating the two
+++ *   access types into upper tiers and the base tier, and comparing refaulted %
+++ *   across all tiers.
+++ *   2) improve buffered io performance by deferring the protection of pages
+++ *   accessed multiple times until the eviction. That is the protection happens
+++ *   in the reclaim path, not the access path.
+++ *
+++ * Pages accessed N times via file descriptors belong to tier order_base_2(N).
+++ * The base tier may be marked by PageReferenced(). All upper tiers are marked
+++ * by PageReferenced() && PageWorkingset(). Additional bits from page->flags are
+++ * used to support more than one upper tier.
 ++ */
+++#define MAX_NR_TIERS		((unsigned int)CONFIG_TIERS_PER_GEN)
+++#define LRU_REFS_FLAGS		(BIT(PG_referenced) | BIT(PG_workingset))
+++
++ /* Whether to keep stats for historical generations. */
++ #ifdef CONFIG_LRU_GEN_STATS
++ #define NR_HIST_GENS		((unsigned int)CONFIG_NR_LRU_GENS)
++@@ -337,6 +361,15 @@ struct lrugen {
++ 	struct list_head lists[MAX_NR_GENS][ANON_AND_FILE][MAX_NR_ZONES];
++ 	/* the sizes of the multigenerational lru lists in pages */
++ 	unsigned long sizes[MAX_NR_GENS][ANON_AND_FILE][MAX_NR_ZONES];
+++	/* the exponential moving average of refaulted */
+++	unsigned long avg_refaulted[ANON_AND_FILE][MAX_NR_TIERS];
+++	/* the exponential moving average of protected+evicted */
+++	unsigned long avg_total[ANON_AND_FILE][MAX_NR_TIERS];
+++	/* the base tier isn't protected, hence the minus one */
+++	unsigned long protected[NR_HIST_GENS][ANON_AND_FILE][MAX_NR_TIERS - 1];
+++	/* incremented without holding the lru lock */
+++	atomic_long_t evicted[NR_HIST_GENS][ANON_AND_FILE][MAX_NR_TIERS];
+++	atomic_long_t refaulted[NR_HIST_GENS][ANON_AND_FILE][MAX_NR_TIERS];
++ 	/* whether the multigenerational lru is enabled */
++ 	bool enabled[ANON_AND_FILE];
++ };
++--- a/mm/swap.c
+++++ b/mm/swap.c
++@@ -389,6 +389,43 @@ static void __lru_cache_activate_page(st
++ 	local_unlock(&lru_pvecs.lock);
++ }
++ 
+++#ifdef CONFIG_LRU_GEN
+++static void page_inc_refs(struct page *page)
+++{
+++	unsigned long refs;
+++	unsigned long old_flags, new_flags;
 ++
-++/* Build program prologue to set up the stack and registers */
-++void build_prologue(struct jit_context *ctx)
+++	if (PageUnevictable(page))
+++		return;
+++
+++	/* see the comment on MAX_NR_TIERS */
+++	do {
+++		new_flags = old_flags = READ_ONCE(page->flags);
+++
+++		if (!(new_flags & BIT(PG_referenced))) {
+++			new_flags |= BIT(PG_referenced);
+++			continue;
+++		}
+++
+++		if (!(new_flags & BIT(PG_workingset))) {
+++			new_flags |= BIT(PG_workingset);
+++			continue;
+++		}
+++
+++		refs = new_flags & LRU_REFS_MASK;
+++		refs = min(refs + BIT(LRU_REFS_PGOFF), LRU_REFS_MASK);
+++
+++		new_flags &= ~LRU_REFS_MASK;
+++		new_flags |= refs;
+++	} while (new_flags != old_flags &&
+++		 cmpxchg(&page->flags, old_flags, new_flags) != old_flags);
+++}
+++#else
+++static void page_inc_refs(struct page *page)
 ++{
-++	const u8 *r1 = bpf2mips32[BPF_REG_1];
-++	const u8 *fp = bpf2mips32[BPF_REG_FP];
-++	int stack, saved, locals, reserved;
+++}
+++#endif /* CONFIG_LRU_GEN */
+++
++ /*
++  * Mark a page as having seen activity.
++  *
++@@ -403,6 +440,11 @@ void mark_page_accessed(struct page *pag
++ {
++ 	page = compound_head(page);
++ 
+++	if (lru_gen_enabled()) {
+++		page_inc_refs(page);
+++		return;
+++	}
+++
++ 	if (!PageReferenced(page)) {
++ 		SetPageReferenced(page);
++ 	} else if (PageUnevictable(page)) {
++--- a/mm/vmscan.c
+++++ b/mm/vmscan.c
++@@ -1145,9 +1145,11 @@ static int __remove_mapping(struct addre
++ 
++ 	if (PageSwapCache(page)) {
++ 		swp_entry_t swap = { .val = page_private(page) };
++-		mem_cgroup_swapout(page, swap);
+++
+++		/* get a shadow entry before page_memcg() is cleared */
++ 		if (reclaimed && !mapping_exiting(mapping))
++ 			shadow = workingset_eviction(page, target_memcg);
+++		mem_cgroup_swapout(page, swap);
++ 		__delete_from_swap_cache(page, swap, shadow);
++ 		xa_unlock_irq(&mapping->i_pages);
++ 		put_swap_page(page, swap);
++@@ -1410,6 +1412,11 @@ retry:
++ 		if (!sc->may_unmap && page_mapped(page))
++ 			goto keep_locked;
++ 
+++		/* lru_gen_look_around() has updated this page? */
+++		if (lru_gen_enabled() && !ignore_references &&
+++		    page_mapped(page) && PageReferenced(page))
+++			goto keep_locked;
+++
++ 		may_enter_fs = (sc->gfp_mask & __GFP_FS) ||
++ 			(PageSwapCache(page) && (sc->gfp_mask & __GFP_IO));
++ 
++@@ -2570,6 +2577,9 @@ static void prepare_scan_count(pg_data_t
++ 	unsigned long file;
++ 	struct lruvec *target_lruvec;
++ 
+++	if (lru_gen_enabled())
+++		return;
+++
++ 	target_lruvec = mem_cgroup_lruvec(sc->target_mem_cgroup, pgdat);
++ 
++ 	/*
++@@ -2910,6 +2920,17 @@ static int page_lru_gen(struct page *pag
++ 	return ((flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
++ }
++ 
+++static int page_lru_tier(struct page *page)
+++{
+++	int refs;
+++	unsigned long flags = READ_ONCE(page->flags);
+++
+++	refs = (flags & LRU_REFS_FLAGS) == LRU_REFS_FLAGS ?
+++	       ((flags & LRU_REFS_MASK) >> LRU_REFS_PGOFF) + 1 : 0;
+++
+++	return lru_tier_from_refs(refs);
+++}
+++
++ static int get_swappiness(struct mem_cgroup *memcg)
++ {
++ 	return mem_cgroup_get_nr_swap_pages(memcg) >= MIN_BATCH_SIZE ?
++@@ -3246,6 +3267,91 @@ done:
++ }
++ 
++ /******************************************************************************
+++ *                          refault feedback loop
+++ ******************************************************************************/
+++
+++/*
+++ * A feedback loop modeled after the PID controller. Currently supports the
+++ * proportional (P) and the integral (I) terms; the derivative (D) term can be
+++ * added if necessary. The setpoint (SP) is the desired position; the process
+++ * variable (PV) is the measured position. The error is the difference between
+++ * the SP and the PV. A positive error results in a positive control output
+++ * correction, which, in our case, is to allow eviction.
+++ *
+++ * The P term is refaulted % of the current generation being evicted. The I
+++ * term is the exponential moving average of refaulted % of previously evicted
+++ * generations, using the smoothing factor 1/2.
+++ *
+++ * Our goal is to maintain proportional refaulted % across all tiers.
+++ */
+++struct ctrl_pos {
+++	unsigned long refaulted;
+++	unsigned long total;
+++	int gain;
+++};
+++
+++static void read_ctrl_pos(struct lruvec *lruvec, int type, int tier, int gain,
+++			  struct ctrl_pos *pos)
+++{
+++	struct lrugen *lrugen = &lruvec->evictable;
+++	int hist = lru_hist_from_seq(lrugen->min_seq[type]);
+++
+++	pos->refaulted = lrugen->avg_refaulted[type][tier] +
+++			 atomic_long_read(&lrugen->refaulted[hist][type][tier]);
+++	pos->total = lrugen->avg_total[type][tier] +
+++		     atomic_long_read(&lrugen->evicted[hist][type][tier]);
+++	if (tier)
+++		pos->total += lrugen->protected[hist][type][tier - 1];
+++	pos->gain = gain;
+++}
+++
+++static void reset_ctrl_pos(struct lruvec *lruvec, int gen, int type)
+++{
+++	int tier;
+++	int hist = lru_hist_from_seq(gen);
+++	struct lrugen *lrugen = &lruvec->evictable;
+++	bool carryover = gen == lru_gen_from_seq(lrugen->min_seq[type]);
+++	bool clear = carryover ? NR_HIST_GENS == 1 : NR_HIST_GENS > 1;
+++
+++	if (!carryover && !clear)
+++		return;
+++
+++	for (tier = 0; tier < MAX_NR_TIERS; tier++) {
+++		if (carryover) {
+++			unsigned long sum;
+++
+++			sum = lrugen->avg_refaulted[type][tier] +
+++			      atomic_long_read(&lrugen->refaulted[hist][type][tier]);
+++			WRITE_ONCE(lrugen->avg_refaulted[type][tier], sum / 2);
+++
+++			sum = lrugen->avg_total[type][tier] +
+++			      atomic_long_read(&lrugen->evicted[hist][type][tier]);
+++			if (tier)
+++				sum += lrugen->protected[hist][type][tier - 1];
+++			WRITE_ONCE(lrugen->avg_total[type][tier], sum / 2);
+++		}
+++
+++		if (clear) {
+++			atomic_long_set(&lrugen->refaulted[hist][type][tier], 0);
+++			atomic_long_set(&lrugen->evicted[hist][type][tier], 0);
+++			if (tier)
+++				WRITE_ONCE(lrugen->protected[hist][type][tier - 1], 0);
+++		}
+++	}
+++}
 ++
+++static bool positive_ctrl_err(struct ctrl_pos *sp, struct ctrl_pos *pv)
+++{
 ++	/*
-++	 * The first two instructions initialize TCC in the reserved (for us)
-++	 * 16-byte area in the parent's stack frame. On a tail call, the
-++	 * calling function jumps into the prologue after these instructions.
+++	 * Allow eviction if the PV has a limited number of refaulted pages or a
+++	 * lower refaulted % than the SP.
 ++	 */
-++	emit(ctx, ori, MIPS_R_T9, MIPS_R_ZERO,
-++	     min(MAX_TAIL_CALL_CNT + 1, 0xffff));
-++	emit(ctx, sw, MIPS_R_T9, 0, MIPS_R_SP);
+++	return pv->refaulted < MIN_BATCH_SIZE ||
+++	       pv->refaulted * max(sp->total, 1UL) * sp->gain <=
+++	       sp->refaulted * max(pv->total, 1UL) * pv->gain;
+++}
+++
+++/******************************************************************************
++  *                          the aging
++  ******************************************************************************/
++ 
++@@ -3265,6 +3371,7 @@ static int page_update_gen(struct page *
++ 
++ 		new_flags &= ~LRU_GEN_MASK;
++ 		new_flags |= (gen + 1UL) << LRU_GEN_PGOFF;
+++		new_flags &= ~(LRU_REFS_MASK | LRU_REFS_FLAGS);
++ 	} while (new_flags != old_flags &&
++ 		 cmpxchg(&page->flags, old_flags, new_flags) != old_flags);
++ 
++@@ -3296,6 +3403,7 @@ static void page_inc_gen(struct page *pa
++ 
++ 		new_flags &= ~LRU_GEN_MASK;
++ 		new_flags |= (new_gen + 1UL) << LRU_GEN_PGOFF;
+++		new_flags &= ~(LRU_REFS_MASK | LRU_REFS_FLAGS);
++ 		/* for end_page_writeback() */
++ 		if (reclaiming)
++ 			new_flags |= BIT(PG_reclaim);
++@@ -3787,6 +3895,7 @@ static bool inc_min_seq(struct lruvec *l
++ 		}
++ 	}
++ 
+++	reset_ctrl_pos(lruvec, gen, type);
++ 	WRITE_ONCE(lrugen->min_seq[type], lrugen->min_seq[type] + 1);
++ 
++ 	return true;
++@@ -3824,6 +3933,8 @@ next:
++ 		if (min_seq[type] == lrugen->min_seq[type])
++ 			continue;
++ 
+++		gen = lru_gen_from_seq(lrugen->min_seq[type]);
+++		reset_ctrl_pos(lruvec, gen, type);
++ 		WRITE_ONCE(lrugen->min_seq[type], min_seq[type]);
++ 		success = true;
++ 	}
++@@ -3885,6 +3996,9 @@ static void inc_max_seq(struct lruvec *l
++ 		}
++ 	}
++ 
+++	for (type = 0; type < ANON_AND_FILE; type++)
+++		reset_ctrl_pos(lruvec, gen, type);
+++
++ 	WRITE_ONCE(lrugen->timestamps[gen], jiffies);
++ 	/* make sure all preceding modifications appear first */
++ 	smp_store_release(&lrugen->max_seq, lrugen->max_seq + 1);
++@@ -4166,6 +4280,433 @@ void lru_gen_look_around(struct page_vma
++ }
++ 
++ /******************************************************************************
+++ *                          the eviction
+++ ******************************************************************************/
+++
+++static bool sort_page(struct page *page, struct lruvec *lruvec, int tier_idx)
+++{
+++	bool success;
+++	int gen = page_lru_gen(page);
+++	int type = page_is_file_lru(page);
+++	int zone = page_zonenum(page);
+++	int tier = page_lru_tier(page);
+++	int delta = thp_nr_pages(page);
+++	struct lrugen *lrugen = &lruvec->evictable;
+++
+++	VM_BUG_ON_PAGE(gen >= MAX_NR_GENS, page);
+++
+++	/* an mlocked page? */
+++	if (!page_evictable(page)) {
+++		success = lru_gen_del_page(page, lruvec, true);
+++		VM_BUG_ON_PAGE(!success, page);
+++		SetPageUnevictable(page);
+++		add_page_to_lru_list(page, lruvec);
+++		__count_vm_events(UNEVICTABLE_PGCULLED, delta);
+++		return true;
+++	}
+++
+++	/* a lazy-free page that has been written into? */
+++	if (type && PageDirty(page) && PageAnon(page)) {
+++		success = lru_gen_del_page(page, lruvec, true);
+++		VM_BUG_ON_PAGE(!success, page);
+++		SetPageSwapBacked(page);
+++		add_page_to_lru_list_tail(page, lruvec);
+++		return true;
+++	}
+++
+++	/* page_update_gen() has updated this page? */
+++	if (gen != lru_gen_from_seq(lrugen->min_seq[type])) {
+++		list_move(&page->lru, &lrugen->lists[gen][type][zone]);
+++		return true;
+++	}
+++
+++	/* protect this page if its tier has a higher refaulted % */
+++	if (tier > tier_idx) {
+++		int hist = lru_hist_from_seq(gen);
+++
+++		page_inc_gen(page, lruvec, false);
+++		WRITE_ONCE(lrugen->protected[hist][type][tier - 1],
+++			   lrugen->protected[hist][type][tier - 1] + delta);
+++		__mod_lruvec_state(lruvec, WORKINGSET_ACTIVATE_BASE + type, delta);
+++		return true;
+++	}
+++
+++	/* mark this page for reclaim if it's pending writeback */
+++	if (PageWriteback(page) || (type && PageDirty(page))) {
+++		page_inc_gen(page, lruvec, true);
+++		return true;
+++	}
+++
+++	return false;
+++}
+++
+++static bool isolate_page(struct page *page, struct lruvec *lruvec, struct scan_control *sc)
+++{
+++	bool success;
+++
+++	if (!sc->may_unmap && page_mapped(page))
+++		return false;
+++
+++	if (!(sc->may_writepage && (sc->gfp_mask & __GFP_IO)) &&
+++	    (PageDirty(page) || (PageAnon(page) && !PageSwapCache(page))))
+++		return false;
+++
+++	if (!get_page_unless_zero(page))
+++		return false;
+++
+++	if (!TestClearPageLRU(page)) {
+++		put_page(page);
+++		return false;
+++	}
+++
+++	success = lru_gen_del_page(page, lruvec, true);
+++	VM_BUG_ON_PAGE(!success, page);
+++
+++	return true;
+++}
+++
+++static int scan_pages(struct lruvec *lruvec, struct scan_control *sc,
+++		      int type, int tier, struct list_head *list)
+++{
+++	int gen, zone;
+++	enum vm_event_item item;
+++	int sorted = 0;
+++	int scanned = 0;
+++	int isolated = 0;
+++	int remaining = MAX_BATCH_SIZE;
+++	struct lrugen *lrugen = &lruvec->evictable;
+++	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+++
+++	VM_BUG_ON(!list_empty(list));
+++
+++	if (get_nr_gens(lruvec, type) == MIN_NR_GENS)
+++		return 0;
+++
+++	gen = lru_gen_from_seq(lrugen->min_seq[type]);
+++
+++	for (zone = sc->reclaim_idx; zone >= 0; zone--) {
+++		LIST_HEAD(moved);
+++		int skipped = 0;
+++		struct list_head *head = &lrugen->lists[gen][type][zone];
+++
+++		while (!list_empty(head)) {
+++			struct page *page = lru_to_page(head);
+++			int delta = thp_nr_pages(page);
+++
+++			VM_BUG_ON_PAGE(PageTail(page), page);
+++			VM_BUG_ON_PAGE(PageUnevictable(page), page);
+++			VM_BUG_ON_PAGE(PageActive(page), page);
+++			VM_BUG_ON_PAGE(page_is_file_lru(page) != type, page);
+++			VM_BUG_ON_PAGE(page_zonenum(page) != zone, page);
+++
+++			prefetchw_prev_lru_page(page, head, flags);
+++
+++			scanned += delta;
+++
+++			if (sort_page(page, lruvec, tier))
+++				sorted += delta;
+++			else if (isolate_page(page, lruvec, sc)) {
+++				list_add(&page->lru, list);
+++				isolated += delta;
+++			} else {
+++				list_move(&page->lru, &moved);
+++				skipped += delta;
+++			}
+++
+++			if (!--remaining || max(isolated, skipped) >= MIN_BATCH_SIZE)
+++				break;
+++		}
+++
+++		if (skipped) {
+++			list_splice(&moved, head);
+++			__count_zid_vm_events(PGSCAN_SKIP, zone, skipped);
+++		}
+++
+++		if (!remaining || isolated >= MIN_BATCH_SIZE)
+++			break;
+++	}
+++
+++	item = current_is_kswapd() ? PGSCAN_KSWAPD : PGSCAN_DIRECT;
+++	if (!cgroup_reclaim(sc)) {
+++		__count_vm_events(item, isolated);
+++		__count_vm_events(PGREFILL, sorted);
+++	}
+++	__count_memcg_events(memcg, item, isolated);
+++	__count_memcg_events(memcg, PGREFILL, sorted);
+++	__count_vm_events(PGSCAN_ANON + type, isolated);
 ++
 ++	/*
-++	 * Register eBPF R1 contains the 32-bit context pointer argument.
-++	 * A 32-bit argument is always passed in MIPS register a0, regardless
-++	 * of CPU endianness. Initialize R1 accordingly and zero-extend.
+++	 * We may have trouble finding eligible pages due to reclaim_idx,
+++	 * may_unmap and may_writepage. Check `remaining` to make sure we won't
+++	 * be stuck if we aren't making enough progress.
 ++	 */
-++#ifdef __BIG_ENDIAN
-++	emit(ctx, move, lo(r1), MIPS_R_A0);
-++#endif
+++	return isolated || !remaining ? scanned : 0;
+++}
 ++
-++	/* === Entry-point for tail calls === */
+++static int get_tier_idx(struct lruvec *lruvec, int type)
+++{
+++	int tier;
+++	struct ctrl_pos sp, pv;
 ++
-++	/* Zero-extend the 32-bit argument */
-++	emit(ctx, move, hi(r1), MIPS_R_ZERO);
+++	/*
+++	 * Ideally we don't want to evict upper tiers that have higher refaulted
+++	 * %. However, we need to leave a margin for the fluctuation in
+++	 * refaulted %. So we use a larger gain factor to make sure upper tiers
+++	 * are indeed more active. We choose 2 because the lowest upper tier
+++	 * would have twice of refaulted % of the base tier, according to their
+++	 * numbers of accesses.
+++	 */
+++	read_ctrl_pos(lruvec, type, 0, 1, &sp);
+++	for (tier = 1; tier < MAX_NR_TIERS; tier++) {
+++		read_ctrl_pos(lruvec, type, tier, 2, &pv);
+++		if (!positive_ctrl_err(&sp, &pv))
+++			break;
+++	}
 ++
-++	/* If the eBPF frame pointer was accessed it must be saved */
-++	if (ctx->accessed & BIT(BPF_REG_FP))
-++		clobber_reg64(ctx, fp);
+++	return tier - 1;
+++}
 ++
-++	/* Compute the stack space needed for callee-saved registers */
-++	saved = hweight32(ctx->clobbered & JIT_CALLEE_REGS) * sizeof(u32);
-++	saved = ALIGN(saved, MIPS_STACK_ALIGNMENT);
+++static int get_type_to_scan(struct lruvec *lruvec, int swappiness, int *tier_idx)
+++{
+++	int type, tier;
+++	struct ctrl_pos sp, pv;
+++	int gain[ANON_AND_FILE] = { swappiness, 200 - swappiness };
 ++
-++	/* Stack space used by eBPF program local data */
-++	locals = ALIGN(ctx->program->aux->stack_depth, MIPS_STACK_ALIGNMENT);
+++	/*
+++	 * Compare refaulted % between the base tiers of anon and file to
+++	 * determine which type to evict. Also need to compare refaulted % of
+++	 * the upper tiers of the selected type with that of the base tier of
+++	 * the other type to determine which tier of the selected type to evict.
+++	 */
+++	read_ctrl_pos(lruvec, 0, 0, gain[0], &sp);
+++	read_ctrl_pos(lruvec, 1, 0, gain[1], &pv);
+++	type = positive_ctrl_err(&sp, &pv);
+++
+++	read_ctrl_pos(lruvec, !type, 0, gain[!type], &sp);
+++	for (tier = 1; tier < MAX_NR_TIERS; tier++) {
+++		read_ctrl_pos(lruvec, type, tier, gain[type], &pv);
+++		if (!positive_ctrl_err(&sp, &pv))
+++			break;
+++	}
+++
+++	*tier_idx = tier - 1;
+++
+++	return type;
+++}
+++
+++static int isolate_pages(struct lruvec *lruvec, struct scan_control *sc, int swappiness,
+++			 int *type_scanned, struct list_head *list)
+++{
+++	int i;
+++	int type;
+++	int scanned;
+++	int tier = -1;
+++	DEFINE_MIN_SEQ(lruvec);
+++
+++	VM_BUG_ON(!seq_is_valid(lruvec));
 ++
 ++	/*
-++	 * If we are emitting function calls, reserve extra stack space for
-++	 * caller-saved registers and function arguments passed on the stack.
-++	 * The required space is computed automatically during resource
-++	 * usage discovery (pass 1).
+++	 * Try to select a type based on generations and swappiness, and if that
+++	 * fails, fall back to get_type_to_scan(). When anon and file are both
+++	 * available from the same generation, swappiness 200 is interpreted as
+++	 * anon first and swappiness 1 is interpreted as file first.
 ++	 */
-++	reserved = ctx->stack_used;
+++	if (!swappiness)
+++		type = 1;
+++	else if (min_seq[0] < min_seq[1])
+++		type = 0;
+++	else if (swappiness == 1)
+++		type = 1;
+++	else if (swappiness == 200)
+++		type = 0;
+++	else
+++		type = get_type_to_scan(lruvec, swappiness, &tier);
 ++
-++	/* Allocate the stack frame */
-++	stack = ALIGN(saved + locals + reserved, MIPS_STACK_ALIGNMENT);
-++	emit(ctx, addiu, MIPS_R_SP, MIPS_R_SP, -stack);
+++	for (i = !swappiness; i < ANON_AND_FILE; i++) {
+++		if (tier < 0)
+++			tier = get_tier_idx(lruvec, type);
 ++
-++	/* Store callee-saved registers on stack */
-++	push_regs(ctx, ctx->clobbered & JIT_CALLEE_REGS, 0, stack - saved);
+++		scanned = scan_pages(lruvec, sc, type, tier, list);
+++		if (scanned)
+++			break;
 ++
-++	/* Initialize the eBPF frame pointer if accessed */
-++	if (ctx->accessed & BIT(BPF_REG_FP))
-++		emit(ctx, addiu, lo(fp), MIPS_R_SP, stack - saved);
+++		type = !type;
+++		tier = -1;
+++	}
 ++
-++	ctx->saved_size = saved;
-++	ctx->stack_size = stack;
+++	*type_scanned = type;
+++
+++	return scanned;
 ++}
 ++
-++/* Build the program epilogue to restore the stack and registers */
-++void build_epilogue(struct jit_context *ctx, int dest_reg)
+++/* Main function used by the foreground, the background and the user-triggered eviction. */
+++static int evict_pages(struct lruvec *lruvec, struct scan_control *sc, int swappiness)
 ++{
-++	/* Restore callee-saved registers from stack */
-++	pop_regs(ctx, ctx->clobbered & JIT_CALLEE_REGS, 0,
-++		 ctx->stack_size - ctx->saved_size);
+++	int type;
+++	int scanned;
+++	int reclaimed;
+++	LIST_HEAD(list);
+++	struct page *page;
+++	enum vm_event_item item;
+++	struct reclaim_stat stat;
+++	struct mm_walk_args *args;
+++	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+++	struct pglist_data *pgdat = lruvec_pgdat(lruvec);
+++
+++	spin_lock_irq(&lruvec->lru_lock);
+++
+++	scanned = isolate_pages(lruvec, sc, swappiness, &type, &list);
+++
+++	if (try_to_inc_min_seq(lruvec, swappiness))
+++		scanned++;
+++
+++	if (get_nr_gens(lruvec, 1) == MIN_NR_GENS)
+++		scanned = 0;
+++
+++	spin_unlock_irq(&lruvec->lru_lock);
+++
+++	if (list_empty(&list))
+++		return scanned;
+++
+++	reclaimed = shrink_page_list(&list, pgdat, sc, &stat, false);
 ++	/*
-++	 * A 32-bit return value is always passed in MIPS register v0,
-++	 * but on big-endian targets the low part of R0 is mapped to v1.
+++	 * We need to prevent rejected pages from being added back to the same
+++	 * lists they were isolated from. Otherwise we may risk looping on them
+++	 * forever.
 ++	 */
-++#ifdef __BIG_ENDIAN
-++	emit(ctx, move, MIPS_R_V0, MIPS_R_V1);
-++#endif
+++	list_for_each_entry(page, &list, lru) {
+++		if (!PageReclaim(page) || !(PageDirty(page) || PageWriteback(page)))
+++			SetPageActive(page);
 ++
-++	/* Jump to the return address and adjust the stack pointer */
-++	emit(ctx, jr, dest_reg);
-++	emit(ctx, addiu, MIPS_R_SP, MIPS_R_SP, ctx->stack_size);
+++		ClearPageReferenced(page);
+++		ClearPageWorkingset(page);
+++	}
+++
+++	spin_lock_irq(&lruvec->lru_lock);
+++
+++	move_pages_to_lru(lruvec, &list);
+++
+++	args = current->reclaim_state ? current->reclaim_state->mm_walk_args : NULL;
+++	if (args && args->batch_size)
+++		reset_batch_size(lruvec, args);
+++
+++	item = current_is_kswapd() ? PGSTEAL_KSWAPD : PGSTEAL_DIRECT;
+++	if (!cgroup_reclaim(sc))
+++		__count_vm_events(item, reclaimed);
+++	__count_memcg_events(memcg, item, reclaimed);
+++	__count_vm_events(PGSTEAL_ANON + type, reclaimed);
+++
+++	spin_unlock_irq(&lruvec->lru_lock);
+++
+++	mem_cgroup_uncharge_list(&list);
+++	free_unref_page_list(&list);
+++
+++	sc->nr_reclaimed += reclaimed;
+++
+++	return scanned;
 ++}
 ++
-++/* Build one eBPF instruction */
-++int build_insn(const struct bpf_insn *insn, struct jit_context *ctx)
+++static long get_nr_to_scan(struct lruvec *lruvec, struct scan_control *sc, int swappiness)
 ++{
-++	const u8 *dst = bpf2mips32[insn->dst_reg];
-++	const u8 *src = bpf2mips32[insn->src_reg];
-++	const u8 *tmp = bpf2mips32[JIT_REG_TMP];
-++	u8 code = insn->code;
-++	s16 off = insn->off;
-++	s32 imm = insn->imm;
-++	s32 val, rel;
-++	u8 alu, jmp;
+++	bool low;
+++	long nr_to_scan;
+++	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+++	int priority = sc->priority;
+++	DEFINE_MAX_SEQ(lruvec);
+++	DEFINE_MIN_SEQ(lruvec);
+++
+++	if (mem_cgroup_below_min(memcg) ||
+++	    (mem_cgroup_below_low(memcg) && !sc->memcg_low_reclaim))
+++		return 0;
 ++
-++	switch (code) {
-++	/* ALU operations */
-++	/* dst = imm */
-++	case BPF_ALU | BPF_MOV | BPF_K:
-++		emit_mov_i(ctx, lo(dst), imm);
-++		emit_zext_ver(ctx, dst);
-++		break;
-++	/* dst = src */
-++	case BPF_ALU | BPF_MOV | BPF_X:
-++		if (imm == 1) {
-++			/* Special mov32 for zext */
-++			emit_mov_i(ctx, hi(dst), 0);
-++		} else {
-++			emit_mov_r(ctx, lo(dst), lo(src));
-++			emit_zext_ver(ctx, dst);
-++		}
-++		break;
-++	/* dst = -dst */
-++	case BPF_ALU | BPF_NEG:
-++		emit_alu_i(ctx, lo(dst), 0, BPF_NEG);
-++		emit_zext_ver(ctx, dst);
-++		break;
-++	/* dst = dst & imm */
-++	/* dst = dst | imm */
-++	/* dst = dst ^ imm */
-++	/* dst = dst << imm */
-++	/* dst = dst >> imm */
-++	/* dst = dst >> imm (arithmetic) */
-++	/* dst = dst + imm */
-++	/* dst = dst - imm */
-++	/* dst = dst * imm */
-++	/* dst = dst / imm */
-++	/* dst = dst % imm */
-++	case BPF_ALU | BPF_OR | BPF_K:
-++	case BPF_ALU | BPF_AND | BPF_K:
-++	case BPF_ALU | BPF_XOR | BPF_K:
-++	case BPF_ALU | BPF_LSH | BPF_K:
-++	case BPF_ALU | BPF_RSH | BPF_K:
-++	case BPF_ALU | BPF_ARSH | BPF_K:
-++	case BPF_ALU | BPF_ADD | BPF_K:
-++	case BPF_ALU | BPF_SUB | BPF_K:
-++	case BPF_ALU | BPF_MUL | BPF_K:
-++	case BPF_ALU | BPF_DIV | BPF_K:
-++	case BPF_ALU | BPF_MOD | BPF_K:
-++		if (!valid_alu_i(BPF_OP(code), imm)) {
-++			emit_mov_i(ctx, MIPS_R_T6, imm);
-++			emit_alu_r(ctx, lo(dst), MIPS_R_T6, BPF_OP(code));
-++		} else if (rewrite_alu_i(BPF_OP(code), imm, &alu, &val)) {
-++			emit_alu_i(ctx, lo(dst), val, alu);
-++		}
-++		emit_zext_ver(ctx, dst);
-++		break;
-++	/* dst = dst & src */
-++	/* dst = dst | src */
-++	/* dst = dst ^ src */
-++	/* dst = dst << src */
-++	/* dst = dst >> src */
-++	/* dst = dst >> src (arithmetic) */
-++	/* dst = dst + src */
-++	/* dst = dst - src */
-++	/* dst = dst * src */
-++	/* dst = dst / src */
-++	/* dst = dst % src */
-++	case BPF_ALU | BPF_AND | BPF_X:
-++	case BPF_ALU | BPF_OR | BPF_X:
-++	case BPF_ALU | BPF_XOR | BPF_X:
-++	case BPF_ALU | BPF_LSH | BPF_X:
-++	case BPF_ALU | BPF_RSH | BPF_X:
-++	case BPF_ALU | BPF_ARSH | BPF_X:
-++	case BPF_ALU | BPF_ADD | BPF_X:
-++	case BPF_ALU | BPF_SUB | BPF_X:
-++	case BPF_ALU | BPF_MUL | BPF_X:
-++	case BPF_ALU | BPF_DIV | BPF_X:
-++	case BPF_ALU | BPF_MOD | BPF_X:
-++		emit_alu_r(ctx, lo(dst), lo(src), BPF_OP(code));
-++		emit_zext_ver(ctx, dst);
-++		break;
-++	/* dst = imm (64-bit) */
-++	case BPF_ALU64 | BPF_MOV | BPF_K:
-++		emit_mov_se_i64(ctx, dst, imm);
-++		break;
-++	/* dst = src (64-bit) */
-++	case BPF_ALU64 | BPF_MOV | BPF_X:
-++		emit_mov_r(ctx, lo(dst), lo(src));
-++		emit_mov_r(ctx, hi(dst), hi(src));
-++		break;
-++	/* dst = -dst (64-bit) */
-++	case BPF_ALU64 | BPF_NEG:
-++		emit_neg_i64(ctx, dst);
-++		break;
-++	/* dst = dst & imm (64-bit) */
-++	case BPF_ALU64 | BPF_AND | BPF_K:
-++		emit_alu_i64(ctx, dst, imm, BPF_OP(code));
-++		break;
-++	/* dst = dst | imm (64-bit) */
-++	/* dst = dst ^ imm (64-bit) */
-++	/* dst = dst + imm (64-bit) */
-++	/* dst = dst - imm (64-bit) */
-++	case BPF_ALU64 | BPF_OR | BPF_K:
-++	case BPF_ALU64 | BPF_XOR | BPF_K:
-++	case BPF_ALU64 | BPF_ADD | BPF_K:
-++	case BPF_ALU64 | BPF_SUB | BPF_K:
-++		if (imm)
-++			emit_alu_i64(ctx, dst, imm, BPF_OP(code));
-++		break;
-++	/* dst = dst << imm (64-bit) */
-++	/* dst = dst >> imm (64-bit) */
-++	/* dst = dst >> imm (64-bit, arithmetic) */
-++	case BPF_ALU64 | BPF_LSH | BPF_K:
-++	case BPF_ALU64 | BPF_RSH | BPF_K:
-++	case BPF_ALU64 | BPF_ARSH | BPF_K:
-++		if (imm)
-++			emit_shift_i64(ctx, dst, imm, BPF_OP(code));
-++		break;
-++	/* dst = dst * imm (64-bit) */
-++	case BPF_ALU64 | BPF_MUL | BPF_K:
-++		emit_mul_i64(ctx, dst, imm);
-++		break;
-++	/* dst = dst / imm (64-bit) */
-++	/* dst = dst % imm (64-bit) */
-++	case BPF_ALU64 | BPF_DIV | BPF_K:
-++	case BPF_ALU64 | BPF_MOD | BPF_K:
-++		/*
-++		 * Sign-extend the immediate value into a temporary register,
-++		 * and then do the operation on this register.
-++		 */
-++		emit_mov_se_i64(ctx, tmp, imm);
-++		emit_divmod_r64(ctx, dst, tmp, BPF_OP(code));
-++		break;
-++	/* dst = dst & src (64-bit) */
-++	/* dst = dst | src (64-bit) */
-++	/* dst = dst ^ src (64-bit) */
-++	/* dst = dst + src (64-bit) */
-++	/* dst = dst - src (64-bit) */
-++	case BPF_ALU64 | BPF_AND | BPF_X:
-++	case BPF_ALU64 | BPF_OR | BPF_X:
-++	case BPF_ALU64 | BPF_XOR | BPF_X:
-++	case BPF_ALU64 | BPF_ADD | BPF_X:
-++	case BPF_ALU64 | BPF_SUB | BPF_X:
-++		emit_alu_r64(ctx, dst, src, BPF_OP(code));
-++		break;
-++	/* dst = dst << src (64-bit) */
-++	/* dst = dst >> src (64-bit) */
-++	/* dst = dst >> src (64-bit, arithmetic) */
-++	case BPF_ALU64 | BPF_LSH | BPF_X:
-++	case BPF_ALU64 | BPF_RSH | BPF_X:
-++	case BPF_ALU64 | BPF_ARSH | BPF_X:
-++		emit_shift_r64(ctx, dst, lo(src), BPF_OP(code));
-++		break;
-++	/* dst = dst * src (64-bit) */
-++	case BPF_ALU64 | BPF_MUL | BPF_X:
-++		emit_mul_r64(ctx, dst, src);
-++		break;
-++	/* dst = dst / src (64-bit) */
-++	/* dst = dst % src (64-bit) */
-++	case BPF_ALU64 | BPF_DIV | BPF_X:
-++	case BPF_ALU64 | BPF_MOD | BPF_X:
-++		emit_divmod_r64(ctx, dst, src, BPF_OP(code));
-++		break;
-++	/* dst = htole(dst) */
-++	/* dst = htobe(dst) */
-++	case BPF_ALU | BPF_END | BPF_FROM_LE:
-++	case BPF_ALU | BPF_END | BPF_FROM_BE:
-++		if (BPF_SRC(code) ==
-++#ifdef __BIG_ENDIAN
-++		    BPF_FROM_LE
-++#else
-++		    BPF_FROM_BE
-++#endif
-++		    )
-++			emit_bswap_r64(ctx, dst, imm);
+++	if (sc->nr_reclaimed >= sc->nr_to_reclaim) {
+++		priority = DEF_PRIORITY;
+++		sc->force_deactivate = 0;
+++	}
+++
+++	nr_to_scan = get_nr_evictable(lruvec, sc, swappiness, max_seq, min_seq, &low);
+++	if (!nr_to_scan)
+++		return 0;
+++
+++	nr_to_scan >>= priority;
+++
+++	if (!mem_cgroup_online(memcg))
+++		nr_to_scan++;
+++
+++	if (!nr_to_scan)
+++		return 0;
+++
+++	if (current_is_kswapd()) {
+++		/* leave the work to lru_gen_age_node() */
+++		if (max_seq - min_seq[1] < MIN_NR_GENS)
+++			return 0;
+++
+++		if (!low)
+++			sc->force_deactivate = 0;
+++
+++		return nr_to_scan;
+++	}
+++
+++	if (max_seq - min_seq[1] >= MIN_NR_GENS)
+++		return nr_to_scan;
+++
+++	/* move onto slab and other memcgs if we haven't tried them all */
+++	if (!sc->force_deactivate) {
+++		sc->skipped_deactivate = 1;
+++		return 0;
+++	}
+++
+++	return try_to_inc_max_seq(lruvec, sc, swappiness, max_seq, true) ? nr_to_scan : 0;
+++}
+++
+++static void lru_gen_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
+++{
+++	struct blk_plug plug;
+++	long scanned = 0;
+++	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+++	struct pglist_data *pgdat = lruvec_pgdat(lruvec);
+++
+++	lru_add_drain();
+++
+++	if (current_is_kswapd())
+++		current->reclaim_state->mm_walk_args = &pgdat->mm_walk_args;
+++
+++	blk_start_plug(&plug);
+++
+++	while (true) {
+++		int delta;
+++		int swappiness;
+++		long nr_to_scan;
+++
+++		if (sc->may_swap)
+++			swappiness = get_swappiness(memcg);
+++		else if (!cgroup_reclaim(sc) && get_swappiness(memcg))
+++			swappiness = 1;
 ++		else
-++			emit_trunc_r64(ctx, dst, imm);
-++		break;
-++	/* dst = imm64 */
-++	case BPF_LD | BPF_IMM | BPF_DW:
-++		emit_mov_i(ctx, lo(dst), imm);
-++		emit_mov_i(ctx, hi(dst), insn[1].imm);
-++		return 1;
-++	/* LDX: dst = *(size *)(src + off) */
-++	case BPF_LDX | BPF_MEM | BPF_W:
-++	case BPF_LDX | BPF_MEM | BPF_H:
-++	case BPF_LDX | BPF_MEM | BPF_B:
-++	case BPF_LDX | BPF_MEM | BPF_DW:
-++		emit_ldx(ctx, dst, lo(src), off, BPF_SIZE(code));
-++		break;
-++	/* ST: *(size *)(dst + off) = imm */
-++	case BPF_ST | BPF_MEM | BPF_W:
-++	case BPF_ST | BPF_MEM | BPF_H:
-++	case BPF_ST | BPF_MEM | BPF_B:
-++	case BPF_ST | BPF_MEM | BPF_DW:
-++		switch (BPF_SIZE(code)) {
-++		case BPF_DW:
-++			/* Sign-extend immediate value into temporary reg */
-++			emit_mov_se_i64(ctx, tmp, imm);
-++			break;
-++		case BPF_W:
-++		case BPF_H:
-++		case BPF_B:
-++			emit_mov_i(ctx, lo(tmp), imm);
-++			break;
-++		}
-++		emit_stx(ctx, lo(dst), tmp, off, BPF_SIZE(code));
-++		break;
-++	/* STX: *(size *)(dst + off) = src */
-++	case BPF_STX | BPF_MEM | BPF_W:
-++	case BPF_STX | BPF_MEM | BPF_H:
-++	case BPF_STX | BPF_MEM | BPF_B:
-++	case BPF_STX | BPF_MEM | BPF_DW:
-++		emit_stx(ctx, lo(dst), src, off, BPF_SIZE(code));
-++		break;
-++	/* Speculation barrier */
-++	case BPF_ST | BPF_NOSPEC:
-++		break;
-++	/* Atomics */
-++	case BPF_STX | BPF_XADD | BPF_W:
-++		switch (imm) {
-++		case BPF_ADD:
-++		case BPF_AND:
-++		case BPF_OR:
-++		case BPF_XOR:
-++			if (cpu_has_llsc)
-++				emit_atomic_r(ctx, lo(dst), lo(src), off, imm);
-++			else /* Non-ll/sc fallback */
-++				emit_atomic_r32(ctx, lo(dst), lo(src),
-++						off, imm);
-++			break;
-++		default:
-++			goto notyet;
-++		}
-++		break;
-++	/* Atomics (64-bit) */
-++	case BPF_STX | BPF_XADD | BPF_DW:
-++		switch (imm) {
-++		case BPF_ADD:
-++		case BPF_AND:
-++		case BPF_OR:
-++		case BPF_XOR:
-++			emit_atomic_r64(ctx, lo(dst), src, off, imm);
-++			break;
-++		default:
-++			goto notyet;
-++		}
-++		break;
-++	/* PC += off if dst == src */
-++	/* PC += off if dst != src */
-++	/* PC += off if dst & src */
-++	/* PC += off if dst > src */
-++	/* PC += off if dst >= src */
-++	/* PC += off if dst < src */
-++	/* PC += off if dst <= src */
-++	/* PC += off if dst > src (signed) */
-++	/* PC += off if dst >= src (signed) */
-++	/* PC += off if dst < src (signed) */
-++	/* PC += off if dst <= src (signed) */
-++	case BPF_JMP32 | BPF_JEQ | BPF_X:
-++	case BPF_JMP32 | BPF_JNE | BPF_X:
-++	case BPF_JMP32 | BPF_JSET | BPF_X:
-++	case BPF_JMP32 | BPF_JGT | BPF_X:
-++	case BPF_JMP32 | BPF_JGE | BPF_X:
-++	case BPF_JMP32 | BPF_JLT | BPF_X:
-++	case BPF_JMP32 | BPF_JLE | BPF_X:
-++	case BPF_JMP32 | BPF_JSGT | BPF_X:
-++	case BPF_JMP32 | BPF_JSGE | BPF_X:
-++	case BPF_JMP32 | BPF_JSLT | BPF_X:
-++	case BPF_JMP32 | BPF_JSLE | BPF_X:
-++		if (off == 0)
-++			break;
-++		setup_jmp_r(ctx, dst == src, BPF_OP(code), off, &jmp, &rel);
-++		emit_jmp_r(ctx, lo(dst), lo(src), rel, jmp);
-++		if (finish_jmp(ctx, jmp, off) < 0)
-++			goto toofar;
-++		break;
-++	/* PC += off if dst == imm */
-++	/* PC += off if dst != imm */
-++	/* PC += off if dst & imm */
-++	/* PC += off if dst > imm */
-++	/* PC += off if dst >= imm */
-++	/* PC += off if dst < imm */
-++	/* PC += off if dst <= imm */
-++	/* PC += off if dst > imm (signed) */
-++	/* PC += off if dst >= imm (signed) */
-++	/* PC += off if dst < imm (signed) */
-++	/* PC += off if dst <= imm (signed) */
-++	case BPF_JMP32 | BPF_JEQ | BPF_K:
-++	case BPF_JMP32 | BPF_JNE | BPF_K:
-++	case BPF_JMP32 | BPF_JSET | BPF_K:
-++	case BPF_JMP32 | BPF_JGT | BPF_K:
-++	case BPF_JMP32 | BPF_JGE | BPF_K:
-++	case BPF_JMP32 | BPF_JLT | BPF_K:
-++	case BPF_JMP32 | BPF_JLE | BPF_K:
-++	case BPF_JMP32 | BPF_JSGT | BPF_K:
-++	case BPF_JMP32 | BPF_JSGE | BPF_K:
-++	case BPF_JMP32 | BPF_JSLT | BPF_K:
-++	case BPF_JMP32 | BPF_JSLE | BPF_K:
-++		if (off == 0)
-++			break;
-++		setup_jmp_i(ctx, imm, 32, BPF_OP(code), off, &jmp, &rel);
-++		if (valid_jmp_i(jmp, imm)) {
-++			emit_jmp_i(ctx, lo(dst), imm, rel, jmp);
-++		} else {
-++			/* Move large immediate to register */
-++			emit_mov_i(ctx, MIPS_R_T6, imm);
-++			emit_jmp_r(ctx, lo(dst), MIPS_R_T6, rel, jmp);
-++		}
-++		if (finish_jmp(ctx, jmp, off) < 0)
-++			goto toofar;
-++		break;
-++	/* PC += off if dst == src */
-++	/* PC += off if dst != src */
-++	/* PC += off if dst & src */
-++	/* PC += off if dst > src */
-++	/* PC += off if dst >= src */
-++	/* PC += off if dst < src */
-++	/* PC += off if dst <= src */
-++	/* PC += off if dst > src (signed) */
-++	/* PC += off if dst >= src (signed) */
-++	/* PC += off if dst < src (signed) */
-++	/* PC += off if dst <= src (signed) */
-++	case BPF_JMP | BPF_JEQ | BPF_X:
-++	case BPF_JMP | BPF_JNE | BPF_X:
-++	case BPF_JMP | BPF_JSET | BPF_X:
-++	case BPF_JMP | BPF_JGT | BPF_X:
-++	case BPF_JMP | BPF_JGE | BPF_X:
-++	case BPF_JMP | BPF_JLT | BPF_X:
-++	case BPF_JMP | BPF_JLE | BPF_X:
-++	case BPF_JMP | BPF_JSGT | BPF_X:
-++	case BPF_JMP | BPF_JSGE | BPF_X:
-++	case BPF_JMP | BPF_JSLT | BPF_X:
-++	case BPF_JMP | BPF_JSLE | BPF_X:
-++		if (off == 0)
-++			break;
-++		setup_jmp_r(ctx, dst == src, BPF_OP(code), off, &jmp, &rel);
-++		emit_jmp_r64(ctx, dst, src, rel, jmp);
-++		if (finish_jmp(ctx, jmp, off) < 0)
-++			goto toofar;
-++		break;
-++	/* PC += off if dst == imm */
-++	/* PC += off if dst != imm */
-++	/* PC += off if dst & imm */
-++	/* PC += off if dst > imm */
-++	/* PC += off if dst >= imm */
-++	/* PC += off if dst < imm */
-++	/* PC += off if dst <= imm */
-++	/* PC += off if dst > imm (signed) */
-++	/* PC += off if dst >= imm (signed) */
-++	/* PC += off if dst < imm (signed) */
-++	/* PC += off if dst <= imm (signed) */
-++	case BPF_JMP | BPF_JEQ | BPF_K:
-++	case BPF_JMP | BPF_JNE | BPF_K:
-++	case BPF_JMP | BPF_JSET | BPF_K:
-++	case BPF_JMP | BPF_JGT | BPF_K:
-++	case BPF_JMP | BPF_JGE | BPF_K:
-++	case BPF_JMP | BPF_JLT | BPF_K:
-++	case BPF_JMP | BPF_JLE | BPF_K:
-++	case BPF_JMP | BPF_JSGT | BPF_K:
-++	case BPF_JMP | BPF_JSGE | BPF_K:
-++	case BPF_JMP | BPF_JSLT | BPF_K:
-++	case BPF_JMP | BPF_JSLE | BPF_K:
-++		if (off == 0)
+++			swappiness = 0;
+++
+++		nr_to_scan = get_nr_to_scan(lruvec, sc, swappiness);
+++		if (!nr_to_scan)
 ++			break;
-++		setup_jmp_i(ctx, imm, 64, BPF_OP(code), off, &jmp, &rel);
-++		emit_jmp_i64(ctx, dst, imm, rel, jmp);
-++		if (finish_jmp(ctx, jmp, off) < 0)
-++			goto toofar;
-++		break;
-++	/* PC += off */
-++	case BPF_JMP | BPF_JA:
-++		if (off == 0)
+++
+++		delta = evict_pages(lruvec, sc, swappiness);
+++		if (!delta)
 ++			break;
-++		if (emit_ja(ctx, off) < 0)
-++			goto toofar;
-++		break;
-++	/* Tail call */
-++	case BPF_JMP | BPF_TAIL_CALL:
-++		if (emit_tail_call(ctx) < 0)
-++			goto invalid;
-++		break;
-++	/* Function call */
-++	case BPF_JMP | BPF_CALL:
-++		if (emit_call(ctx, insn) < 0)
-++			goto invalid;
-++		break;
-++	/* Function return */
-++	case BPF_JMP | BPF_EXIT:
-++		/*
-++		 * Optimization: when last instruction is EXIT
-++		 * simply continue to epilogue.
-++		 */
-++		if (ctx->bpf_index == ctx->program->len - 1)
+++
+++		scanned += delta;
+++		if (scanned >= nr_to_scan)
 ++			break;
-++		if (emit_exit(ctx) < 0)
-++			goto toofar;
-++		break;
 ++
-++	default:
-++invalid:
-++		pr_err_once("unknown opcode %02x\n", code);
-++		return -EINVAL;
-++notyet:
-++		pr_info_once("*** NOT YET: opcode %02x ***\n", code);
-++		return -EFAULT;
-++toofar:
-++		pr_info_once("*** TOO FAR: jump at %u opcode %02x ***\n",
-++			     ctx->bpf_index, code);
-++		return -E2BIG;
+++		cond_resched();
 ++	}
-++	return 0;
+++
+++	blk_finish_plug(&plug);
+++
+++	if (current_is_kswapd())
+++		current->reclaim_state->mm_walk_args = NULL;
 ++}
-diff --git a/target/linux/generic/backport-5.15/050-v5.16-03-mips-bpf-Add-new-eBPF-JIT-for-64-bit-MIPS.patch b/target/linux/generic/backport-5.15/050-v5.16-03-mips-bpf-Add-new-eBPF-JIT-for-64-bit-MIPS.patch
-new file mode 100644
-index 0000000000..38b46c0b76
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/050-v5.16-03-mips-bpf-Add-new-eBPF-JIT-for-64-bit-MIPS.patch
-@@ -0,0 +1,1005 @@
-+From: Johan Almbladh <johan.almbladh@anyfinetworks.com>
-+Date: Tue, 5 Oct 2021 18:54:05 +0200
-+Subject: [PATCH] mips: bpf: Add new eBPF JIT for 64-bit MIPS
-+
-+This is an implementation on of an eBPF JIT for 64-bit MIPS III-V and
-+MIPS64r1-r6. It uses the same framework introduced by the 32-bit JIT.
-+
-+Signed-off-by: Johan Almbladh <johan.almbladh@anyfinetworks.com>
-+---
-+ create mode 100644 arch/mips/net/bpf_jit_comp64.c
-+
-+--- /dev/null
-++++ b/arch/mips/net/bpf_jit_comp64.c
-+@@ -0,0 +1,991 @@
-++// SPDX-License-Identifier: GPL-2.0-only
-++/*
-++ * Just-In-Time compiler for eBPF bytecode on MIPS.
-++ * Implementation of JIT functions for 64-bit CPUs.
-++ *
-++ * Copyright (c) 2021 Anyfi Networks AB.
-++ * Author: Johan Almbladh <johan.almbladh@gmail.com>
-++ *
-++ * Based on code and ideas from
-++ * Copyright (c) 2017 Cavium, Inc.
-++ * Copyright (c) 2017 Shubham Bansal <illusionist.neo@gmail.com>
-++ * Copyright (c) 2011 Mircea Gherzan <mgherzan@gmail.com>
-++ */
 ++
-++#include <linux/errno.h>
-++#include <linux/filter.h>
-++#include <linux/bpf.h>
-++#include <asm/cpu-features.h>
-++#include <asm/isa-rev.h>
-++#include <asm/uasm.h>
+++/******************************************************************************
++  *                          state change
++  ******************************************************************************/
++ 
++@@ -4420,6 +4961,10 @@ static void lru_gen_age_node(struct pgli
++ {
++ }
++ 
+++static void lru_gen_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
+++{
+++}
 ++
-++#include "bpf_jit_comp.h"
++ #endif /* CONFIG_LRU_GEN */
++ 
++ static void shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
++@@ -4433,6 +4978,11 @@ static void shrink_lruvec(struct lruvec
++ 	struct blk_plug plug;
++ 	bool scan_adjusted;
++ 
+++	if (lru_gen_enabled()) {
+++		lru_gen_shrink_lruvec(lruvec, sc);
+++		return;
+++	}
 ++
-++/* MIPS t0-t3 are not available in the n64 ABI */
-++#undef MIPS_R_T0
-++#undef MIPS_R_T1
-++#undef MIPS_R_T2
-++#undef MIPS_R_T3
++ 	get_scan_count(lruvec, sc, nr);
++ 
++ 	/* Record the original scan target for proportional adjustments later */
++@@ -4906,6 +5456,9 @@ static void snapshot_refaults(struct mem
++ 	struct lruvec *target_lruvec;
++ 	unsigned long refaults;
++ 
+++	if (lru_gen_enabled())
+++		return;
 ++
-++/* Stack is 16-byte aligned in n64 ABI */
-++#define MIPS_STACK_ALIGNMENT 16
++ 	target_lruvec = mem_cgroup_lruvec(target_memcg, pgdat);
++ 	refaults = lruvec_page_state(target_lruvec, WORKINGSET_ACTIVATE_ANON);
++ 	target_lruvec->refaults[0] = refaults;
++--- a/mm/workingset.c
+++++ b/mm/workingset.c
++@@ -187,7 +187,6 @@ static unsigned int bucket_order __read_
++ static void *pack_shadow(int memcgid, pg_data_t *pgdat, unsigned long eviction,
++ 			 bool workingset)
++ {
++-	eviction >>= bucket_order;
++ 	eviction &= EVICTION_MASK;
++ 	eviction = (eviction << MEM_CGROUP_ID_SHIFT) | memcgid;
++ 	eviction = (eviction << NODES_SHIFT) | pgdat->node_id;
++@@ -212,10 +211,117 @@ static void unpack_shadow(void *shadow,
++ 
++ 	*memcgidp = memcgid;
++ 	*pgdat = NODE_DATA(nid);
++-	*evictionp = entry << bucket_order;
+++	*evictionp = entry;
++ 	*workingsetp = workingset;
++ }
++ 
+++#ifdef CONFIG_LRU_GEN
 ++
-++/* Extra 64-bit eBPF registers used by JIT */
-++#define JIT_REG_TC (MAX_BPF_JIT_REG + 0)
-++#define JIT_REG_ZX (MAX_BPF_JIT_REG + 1)
+++static int page_lru_refs(struct page *page)
+++{
+++	unsigned long flags = READ_ONCE(page->flags);
 ++
-++/* Number of prologue bytes to skip when doing a tail call */
-++#define JIT_TCALL_SKIP 4
+++	BUILD_BUG_ON(LRU_GEN_WIDTH + LRU_REFS_WIDTH > BITS_PER_LONG - EVICTION_SHIFT);
 ++
-++/* Callee-saved CPU registers that the JIT must preserve */
-++#define JIT_CALLEE_REGS   \
-++	(BIT(MIPS_R_S0) | \
-++	 BIT(MIPS_R_S1) | \
-++	 BIT(MIPS_R_S2) | \
-++	 BIT(MIPS_R_S3) | \
-++	 BIT(MIPS_R_S4) | \
-++	 BIT(MIPS_R_S5) | \
-++	 BIT(MIPS_R_S6) | \
-++	 BIT(MIPS_R_S7) | \
-++	 BIT(MIPS_R_GP) | \
-++	 BIT(MIPS_R_FP) | \
-++	 BIT(MIPS_R_RA))
+++	/* see the comment on MAX_NR_TIERS */
+++	return flags & BIT(PG_workingset) ? (flags & LRU_REFS_MASK) >> LRU_REFS_PGOFF : 0;
+++}
 ++
-++/* Caller-saved CPU registers available for JIT use */
-++#define JIT_CALLER_REGS	  \
-++	(BIT(MIPS_R_A5) | \
-++	 BIT(MIPS_R_A6) | \
-++	 BIT(MIPS_R_A7))
-++/*
-++ * Mapping of 64-bit eBPF registers to 64-bit native MIPS registers.
-++ * MIPS registers t4 - t7 may be used by the JIT as temporary registers.
-++ * MIPS registers t8 - t9 are reserved for single-register common functions.
-++ */
-++static const u8 bpf2mips64[] = {
-++	/* Return value from in-kernel function, and exit value from eBPF */
-++	[BPF_REG_0] = MIPS_R_V0,
-++	/* Arguments from eBPF program to in-kernel function */
-++	[BPF_REG_1] = MIPS_R_A0,
-++	[BPF_REG_2] = MIPS_R_A1,
-++	[BPF_REG_3] = MIPS_R_A2,
-++	[BPF_REG_4] = MIPS_R_A3,
-++	[BPF_REG_5] = MIPS_R_A4,
-++	/* Callee-saved registers that in-kernel function will preserve */
-++	[BPF_REG_6] = MIPS_R_S0,
-++	[BPF_REG_7] = MIPS_R_S1,
-++	[BPF_REG_8] = MIPS_R_S2,
-++	[BPF_REG_9] = MIPS_R_S3,
-++	/* Read-only frame pointer to access the eBPF stack */
-++	[BPF_REG_FP] = MIPS_R_FP,
-++	/* Temporary register for blinding constants */
-++	[BPF_REG_AX] = MIPS_R_AT,
-++	/* Tail call count register, caller-saved */
-++	[JIT_REG_TC] = MIPS_R_A5,
-++	/* Constant for register zero-extension */
-++	[JIT_REG_ZX] = MIPS_R_V1,
-++};
+++/* Return a token to be stored in the shadow entry of a page being evicted. */
+++static void *lru_gen_eviction(struct page *page)
+++{
+++	int hist, tier;
+++	unsigned long token;
+++	unsigned long min_seq;
+++	struct lruvec *lruvec;
+++	struct lrugen *lrugen;
+++	int type = page_is_file_lru(page);
+++	int refs = page_lru_refs(page);
+++	int delta = thp_nr_pages(page);
+++	bool workingset = PageWorkingset(page);
+++	struct mem_cgroup *memcg = page_memcg(page);
+++	struct pglist_data *pgdat = page_pgdat(page);
+++
+++	lruvec = mem_cgroup_lruvec(memcg, pgdat);
+++	lrugen = &lruvec->evictable;
+++	min_seq = READ_ONCE(lrugen->min_seq[type]);
+++	token = (min_seq << LRU_REFS_WIDTH) | refs;
+++
+++	hist = lru_hist_from_seq(min_seq);
+++	tier = lru_tier_from_refs(refs + workingset);
+++	atomic_long_add(delta, &lrugen->evicted[hist][type][tier]);
+++
+++	return pack_shadow(mem_cgroup_id(memcg), pgdat, token, workingset);
+++}
 ++
-++/*
-++ * MIPS 32-bit operations on 64-bit registers generate a sign-extended
-++ * result. However, the eBPF ISA mandates zero-extension, so we rely on the
-++ * verifier to add that for us (emit_zext_ver). In addition, ALU arithmetic
-++ * operations, right shift and byte swap require properly sign-extended
-++ * operands or the result is unpredictable. We emit explicit sign-extensions
-++ * in those cases.
-++ */
+++/* Count a refaulted page based on the token stored in its shadow entry. */
+++static void lru_gen_refault(struct page *page, void *shadow)
+++{
+++	int hist, tier, refs;
+++	int memcg_id;
+++	bool workingset;
+++	unsigned long token;
+++	unsigned long min_seq;
+++	struct lruvec *lruvec;
+++	struct lrugen *lrugen;
+++	struct mem_cgroup *memcg;
+++	struct pglist_data *pgdat;
+++	int type = page_is_file_lru(page);
+++	int delta = thp_nr_pages(page);
+++
+++	unpack_shadow(shadow, &memcg_id, &pgdat, &token, &workingset);
+++	if (page_pgdat(page) != pgdat)
+++		return;
 ++
-++/* Sign extension */
-++static void emit_sext(struct jit_context *ctx, u8 dst, u8 src)
+++	rcu_read_lock();
+++	memcg = page_memcg_rcu(page);
+++	if (mem_cgroup_id(memcg) != memcg_id)
+++		goto unlock;
+++
+++	refs = token & (BIT(LRU_REFS_WIDTH) - 1);
+++	if (refs && !workingset)
+++		goto unlock;
+++
+++	token >>= LRU_REFS_WIDTH;
+++	lruvec = mem_cgroup_lruvec(memcg, pgdat);
+++	lrugen = &lruvec->evictable;
+++	min_seq = READ_ONCE(lrugen->min_seq[type]);
+++	if (token != (min_seq & (EVICTION_MASK >> LRU_REFS_WIDTH)))
+++		goto unlock;
+++
+++	hist = lru_hist_from_seq(min_seq);
+++	tier = lru_tier_from_refs(refs + workingset);
+++	atomic_long_add(delta, &lrugen->refaulted[hist][type][tier]);
+++	mod_lruvec_state(lruvec, WORKINGSET_REFAULT_BASE + type, delta);
+++
+++	/*
+++	 * Tiers don't offer any protection to pages accessed via page tables.
+++	 * That's what generations do. Tiers can't fully protect pages after
+++	 * their numbers of accesses has exceeded the max value. Conservatively
+++	 * count these two conditions as stalls even though they might not
+++	 * indicate any real memory pressure.
+++	 */
+++	if (task_in_nonseq_fault() || refs + workingset == BIT(LRU_REFS_WIDTH)) {
+++		SetPageWorkingset(page);
+++		mod_lruvec_state(lruvec, WORKINGSET_RESTORE_BASE + type, delta);
+++	}
+++unlock:
+++	rcu_read_unlock();
+++}
+++
+++#else
+++
+++static void *lru_gen_eviction(struct page *page)
 ++{
-++	emit(ctx, sll, dst, src, 0);
-++	clobber_reg(ctx, dst);
+++	return NULL;
 ++}
 ++
-++/* Zero extension */
-++static void emit_zext(struct jit_context *ctx, u8 dst)
+++static void lru_gen_refault(struct page *page, void *shadow)
 ++{
-++	if (cpu_has_mips64r2 || cpu_has_mips64r6) {
-++		emit(ctx, dinsu, dst, MIPS_R_ZERO, 32, 32);
-++	} else {
-++		emit(ctx, and, dst, dst, bpf2mips64[JIT_REG_ZX]);
-++		access_reg(ctx, JIT_REG_ZX); /* We need the ZX register */
+++}
+++
+++#endif /* CONFIG_LRU_GEN */
+++
++ /**
++  * workingset_age_nonresident - age non-resident entries as LRU ages
++  * @lruvec: the lruvec that was aged
++@@ -264,10 +370,14 @@ void *workingset_eviction(struct page *p
++ 	VM_BUG_ON_PAGE(page_count(page), page);
++ 	VM_BUG_ON_PAGE(!PageLocked(page), page);
++ 
+++	if (lru_gen_enabled())
+++		return lru_gen_eviction(page);
+++
++ 	lruvec = mem_cgroup_lruvec(target_memcg, pgdat);
++ 	/* XXX: target_memcg can be NULL, go through lruvec */
++ 	memcgid = mem_cgroup_id(lruvec_memcg(lruvec));
++ 	eviction = atomic_long_read(&lruvec->nonresident_age);
+++	eviction >>= bucket_order;
++ 	workingset_age_nonresident(lruvec, thp_nr_pages(page));
++ 	return pack_shadow(memcgid, pgdat, eviction, PageWorkingset(page));
++ }
++@@ -296,7 +406,13 @@ void workingset_refault(struct page *pag
++ 	bool workingset;
++ 	int memcgid;
++ 
+++	if (lru_gen_enabled()) {
+++		lru_gen_refault(page, shadow);
+++		return;
 ++	}
-++	clobber_reg(ctx, dst);
+++
++ 	unpack_shadow(shadow, &memcgid, &pgdat, &eviction, &workingset);
+++	eviction <<= bucket_order;
++ 
++ 	rcu_read_lock();
++ 	/*
+diff --git a/target/linux/generic/backport-5.15/020-v6.1-08-mm-multigenerational-lru-user-interface.patch b/target/linux/generic/backport-5.15/020-v6.1-08-mm-multigenerational-lru-user-interface.patch
+new file mode 100644
+index 0000000000..a1a749fc38
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/020-v6.1-08-mm-multigenerational-lru-user-interface.patch
+@@ -0,0 +1,496 @@
++From 5cc7fdec54e87e32b4fb0f07d84b21769d5f8d92 Mon Sep 17 00:00:00 2001
++From: Yu Zhao <yuzhao@google.com>
++Date: Mon, 25 Jan 2021 21:38:02 -0700
++Subject: [PATCH 08/10] mm: multigenerational lru: user interface
++
++Add /sys/kernel/mm/lru_gen/enabled to enable and disable the
++multigenerational lru at runtime.
++
++Add /sys/kernel/mm/lru_gen/min_ttl_ms to protect the working set of a
++given number of milliseconds. The OOM killer is invoked if this
++working set cannot be kept in memory.
++
++Add /sys/kernel/debug/lru_gen to monitor the multigenerational lru and
++invoke the aging and the eviction. This file has the following output:
++  memcg  memcg_id  memcg_path
++    node  node_id
++      min_gen  birth_time  anon_size  file_size
++      ...
++      max_gen  birth_time  anon_size  file_size
++
++min_gen is the oldest generation number and max_gen is the youngest
++generation number. birth_time is in milliseconds. anon_size and
++file_size are in pages.
++
++This file takes the following input:
++  + memcg_id node_id max_gen [swappiness] [use_bloom_filter]
++  - memcg_id node_id min_gen [swappiness] [nr_to_reclaim]
++
++The first command line invokes the aging, which scans PTEs for
++accessed pages and then creates the next generation max_gen+1. A swap
++file and a non-zero swappiness, which overrides vm.swappiness, are
++required to scan PTEs mapping anon pages. The second command line
++invokes the eviction, which evicts generations less than or equal to
++min_gen. min_gen should be less than max_gen-1 as max_gen and
++max_gen-1 are not fully aged and therefore cannot be evicted.
++Setting nr_to_reclaim to N limits the number of pages to evict.
++Setting use_bloom_filter to 0 overrides the default behavior which
++only scans PTE tables found populated. Multiple command lines are
++supported, as is concatenation with delimiters "," and ";".
++
++Signed-off-by: Yu Zhao <yuzhao@google.com>
++Tested-by: Konstantin Kharlamov <Hi-Angel@yandex.ru>
++Change-Id: I4448e60029badbe347aa3b624f429b280cc3a3d3
++---
++ include/linux/nodemask.h |   1 +
++ mm/vmscan.c              | 415 +++++++++++++++++++++++++++++++++++++++
++ 2 files changed, 416 insertions(+)
++
++--- a/include/linux/nodemask.h
+++++ b/include/linux/nodemask.h
++@@ -485,6 +485,7 @@ static inline int num_node_state(enum no
++ #define first_online_node	0
++ #define first_memory_node	0
++ #define next_online_node(nid)	(MAX_NUMNODES)
+++#define next_memory_node(nid)	(MAX_NUMNODES)
++ #define nr_node_ids		1U
++ #define nr_online_nodes		1U
++ 
++--- a/mm/vmscan.c
+++++ b/mm/vmscan.c
++@@ -53,6 +53,8 @@
++ #include <linux/memory.h>
++ #include <linux/pagewalk.h>
++ #include <linux/shmem_fs.h>
+++#include <linux/ctype.h>
+++#include <linux/debugfs.h>
++ 
++ #include <asm/tlbflush.h>
++ #include <asm/div64.h>
++@@ -4882,6 +4884,413 @@ unlock:
++ }
++ 
++ /******************************************************************************
+++ *                          sysfs interface
+++ ******************************************************************************/
+++
+++static ssize_t show_min_ttl(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+++{
+++	return sprintf(buf, "%u\n", jiffies_to_msecs(READ_ONCE(lru_gen_min_ttl)));
 ++}
 ++
-++/* Zero extension, if verifier does not do it for us  */
-++static void emit_zext_ver(struct jit_context *ctx, u8 dst)
+++static ssize_t store_min_ttl(struct kobject *kobj, struct kobj_attribute *attr,
+++			     const char *buf, size_t len)
 ++{
-++	if (!ctx->program->aux->verifier_zext)
-++		emit_zext(ctx, dst);
+++	unsigned int msecs;
+++
+++	if (kstrtouint(buf, 10, &msecs))
+++		return -EINVAL;
+++
+++	WRITE_ONCE(lru_gen_min_ttl, msecs_to_jiffies(msecs));
+++
+++	return len;
 ++}
 ++
-++/* dst = imm (64-bit) */
-++static void emit_mov_i64(struct jit_context *ctx, u8 dst, u64 imm64)
+++static struct kobj_attribute lru_gen_min_ttl_attr = __ATTR(
+++	min_ttl_ms, 0644, show_min_ttl, store_min_ttl
+++);
+++
+++static ssize_t show_enable(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
 ++{
-++	if (imm64 >= 0xffffffffffff8000ULL || imm64 < 0x8000ULL) {
-++		emit(ctx, daddiu, dst, MIPS_R_ZERO, (s16)imm64);
-++	} else if (imm64 >= 0xffffffff80000000ULL ||
-++		   (imm64 < 0x80000000 && imm64 > 0xffff)) {
-++		emit(ctx, lui, dst, (s16)(imm64 >> 16));
-++		emit(ctx, ori, dst, dst, (u16)imm64 & 0xffff);
-++	} else {
-++		u8 acc = MIPS_R_ZERO;
-++		int k;
+++	return snprintf(buf, PAGE_SIZE, "%d\n", lru_gen_enabled());
+++}
 ++
-++		for (k = 0; k < 4; k++) {
-++			u16 half = imm64 >> (48 - 16 * k);
+++static ssize_t store_enable(struct kobject *kobj, struct kobj_attribute *attr,
+++			    const char *buf, size_t len)
+++{
+++	bool enable;
 ++
-++			if (acc == dst)
-++				emit(ctx, dsll, dst, dst, 16);
+++	if (kstrtobool(buf, &enable))
+++		return -EINVAL;
 ++
-++			if (half) {
-++				emit(ctx, ori, dst, acc, half);
-++				acc = dst;
-++			}
+++	lru_gen_change_state(enable, true, false);
+++
+++	return len;
+++}
+++
+++static struct kobj_attribute lru_gen_enabled_attr = __ATTR(
+++	enabled, 0644, show_enable, store_enable
+++);
+++
+++static struct attribute *lru_gen_attrs[] = {
+++	&lru_gen_min_ttl_attr.attr,
+++	&lru_gen_enabled_attr.attr,
+++	NULL
+++};
+++
+++static struct attribute_group lru_gen_attr_group = {
+++	.name = "lru_gen",
+++	.attrs = lru_gen_attrs,
+++};
+++
+++/******************************************************************************
+++ *                          debugfs interface
+++ ******************************************************************************/
+++
+++static void *lru_gen_seq_start(struct seq_file *m, loff_t *pos)
+++{
+++	struct mem_cgroup *memcg;
+++	loff_t nr_to_skip = *pos;
+++
+++	m->private = kvmalloc(PATH_MAX, GFP_KERNEL);
+++	if (!m->private)
+++		return ERR_PTR(-ENOMEM);
+++
+++	memcg = mem_cgroup_iter(NULL, NULL, NULL);
+++	do {
+++		int nid;
+++
+++		for_each_node_state(nid, N_MEMORY) {
+++			if (!nr_to_skip--)
+++				return get_lruvec(nid, memcg);
 ++		}
-++	}
-++	clobber_reg(ctx, dst);
+++	} while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)));
+++
+++	return NULL;
 ++}
 ++
-++/* ALU immediate operation (64-bit) */
-++static void emit_alu_i64(struct jit_context *ctx, u8 dst, s32 imm, u8 op)
+++static void lru_gen_seq_stop(struct seq_file *m, void *v)
 ++{
-++	switch (BPF_OP(op)) {
-++	/* dst = dst | imm */
-++	case BPF_OR:
-++		emit(ctx, ori, dst, dst, (u16)imm);
-++		break;
-++	/* dst = dst ^ imm */
-++	case BPF_XOR:
-++		emit(ctx, xori, dst, dst, (u16)imm);
-++		break;
-++	/* dst = -dst */
-++	case BPF_NEG:
-++		emit(ctx, dsubu, dst, MIPS_R_ZERO, dst);
-++		break;
-++	/* dst = dst << imm */
-++	case BPF_LSH:
-++		emit(ctx, dsll_safe, dst, dst, imm);
-++		break;
-++	/* dst = dst >> imm */
-++	case BPF_RSH:
-++		emit(ctx, dsrl_safe, dst, dst, imm);
-++		break;
-++	/* dst = dst >> imm (arithmetic) */
-++	case BPF_ARSH:
-++		emit(ctx, dsra_safe, dst, dst, imm);
-++		break;
-++	/* dst = dst + imm */
-++	case BPF_ADD:
-++		emit(ctx, daddiu, dst, dst, imm);
-++		break;
-++	/* dst = dst - imm */
-++	case BPF_SUB:
-++		emit(ctx, daddiu, dst, dst, -imm);
-++		break;
-++	default:
-++		/* Width-generic operations */
-++		emit_alu_i(ctx, dst, imm, op);
-++	}
-++	clobber_reg(ctx, dst);
+++	if (!IS_ERR_OR_NULL(v))
+++		mem_cgroup_iter_break(NULL, lruvec_memcg(v));
+++
+++	kvfree(m->private);
+++	m->private = NULL;
 ++}
 ++
-++/* ALU register operation (64-bit) */
-++static void emit_alu_r64(struct jit_context *ctx, u8 dst, u8 src, u8 op)
+++static void *lru_gen_seq_next(struct seq_file *m, void *v, loff_t *pos)
 ++{
-++	switch (BPF_OP(op)) {
-++	/* dst = dst << src */
-++	case BPF_LSH:
-++		emit(ctx, dsllv, dst, dst, src);
-++		break;
-++	/* dst = dst >> src */
-++	case BPF_RSH:
-++		emit(ctx, dsrlv, dst, dst, src);
-++		break;
-++	/* dst = dst >> src (arithmetic) */
-++	case BPF_ARSH:
-++		emit(ctx, dsrav, dst, dst, src);
-++		break;
-++	/* dst = dst + src */
-++	case BPF_ADD:
-++		emit(ctx, daddu, dst, dst, src);
-++		break;
-++	/* dst = dst - src */
-++	case BPF_SUB:
-++		emit(ctx, dsubu, dst, dst, src);
-++		break;
-++	/* dst = dst * src */
-++	case BPF_MUL:
-++		if (cpu_has_mips64r6) {
-++			emit(ctx, dmulu, dst, dst, src);
-++		} else {
-++			emit(ctx, dmultu, dst, src);
-++			emit(ctx, mflo, dst);
-++		}
-++		break;
-++	/* dst = dst / src */
-++	case BPF_DIV:
-++		if (cpu_has_mips64r6) {
-++			emit(ctx, ddivu_r6, dst, dst, src);
-++		} else {
-++			emit(ctx, ddivu, dst, src);
-++			emit(ctx, mflo, dst);
-++		}
-++		break;
-++	/* dst = dst % src */
-++	case BPF_MOD:
-++		if (cpu_has_mips64r6) {
-++			emit(ctx, dmodu, dst, dst, src);
-++		} else {
-++			emit(ctx, ddivu, dst, src);
-++			emit(ctx, mfhi, dst);
-++		}
-++		break;
-++	default:
-++		/* Width-generic operations */
-++		emit_alu_r(ctx, dst, src, op);
+++	int nid = lruvec_pgdat(v)->node_id;
+++	struct mem_cgroup *memcg = lruvec_memcg(v);
+++
+++	++*pos;
+++
+++	nid = next_memory_node(nid);
+++	if (nid == MAX_NUMNODES) {
+++		memcg = mem_cgroup_iter(NULL, memcg, NULL);
+++		if (!memcg)
+++			return NULL;
+++
+++		nid = first_memory_node;
 ++	}
-++	clobber_reg(ctx, dst);
+++
+++	return get_lruvec(nid, memcg);
 ++}
 ++
-++/* Swap sub words in a register double word */
-++static void emit_swap_r64(struct jit_context *ctx, u8 dst, u8 mask, u32 bits)
+++static void lru_gen_seq_show_full(struct seq_file *m, struct lruvec *lruvec,
+++				  unsigned long max_seq, unsigned long *min_seq,
+++				  unsigned long seq)
 ++{
-++	u8 tmp = MIPS_R_T9;
+++	int i;
+++	int type, tier;
+++	int hist = lru_hist_from_seq(seq);
+++	struct lrugen *lrugen = &lruvec->evictable;
+++
+++	for (tier = 0; tier < MAX_NR_TIERS; tier++) {
+++		seq_printf(m, "            %10d", tier);
+++		for (type = 0; type < ANON_AND_FILE; type++) {
+++			unsigned long n[3] = {};
+++
+++			if (seq == max_seq) {
+++				n[0] = READ_ONCE(lrugen->avg_refaulted[type][tier]);
+++				n[1] = READ_ONCE(lrugen->avg_total[type][tier]);
+++
+++				seq_printf(m, " %10luR %10luT %10lu ", n[0], n[1], n[2]);
+++			} else if (seq == min_seq[type] || NR_HIST_GENS > 1) {
+++				n[0] = atomic_long_read(&lrugen->refaulted[hist][type][tier]);
+++				n[1] = atomic_long_read(&lrugen->evicted[hist][type][tier]);
+++				if (tier)
+++					n[2] = READ_ONCE(lrugen->protected[hist][type][tier - 1]);
+++
+++				seq_printf(m, " %10lur %10lue %10lup", n[0], n[1], n[2]);
+++			} else
+++				seq_puts(m, "          0           0           0 ");
+++		}
+++		seq_putc(m, '\n');
+++	}
 ++
-++	emit(ctx, and, tmp, dst, mask);  /* tmp = dst & mask  */
-++	emit(ctx, dsll, tmp, tmp, bits); /* tmp = tmp << bits */
-++	emit(ctx, dsrl, dst, dst, bits); /* dst = dst >> bits */
-++	emit(ctx, and, dst, dst, mask);  /* dst = dst & mask  */
-++	emit(ctx, or, dst, dst, tmp);    /* dst = dst | tmp   */
+++	seq_puts(m, "                      ");
+++	for (i = 0; i < NR_MM_STATS; i++) {
+++		if (seq == max_seq && NR_HIST_GENS == 1)
+++			seq_printf(m, " %10lu%c", READ_ONCE(lruvec->mm_walk.stats[hist][i]),
+++				   toupper(MM_STAT_CODES[i]));
+++		else if (seq != max_seq && NR_HIST_GENS > 1)
+++			seq_printf(m, " %10lu%c", READ_ONCE(lruvec->mm_walk.stats[hist][i]),
+++				   MM_STAT_CODES[i]);
+++		else
+++			seq_puts(m, "          0 ");
+++	}
+++	seq_putc(m, '\n');
 ++}
 ++
-++/* Swap bytes and truncate a register double word, word or half word */
-++static void emit_bswap_r64(struct jit_context *ctx, u8 dst, u32 width)
+++static int lru_gen_seq_show(struct seq_file *m, void *v)
 ++{
-++	switch (width) {
-++	/* Swap bytes in a double word */
-++	case 64:
-++		if (cpu_has_mips64r2 || cpu_has_mips64r6) {
-++			emit(ctx, dsbh, dst, dst);
-++			emit(ctx, dshd, dst, dst);
-++		} else {
-++			u8 t1 = MIPS_R_T6;
-++			u8 t2 = MIPS_R_T7;
+++	unsigned long seq;
+++	bool full = !debugfs_real_fops(m->file)->write;
+++	struct lruvec *lruvec = v;
+++	struct lrugen *lrugen = &lruvec->evictable;
+++	int nid = lruvec_pgdat(lruvec)->node_id;
+++	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+++	DEFINE_MAX_SEQ(lruvec);
+++	DEFINE_MIN_SEQ(lruvec);
+++
+++	if (nid == first_memory_node) {
+++		const char *path = memcg ? m->private : "";
+++
+++#ifdef CONFIG_MEMCG
+++		if (memcg)
+++			cgroup_path(memcg->css.cgroup, m->private, PATH_MAX);
+++#endif
+++		seq_printf(m, "memcg %5hu %s\n", mem_cgroup_id(memcg), path);
+++	}
 ++
-++			emit(ctx, dsll32, t2, dst, 0);  /* t2 = dst << 32    */
-++			emit(ctx, dsrl32, dst, dst, 0); /* dst = dst >> 32   */
-++			emit(ctx, or, dst, dst, t2);    /* dst = dst | t2    */
+++	seq_printf(m, " node %5d\n", nid);
 ++
-++			emit(ctx, ori, t2, MIPS_R_ZERO, 0xffff);
-++			emit(ctx, dsll32, t1, t2, 0);   /* t1 = t2 << 32     */
-++			emit(ctx, or, t1, t1, t2);      /* t1 = t1 | t2      */
-++			emit_swap_r64(ctx, dst, t1, 16);/* dst = swap16(dst) */
+++	if (!full)
+++		seq = min_seq[0];
+++	else if (max_seq >= MAX_NR_GENS)
+++		seq = max_seq - MAX_NR_GENS + 1;
+++	else
+++		seq = 0;
 ++
-++			emit(ctx, lui, t2, 0xff);       /* t2 = 0x00ff0000   */
-++			emit(ctx, ori, t2, t2, 0xff);   /* t2 = t2 | 0x00ff  */
-++			emit(ctx, dsll32, t1, t2, 0);   /* t1 = t2 << 32     */
-++			emit(ctx, or, t1, t1, t2);      /* t1 = t1 | t2      */
-++			emit_swap_r64(ctx, dst, t1, 8); /* dst = swap8(dst)  */
+++	for (; seq <= max_seq; seq++) {
+++		int gen, type, zone;
+++		unsigned int msecs;
+++
+++		gen = lru_gen_from_seq(seq);
+++		msecs = jiffies_to_msecs(jiffies - READ_ONCE(lrugen->timestamps[gen]));
+++
+++		seq_printf(m, " %10lu %10u", seq, msecs);
+++
+++		for (type = 0; type < ANON_AND_FILE; type++) {
+++			long size = 0;
+++
+++			if (seq < min_seq[type]) {
+++				seq_puts(m, "         -0 ");
+++				continue;
+++			}
+++
+++			for (zone = 0; zone < MAX_NR_ZONES; zone++)
+++				size += READ_ONCE(lrugen->sizes[gen][type][zone]);
+++
+++			seq_printf(m, " %10lu ", max(size, 0L));
 ++		}
-++		break;
-++	/* Swap bytes in a half word */
-++	/* Swap bytes in a word */
-++	case 32:
-++	case 16:
-++		emit_sext(ctx, dst, dst);
-++		emit_bswap_r(ctx, dst, width);
-++		if (cpu_has_mips64r2 || cpu_has_mips64r6)
-++			emit_zext(ctx, dst);
-++		break;
-++	}
-++	clobber_reg(ctx, dst);
-++}
 ++
-++/* Truncate a register double word, word or half word */
-++static void emit_trunc_r64(struct jit_context *ctx, u8 dst, u32 width)
-++{
-++	switch (width) {
-++	case 64:
-++		break;
-++	/* Zero-extend a word */
-++	case 32:
-++		emit_zext(ctx, dst);
-++		break;
-++	/* Zero-extend a half word */
-++	case 16:
-++		emit(ctx, andi, dst, dst, 0xffff);
-++		break;
-++	}
-++	clobber_reg(ctx, dst);
-++}
+++		seq_putc(m, '\n');
 ++
-++/* Load operation: dst = *(size*)(src + off) */
-++static void emit_ldx(struct jit_context *ctx, u8 dst, u8 src, s16 off, u8 size)
-++{
-++	switch (size) {
-++	/* Load a byte */
-++	case BPF_B:
-++		emit(ctx, lbu, dst, off, src);
-++		break;
-++	/* Load a half word */
-++	case BPF_H:
-++		emit(ctx, lhu, dst, off, src);
-++		break;
-++	/* Load a word */
-++	case BPF_W:
-++		emit(ctx, lwu, dst, off, src);
-++		break;
-++	/* Load a double word */
-++	case BPF_DW:
-++		emit(ctx, ld, dst, off, src);
-++		break;
+++		if (full)
+++			lru_gen_seq_show_full(m, lruvec, max_seq, min_seq, seq);
 ++	}
-++	clobber_reg(ctx, dst);
+++
+++	return 0;
 ++}
 ++
-++/* Store operation: *(size *)(dst + off) = src */
-++static void emit_stx(struct jit_context *ctx, u8 dst, u8 src, s16 off, u8 size)
+++static const struct seq_operations lru_gen_seq_ops = {
+++	.start = lru_gen_seq_start,
+++	.stop = lru_gen_seq_stop,
+++	.next = lru_gen_seq_next,
+++	.show = lru_gen_seq_show,
+++};
+++
+++static int run_aging(struct lruvec *lruvec, struct scan_control *sc, int swappiness,
+++		     unsigned long seq, bool use_filter)
 ++{
-++	switch (size) {
-++	/* Store a byte */
-++	case BPF_B:
-++		emit(ctx, sb, src, off, dst);
-++		break;
-++	/* Store a half word */
-++	case BPF_H:
-++		emit(ctx, sh, src, off, dst);
-++		break;
-++	/* Store a word */
-++	case BPF_W:
-++		emit(ctx, sw, src, off, dst);
-++		break;
-++	/* Store a double word */
-++	case BPF_DW:
-++		emit(ctx, sd, src, off, dst);
-++		break;
-++	}
+++	DEFINE_MAX_SEQ(lruvec);
+++
+++	if (seq == max_seq)
+++		try_to_inc_max_seq(lruvec, sc, swappiness, max_seq, use_filter);
+++
+++	return seq > max_seq ? -EINVAL : 0;
 ++}
 ++
-++/* Atomic read-modify-write */
-++static void emit_atomic_r64(struct jit_context *ctx,
-++			    u8 dst, u8 src, s16 off, u8 code)
+++static int run_eviction(struct lruvec *lruvec, struct scan_control *sc, int swappiness,
+++			unsigned long seq, unsigned long nr_to_reclaim)
 ++{
-++	u8 t1 = MIPS_R_T6;
-++	u8 t2 = MIPS_R_T7;
+++	struct blk_plug plug;
+++	int err = -EINTR;
+++	DEFINE_MAX_SEQ(lruvec);
 ++
-++	emit(ctx, lld, t1, off, dst);
-++	switch (code) {
-++	case BPF_ADD:
-++		emit(ctx, daddu, t2, t1, src);
-++		break;
-++	case BPF_AND:
-++		emit(ctx, and, t2, t1, src);
-++		break;
-++	case BPF_OR:
-++		emit(ctx, or, t2, t1, src);
-++		break;
-++	case BPF_XOR:
-++		emit(ctx, xor, t2, t1, src);
-++		break;
+++	if (seq >= max_seq - 1)
+++		return -EINVAL;
+++
+++	sc->nr_reclaimed = 0;
+++
+++	blk_start_plug(&plug);
+++
+++	while (!signal_pending(current)) {
+++		DEFINE_MIN_SEQ(lruvec);
+++
+++		if (seq < min_seq[!swappiness] || sc->nr_reclaimed >= nr_to_reclaim ||
+++		    !evict_pages(lruvec, sc, swappiness)) {
+++			err = 0;
+++			break;
+++		}
+++
+++		cond_resched();
 ++	}
-++	emit(ctx, scd, t2, off, dst);
-++	emit(ctx, beqz, t2, -16);
-++	emit(ctx, nop); /* Delay slot */
+++
+++	blk_finish_plug(&plug);
+++
+++	return err;
 ++}
 ++
-++/* Function call */
-++static int emit_call(struct jit_context *ctx, const struct bpf_insn *insn)
+++static int run_cmd(char cmd, int memcg_id, int nid, struct scan_control *sc,
+++		   int swappiness, unsigned long seq, unsigned long opt)
 ++{
-++	u8 zx = bpf2mips64[JIT_REG_ZX];
-++	u8 tmp = MIPS_R_T6;
-++	bool fixed;
-++	u64 addr;
+++	struct lruvec *lruvec;
+++	int err = -EINVAL;
+++	struct mem_cgroup *memcg = NULL;
+++
+++	if (!mem_cgroup_disabled()) {
+++		rcu_read_lock();
+++		memcg = mem_cgroup_from_id(memcg_id);
+++#ifdef CONFIG_MEMCG
+++		if (memcg && !css_tryget(&memcg->css))
+++			memcg = NULL;
+++#endif
+++		rcu_read_unlock();
 ++
-++	/* Decode the call address */
-++	if (bpf_jit_get_func_addr(ctx->program, insn, false,
-++				  &addr, &fixed) < 0)
-++		return -1;
-++	if (!fixed)
-++		return -1;
+++		if (!memcg)
+++			goto done;
+++	}
+++	if (memcg_id != mem_cgroup_id(memcg))
+++		goto done;
 ++
-++	/* Push caller-saved registers on stack */
-++	push_regs(ctx, ctx->clobbered & JIT_CALLER_REGS, 0, 0);
+++	if (nid < 0 || nid >= MAX_NUMNODES || !node_state(nid, N_MEMORY))
+++		goto done;
 ++
-++	/* Emit function call */
-++	emit_mov_i64(ctx, tmp, addr);
-++	emit(ctx, jalr, MIPS_R_RA, tmp);
-++	emit(ctx, nop); /* Delay slot */
+++	lruvec = get_lruvec(nid, memcg);
 ++
-++	/* Restore caller-saved registers */
-++	pop_regs(ctx, ctx->clobbered & JIT_CALLER_REGS, 0, 0);
+++	if (swappiness < 0)
+++		swappiness = get_swappiness(memcg);
+++	else if (swappiness > 200)
+++		goto done;
 ++
-++	/* Re-initialize the JIT zero-extension register if accessed */
-++	if (ctx->accessed & BIT(JIT_REG_ZX)) {
-++		emit(ctx, daddiu, zx, MIPS_R_ZERO, -1);
-++		emit(ctx, dsrl32, zx, zx, 0);
+++	switch (cmd) {
+++	case '+':
+++		err = run_aging(lruvec, sc, swappiness, seq, opt);
+++		break;
+++	case '-':
+++		err = run_eviction(lruvec, sc, swappiness, seq, opt);
+++		break;
 ++	}
+++done:
+++	mem_cgroup_put(memcg);
 ++
-++	clobber_reg(ctx, MIPS_R_RA);
-++	clobber_reg(ctx, MIPS_R_V0);
-++	clobber_reg(ctx, MIPS_R_V1);
-++	return 0;
+++	return err;
 ++}
 ++
-++/* Function tail call */
-++static int emit_tail_call(struct jit_context *ctx)
+++static ssize_t lru_gen_seq_write(struct file *file, const char __user *src,
+++				 size_t len, loff_t *pos)
 ++{
-++	u8 ary = bpf2mips64[BPF_REG_2];
-++	u8 ind = bpf2mips64[BPF_REG_3];
-++	u8 tcc = bpf2mips64[JIT_REG_TC];
-++	u8 tmp = MIPS_R_T6;
-++	int off;
+++	void *buf;
+++	char *cur, *next;
+++	unsigned int flags;
+++	int err = 0;
+++	struct scan_control sc = {
+++		.may_writepage = 1,
+++		.may_unmap = 1,
+++		.may_swap = 1,
+++		.reclaim_idx = MAX_NR_ZONES - 1,
+++		.gfp_mask = GFP_KERNEL,
+++	};
 ++
-++	/*
-++	 * Tail call:
-++	 * eBPF R1 - function argument (context ptr), passed in a0-a1
-++	 * eBPF R2 - ptr to object with array of function entry points
-++	 * eBPF R3 - array index of function to be called
-++	 */
+++	buf = kvmalloc(len + 1, GFP_KERNEL);
+++	if (!buf)
+++		return -ENOMEM;
 ++
-++	/* if (ind >= ary->map.max_entries) goto out */
-++	off = offsetof(struct bpf_array, map.max_entries);
-++	if (off > 0x7fff)
-++		return -1;
-++	emit(ctx, lwu, tmp, off, ary);            /* tmp = ary->map.max_entrs*/
-++	emit(ctx, sltu, tmp, ind, tmp);           /* tmp = ind < t1          */
-++	emit(ctx, beqz, tmp, get_offset(ctx, 1)); /* PC += off(1) if tmp == 0*/
+++	if (copy_from_user(buf, src, len)) {
+++		kvfree(buf);
+++		return -EFAULT;
+++	}
 ++
-++	/* if (--TCC < 0) goto out */
-++	emit(ctx, daddiu, tcc, tcc, -1);          /* tcc-- (delay slot)      */
-++	emit(ctx, bltz, tcc, get_offset(ctx, 1)); /* PC += off(1) if tcc < 0 */
-++						  /* (next insn delay slot)  */
-++	/* prog = ary->ptrs[ind] */
-++	off = offsetof(struct bpf_array, ptrs);
-++	if (off > 0x7fff)
-++		return -1;
-++	emit(ctx, dsll, tmp, ind, 3);             /* tmp = ind << 3          */
-++	emit(ctx, daddu, tmp, tmp, ary);          /* tmp += ary              */
-++	emit(ctx, ld, tmp, off, tmp);             /* tmp = *(tmp + off)      */
+++	next = buf;
+++	next[len] = '\0';
 ++
-++	/* if (prog == 0) goto out */
-++	emit(ctx, beqz, tmp, get_offset(ctx, 1)); /* PC += off(1) if tmp == 0*/
-++	emit(ctx, nop);                           /* Delay slot              */
+++	sc.reclaim_state.mm_walk_args = alloc_mm_walk_args();
+++	if (!sc.reclaim_state.mm_walk_args) {
+++		kvfree(buf);
+++		return -ENOMEM;
+++	}
 ++
-++	/* func = prog->bpf_func + 8 (prologue skip offset) */
-++	off = offsetof(struct bpf_prog, bpf_func);
-++	if (off > 0x7fff)
-++		return -1;
-++	emit(ctx, ld, tmp, off, tmp);                /* tmp = *(tmp + off)   */
-++	emit(ctx, daddiu, tmp, tmp, JIT_TCALL_SKIP); /* tmp += skip (4)      */
+++	flags = memalloc_noreclaim_save();
+++	set_task_reclaim_state(current, &sc.reclaim_state);
+++
+++	while ((cur = strsep(&next, ",;\n"))) {
+++		int n;
+++		int end;
+++		char cmd;
+++		unsigned int memcg_id;
+++		unsigned int nid;
+++		unsigned long seq;
+++		unsigned int swappiness = -1;
+++		unsigned long opt = -1;
+++
+++		cur = skip_spaces(cur);
+++		if (!*cur)
+++			continue;
 ++
-++	/* goto func */
-++	build_epilogue(ctx, tmp);
-++	access_reg(ctx, JIT_REG_TC);
-++	return 0;
-++}
+++		n = sscanf(cur, "%c %u %u %lu %n %u %n %lu %n", &cmd, &memcg_id, &nid,
+++			   &seq, &end, &swappiness, &end, &opt, &end);
+++		if (n < 4 || cur[end]) {
+++			err = -EINVAL;
+++			break;
+++		}
 ++
-++/*
-++ * Stack frame layout for a JITed program (stack grows down).
-++ *
-++ * Higher address  : Previous stack frame      :
-++ *                 +===========================+  <--- MIPS sp before call
-++ *                 | Callee-saved registers,   |
-++ *                 | including RA and FP       |
-++ *                 +---------------------------+  <--- eBPF FP (MIPS fp)
-++ *                 | Local eBPF variables      |
-++ *                 | allocated by program      |
-++ *                 +---------------------------+
-++ *                 | Reserved for caller-saved |
-++ *                 | registers                 |
-++ * Lower address   +===========================+  <--- MIPS sp
-++ */
+++		err = run_cmd(cmd, memcg_id, nid, &sc, swappiness, seq, opt);
+++		if (err)
+++			break;
+++	}
 ++
-++/* Build program prologue to set up the stack and registers */
-++void build_prologue(struct jit_context *ctx)
-++{
-++	u8 fp = bpf2mips64[BPF_REG_FP];
-++	u8 tc = bpf2mips64[JIT_REG_TC];
-++	u8 zx = bpf2mips64[JIT_REG_ZX];
-++	int stack, saved, locals, reserved;
+++	set_task_reclaim_state(current, NULL);
+++	memalloc_noreclaim_restore(flags);
 ++
-++	/*
-++	 * The first instruction initializes the tail call count register.
-++	 * On a tail call, the calling function jumps into the prologue
-++	 * after this instruction.
-++	 */
-++	emit(ctx, addiu, tc, MIPS_R_ZERO, min(MAX_TAIL_CALL_CNT + 1, 0xffff));
+++	free_mm_walk_args(sc.reclaim_state.mm_walk_args);
+++	kvfree(buf);
 ++
-++	/* === Entry-point for tail calls === */
+++	return err ? : len;
+++}
 ++
-++	/*
-++	 * If the eBPF frame pointer and tail call count registers were
-++	 * accessed they must be preserved. Mark them as clobbered here
-++	 * to save and restore them on the stack as needed.
-++	 */
-++	if (ctx->accessed & BIT(BPF_REG_FP))
-++		clobber_reg(ctx, fp);
-++	if (ctx->accessed & BIT(JIT_REG_TC))
-++		clobber_reg(ctx, tc);
-++	if (ctx->accessed & BIT(JIT_REG_ZX))
-++		clobber_reg(ctx, zx);
+++static int lru_gen_seq_open(struct inode *inode, struct file *file)
+++{
+++	return seq_open(file, &lru_gen_seq_ops);
+++}
 ++
-++	/* Compute the stack space needed for callee-saved registers */
-++	saved = hweight32(ctx->clobbered & JIT_CALLEE_REGS) * sizeof(u64);
-++	saved = ALIGN(saved, MIPS_STACK_ALIGNMENT);
+++static const struct file_operations lru_gen_rw_fops = {
+++	.open = lru_gen_seq_open,
+++	.read = seq_read,
+++	.write = lru_gen_seq_write,
+++	.llseek = seq_lseek,
+++	.release = seq_release,
+++};
 ++
-++	/* Stack space used by eBPF program local data */
-++	locals = ALIGN(ctx->program->aux->stack_depth, MIPS_STACK_ALIGNMENT);
+++static const struct file_operations lru_gen_ro_fops = {
+++	.open = lru_gen_seq_open,
+++	.read = seq_read,
+++	.llseek = seq_lseek,
+++	.release = seq_release,
+++};
 ++
-++	/*
-++	 * If we are emitting function calls, reserve extra stack space for
-++	 * caller-saved registers needed by the JIT. The required space is
-++	 * computed automatically during resource usage discovery (pass 1).
-++	 */
-++	reserved = ctx->stack_used;
+++/******************************************************************************
++  *                          initialization
++  ******************************************************************************/
++ 
++@@ -4951,6 +5360,12 @@ static int __init init_lru_gen(void)
++ 	BUILD_BUG_ON(BIT(LRU_GEN_WIDTH) <= MAX_NR_GENS);
++ 	BUILD_BUG_ON(sizeof(MM_STAT_CODES) != NR_MM_STATS + 1);
++ 
+++	if (sysfs_create_group(mm_kobj, &lru_gen_attr_group))
+++		pr_err("lru_gen: failed to create sysfs group\n");
 ++
-++	/* Allocate the stack frame */
-++	stack = ALIGN(saved + locals + reserved, MIPS_STACK_ALIGNMENT);
-++	if (stack)
-++		emit(ctx, daddiu, MIPS_R_SP, MIPS_R_SP, -stack);
+++	debugfs_create_file("lru_gen", 0644, NULL, NULL, &lru_gen_rw_fops);
+++	debugfs_create_file("lru_gen_full", 0444, NULL, NULL, &lru_gen_ro_fops);
 ++
-++	/* Store callee-saved registers on stack */
-++	push_regs(ctx, ctx->clobbered & JIT_CALLEE_REGS, 0, stack - saved);
++ 	return 0;
++ };
++ late_initcall(init_lru_gen);
+diff --git a/target/linux/generic/backport-5.15/020-v6.1-09-mm-multigenerational-lru-Kconfig.patch b/target/linux/generic/backport-5.15/020-v6.1-09-mm-multigenerational-lru-Kconfig.patch
+new file mode 100644
+index 0000000000..4462549f99
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/020-v6.1-09-mm-multigenerational-lru-Kconfig.patch
+@@ -0,0 +1,80 @@
++From 3008095eb835d207dd7e5b60899aad17f32aa9f7 Mon Sep 17 00:00:00 2001
++From: Yu Zhao <yuzhao@google.com>
++Date: Mon, 25 Jan 2021 21:47:24 -0700
++Subject: [PATCH 09/10] mm: multigenerational lru: Kconfig
++
++Add configuration options for the multigenerational lru.
++
++Signed-off-by: Yu Zhao <yuzhao@google.com>
++Tested-by: Konstantin Kharlamov <Hi-Angel@yandex.ru>
++Change-Id: Ic74ea07f8fb5f56e6904a1b80c3c286bc2911635
++---
++ mm/Kconfig | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
++ 1 file changed, 59 insertions(+)
++
++--- a/mm/Kconfig
+++++ b/mm/Kconfig
++@@ -899,4 +899,63 @@ config SECRETMEM
++ 
++ source "mm/damon/Kconfig"
++ 
+++# the multigenerational lru {
+++config LRU_GEN
+++	bool "Multigenerational LRU"
+++	depends on MMU
+++	# the following options may leave not enough spare bits in page->flags
+++	depends on !MAXSMP && (64BIT || !SPARSEMEM || SPARSEMEM_VMEMMAP)
+++	help
+++	  A high performance LRU implementation to heavily overcommit workloads
+++	  that are not IO bound. See Documentation/vm/multigen_lru.rst for
+++	  details.
 ++
-++	/* Initialize the eBPF frame pointer if accessed */
-++	if (ctx->accessed & BIT(BPF_REG_FP))
-++		emit(ctx, daddiu, fp, MIPS_R_SP, stack - saved);
+++	  Warning: do not enable this option unless you plan to use it because
+++	  it introduces a small per-process and per-memcg and per-node memory
+++	  overhead.
 ++
-++	/* Initialize the ePF JIT zero-extension register if accessed */
-++	if (ctx->accessed & BIT(JIT_REG_ZX)) {
-++		emit(ctx, daddiu, zx, MIPS_R_ZERO, -1);
-++		emit(ctx, dsrl32, zx, zx, 0);
-++	}
+++config LRU_GEN_ENABLED
+++	bool "Turn on by default"
+++	depends on LRU_GEN
+++	help
+++	  The default value of /sys/kernel/mm/lru_gen/enabled is 0. This option
+++	  changes it to 1.
 ++
-++	ctx->saved_size = saved;
-++	ctx->stack_size = stack;
-++}
+++	  Warning: the default value is the fast path. See
+++	  Documentation/static-keys.txt for details.
 ++
-++/* Build the program epilogue to restore the stack and registers */
-++void build_epilogue(struct jit_context *ctx, int dest_reg)
-++{
-++	/* Restore callee-saved registers from stack */
-++	pop_regs(ctx, ctx->clobbered & JIT_CALLEE_REGS, 0,
-++		 ctx->stack_size - ctx->saved_size);
+++config LRU_GEN_STATS
+++	bool "Full stats for debugging"
+++	depends on LRU_GEN
+++	help
+++	  This option keeps full stats for each generation, which can be read
+++	  from /sys/kernel/debug/lru_gen_full.
+++
+++	  Warning: do not enable this option unless you plan to use it because
+++	  it introduces an additional small per-process and per-memcg and
+++	  per-node memory overhead.
+++
+++config NR_LRU_GENS
+++	int "Max number of generations"
+++	depends on LRU_GEN
+++	range 4 31
+++	default 7
+++	help
+++	  This will use order_base_2(N+1) spare bits from page flags.
 ++
-++	/* Release the stack frame */
-++	if (ctx->stack_size)
-++		emit(ctx, daddiu, MIPS_R_SP, MIPS_R_SP, ctx->stack_size);
+++	  Warning: do not use numbers larger than necessary because each
+++	  generation introduces a small per-node and per-memcg memory overhead.
 ++
-++	/* Jump to return address and sign-extend the 32-bit return value */
-++	emit(ctx, jr, dest_reg);
-++	emit(ctx, sll, MIPS_R_V0, MIPS_R_V0, 0); /* Delay slot */
-++}
+++config TIERS_PER_GEN
+++	int "Number of tiers per generation"
+++	depends on LRU_GEN
+++	range 2 5
+++	default 4
+++	help
+++	  This will use N-2 spare bits from page flags.
 ++
-++/* Build one eBPF instruction */
-++int build_insn(const struct bpf_insn *insn, struct jit_context *ctx)
-++{
-++	u8 dst = bpf2mips64[insn->dst_reg];
-++	u8 src = bpf2mips64[insn->src_reg];
-++	u8 code = insn->code;
-++	s16 off = insn->off;
-++	s32 imm = insn->imm;
-++	s32 val, rel;
-++	u8 alu, jmp;
+++	  Larger values generally offer better protection to active pages under
+++	  heavy buffered I/O workloads.
+++# }
 ++
-++	switch (code) {
-++	/* ALU operations */
-++	/* dst = imm */
-++	case BPF_ALU | BPF_MOV | BPF_K:
-++		emit_mov_i(ctx, dst, imm);
-++		emit_zext_ver(ctx, dst);
-++		break;
-++	/* dst = src */
-++	case BPF_ALU | BPF_MOV | BPF_X:
-++		if (imm == 1) {
-++			/* Special mov32 for zext */
-++			emit_zext(ctx, dst);
-++		} else {
-++			emit_mov_r(ctx, dst, src);
-++			emit_zext_ver(ctx, dst);
-++		}
-++		break;
-++	/* dst = -dst */
-++	case BPF_ALU | BPF_NEG:
-++		emit_sext(ctx, dst, dst);
-++		emit_alu_i(ctx, dst, 0, BPF_NEG);
-++		emit_zext_ver(ctx, dst);
-++		break;
-++	/* dst = dst & imm */
-++	/* dst = dst | imm */
-++	/* dst = dst ^ imm */
-++	/* dst = dst << imm */
-++	case BPF_ALU | BPF_OR | BPF_K:
-++	case BPF_ALU | BPF_AND | BPF_K:
-++	case BPF_ALU | BPF_XOR | BPF_K:
-++	case BPF_ALU | BPF_LSH | BPF_K:
-++		if (!valid_alu_i(BPF_OP(code), imm)) {
-++			emit_mov_i(ctx, MIPS_R_T4, imm);
-++			emit_alu_r(ctx, dst, MIPS_R_T4, BPF_OP(code));
-++		} else if (rewrite_alu_i(BPF_OP(code), imm, &alu, &val)) {
-++			emit_alu_i(ctx, dst, val, alu);
-++		}
-++		emit_zext_ver(ctx, dst);
-++		break;
-++	/* dst = dst >> imm */
-++	/* dst = dst >> imm (arithmetic) */
-++	/* dst = dst + imm */
-++	/* dst = dst - imm */
-++	/* dst = dst * imm */
-++	/* dst = dst / imm */
-++	/* dst = dst % imm */
-++	case BPF_ALU | BPF_RSH | BPF_K:
-++	case BPF_ALU | BPF_ARSH | BPF_K:
-++	case BPF_ALU | BPF_ADD | BPF_K:
-++	case BPF_ALU | BPF_SUB | BPF_K:
-++	case BPF_ALU | BPF_MUL | BPF_K:
-++	case BPF_ALU | BPF_DIV | BPF_K:
-++	case BPF_ALU | BPF_MOD | BPF_K:
-++		if (!valid_alu_i(BPF_OP(code), imm)) {
-++			emit_sext(ctx, dst, dst);
-++			emit_mov_i(ctx, MIPS_R_T4, imm);
-++			emit_alu_r(ctx, dst, MIPS_R_T4, BPF_OP(code));
-++		} else if (rewrite_alu_i(BPF_OP(code), imm, &alu, &val)) {
-++			emit_sext(ctx, dst, dst);
-++			emit_alu_i(ctx, dst, val, alu);
-++		}
-++		emit_zext_ver(ctx, dst);
-++		break;
-++	/* dst = dst & src */
-++	/* dst = dst | src */
-++	/* dst = dst ^ src */
-++	/* dst = dst << src */
-++	case BPF_ALU | BPF_AND | BPF_X:
-++	case BPF_ALU | BPF_OR | BPF_X:
-++	case BPF_ALU | BPF_XOR | BPF_X:
-++	case BPF_ALU | BPF_LSH | BPF_X:
-++		emit_alu_r(ctx, dst, src, BPF_OP(code));
-++		emit_zext_ver(ctx, dst);
-++		break;
-++	/* dst = dst >> src */
-++	/* dst = dst >> src (arithmetic) */
-++	/* dst = dst + src */
-++	/* dst = dst - src */
-++	/* dst = dst * src */
-++	/* dst = dst / src */
-++	/* dst = dst % src */
-++	case BPF_ALU | BPF_RSH | BPF_X:
-++	case BPF_ALU | BPF_ARSH | BPF_X:
-++	case BPF_ALU | BPF_ADD | BPF_X:
-++	case BPF_ALU | BPF_SUB | BPF_X:
-++	case BPF_ALU | BPF_MUL | BPF_X:
-++	case BPF_ALU | BPF_DIV | BPF_X:
-++	case BPF_ALU | BPF_MOD | BPF_X:
-++		emit_sext(ctx, dst, dst);
-++		emit_sext(ctx, MIPS_R_T4, src);
-++		emit_alu_r(ctx, dst, MIPS_R_T4, BPF_OP(code));
-++		emit_zext_ver(ctx, dst);
-++		break;
-++	/* dst = imm (64-bit) */
-++	case BPF_ALU64 | BPF_MOV | BPF_K:
-++		emit_mov_i(ctx, dst, imm);
-++		break;
-++	/* dst = src (64-bit) */
-++	case BPF_ALU64 | BPF_MOV | BPF_X:
-++		emit_mov_r(ctx, dst, src);
-++		break;
-++	/* dst = -dst (64-bit) */
-++	case BPF_ALU64 | BPF_NEG:
-++		emit_alu_i64(ctx, dst, 0, BPF_NEG);
-++		break;
-++	/* dst = dst & imm (64-bit) */
-++	/* dst = dst | imm (64-bit) */
-++	/* dst = dst ^ imm (64-bit) */
-++	/* dst = dst << imm (64-bit) */
-++	/* dst = dst >> imm (64-bit) */
-++	/* dst = dst >> imm ((64-bit, arithmetic) */
-++	/* dst = dst + imm (64-bit) */
-++	/* dst = dst - imm (64-bit) */
-++	/* dst = dst * imm (64-bit) */
-++	/* dst = dst / imm (64-bit) */
-++	/* dst = dst % imm (64-bit) */
-++	case BPF_ALU64 | BPF_AND | BPF_K:
-++	case BPF_ALU64 | BPF_OR | BPF_K:
-++	case BPF_ALU64 | BPF_XOR | BPF_K:
-++	case BPF_ALU64 | BPF_LSH | BPF_K:
-++	case BPF_ALU64 | BPF_RSH | BPF_K:
-++	case BPF_ALU64 | BPF_ARSH | BPF_K:
-++	case BPF_ALU64 | BPF_ADD | BPF_K:
-++	case BPF_ALU64 | BPF_SUB | BPF_K:
-++	case BPF_ALU64 | BPF_MUL | BPF_K:
-++	case BPF_ALU64 | BPF_DIV | BPF_K:
-++	case BPF_ALU64 | BPF_MOD | BPF_K:
-++		if (!valid_alu_i(BPF_OP(code), imm)) {
-++			emit_mov_i(ctx, MIPS_R_T4, imm);
-++			emit_alu_r64(ctx, dst, MIPS_R_T4, BPF_OP(code));
-++		} else if (rewrite_alu_i(BPF_OP(code), imm, &alu, &val)) {
-++			emit_alu_i64(ctx, dst, val, alu);
-++		}
-++		break;
-++	/* dst = dst & src (64-bit) */
-++	/* dst = dst | src (64-bit) */
-++	/* dst = dst ^ src (64-bit) */
-++	/* dst = dst << src (64-bit) */
-++	/* dst = dst >> src (64-bit) */
-++	/* dst = dst >> src (64-bit, arithmetic) */
-++	/* dst = dst + src (64-bit) */
-++	/* dst = dst - src (64-bit) */
-++	/* dst = dst * src (64-bit) */
-++	/* dst = dst / src (64-bit) */
-++	/* dst = dst % src (64-bit) */
-++	case BPF_ALU64 | BPF_AND | BPF_X:
-++	case BPF_ALU64 | BPF_OR | BPF_X:
-++	case BPF_ALU64 | BPF_XOR | BPF_X:
-++	case BPF_ALU64 | BPF_LSH | BPF_X:
-++	case BPF_ALU64 | BPF_RSH | BPF_X:
-++	case BPF_ALU64 | BPF_ARSH | BPF_X:
-++	case BPF_ALU64 | BPF_ADD | BPF_X:
-++	case BPF_ALU64 | BPF_SUB | BPF_X:
-++	case BPF_ALU64 | BPF_MUL | BPF_X:
-++	case BPF_ALU64 | BPF_DIV | BPF_X:
-++	case BPF_ALU64 | BPF_MOD | BPF_X:
-++		emit_alu_r64(ctx, dst, src, BPF_OP(code));
-++		break;
-++	/* dst = htole(dst) */
-++	/* dst = htobe(dst) */
-++	case BPF_ALU | BPF_END | BPF_FROM_LE:
-++	case BPF_ALU | BPF_END | BPF_FROM_BE:
-++		if (BPF_SRC(code) ==
-++#ifdef __BIG_ENDIAN
-++		    BPF_FROM_LE
-++#else
-++		    BPF_FROM_BE
-++#endif
-++		    )
-++			emit_bswap_r64(ctx, dst, imm);
-++		else
-++			emit_trunc_r64(ctx, dst, imm);
-++		break;
-++	/* dst = imm64 */
-++	case BPF_LD | BPF_IMM | BPF_DW:
-++		emit_mov_i64(ctx, dst, (u32)imm | ((u64)insn[1].imm << 32));
-++		return 1;
-++	/* LDX: dst = *(size *)(src + off) */
-++	case BPF_LDX | BPF_MEM | BPF_W:
-++	case BPF_LDX | BPF_MEM | BPF_H:
-++	case BPF_LDX | BPF_MEM | BPF_B:
-++	case BPF_LDX | BPF_MEM | BPF_DW:
-++		emit_ldx(ctx, dst, src, off, BPF_SIZE(code));
-++		break;
-++	/* ST: *(size *)(dst + off) = imm */
-++	case BPF_ST | BPF_MEM | BPF_W:
-++	case BPF_ST | BPF_MEM | BPF_H:
-++	case BPF_ST | BPF_MEM | BPF_B:
-++	case BPF_ST | BPF_MEM | BPF_DW:
-++		emit_mov_i(ctx, MIPS_R_T4, imm);
-++		emit_stx(ctx, dst, MIPS_R_T4, off, BPF_SIZE(code));
-++		break;
-++	/* STX: *(size *)(dst + off) = src */
-++	case BPF_STX | BPF_MEM | BPF_W:
-++	case BPF_STX | BPF_MEM | BPF_H:
-++	case BPF_STX | BPF_MEM | BPF_B:
-++	case BPF_STX | BPF_MEM | BPF_DW:
-++		emit_stx(ctx, dst, src, off, BPF_SIZE(code));
-++		break;
-++	/* Speculation barrier */
-++	case BPF_ST | BPF_NOSPEC:
-++		break;
-++	/* Atomics */
-++	case BPF_STX | BPF_XADD | BPF_W:
-++	case BPF_STX | BPF_XADD | BPF_DW:
-++		switch (imm) {
-++		case BPF_ADD:
-++		case BPF_AND:
-++		case BPF_OR:
-++		case BPF_XOR:
-++			if (BPF_SIZE(code) == BPF_DW) {
-++				emit_atomic_r64(ctx, dst, src, off, imm);
-++			} else { /* 32-bit, no fetch */
-++				emit_sext(ctx, MIPS_R_T4, src);
-++				emit_atomic_r(ctx, dst, MIPS_R_T4, off, imm);
-++			}
-++			break;
-++		default:
-++			goto notyet;
-++		}
-++		break;
-++	/* PC += off if dst == src */
-++	/* PC += off if dst != src */
-++	/* PC += off if dst & src */
-++	/* PC += off if dst > src */
-++	/* PC += off if dst >= src */
-++	/* PC += off if dst < src */
-++	/* PC += off if dst <= src */
-++	/* PC += off if dst > src (signed) */
-++	/* PC += off if dst >= src (signed) */
-++	/* PC += off if dst < src (signed) */
-++	/* PC += off if dst <= src (signed) */
-++	case BPF_JMP32 | BPF_JEQ | BPF_X:
-++	case BPF_JMP32 | BPF_JNE | BPF_X:
-++	case BPF_JMP32 | BPF_JSET | BPF_X:
-++	case BPF_JMP32 | BPF_JGT | BPF_X:
-++	case BPF_JMP32 | BPF_JGE | BPF_X:
-++	case BPF_JMP32 | BPF_JLT | BPF_X:
-++	case BPF_JMP32 | BPF_JLE | BPF_X:
-++	case BPF_JMP32 | BPF_JSGT | BPF_X:
-++	case BPF_JMP32 | BPF_JSGE | BPF_X:
-++	case BPF_JMP32 | BPF_JSLT | BPF_X:
-++	case BPF_JMP32 | BPF_JSLE | BPF_X:
-++		if (off == 0)
-++			break;
-++		setup_jmp_r(ctx, dst == src, BPF_OP(code), off, &jmp, &rel);
-++		emit_sext(ctx, MIPS_R_T4, dst); /* Sign-extended dst */
-++		emit_sext(ctx, MIPS_R_T5, src); /* Sign-extended src */
-++		emit_jmp_r(ctx, MIPS_R_T4, MIPS_R_T5, rel, jmp);
-++		if (finish_jmp(ctx, jmp, off) < 0)
-++			goto toofar;
-++		break;
-++	/* PC += off if dst == imm */
-++	/* PC += off if dst != imm */
-++	/* PC += off if dst & imm */
-++	/* PC += off if dst > imm */
-++	/* PC += off if dst >= imm */
-++	/* PC += off if dst < imm */
-++	/* PC += off if dst <= imm */
-++	/* PC += off if dst > imm (signed) */
-++	/* PC += off if dst >= imm (signed) */
-++	/* PC += off if dst < imm (signed) */
-++	/* PC += off if dst <= imm (signed) */
-++	case BPF_JMP32 | BPF_JEQ | BPF_K:
-++	case BPF_JMP32 | BPF_JNE | BPF_K:
-++	case BPF_JMP32 | BPF_JSET | BPF_K:
-++	case BPF_JMP32 | BPF_JGT | BPF_K:
-++	case BPF_JMP32 | BPF_JGE | BPF_K:
-++	case BPF_JMP32 | BPF_JLT | BPF_K:
-++	case BPF_JMP32 | BPF_JLE | BPF_K:
-++	case BPF_JMP32 | BPF_JSGT | BPF_K:
-++	case BPF_JMP32 | BPF_JSGE | BPF_K:
-++	case BPF_JMP32 | BPF_JSLT | BPF_K:
-++	case BPF_JMP32 | BPF_JSLE | BPF_K:
-++		if (off == 0)
-++			break;
-++		setup_jmp_i(ctx, imm, 32, BPF_OP(code), off, &jmp, &rel);
-++		emit_sext(ctx, MIPS_R_T4, dst); /* Sign-extended dst */
-++		if (valid_jmp_i(jmp, imm)) {
-++			emit_jmp_i(ctx, MIPS_R_T4, imm, rel, jmp);
-++		} else {
-++			/* Move large immediate to register, sign-extended */
-++			emit_mov_i(ctx, MIPS_R_T5, imm);
-++			emit_jmp_r(ctx, MIPS_R_T4, MIPS_R_T5, rel, jmp);
-++		}
-++		if (finish_jmp(ctx, jmp, off) < 0)
-++			goto toofar;
-++		break;
-++	/* PC += off if dst == src */
-++	/* PC += off if dst != src */
-++	/* PC += off if dst & src */
-++	/* PC += off if dst > src */
-++	/* PC += off if dst >= src */
-++	/* PC += off if dst < src */
-++	/* PC += off if dst <= src */
-++	/* PC += off if dst > src (signed) */
-++	/* PC += off if dst >= src (signed) */
-++	/* PC += off if dst < src (signed) */
-++	/* PC += off if dst <= src (signed) */
-++	case BPF_JMP | BPF_JEQ | BPF_X:
-++	case BPF_JMP | BPF_JNE | BPF_X:
-++	case BPF_JMP | BPF_JSET | BPF_X:
-++	case BPF_JMP | BPF_JGT | BPF_X:
-++	case BPF_JMP | BPF_JGE | BPF_X:
-++	case BPF_JMP | BPF_JLT | BPF_X:
-++	case BPF_JMP | BPF_JLE | BPF_X:
-++	case BPF_JMP | BPF_JSGT | BPF_X:
-++	case BPF_JMP | BPF_JSGE | BPF_X:
-++	case BPF_JMP | BPF_JSLT | BPF_X:
-++	case BPF_JMP | BPF_JSLE | BPF_X:
-++		if (off == 0)
-++			break;
-++		setup_jmp_r(ctx, dst == src, BPF_OP(code), off, &jmp, &rel);
-++		emit_jmp_r(ctx, dst, src, rel, jmp);
-++		if (finish_jmp(ctx, jmp, off) < 0)
-++			goto toofar;
-++		break;
-++	/* PC += off if dst == imm */
-++	/* PC += off if dst != imm */
-++	/* PC += off if dst & imm */
-++	/* PC += off if dst > imm */
-++	/* PC += off if dst >= imm */
-++	/* PC += off if dst < imm */
-++	/* PC += off if dst <= imm */
-++	/* PC += off if dst > imm (signed) */
-++	/* PC += off if dst >= imm (signed) */
-++	/* PC += off if dst < imm (signed) */
-++	/* PC += off if dst <= imm (signed) */
-++	case BPF_JMP | BPF_JEQ | BPF_K:
-++	case BPF_JMP | BPF_JNE | BPF_K:
-++	case BPF_JMP | BPF_JSET | BPF_K:
-++	case BPF_JMP | BPF_JGT | BPF_K:
-++	case BPF_JMP | BPF_JGE | BPF_K:
-++	case BPF_JMP | BPF_JLT | BPF_K:
-++	case BPF_JMP | BPF_JLE | BPF_K:
-++	case BPF_JMP | BPF_JSGT | BPF_K:
-++	case BPF_JMP | BPF_JSGE | BPF_K:
-++	case BPF_JMP | BPF_JSLT | BPF_K:
-++	case BPF_JMP | BPF_JSLE | BPF_K:
-++		if (off == 0)
-++			break;
-++		setup_jmp_i(ctx, imm, 64, BPF_OP(code), off, &jmp, &rel);
-++		if (valid_jmp_i(jmp, imm)) {
-++			emit_jmp_i(ctx, dst, imm, rel, jmp);
-++		} else {
-++			/* Move large immediate to register */
-++			emit_mov_i(ctx, MIPS_R_T4, imm);
-++			emit_jmp_r(ctx, dst, MIPS_R_T4, rel, jmp);
-++		}
-++		if (finish_jmp(ctx, jmp, off) < 0)
-++			goto toofar;
-++		break;
-++	/* PC += off */
-++	case BPF_JMP | BPF_JA:
-++		if (off == 0)
-++			break;
-++		if (emit_ja(ctx, off) < 0)
-++			goto toofar;
-++		break;
-++	/* Tail call */
-++	case BPF_JMP | BPF_TAIL_CALL:
-++		if (emit_tail_call(ctx) < 0)
-++			goto invalid;
-++		break;
-++	/* Function call */
-++	case BPF_JMP | BPF_CALL:
-++		if (emit_call(ctx, insn) < 0)
-++			goto invalid;
-++		break;
-++	/* Function return */
-++	case BPF_JMP | BPF_EXIT:
-++		/*
-++		 * Optimization: when last instruction is EXIT
-++		 * simply continue to epilogue.
-++		 */
-++		if (ctx->bpf_index == ctx->program->len - 1)
-++			break;
-++		if (emit_exit(ctx) < 0)
-++			goto toofar;
-++		break;
-++
-++	default:
-++invalid:
-++		pr_err_once("unknown opcode %02x\n", code);
-++		return -EINVAL;
-++notyet:
-++		pr_info_once("*** NOT YET: opcode %02x ***\n", code);
-++		return -EFAULT;
-++toofar:
-++		pr_info_once("*** TOO FAR: jump at %u opcode %02x ***\n",
-++			     ctx->bpf_index, code);
-++		return -E2BIG;
-++	}
-++	return 0;
-++}
-diff --git a/target/linux/generic/backport-5.15/050-v5.16-04-mips-bpf-Add-JIT-workarounds-for-CPU-errata.patch b/target/linux/generic/backport-5.15/050-v5.16-04-mips-bpf-Add-JIT-workarounds-for-CPU-errata.patch
++ endmenu
+diff --git a/target/linux/generic/backport-5.15/020-v6.1-10-mm-multigenerational-lru-documentation.patch b/target/linux/generic/backport-5.15/020-v6.1-10-mm-multigenerational-lru-documentation.patch
 new file mode 100644
-index 0000000000..63553ebe58
+index 0000000000..f4716fb68d
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/050-v5.16-04-mips-bpf-Add-JIT-workarounds-for-CPU-errata.patch
-@@ -0,0 +1,120 @@
-+From: Johan Almbladh <johan.almbladh@anyfinetworks.com>
-+Date: Tue, 5 Oct 2021 18:54:06 +0200
-+Subject: [PATCH] mips: bpf: Add JIT workarounds for CPU errata
-+
-+This patch adds workarounds for the following CPU errata to the MIPS
-+eBPF JIT, if enabled in the kernel configuration.
-+
-+  - R10000 ll/sc weak ordering
-+  - Loongson-3 ll/sc weak ordering
-+  - Loongson-2F jump hang
-+
-+The Loongson-2F nop errata is implemented in uasm, which the JIT uses,
-+so no additional mitigations are needed for that.
-+
-+Signed-off-by: Johan Almbladh <johan.almbladh@anyfinetworks.com>
-+Reviewed-by: Jiaxun Yang <jiaxun.yang@flygoat.com>
++++ b/target/linux/generic/backport-5.15/020-v6.1-10-mm-multigenerational-lru-documentation.patch
+@@ -0,0 +1,161 @@
++From f59c618ed70a1e48accc4cad91a200966f2569c9 Mon Sep 17 00:00:00 2001
++From: Yu Zhao <yuzhao@google.com>
++Date: Tue, 2 Feb 2021 01:27:45 -0700
++Subject: [PATCH 10/10] mm: multigenerational lru: documentation
++
++Add Documentation/vm/multigen_lru.rst.
++
++Signed-off-by: Yu Zhao <yuzhao@google.com>
++Tested-by: Konstantin Kharlamov <Hi-Angel@yandex.ru>
++Change-Id: I1902178bcbb5adfa0a748c4d284a6456059bdd7e
 +---
++ Documentation/vm/index.rst        |   1 +
++ Documentation/vm/multigen_lru.rst | 132 ++++++++++++++++++++++++++++++
++ 2 files changed, 133 insertions(+)
++ create mode 100644 Documentation/vm/multigen_lru.rst
 +
-+--- a/arch/mips/net/bpf_jit_comp.c
-++++ b/arch/mips/net/bpf_jit_comp.c
-+@@ -404,6 +404,7 @@ void emit_alu_r(struct jit_context *ctx,
-+ /* Atomic read-modify-write (32-bit) */
-+ void emit_atomic_r(struct jit_context *ctx, u8 dst, u8 src, s16 off, u8 code)
-+ {
-++	LLSC_sync(ctx);
-+ 	emit(ctx, ll, MIPS_R_T9, off, dst);
-+ 	switch (code) {
-+ 	case BPF_ADD:
-+@@ -420,18 +421,19 @@ void emit_atomic_r(struct jit_context *c
-+ 		break;
-+ 	}
-+ 	emit(ctx, sc, MIPS_R_T8, off, dst);
-+-	emit(ctx, beqz, MIPS_R_T8, -16);
-++	emit(ctx, LLSC_beqz, MIPS_R_T8, -16 - LLSC_offset);
-+ 	emit(ctx, nop); /* Delay slot */
-+ }
-+ 
-+ /* Atomic compare-and-exchange (32-bit) */
-+ void emit_cmpxchg_r(struct jit_context *ctx, u8 dst, u8 src, u8 res, s16 off)
-+ {
-++	LLSC_sync(ctx);
-+ 	emit(ctx, ll, MIPS_R_T9, off, dst);
-+ 	emit(ctx, bne, MIPS_R_T9, res, 12);
-+ 	emit(ctx, move, MIPS_R_T8, src);     /* Delay slot */
-+ 	emit(ctx, sc, MIPS_R_T8, off, dst);
-+-	emit(ctx, beqz, MIPS_R_T8, -20);
-++	emit(ctx, LLSC_beqz, MIPS_R_T8, -20 - LLSC_offset);
-+ 	emit(ctx, move, res, MIPS_R_T9);     /* Delay slot */
-+ 	clobber_reg(ctx, res);
-+ }
-+--- a/arch/mips/net/bpf_jit_comp.h
-++++ b/arch/mips/net/bpf_jit_comp.h
-+@@ -87,7 +87,7 @@ struct jit_context {
-+ };
++--- a/Documentation/vm/index.rst
+++++ b/Documentation/vm/index.rst
++@@ -17,6 +17,7 @@ various features of the Linux memory man
 + 
-+ /* Emit the instruction if the JIT memory space has been allocated */
-+-#define emit(ctx, func, ...)					\
-++#define __emit(ctx, func, ...)					\
-+ do {								\
-+ 	if ((ctx)->target != NULL) {				\
-+ 		u32 *p = &(ctx)->target[ctx->jit_index];	\
-+@@ -95,6 +95,30 @@ do {								\
-+ 	}							\
-+ 	(ctx)->jit_index++;					\
-+ } while (0)
-++#define emit(...) __emit(__VA_ARGS__)
-++
-++/* Workaround for R10000 ll/sc errata */
-++#ifdef CONFIG_WAR_R10000
-++#define LLSC_beqz	beqzl
-++#else
-++#define LLSC_beqz	beqz
-++#endif
-++
-++/* Workaround for Loongson-3 ll/sc errata */
-++#ifdef CONFIG_CPU_LOONGSON3_WORKAROUNDS
-++#define LLSC_sync(ctx)	emit(ctx, sync, 0)
-++#define LLSC_offset	4
-++#else
-++#define LLSC_sync(ctx)
-++#define LLSC_offset	0
-++#endif
-++
-++/* Workaround for Loongson-2F jump errata */
-++#ifdef CONFIG_CPU_JUMP_WORKAROUNDS
-++#define JALR_MASK	0xffffffffcfffffffULL
-++#else
-++#define JALR_MASK	(~0ULL)
-++#endif
++    swap_numa
++    zswap
+++   multigen_lru
 + 
-+ /*
-+  * Mark a BPF register as accessed, it needs to be
-+--- a/arch/mips/net/bpf_jit_comp64.c
-++++ b/arch/mips/net/bpf_jit_comp64.c
-+@@ -375,6 +375,7 @@ static void emit_atomic_r64(struct jit_c
-+ 	u8 t1 = MIPS_R_T6;
-+ 	u8 t2 = MIPS_R_T7;
++ Kernel developers MM documentation
++ ==================================
++--- /dev/null
+++++ b/Documentation/vm/multigen_lru.rst
++@@ -0,0 +1,132 @@
+++.. SPDX-License-Identifier: GPL-2.0
+++
+++=====================
+++Multigenerational LRU
+++=====================
+++
+++Quick Start
+++===========
+++Build Configurations
+++--------------------
+++:Required: Set ``CONFIG_LRU_GEN=y``.
+++
+++:Optional: Set ``CONFIG_LRU_GEN_ENABLED=y`` to turn the feature on by
+++ default.
+++
+++Runtime Configurations
+++----------------------
+++:Required: Write ``1`` to ``/sys/kernel/mm/lru_gen/enable`` if the
+++ feature was not turned on by default.
+++
+++:Optional: Write ``N`` to ``/sys/kernel/mm/lru_gen/min_ttl_ms`` to
+++ protect the working set of ``N`` milliseconds. The OOM killer is
+++ invoked if this working set cannot be kept in memory.
+++
+++:Optional: Read ``/sys/kernel/debug/lru_gen`` to confirm the feature
+++ is turned on. This file has the following output:
+++
+++::
+++
+++  memcg  memcg_id  memcg_path
+++    node  node_id
+++      min_gen  birth_time  anon_size  file_size
+++      ...
+++      max_gen  birth_time  anon_size  file_size
+++
+++``min_gen`` is the oldest generation number and ``max_gen`` is the
+++youngest generation number. ``birth_time`` is in milliseconds.
+++``anon_size`` and ``file_size`` are in pages.
+++
+++Phones/Laptops/Workstations
+++---------------------------
+++No additional configurations required.
+++
+++Servers/Data Centers
+++--------------------
+++:To support more generations: Change ``CONFIG_NR_LRU_GENS`` to a
+++ larger number.
+++
+++:To support more tiers: Change ``CONFIG_TIERS_PER_GEN`` to a larger
+++ number.
+++
+++:To support full stats: Set ``CONFIG_LRU_GEN_STATS=y``.
+++
+++:Working set estimation: Write ``+ memcg_id node_id max_gen
+++ [swappiness] [use_bloom_filter]`` to ``/sys/kernel/debug/lru_gen`` to
+++ invoke the aging, which scans PTEs for accessed pages and then
+++ creates the next generation ``max_gen+1``. A swap file and a non-zero
+++ ``swappiness``, which overrides ``vm.swappiness``, are required to
+++ scan PTEs mapping anon pages. Set ``use_bloom_filter`` to 0 to
+++ override the default behavior which only scans PTE tables found
+++ populated.
+++
+++:Proactive reclaim: Write ``- memcg_id node_id min_gen [swappiness]
+++ [nr_to_reclaim]`` to ``/sys/kernel/debug/lru_gen`` to invoke the
+++ eviction, which evicts generations less than or equal to ``min_gen``.
+++ ``min_gen`` should be less than ``max_gen-1`` as ``max_gen`` and
+++ ``max_gen-1`` are not fully aged and therefore cannot be evicted.
+++ Use ``nr_to_reclaim`` to limit the number of pages to evict. Multiple
+++ command lines are supported, so does concatenation with delimiters
+++ ``,`` and ``;``.
+++
+++Framework
+++=========
+++For each ``lruvec``, evictable pages are divided into multiple
+++generations. The youngest generation number is stored in
+++``lrugen->max_seq`` for both anon and file types as they are aged on
+++an equal footing. The oldest generation numbers are stored in
+++``lrugen->min_seq[]`` separately for anon and file types as clean
+++file pages can be evicted regardless of swap and writeback
+++constraints. These three variables are monotonically increasing.
+++Generation numbers are truncated into
+++``order_base_2(CONFIG_NR_LRU_GENS+1)`` bits in order to fit into
+++``page->flags``. The sliding window technique is used to prevent
+++truncated generation numbers from overlapping. Each truncated
+++generation number is an index to an array of per-type and per-zone
+++lists ``lrugen->lists``.
+++
+++Each generation is divided into multiple tiers. Tiers represent
+++different ranges of numbers of accesses from file descriptors only.
+++Pages accessed ``N`` times via file descriptors belong to tier
+++``order_base_2(N)``. Each generation contains at most
+++``CONFIG_TIERS_PER_GEN`` tiers, and they require additional
+++``CONFIG_TIERS_PER_GEN-2`` bits in ``page->flags``. In contrast to
+++moving between generations which requires list operations, moving
+++between tiers only involves operations on ``page->flags`` and
+++therefore has a negligible cost. A feedback loop modeled after the PID
+++controller monitors refaulted % across all tiers and decides when to
+++protect pages from which tiers.
+++
+++The framework comprises two conceptually independent components: the
+++aging and the eviction, which can be invoked separately from user
+++space for the purpose of working set estimation and proactive reclaim.
+++
+++Aging
+++-----
+++The aging produces young generations. Given an ``lruvec``, the aging
+++traverses ``lruvec_memcg()->mm_list`` and calls ``walk_page_range()``
+++to scan PTEs for accessed pages (a ``mm_struct`` list is maintained
+++for each ``memcg``). Upon finding one, the aging updates its
+++generation number to ``max_seq`` (modulo ``CONFIG_NR_LRU_GENS``).
+++After each round of traversal, the aging increments ``max_seq``. The
+++aging is due when ``min_seq[]`` reaches ``max_seq-1``.
+++
+++Eviction
+++--------
+++The eviction consumes old generations. Given an ``lruvec``, the
+++eviction scans pages on the per-zone lists indexed by anon and file
+++``min_seq[]`` (modulo ``CONFIG_NR_LRU_GENS``). It first tries to
+++select a type based on the values of ``min_seq[]``. If they are
+++equal, it selects the type that has a lower refaulted %. The eviction
+++sorts a page according to its updated generation number if the aging
+++has found this page accessed. It also moves a page to the next
+++generation if this page is from an upper tier that has a higher
+++refaulted % than the base tier. The eviction increments ``min_seq[]``
+++of a selected type when it finds all the per-zone lists indexed by
+++``min_seq[]`` of this selected type are empty.
+++
+++To-do List
+++==========
+++KVM Optimization
+++----------------
+++Support shadow page table walk.
+diff --git a/target/linux/generic/backport-5.15/021-v6.1-mm-mglru-don-t-sync-disk-for-each-aging-cycle.patch b/target/linux/generic/backport-5.15/021-v6.1-mm-mglru-don-t-sync-disk-for-each-aging-cycle.patch
+new file mode 100644
+index 0000000000..269e5eb204
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/021-v6.1-mm-mglru-don-t-sync-disk-for-each-aging-cycle.patch
+@@ -0,0 +1,32 @@
++From 14aa8b2d5c2ebead01b542f62d68029023054774 Mon Sep 17 00:00:00 2001
++From: Yu Zhao <yuzhao@google.com>
++Date: Wed, 28 Sep 2022 13:36:58 -0600
++Subject: [PATCH 1/1] mm/mglru: don't sync disk for each aging cycle
++
++wakeup_flusher_threads() was added under the assumption that if a system
++runs out of clean cold pages, it might want to write back dirty pages more
++aggressively so that they can become clean and be dropped.
++
++However, doing so can breach the rate limit a system wants to impose on
++writeback, resulting in early SSD wearout.
++
++Link: https://lkml.kernel.org/r/YzSiWq9UEER5LKup@google.com
++Fixes: bd74fdaea146 ("mm: multi-gen LRU: support page table walks")
++Signed-off-by: Yu Zhao <yuzhao@google.com>
++Reported-by: Axel Rasmussen <axelrasmussen@google.com>
++Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
++---
++ mm/vmscan.c | 2 --
++ 1 file changed, 2 deletions(-)
++
++--- a/mm/vmscan.c
+++++ b/mm/vmscan.c
++@@ -4072,8 +4072,6 @@ static bool try_to_inc_max_seq(struct lr
++ 	if (wq_has_sleeper(&lruvec->mm_walk.wait))
++ 		wake_up_all(&lruvec->mm_walk.wait);
 + 
-++	LLSC_sync(ctx);
-+ 	emit(ctx, lld, t1, off, dst);
-+ 	switch (code) {
-+ 	case BPF_ADD:
-+@@ -391,7 +392,7 @@ static void emit_atomic_r64(struct jit_c
-+ 		break;
-+ 	}
-+ 	emit(ctx, scd, t2, off, dst);
-+-	emit(ctx, beqz, t2, -16);
-++	emit(ctx, LLSC_beqz, t2, -16 - LLSC_offset);
-+ 	emit(ctx, nop); /* Delay slot */
++-	wakeup_flusher_threads(WB_REASON_VMSCAN);
++-
++ 	return true;
 + }
 + 
-+@@ -414,7 +415,7 @@ static int emit_call(struct jit_context
-+ 	push_regs(ctx, ctx->clobbered & JIT_CALLER_REGS, 0, 0);
-+ 
-+ 	/* Emit function call */
-+-	emit_mov_i64(ctx, tmp, addr);
-++	emit_mov_i64(ctx, tmp, addr & JALR_MASK);
-+ 	emit(ctx, jalr, MIPS_R_RA, tmp);
-+ 	emit(ctx, nop); /* Delay slot */
-+ 
-diff --git a/target/linux/generic/backport-5.15/050-v5.16-05-mips-bpf-Enable-eBPF-JITs.patch b/target/linux/generic/backport-5.15/050-v5.16-05-mips-bpf-Enable-eBPF-JITs.patch
+diff --git a/target/linux/generic/backport-5.15/050-v5.16-00-MIPS-uasm-Enable-muhu-opcode-for-MIPS-R6.patch b/target/linux/generic/backport-5.15/050-v5.16-00-MIPS-uasm-Enable-muhu-opcode-for-MIPS-R6.patch
 new file mode 100644
-index 0000000000..41b3d8139f
+index 0000000000..82feb7421d
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/050-v5.16-05-mips-bpf-Enable-eBPF-JITs.patch
-@@ -0,0 +1,61 @@
++++ b/target/linux/generic/backport-5.15/050-v5.16-00-MIPS-uasm-Enable-muhu-opcode-for-MIPS-R6.patch
+@@ -0,0 +1,65 @@
 +From: Johan Almbladh <johan.almbladh@anyfinetworks.com>
-+Date: Tue, 5 Oct 2021 18:54:07 +0200
-+Subject: [PATCH] mips: bpf: Enable eBPF JITs
++Date: Tue, 5 Oct 2021 18:54:02 +0200
++Subject: [PATCH] MIPS: uasm: Enable muhu opcode for MIPS R6
 +
-+This patch enables the new eBPF JITs for 32-bit and 64-bit MIPS. It also
-+disables the old cBPF JIT to so cBPF programs are converted to use the
-+new JIT.
++Enable the 'muhu' instruction, complementing the existing 'mulu', needed
++to implement a MIPS32 BPF JIT.
 +
-+Workarounds for R4000 CPU errata are not implemented by the JIT, so the
-+JIT is disabled if any of those workarounds are configured.
++Also fix a typo in the existing definition of 'dmulu'.
++
++Signed-off-by: Tony Ambardar <Tony.Ambardar@gmail.com>
++
++This patch is a dependency for my 32-bit MIPS eBPF JIT.
 +
 +Signed-off-by: Johan Almbladh <johan.almbladh@anyfinetworks.com>
 +---
 +
-+--- a/MAINTAINERS
-++++ b/MAINTAINERS
-+@@ -3430,6 +3430,7 @@ S:	Supported
-+ F:	arch/arm64/net/
-+ 
-+ BPF JIT for MIPS (32-BIT AND 64-BIT)
-++M:	Johan Almbladh <johan.almbladh@anyfinetworks.com>
-+ M:	Paul Burton <paulburton@kernel.org>
-+ L:	netdev@vger.kernel.org
-+ L:	bpf@vger.kernel.org
-+--- a/arch/mips/Kconfig
-++++ b/arch/mips/Kconfig
-+@@ -57,7 +57,6 @@ config MIPS
-+ 	select HAVE_ARCH_TRACEHOOK
-+ 	select HAVE_ARCH_TRANSPARENT_HUGEPAGE if CPU_SUPPORTS_HUGEPAGES
-+ 	select HAVE_ASM_MODVERSIONS
-+-	select HAVE_CBPF_JIT if !64BIT && !CPU_MICROMIPS
-+ 	select HAVE_CONTEXT_TRACKING
-+ 	select HAVE_TIF_NOHZ
-+ 	select HAVE_C_RECORDMCOUNT
-+@@ -65,7 +64,10 @@ config MIPS
-+ 	select HAVE_DEBUG_STACKOVERFLOW
-+ 	select HAVE_DMA_CONTIGUOUS
-+ 	select HAVE_DYNAMIC_FTRACE
-+-	select HAVE_EBPF_JIT if 64BIT && !CPU_MICROMIPS && TARGET_ISA_REV >= 2
-++	select HAVE_EBPF_JIT if !CPU_MICROMIPS && \
-++				!CPU_DADDI_WORKAROUNDS && \
-++				!CPU_R4000_WORKAROUNDS && \
-++				!CPU_R4400_WORKAROUNDS
-+ 	select HAVE_EXIT_THREAD
-+ 	select HAVE_FAST_GUP
-+ 	select HAVE_FTRACE_MCOUNT_RECORD
-+--- a/arch/mips/net/Makefile
-++++ b/arch/mips/net/Makefile
-+@@ -2,9 +2,10 @@
-+ # MIPS networking code
-+ 
-+ obj-$(CONFIG_MIPS_CBPF_JIT) += bpf_jit.o bpf_jit_asm.o
-++obj-$(CONFIG_MIPS_EBPF_JIT) += bpf_jit_comp.o
-+ 
-+ ifeq ($(CONFIG_32BIT),y)
-+-        obj-$(CONFIG_MIPS_EBPF_JIT) += bpf_jit_comp.o bpf_jit_comp32.o
-++        obj-$(CONFIG_MIPS_EBPF_JIT) += bpf_jit_comp32.o
-+ else
-+-        obj-$(CONFIG_MIPS_EBPF_JIT) += ebpf_jit.o
-++        obj-$(CONFIG_MIPS_EBPF_JIT) += bpf_jit_comp64.o
-+ endif
-diff --git a/target/linux/generic/backport-5.15/050-v5.16-06-mips-bpf-Remove-old-BPF-JIT-implementations.patch b/target/linux/generic/backport-5.15/050-v5.16-06-mips-bpf-Remove-old-BPF-JIT-implementations.patch
++--- a/arch/mips/include/asm/uasm.h
+++++ b/arch/mips/include/asm/uasm.h
++@@ -145,6 +145,7 @@ Ip_u1(_mtlo);
++ Ip_u3u1u2(_mul);
++ Ip_u1u2(_multu);
++ Ip_u3u1u2(_mulu);
+++Ip_u3u1u2(_muhu);
++ Ip_u3u1u2(_nor);
++ Ip_u3u1u2(_or);
++ Ip_u2u1u3(_ori);
++--- a/arch/mips/mm/uasm-mips.c
+++++ b/arch/mips/mm/uasm-mips.c
++@@ -90,7 +90,7 @@ static const struct insn insn_table[insn
++ 				RS | RT | RD},
++ 	[insn_dmtc0]	= {M(cop0_op, dmtc_op, 0, 0, 0, 0), RT | RD | SET},
++ 	[insn_dmultu]	= {M(spec_op, 0, 0, 0, 0, dmultu_op), RS | RT},
++-	[insn_dmulu]	= {M(spec_op, 0, 0, 0, dmult_dmul_op, dmultu_op),
+++	[insn_dmulu]	= {M(spec_op, 0, 0, 0, dmultu_dmulu_op, dmultu_op),
++ 				RS | RT | RD},
++ 	[insn_drotr]	= {M(spec_op, 1, 0, 0, 0, dsrl_op), RT | RD | RE},
++ 	[insn_drotr32]	= {M(spec_op, 1, 0, 0, 0, dsrl32_op), RT | RD | RE},
++@@ -150,6 +150,8 @@ static const struct insn insn_table[insn
++ 	[insn_mtlo]	= {M(spec_op, 0, 0, 0, 0, mtlo_op), RS},
++ 	[insn_mulu]	= {M(spec_op, 0, 0, 0, multu_mulu_op, multu_op),
++ 				RS | RT | RD},
+++	[insn_muhu]	= {M(spec_op, 0, 0, 0, multu_muhu_op, multu_op),
+++				RS | RT | RD},
++ #ifndef CONFIG_CPU_MIPSR6
++ 	[insn_mul]	= {M(spec2_op, 0, 0, 0, 0, mul_op), RS | RT | RD},
++ #else
++--- a/arch/mips/mm/uasm.c
+++++ b/arch/mips/mm/uasm.c
++@@ -59,7 +59,7 @@ enum opcode {
++ 	insn_lddir, insn_ldpte, insn_ldx, insn_lh, insn_lhu, insn_ll, insn_lld,
++ 	insn_lui, insn_lw, insn_lwu, insn_lwx, insn_mfc0, insn_mfhc0, insn_mfhi,
++ 	insn_mflo, insn_modu, insn_movn, insn_movz, insn_mtc0, insn_mthc0,
++-	insn_mthi, insn_mtlo, insn_mul, insn_multu, insn_mulu, insn_nor,
+++	insn_mthi, insn_mtlo, insn_mul, insn_multu, insn_mulu, insn_muhu, insn_nor,
++ 	insn_or, insn_ori, insn_pref, insn_rfe, insn_rotr, insn_sb, insn_sc,
++ 	insn_scd, insn_seleqz, insn_selnez, insn_sd, insn_sh, insn_sll,
++ 	insn_sllv, insn_slt, insn_slti, insn_sltiu, insn_sltu, insn_sra,
++@@ -344,6 +344,7 @@ I_u1(_mtlo)
++ I_u3u1u2(_mul)
++ I_u1u2(_multu)
++ I_u3u1u2(_mulu)
+++I_u3u1u2(_muhu)
++ I_u3u1u2(_nor)
++ I_u3u1u2(_or)
++ I_u2u1u3(_ori)
+diff --git a/target/linux/generic/backport-5.15/050-v5.16-01-mips-uasm-Add-workaround-for-Loongson-2F-nop-CPU-err.patch b/target/linux/generic/backport-5.15/050-v5.16-01-mips-uasm-Add-workaround-for-Loongson-2F-nop-CPU-err.patch
 new file mode 100644
-index 0000000000..e25c336831
+index 0000000000..3a4d573f80
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/050-v5.16-06-mips-bpf-Remove-old-BPF-JIT-implementations.patch
-@@ -0,0 +1,387 @@
++++ b/target/linux/generic/backport-5.15/050-v5.16-01-mips-uasm-Add-workaround-for-Loongson-2F-nop-CPU-err.patch
+@@ -0,0 +1,31 @@
 +From: Johan Almbladh <johan.almbladh@anyfinetworks.com>
-+Date: Tue, 5 Oct 2021 18:54:08 +0200
-+Subject: [PATCH] mips: bpf: Remove old BPF JIT implementations
++Date: Tue, 5 Oct 2021 18:54:03 +0200
++Subject: [PATCH] mips: uasm: Add workaround for Loongson-2F nop CPU errata
 +
-+This patch removes the old 32-bit cBPF and 64-bit eBPF JIT implementations.
-+They are replaced by a new eBPF implementation that supports both 32-bit
-+and 64-bit MIPS CPUs.
++This patch implements a workaround for the Loongson-2F nop in generated,
++code, if the existing option CONFIG_CPU_NOP_WORKAROUND is set. Before,
++the binutils option -mfix-loongson2f-nop was enabled, but no workaround
++was done when emitting MIPS code. Now, the nop pseudo instruction is
++emitted as "or ax,ax,zero" instead of the default "sll zero,zero,0". This
++is consistent with the workaround implemented by binutils.
++
++Link: https://sourceware.org/legacy-ml/binutils/2009-11/msg00387.html
 +
 +Signed-off-by: Johan Almbladh <johan.almbladh@anyfinetworks.com>
++Reviewed-by: Jiaxun Yang <jiaxun.yang@flygoat.com>
 +---
-+ delete mode 100644 arch/mips/net/bpf_jit.c
-+ delete mode 100644 arch/mips/net/bpf_jit.h
-+ delete mode 100644 arch/mips/net/bpf_jit_asm.S
-+ delete mode 100644 arch/mips/net/ebpf_jit.c
 +
-+--- a/arch/mips/net/bpf_jit.h
-++++ /dev/null
-+@@ -1,81 +0,0 @@
-+-/* SPDX-License-Identifier: GPL-2.0-only */
-+-/*
-+- * Just-In-Time compiler for BPF filters on MIPS
-+- *
-+- * Copyright (c) 2014 Imagination Technologies Ltd.
-+- * Author: Markos Chandras <markos.chandras@imgtec.com>
-+- */
-+-
-+-#ifndef BPF_JIT_MIPS_OP_H
-+-#define BPF_JIT_MIPS_OP_H
-+-
-+-/* Registers used by JIT */
-+-#define MIPS_R_ZERO	0
-+-#define MIPS_R_V0	2
-+-#define MIPS_R_A0	4
-+-#define MIPS_R_A1	5
-+-#define MIPS_R_T4	12
-+-#define MIPS_R_T5	13
-+-#define MIPS_R_T6	14
-+-#define MIPS_R_T7	15
-+-#define MIPS_R_S0	16
-+-#define MIPS_R_S1	17
-+-#define MIPS_R_S2	18
-+-#define MIPS_R_S3	19
-+-#define MIPS_R_S4	20
-+-#define MIPS_R_S5	21
-+-#define MIPS_R_S6	22
-+-#define MIPS_R_S7	23
-+-#define MIPS_R_SP	29
-+-#define MIPS_R_RA	31
-+-
-+-/* Conditional codes */
-+-#define MIPS_COND_EQ	0x1
-+-#define MIPS_COND_GE	(0x1 << 1)
-+-#define MIPS_COND_GT	(0x1 << 2)
-+-#define MIPS_COND_NE	(0x1 << 3)
-+-#define MIPS_COND_ALL	(0x1 << 4)
-+-/* Conditionals on X register or K immediate */
-+-#define MIPS_COND_X	(0x1 << 5)
-+-#define MIPS_COND_K	(0x1 << 6)
-+-
-+-#define r_ret	MIPS_R_V0
-+-
-+-/*
-+- * Use 2 scratch registers to avoid pipeline interlocks.
-+- * There is no overhead during epilogue and prologue since
-+- * any of the $s0-$s6 registers will only be preserved if
-+- * they are going to actually be used.
-+- */
-+-#define r_skb_hl	MIPS_R_S0 /* skb header length */
-+-#define r_skb_data	MIPS_R_S1 /* skb actual data */
-+-#define r_off		MIPS_R_S2
-+-#define r_A		MIPS_R_S3
-+-#define r_X		MIPS_R_S4
-+-#define r_skb		MIPS_R_S5
-+-#define r_M		MIPS_R_S6
-+-#define r_skb_len	MIPS_R_S7
-+-#define r_s0		MIPS_R_T4 /* scratch reg 1 */
-+-#define r_s1		MIPS_R_T5 /* scratch reg 2 */
-+-#define r_tmp_imm	MIPS_R_T6 /* No need to preserve this */
-+-#define r_tmp		MIPS_R_T7 /* No need to preserve this */
-+-#define r_zero		MIPS_R_ZERO
-+-#define r_sp		MIPS_R_SP
-+-#define r_ra		MIPS_R_RA
-+-
-+-#ifndef __ASSEMBLY__
-+-
-+-/* Declare ASM helpers */
-+-
-+-#define DECLARE_LOAD_FUNC(func) \
-+-	extern u8 func(unsigned long *skb, int offset); \
-+-	extern u8 func##_negative(unsigned long *skb, int offset); \
-+-	extern u8 func##_positive(unsigned long *skb, int offset)
-+-
-+-DECLARE_LOAD_FUNC(sk_load_word);
-+-DECLARE_LOAD_FUNC(sk_load_half);
-+-DECLARE_LOAD_FUNC(sk_load_byte);
-+-
-+-#endif
-+-
-+-#endif /* BPF_JIT_MIPS_OP_H */
-+--- a/arch/mips/net/bpf_jit_asm.S
-++++ /dev/null
-+@@ -1,285 +0,0 @@
-+-/*
-+- * bpf_jib_asm.S: Packet/header access helper functions for MIPS/MIPS64 BPF
-+- * compiler.
-+- *
-+- * Copyright (C) 2015 Imagination Technologies Ltd.
-+- * Author: Markos Chandras <markos.chandras@imgtec.com>
-+- *
-+- * This program is free software; you can redistribute it and/or modify it
-+- * under the terms of the GNU General Public License as published by the
-+- * Free Software Foundation; version 2 of the License.
-+- */
-+-
-+-#include <asm/asm.h>
-+-#include <asm/isa-rev.h>
-+-#include <asm/regdef.h>
-+-#include "bpf_jit.h"
-+-
-+-/* ABI
-+- *
-+- * r_skb_hl	skb header length
-+- * r_skb_data	skb data
-+- * r_off(a1)	offset register
-+- * r_A		BPF register A
-+- * r_X		PF register X
-+- * r_skb(a0)	*skb
-+- * r_M		*scratch memory
-+- * r_skb_le	skb length
-+- * r_s0		Scratch register 0
-+- * r_s1		Scratch register 1
-+- *
-+- * On entry:
-+- * a0: *skb
-+- * a1: offset (imm or imm + X)
-+- *
-+- * All non-BPF-ABI registers are free for use. On return, we only
-+- * care about r_ret. The BPF-ABI registers are assumed to remain
-+- * unmodified during the entire filter operation.
-+- */
-+-
-+-#define skb	a0
-+-#define offset	a1
-+-#define SKF_LL_OFF  (-0x200000) /* Can't include linux/filter.h in assembly */
-+-
-+-	/* We know better :) so prevent assembler reordering etc */
-+-	.set 	noreorder
-+-
-+-#define is_offset_negative(TYPE)				\
-+-	/* If offset is negative we have more work to do */	\
-+-	slti	t0, offset, 0;					\
-+-	bgtz	t0, bpf_slow_path_##TYPE##_neg;			\
-+-	/* Be careful what follows in DS. */
-+-
-+-#define is_offset_in_header(SIZE, TYPE)				\
-+-	/* Reading from header? */				\
-+-	addiu	$r_s0, $r_skb_hl, -SIZE;			\
-+-	slt	t0, $r_s0, offset;				\
-+-	bgtz	t0, bpf_slow_path_##TYPE;			\
-+-
-+-LEAF(sk_load_word)
-+-	is_offset_negative(word)
-+-FEXPORT(sk_load_word_positive)
-+-	is_offset_in_header(4, word)
-+-	/* Offset within header boundaries */
-+-	PTR_ADDU t1, $r_skb_data, offset
-+-	.set	reorder
-+-	lw	$r_A, 0(t1)
-+-	.set	noreorder
-+-#ifdef CONFIG_CPU_LITTLE_ENDIAN
-+-# if MIPS_ISA_REV >= 2
-+-	wsbh	t0, $r_A
-+-	rotr	$r_A, t0, 16
-+-# else
-+-	sll	t0, $r_A, 24
-+-	srl	t1, $r_A, 24
-+-	srl	t2, $r_A, 8
-+-	or	t0, t0, t1
-+-	andi	t2, t2, 0xff00
-+-	andi	t1, $r_A, 0xff00
-+-	or	t0, t0, t2
-+-	sll	t1, t1, 8
-+-	or	$r_A, t0, t1
-+-# endif
-+-#endif
-+-	jr	$r_ra
-+-	 move	$r_ret, zero
-+-	END(sk_load_word)
-+-
-+-LEAF(sk_load_half)
-+-	is_offset_negative(half)
-+-FEXPORT(sk_load_half_positive)
-+-	is_offset_in_header(2, half)
-+-	/* Offset within header boundaries */
-+-	PTR_ADDU t1, $r_skb_data, offset
-+-	lhu	$r_A, 0(t1)
-+-#ifdef CONFIG_CPU_LITTLE_ENDIAN
-+-# if MIPS_ISA_REV >= 2
-+-	wsbh	$r_A, $r_A
-+-# else
-+-	sll	t0, $r_A, 8
-+-	srl	t1, $r_A, 8
-+-	andi	t0, t0, 0xff00
-+-	or	$r_A, t0, t1
-+-# endif
-+-#endif
-+-	jr	$r_ra
-+-	 move	$r_ret, zero
-+-	END(sk_load_half)
-+-
-+-LEAF(sk_load_byte)
-+-	is_offset_negative(byte)
-+-FEXPORT(sk_load_byte_positive)
-+-	is_offset_in_header(1, byte)
-+-	/* Offset within header boundaries */
-+-	PTR_ADDU t1, $r_skb_data, offset
-+-	lbu	$r_A, 0(t1)
-+-	jr	$r_ra
-+-	 move	$r_ret, zero
-+-	END(sk_load_byte)
-+-
-+-/*
-+- * call skb_copy_bits:
-+- * (prototype in linux/skbuff.h)
-+- *
-+- * int skb_copy_bits(sk_buff *skb, int offset, void *to, int len)
-+- *
-+- * o32 mandates we leave 4 spaces for argument registers in case
-+- * the callee needs to use them. Even though we don't care about
-+- * the argument registers ourselves, we need to allocate that space
-+- * to remain ABI compliant since the callee may want to use that space.
-+- * We also allocate 2 more spaces for $r_ra and our return register (*to).
-+- *
-+- * n64 is a bit different. The *caller* will allocate the space to preserve
-+- * the arguments. So in 64-bit kernels, we allocate the 4-arg space for no
-+- * good reason but it does not matter that much really.
-+- *
-+- * (void *to) is returned in r_s0
-+- *
-+- */
-+-#ifdef CONFIG_CPU_LITTLE_ENDIAN
-+-#define DS_OFFSET(SIZE) (4 * SZREG)
-+-#else
-+-#define DS_OFFSET(SIZE) ((4 * SZREG) + (4 - SIZE))
-+-#endif
-+-#define bpf_slow_path_common(SIZE)				\
-+-	/* Quick check. Are we within reasonable boundaries? */ \
-+-	LONG_ADDIU	$r_s1, $r_skb_len, -SIZE;		\
-+-	sltu		$r_s0, offset, $r_s1;			\
-+-	beqz		$r_s0, fault;				\
-+-	/* Load 4th argument in DS */				\
-+-	 LONG_ADDIU	a3, zero, SIZE;				\
-+-	PTR_ADDIU	$r_sp, $r_sp, -(6 * SZREG);		\
-+-	PTR_LA		t0, skb_copy_bits;			\
-+-	PTR_S		$r_ra, (5 * SZREG)($r_sp);		\
-+-	/* Assign low slot to a2 */				\
-+-	PTR_ADDIU	a2, $r_sp, DS_OFFSET(SIZE);		\
-+-	jalr		t0;					\
-+-	/* Reset our destination slot (DS but it's ok) */	\
-+-	 INT_S		zero, (4 * SZREG)($r_sp);		\
-+-	/*							\
-+-	 * skb_copy_bits returns 0 on success and -EFAULT	\
-+-	 * on error. Our data live in a2. Do not bother with	\
-+-	 * our data if an error has been returned.		\
-+-	 */							\
-+-	/* Restore our frame */					\
-+-	PTR_L		$r_ra, (5 * SZREG)($r_sp);		\
-+-	INT_L		$r_s0, (4 * SZREG)($r_sp);		\
-+-	bltz		v0, fault;				\
-+-	 PTR_ADDIU	$r_sp, $r_sp, 6 * SZREG;		\
-+-	move		$r_ret, zero;				\
-+-
-+-NESTED(bpf_slow_path_word, (6 * SZREG), $r_sp)
-+-	bpf_slow_path_common(4)
-+-#ifdef CONFIG_CPU_LITTLE_ENDIAN
-+-# if MIPS_ISA_REV >= 2
-+-	wsbh	t0, $r_s0
-+-	jr	$r_ra
-+-	 rotr	$r_A, t0, 16
-+-# else
-+-	sll	t0, $r_s0, 24
-+-	srl	t1, $r_s0, 24
-+-	srl	t2, $r_s0, 8
-+-	or	t0, t0, t1
-+-	andi	t2, t2, 0xff00
-+-	andi	t1, $r_s0, 0xff00
-+-	or	t0, t0, t2
-+-	sll	t1, t1, 8
-+-	jr	$r_ra
-+-	 or	$r_A, t0, t1
-+-# endif
-+-#else
-+-	jr	$r_ra
-+-	 move	$r_A, $r_s0
-+-#endif
-+-
-+-	END(bpf_slow_path_word)
-+-
-+-NESTED(bpf_slow_path_half, (6 * SZREG), $r_sp)
-+-	bpf_slow_path_common(2)
-+-#ifdef CONFIG_CPU_LITTLE_ENDIAN
-+-# if MIPS_ISA_REV >= 2
-+-	jr	$r_ra
-+-	 wsbh	$r_A, $r_s0
-+-# else
-+-	sll	t0, $r_s0, 8
-+-	andi	t1, $r_s0, 0xff00
-+-	andi	t0, t0, 0xff00
-+-	srl	t1, t1, 8
-+-	jr	$r_ra
-+-	 or	$r_A, t0, t1
-+-# endif
-+-#else
-+-	jr	$r_ra
-+-	 move	$r_A, $r_s0
-+-#endif
-+-
-+-	END(bpf_slow_path_half)
-+-
-+-NESTED(bpf_slow_path_byte, (6 * SZREG), $r_sp)
-+-	bpf_slow_path_common(1)
-+-	jr	$r_ra
-+-	 move	$r_A, $r_s0
-+-
-+-	END(bpf_slow_path_byte)
-+-
-+-/*
-+- * Negative entry points
-+- */
-+-	.macro bpf_is_end_of_data
-+-	li	t0, SKF_LL_OFF
-+-	/* Reading link layer data? */
-+-	slt	t1, offset, t0
-+-	bgtz	t1, fault
-+-	/* Be careful what follows in DS. */
-+-	.endm
-+-/*
-+- * call skb_copy_bits:
-+- * (prototype in linux/filter.h)
-+- *
-+- * void *bpf_internal_load_pointer_neg_helper(const struct sk_buff *skb,
-+- *                                            int k, unsigned int size)
-+- *
-+- * see above (bpf_slow_path_common) for ABI restrictions
-+- */
-+-#define bpf_negative_common(SIZE)					\
-+-	PTR_ADDIU	$r_sp, $r_sp, -(6 * SZREG);			\
-+-	PTR_LA		t0, bpf_internal_load_pointer_neg_helper;	\
-+-	PTR_S		$r_ra, (5 * SZREG)($r_sp);			\
-+-	jalr		t0;						\
-+-	 li		a2, SIZE;					\
-+-	PTR_L		$r_ra, (5 * SZREG)($r_sp);			\
-+-	/* Check return pointer */					\
-+-	beqz		v0, fault;					\
-+-	 PTR_ADDIU	$r_sp, $r_sp, 6 * SZREG;			\
-+-	/* Preserve our pointer */					\
-+-	move		$r_s0, v0;					\
-+-	/* Set return value */						\
-+-	move		$r_ret, zero;					\
-+-
-+-bpf_slow_path_word_neg:
-+-	bpf_is_end_of_data
-+-NESTED(sk_load_word_negative, (6 * SZREG), $r_sp)
-+-	bpf_negative_common(4)
-+-	jr	$r_ra
-+-	 lw	$r_A, 0($r_s0)
-+-	END(sk_load_word_negative)
-+-
-+-bpf_slow_path_half_neg:
-+-	bpf_is_end_of_data
-+-NESTED(sk_load_half_negative, (6 * SZREG), $r_sp)
-+-	bpf_negative_common(2)
-+-	jr	$r_ra
-+-	 lhu	$r_A, 0($r_s0)
-+-	END(sk_load_half_negative)
-+-
-+-bpf_slow_path_byte_neg:
-+-	bpf_is_end_of_data
-+-NESTED(sk_load_byte_negative, (6 * SZREG), $r_sp)
-+-	bpf_negative_common(1)
-+-	jr	$r_ra
-+-	 lbu	$r_A, 0($r_s0)
-+-	END(sk_load_byte_negative)
-+-
-+-fault:
-+-	jr	$r_ra
-+-	 addiu $r_ret, zero, 1
-diff --git a/target/linux/generic/backport-5.15/081-net-next-regmap-allow-to-define-reg_update_bits-for-no-bus.patch b/target/linux/generic/backport-5.15/081-net-next-regmap-allow-to-define-reg_update_bits-for-no-bus.patch
++--- a/arch/mips/include/asm/uasm.h
+++++ b/arch/mips/include/asm/uasm.h
++@@ -249,7 +249,11 @@ static inline void uasm_l##lb(struct uas
++ #define uasm_i_bnezl(buf, rs, off) uasm_i_bnel(buf, rs, 0, off)
++ #define uasm_i_ehb(buf) uasm_i_sll(buf, 0, 0, 3)
++ #define uasm_i_move(buf, a, b) UASM_i_ADDU(buf, a, 0, b)
+++#ifdef CONFIG_CPU_NOP_WORKAROUNDS
+++#define uasm_i_nop(buf) uasm_i_or(buf, 1, 1, 0)
+++#else
++ #define uasm_i_nop(buf) uasm_i_sll(buf, 0, 0, 0)
+++#endif
++ #define uasm_i_ssnop(buf) uasm_i_sll(buf, 0, 0, 1)
++ 
++ static inline void uasm_i_drotr_safe(u32 **p, unsigned int a1,
+diff --git a/target/linux/generic/backport-5.15/050-v5.16-02-mips-bpf-Add-eBPF-JIT-for-32-bit-MIPS.patch b/target/linux/generic/backport-5.15/050-v5.16-02-mips-bpf-Add-eBPF-JIT-for-32-bit-MIPS.patch
 new file mode 100644
-index 0000000000..e4c0833ae7
+index 0000000000..7980659961
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/081-net-next-regmap-allow-to-define-reg_update_bits-for-no-bus.patch
-@@ -0,0 +1,52 @@
-+From 02d6fdecb9c38de19065f6bed8d5214556fd061d Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Thu, 4 Nov 2021 16:00:40 +0100
-+Subject: regmap: allow to define reg_update_bits for no bus configuration
++++ b/target/linux/generic/backport-5.15/050-v5.16-02-mips-bpf-Add-eBPF-JIT-for-32-bit-MIPS.patch
+@@ -0,0 +1,3078 @@
++From: Johan Almbladh <johan.almbladh@anyfinetworks.com>
++Date: Tue, 5 Oct 2021 18:54:04 +0200
++Subject: [PATCH] mips: bpf: Add eBPF JIT for 32-bit MIPS
 +
-+Some device requires a special handling for reg_update_bits and can't use
-+the normal regmap read write logic. An example is when locking is
-+handled by the device and rmw operations requires to do atomic operations.
-+Allow to declare a dedicated function in regmap_config for
-+reg_update_bits in no bus configuration.
++This is an implementation of an eBPF JIT for 32-bit MIPS I-V and MIPS32.
++The implementation supports all 32-bit and 64-bit ALU and JMP operations,
++including the recently-added atomics. 64-bit div/mod and 64-bit atomics
++are implemented using function calls to math64 and atomic64 functions,
++respectively. All 32-bit operations are implemented natively by the JIT,
++except if the CPU lacks ll/sc instructions.
 +
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Link: https://lore.kernel.org/r/20211104150040.1260-1-ansuelsmth@gmail.com
-+Signed-off-by: Mark Brown <broonie@kernel.org>
-+---
-+ drivers/base/regmap/regmap.c | 1 +
-+ include/linux/regmap.h       | 7 +++++++
-+ 2 files changed, 8 insertions(+)
-+
-+--- a/drivers/base/regmap/regmap.c
-++++ b/drivers/base/regmap/regmap.c
-+@@ -877,6 +877,7 @@ struct regmap *__regmap_init(struct devi
-+ 	if (!bus) {
-+ 		map->reg_read  = config->reg_read;
-+ 		map->reg_write = config->reg_write;
-++		map->reg_update_bits = config->reg_update_bits;
-+ 
-+ 		map->defer_caching = false;
-+ 		goto skip_format_initialization;
-+--- a/include/linux/regmap.h
-++++ b/include/linux/regmap.h
-+@@ -290,6 +290,11 @@ typedef void (*regmap_unlock)(void *);
-+  *		  read operation on a bus such as SPI, I2C, etc. Most of the
-+  *		  devices do not need this.
-+  * @reg_write:	  Same as above for writing.
-++ * @reg_update_bits: Optional callback that if filled will be used to perform
-++ *		     all the update_bits(rmw) operation. Should only be provided
-++ *		     if the function require special handling with lock and reg
-++ *		     handling and the operation cannot be represented as a simple
-++ *		     update_bits operation on a bus such as SPI, I2C, etc.
-+  * @fast_io:	  Register IO is fast. Use a spinlock instead of a mutex
-+  *	     	  to perform locking. This field is ignored if custom lock/unlock
-+  *	     	  functions are used (see fields lock/unlock of struct regmap_config).
-+@@ -372,6 +377,8 @@ struct regmap_config {
-+ 
-+ 	int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
-+ 	int (*reg_write)(void *context, unsigned int reg, unsigned int val);
-++	int (*reg_update_bits)(void *context, unsigned int reg,
-++			       unsigned int mask, unsigned int val);
-+ 
-+ 	bool fast_io;
-+ 
-diff --git a/target/linux/generic/backport-5.15/100-v5.18-tty-serial-bcm63xx-use-more-precise-Kconfig-symbol.patch b/target/linux/generic/backport-5.15/100-v5.18-tty-serial-bcm63xx-use-more-precise-Kconfig-symbol.patch
-new file mode 100644
-index 0000000000..7de3cbbda0
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/100-v5.18-tty-serial-bcm63xx-use-more-precise-Kconfig-symbol.patch
-@@ -0,0 +1,37 @@
-+From 0dc0da881b4574d1e04a079ab2ea75da61f5ad2e Mon Sep 17 00:00:00 2001
-+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
-+Date: Fri, 11 Mar 2022 10:32:33 +0100
-+Subject: [PATCH] tty: serial: bcm63xx: use more precise Kconfig symbol
-+MIME-Version: 1.0
-+Content-Type: text/plain; charset=UTF-8
-+Content-Transfer-Encoding: 8bit
-+
-+Patches lowering SERIAL_BCM63XX dependencies led to a discussion and
-+documentation change regarding "depends" usage. Adjust Kconfig entry to
-+match current guidelines. Make this symbol available for relevant
-+architectures only.
-+
-+Cc: Geert Uytterhoeven <geert@linux-m68k.org>
-+Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
-+Acked-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
-+Ref: f35a07f92616 ("tty: serial: bcm63xx: lower driver dependencies")
-+Ref: 18084e435ff6 ("Documentation/kbuild: Document platform dependency practises")
-+Link: https://lore.kernel.org/r/20220311093233.10012-1-zajec5@gmail.com
-+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-+---
-+ drivers/tty/serial/Kconfig | 3 ++-
-+ 1 file changed, 2 insertions(+), 1 deletion(-)
-+
-+--- a/drivers/tty/serial/Kconfig
-++++ b/drivers/tty/serial/Kconfig
-+@@ -1098,7 +1098,8 @@ config SERIAL_TIMBERDALE
-+ config SERIAL_BCM63XX
-+ 	tristate "Broadcom BCM63xx/BCM33xx UART support"
-+ 	select SERIAL_CORE
-+-	depends on COMMON_CLK
-++	depends on ARCH_BCM4908 || ARCH_BCM_63XX || BCM63XX || BMIPS_GENERIC || COMPILE_TEST
-++	default ARCH_BCM4908 || ARCH_BCM_63XX || BCM63XX || BMIPS_GENERIC
-+ 	help
-+ 	  This enables the driver for the onchip UART core found on
-+ 	  the following chipsets:
-diff --git a/target/linux/generic/backport-5.15/200-v5.18-tools-resolve_btfids-Build-with-host-flags.patch b/target/linux/generic/backport-5.15/200-v5.18-tools-resolve_btfids-Build-with-host-flags.patch
-new file mode 100644
-index 0000000000..caec8db5d6
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/200-v5.18-tools-resolve_btfids-Build-with-host-flags.patch
-@@ -0,0 +1,49 @@
-+From cdbc4e3399ed8cdcf234a85f7a2482b622379e82 Mon Sep 17 00:00:00 2001
-+From: Connor O'Brien <connoro@google.com>
-+Date: Wed, 12 Jan 2022 00:25:03 +0000
-+Subject: [PATCH] tools/resolve_btfids: Build with host flags
-+
-+resolve_btfids is built using $(HOSTCC) and $(HOSTLD) but does not
-+pick up the corresponding flags. As a result, host-specific settings
-+(such as a sysroot specified via HOSTCFLAGS=--sysroot=..., or a linker
-+specified via HOSTLDFLAGS=-fuse-ld=...) will not be respected.
-+
-+Fix this by setting CFLAGS to KBUILD_HOSTCFLAGS and LDFLAGS to
-+KBUILD_HOSTLDFLAGS.
-+
-+Also pass the cflags through to libbpf via EXTRA_CFLAGS to ensure that
-+the host libbpf is built with flags consistent with resolve_btfids.
-+
-+Signed-off-by: Connor O'Brien <connoro@google.com>
-+Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
-+Acked-by: Song Liu <songliubraving@fb.com>
-+Link: https://lore.kernel.org/bpf/20220112002503.115968-1-connoro@google.com
-+(cherry picked from commit 0e3a1c902ffb56e9fe4416f0cd382c97b09ecbf6)
-+Signed-off-by: Stijn Tintel <stijn@linux-ipv6.be>
-+---
-+ tools/bpf/resolve_btfids/Makefile | 6 ++++--
-+ 1 file changed, 4 insertions(+), 2 deletions(-)
++Register mapping
++================
++All 64-bit eBPF registers are mapped to native 32-bit MIPS register pairs,
++and does not use any stack scratch space for register swapping. This means
++that all eBPF register data is kept in CPU registers all the time, and
++this simplifies the register management a lot. It also reduces the JIT's
++pressure on temporary registers since we do not have to move data around.
 +
-+--- a/tools/bpf/resolve_btfids/Makefile
-++++ b/tools/bpf/resolve_btfids/Makefile
-+@@ -23,6 +23,8 @@ CC       = $(HOSTCC)
-+ LD       = $(HOSTLD)
-+ ARCH     = $(HOSTARCH)
-+ RM      ?= rm
-++CFLAGS  := $(KBUILD_HOSTCFLAGS)
-++LDFLAGS := $(KBUILD_HOSTLDFLAGS)
-+ 
-+ OUTPUT ?= $(srctree)/tools/bpf/resolve_btfids/
-+ 
-+@@ -45,9 +47,9 @@ $(SUBCMDOBJ): fixdep FORCE | $(OUTPUT)/l
-+ 	$(Q)$(MAKE) -C $(SUBCMD_SRC) OUTPUT=$(abspath $(dir $@))/ $(abspath $@)
-+ 
-+ $(BPFOBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf
-+-	$(Q)$(MAKE) $(submake_extras) -C $(LIBBPF_SRC)  OUTPUT=$(abspath $(dir $@))/ $(abspath $@)
-++	$(Q)$(MAKE) $(submake_extras) -C $(LIBBPF_SRC)  OUTPUT=$(abspath $(dir $@))/ EXTRA_CFLAGS="$(CFLAGS)" $(abspath $@)
-+ 
-+-CFLAGS := -g \
-++CFLAGS += -g \
-+           -I$(srctree)/tools/include \
-+           -I$(srctree)/tools/include/uapi \
-+           -I$(LIBBPF_SRC) \
-diff --git a/target/linux/generic/backport-5.15/300-v5.18-pinctrl-qcom-Return--EINVAL-for-setting-affinity-if-no-IRQ-parent.patch b/target/linux/generic/backport-5.15/300-v5.18-pinctrl-qcom-Return--EINVAL-for-setting-affinity-if-no-IRQ-parent.patch
-new file mode 100644
-index 0000000000..18a8752a18
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/300-v5.18-pinctrl-qcom-Return--EINVAL-for-setting-affinity-if-no-IRQ-parent.patch
-@@ -0,0 +1,48 @@
-+From: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
-+To: linus.walleij@linaro.org
-+Cc: bjorn.andersson@linaro.org, dianders@chromium.org,
-+        linux-arm-msm@vger.kernel.org, linux-gpio@vger.kernel.org,
-+        linux-kernel@vger.kernel.org,
-+        Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
-+Subject: [PATCH] pinctrl: qcom: Return -EINVAL for setting affinity if no IRQ
-+ parent
-+Date: Thu, 13 Jan 2022 21:56:17 +0530
-+Message-Id: <20220113162617.131697-1-manivannan.sadhasivam@linaro.org>
++Native register pairs are ordered according to CPU endiannes, following
++the O32 calling convention for passing 64-bit arguments and return values.
++The eBPF return value, arguments and callee-saved registers are mapped to
++their native MIPS equivalents.
 +
-+The MSM GPIO IRQ controller relies on the parent IRQ controller to set the
-+CPU affinity for the IRQ. And this is only valid if there is any wakeup
-+parent available and defined in DT.
++Since the 32 highest bits in the eBPF FP (frame pointer) register are
++always zero, only one general-purpose register is actually needed for the
++mapping. The MIPS fp register is used for this purpose. The high bits are
++mapped to MIPS register r0. This saves us one CPU register, which is much
++needed for temporaries, while still allowing us to treat the R10 (FP)
++register just like any other eBPF register in the JIT.
 +
-+For the case of no parent IRQ controller defined in DT,
-+msm_gpio_irq_set_affinity() and msm_gpio_irq_set_vcpu_affinity() should
-+return -EINVAL instead of 0 as the affinity can't be set.
++The MIPS gp (global pointer) and at (assembler temporary) registers are
++used as internal temporary registers for constant blinding. CPU registers
++t6-t9 are used internally by the JIT when constructing more complex 64-bit
++operations. This is precisely what is needed - two registers to store an
++operand value, and two more as scratch registers when performing the
++operation.
 +
-+Otherwise, below warning will be printed by genirq:
++The register mapping is shown below.
 +
-+genirq: irq_chip msmgpio did not update eff. affinity mask of irq 70
++    R0 - $v1, $v0   return value
++    R1 - $a1, $a0   argument 1, passed in registers
++    R2 - $a3, $a2   argument 2, passed in registers
++    R3 - $t1, $t0   argument 3, passed on stack
++    R4 - $t3, $t2   argument 4, passed on stack
++    R5 - $t4, $t3   argument 5, passed on stack
++    R6 - $s1, $s0   callee-saved
++    R7 - $s3, $s2   callee-saved
++    R8 - $s5, $s4   callee-saved
++    R9 - $s7, $s6   callee-saved
++    FP - $r0, $fp   32-bit frame pointer
++    AX - $gp, $at   constant-blinding
++         $t6 - $t9  unallocated, JIT temporaries
 +
-+Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
-+---
-+ drivers/pinctrl/qcom/pinctrl-msm.c | 4 ++--
-+ 1 file changed, 2 insertions(+), 2 deletions(-)
++Jump offsets
++============
++The JIT tries to map all conditional JMP operations to MIPS conditional
++PC-relative branches. The MIPS branch offset field is 18 bits, in bytes,
++which is equivalent to the eBPF 16-bit instruction offset. However, since
++the JIT may emit more than one CPU instruction per eBPF instruction, the
++field width may overflow. If that happens, the JIT converts the long
++conditional jump to a short PC-relative branch with the condition
++inverted, jumping over a long unconditional absolute jmp (j).
 +
-+--- a/drivers/pinctrl/qcom/pinctrl-msm.c
-++++ b/drivers/pinctrl/qcom/pinctrl-msm.c
-+@@ -1157,7 +1157,7 @@ static int msm_gpio_irq_set_affinity(str
-+ 	if (d->parent_data && test_bit(d->hwirq, pctrl->skip_wake_irqs))
-+ 		return irq_chip_set_affinity_parent(d, dest, force);
-+ 
-+-	return 0;
-++	return -EINVAL;
-+ }
-+ 
-+ static int msm_gpio_irq_set_vcpu_affinity(struct irq_data *d, void *vcpu_info)
-+@@ -1168,7 +1168,7 @@ static int msm_gpio_irq_set_vcpu_affinit
-+ 	if (d->parent_data && test_bit(d->hwirq, pctrl->skip_wake_irqs))
-+ 		return irq_chip_set_vcpu_affinity_parent(d, vcpu_info);
-+ 
-+-	return 0;
-++	return -EINVAL;
-+ }
-+ 
-+ static void msm_gpio_irq_handler(struct irq_desc *desc)
-diff --git a/target/linux/generic/backport-5.15/343-netfilter-nft_flow_offload-handle-netdevice-events-f.patch b/target/linux/generic/backport-5.15/343-netfilter-nft_flow_offload-handle-netdevice-events-f.patch
-new file mode 100644
-index 0000000000..19ec9d9409
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/343-netfilter-nft_flow_offload-handle-netdevice-events-f.patch
-@@ -0,0 +1,106 @@
-+From: Pablo Neira Ayuso <pablo@netfilter.org>
-+Date: Thu, 25 Jan 2018 12:58:55 +0100
-+Subject: [PATCH] netfilter: nft_flow_offload: handle netdevice events from
-+ nf_flow_table
++This conversion will change the instruction offset mapping used for jumps,
++and may in turn result in more branch offset overflows. The JIT therefore
++dry-runs the translation until no more branches are converted and the
++offsets do not change anymore. There is an upper bound on this of course,
++and if the JIT hits that limit, the last two iterations are run with all
++branches being converted.
 +
-+Move the code that deals with device events to the core.
++Tail call count
++===============
++The current tail call count is stored in the 16-byte area of the caller's
++stack frame that is reserved for the callee in the o32 ABI. The value is
++initialized in the prologue, and propagated to the tail-callee by skipping
++the initialization instructions when emitting the tail call.
 +
-+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
++Signed-off-by: Johan Almbladh <johan.almbladh@anyfinetworks.com>
 +---
++ create mode 100644 arch/mips/net/bpf_jit_comp.c
++ create mode 100644 arch/mips/net/bpf_jit_comp.h
++ create mode 100644 arch/mips/net/bpf_jit_comp32.c
 +
-+--- a/net/netfilter/nf_flow_table_core.c
-++++ b/net/netfilter/nf_flow_table_core.c
-+@@ -608,13 +608,41 @@ void nf_flow_table_free(struct nf_flowta
-+ }
-+ EXPORT_SYMBOL_GPL(nf_flow_table_free);
++--- a/arch/mips/net/Makefile
+++++ b/arch/mips/net/Makefile
++@@ -2,4 +2,9 @@
++ # MIPS networking code
 + 
-++static int nf_flow_table_netdev_event(struct notifier_block *this,
-++				      unsigned long event, void *ptr)
++ obj-$(CONFIG_MIPS_CBPF_JIT) += bpf_jit.o bpf_jit_asm.o
++-obj-$(CONFIG_MIPS_EBPF_JIT) += ebpf_jit.o
+++
+++ifeq ($(CONFIG_32BIT),y)
+++        obj-$(CONFIG_MIPS_EBPF_JIT) += bpf_jit_comp.o bpf_jit_comp32.o
+++else
+++        obj-$(CONFIG_MIPS_EBPF_JIT) += ebpf_jit.o
+++endif
++--- /dev/null
+++++ b/arch/mips/net/bpf_jit_comp.c
++@@ -0,0 +1,1020 @@
+++// SPDX-License-Identifier: GPL-2.0-only
+++/*
+++ * Just-In-Time compiler for eBPF bytecode on MIPS.
+++ * Implementation of JIT functions common to 32-bit and 64-bit CPUs.
+++ *
+++ * Copyright (c) 2021 Anyfi Networks AB.
+++ * Author: Johan Almbladh <johan.almbladh@gmail.com>
+++ *
+++ * Based on code and ideas from
+++ * Copyright (c) 2017 Cavium, Inc.
+++ * Copyright (c) 2017 Shubham Bansal <illusionist.neo@gmail.com>
+++ * Copyright (c) 2011 Mircea Gherzan <mgherzan@gmail.com>
+++ */
+++
+++/*
+++ * Code overview
+++ * =============
+++ *
+++ * - bpf_jit_comp.h
+++ *   Common definitions and utilities.
+++ *
+++ * - bpf_jit_comp.c
+++ *   Implementation of JIT top-level logic and exported JIT API functions.
+++ *   Implementation of internal operations shared by 32-bit and 64-bit code.
+++ *   JMP and ALU JIT control code, register control code, shared ALU and
+++ *   JMP/JMP32 JIT operations.
+++ *
+++ * - bpf_jit_comp32.c
+++ *   Implementation of functions to JIT prologue, epilogue and a single eBPF
+++ *   instruction for 32-bit MIPS CPUs. The functions use shared operations
+++ *   where possible, and implement the rest for 32-bit MIPS such as ALU64
+++ *   operations.
+++ *
+++ * - bpf_jit_comp64.c
+++ *   Ditto, for 64-bit MIPS CPUs.
+++ *
+++ * Zero and sign extension
+++ * ========================
+++ * 32-bit MIPS instructions on 64-bit MIPS registers use sign extension,
+++ * but the eBPF instruction set mandates zero extension. We let the verifier
+++ * insert explicit zero-extensions after 32-bit ALU operations, both for
+++ * 32-bit and 64-bit MIPS JITs. Conditional JMP32 operations on 64-bit MIPs
+++ * are JITed with sign extensions inserted when so expected.
+++ *
+++ * ALU operations
+++ * ==============
+++ * ALU operations on 32/64-bit MIPS and ALU64 operations on 64-bit MIPS are
+++ * JITed in the following steps. ALU64 operations on 32-bit MIPS are more
+++ * complicated and therefore only processed by special implementations in
+++ * step (3).
+++ *
+++ * 1) valid_alu_i:
+++ *    Determine if an immediate operation can be emitted as such, or if
+++ *    we must fall back to the register version.
+++ *
+++ * 2) rewrite_alu_i:
+++ *    Convert BPF operation and immediate value to a canonical form for
+++ *    JITing. In some degenerate cases this form may be a no-op.
+++ *
+++ * 3) emit_alu_{i,i64,r,64}:
+++ *    Emit instructions for an ALU or ALU64 immediate or register operation.
+++ *
+++ * JMP operations
+++ * ==============
+++ * JMP and JMP32 operations require an JIT instruction offset table for
+++ * translating the jump offset. This table is computed by dry-running the
+++ * JIT without actually emitting anything. However, the computed PC-relative
+++ * offset may overflow the 18-bit offset field width of the native MIPS
+++ * branch instruction. In such cases, the long jump is converted into the
+++ * following sequence.
+++ *
+++ *    <branch> !<cond> +2    Inverted PC-relative branch
+++ *    nop                    Delay slot
+++ *    j <offset>             Unconditional absolute long jump
+++ *    nop                    Delay slot
+++ *
+++ * Since this converted sequence alters the offset table, all offsets must
+++ * be re-calculated. This may in turn trigger new branch conversions, so
+++ * the process is repeated until no further changes are made. Normally it
+++ * completes in 1-2 iterations. If JIT_MAX_ITERATIONS should reached, we
+++ * fall back to converting every remaining jump operation. The branch
+++ * conversion is independent of how the JMP or JMP32 condition is JITed.
+++ *
+++ * JMP32 and JMP operations are JITed as follows.
+++ *
+++ * 1) setup_jmp_{i,r}:
+++ *    Convert jump conditional and offset into a form that can be JITed.
+++ *    This form may be a no-op, a canonical form, or an inverted PC-relative
+++ *    jump if branch conversion is necessary.
+++ *
+++ * 2) valid_jmp_i:
+++ *    Determine if an immediate operations can be emitted as such, or if
+++ *    we must fall back to the register version. Applies to JMP32 for 32-bit
+++ *    MIPS, and both JMP and JMP32 for 64-bit MIPS.
+++ *
+++ * 3) emit_jmp_{i,i64,r,r64}:
+++ *    Emit instructions for an JMP or JMP32 immediate or register operation.
+++ *
+++ * 4) finish_jmp_{i,r}:
+++ *    Emit any instructions needed to finish the jump. This includes a nop
+++ *    for the delay slot if a branch was emitted, and a long absolute jump
+++ *    if the branch was converted.
+++ */
+++
+++#include <linux/limits.h>
+++#include <linux/bitops.h>
+++#include <linux/errno.h>
+++#include <linux/filter.h>
+++#include <linux/bpf.h>
+++#include <linux/slab.h>
+++#include <asm/bitops.h>
+++#include <asm/cacheflush.h>
+++#include <asm/cpu-features.h>
+++#include <asm/isa-rev.h>
+++#include <asm/uasm.h>
+++
+++#include "bpf_jit_comp.h"
+++
+++/* Convenience macros for descriptor access */
+++#define CONVERTED(desc)	((desc) & JIT_DESC_CONVERT)
+++#define INDEX(desc)	((desc) & ~JIT_DESC_CONVERT)
+++
+++/*
+++ * Push registers on the stack, starting at a given depth from the stack
+++ * pointer and increasing. The next depth to be written is returned.
+++ */
+++int push_regs(struct jit_context *ctx, u32 mask, u32 excl, int depth)
 ++{
-++	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+++	int reg;
 ++
-++	if (event != NETDEV_DOWN)
-++		return NOTIFY_DONE;
+++	for (reg = 0; reg < BITS_PER_BYTE * sizeof(mask); reg++)
+++		if (mask & BIT(reg)) {
+++			if ((excl & BIT(reg)) == 0) {
+++				if (sizeof(long) == 4)
+++					emit(ctx, sw, reg, depth, MIPS_R_SP);
+++				else /* sizeof(long) == 8 */
+++					emit(ctx, sd, reg, depth, MIPS_R_SP);
+++			}
+++			depth += sizeof(long);
+++		}
+++
+++	ctx->stack_used = max((int)ctx->stack_used, depth);
+++	return depth;
+++}
+++
+++/*
+++ * Pop registers from the stack, starting at a given depth from the stack
+++ * pointer and increasing. The next depth to be read is returned.
+++ */
+++int pop_regs(struct jit_context *ctx, u32 mask, u32 excl, int depth)
+++{
+++	int reg;
 ++
-++	nf_flow_table_cleanup(dev);
+++	for (reg = 0; reg < BITS_PER_BYTE * sizeof(mask); reg++)
+++		if (mask & BIT(reg)) {
+++			if ((excl & BIT(reg)) == 0) {
+++				if (sizeof(long) == 4)
+++					emit(ctx, lw, reg, depth, MIPS_R_SP);
+++				else /* sizeof(long) == 8 */
+++					emit(ctx, ld, reg, depth, MIPS_R_SP);
+++			}
+++			depth += sizeof(long);
+++		}
 ++
-++	return NOTIFY_DONE;
+++	return depth;
 ++}
 ++
-++static struct notifier_block flow_offload_netdev_notifier = {
-++	.notifier_call	= nf_flow_table_netdev_event,
-++};
+++/* Compute the 28-bit jump target address from a BPF program location */
+++int get_target(struct jit_context *ctx, u32 loc)
+++{
+++	u32 index = INDEX(ctx->descriptors[loc]);
+++	unsigned long pc = (unsigned long)&ctx->target[ctx->jit_index];
+++	unsigned long addr = (unsigned long)&ctx->target[index];
 ++
-+ static int __init nf_flow_table_module_init(void)
-+ {
-+-	return nf_flow_table_offload_init();
-++	int ret;
+++	if (!ctx->target)
+++		return 0;
 ++
-++	ret = nf_flow_table_offload_init();
-++	if (ret)
-++		return ret;
+++	if ((addr ^ pc) & ~MIPS_JMP_MASK)
+++		return -1;
 ++
-++	ret = register_netdevice_notifier(&flow_offload_netdev_notifier);
-++	if (ret)
-++		nf_flow_table_offload_exit();
+++	return addr & MIPS_JMP_MASK;
+++}
 ++
-++	return ret;
-+ }
-+ 
-+ static void __exit nf_flow_table_module_exit(void)
-+ {
-++	unregister_netdevice_notifier(&flow_offload_netdev_notifier);
-+ 	nf_flow_table_offload_exit();
-+ }
-+ 
-+--- a/net/netfilter/nft_flow_offload.c
-++++ b/net/netfilter/nft_flow_offload.c
-+@@ -444,47 +444,14 @@ static struct nft_expr_type nft_flow_off
-+ 	.owner		= THIS_MODULE,
-+ };
-+ 
-+-static int flow_offload_netdev_event(struct notifier_block *this,
-+-				     unsigned long event, void *ptr)
-+-{
-+-	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
-+-
-+-	if (event != NETDEV_DOWN)
-+-		return NOTIFY_DONE;
-+-
-+-	nf_flow_table_cleanup(dev);
-+-
-+-	return NOTIFY_DONE;
-+-}
-+-
-+-static struct notifier_block flow_offload_netdev_notifier = {
-+-	.notifier_call	= flow_offload_netdev_event,
-+-};
-+-
-+ static int __init nft_flow_offload_module_init(void)
-+ {
-+-	int err;
-+-
-+-	err = register_netdevice_notifier(&flow_offload_netdev_notifier);
-+-	if (err)
-+-		goto err;
-+-
-+-	err = nft_register_expr(&nft_flow_offload_type);
-+-	if (err < 0)
-+-		goto register_expr;
-+-
-+-	return 0;
-+-
-+-register_expr:
-+-	unregister_netdevice_notifier(&flow_offload_netdev_notifier);
-+-err:
-+-	return err;
-++	return nft_register_expr(&nft_flow_offload_type);
-+ }
-+ 
-+ static void __exit nft_flow_offload_module_exit(void)
-+ {
-+ 	nft_unregister_expr(&nft_flow_offload_type);
-+-	unregister_netdevice_notifier(&flow_offload_netdev_notifier);
-+ }
-+ 
-+ module_init(nft_flow_offload_module_init);
-diff --git a/target/linux/generic/backport-5.15/400-v5.19-mtd-call-of_platform_populate-for-MTD-partitions.patch b/target/linux/generic/backport-5.15/400-v5.19-mtd-call-of_platform_populate-for-MTD-partitions.patch
-new file mode 100644
-index 0000000000..1f3aae13b4
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/400-v5.19-mtd-call-of_platform_populate-for-MTD-partitions.patch
-@@ -0,0 +1,72 @@
-+From bcdf0315a61a29eb753a607d3a85a4032de72d94 Mon Sep 17 00:00:00 2001
-+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
-+Date: Tue, 10 May 2022 15:12:59 +0200
-+Subject: [PATCH] mtd: call of_platform_populate() for MTD partitions
-+MIME-Version: 1.0
-+Content-Type: text/plain; charset=UTF-8
-+Content-Transfer-Encoding: 8bit
-+
-+Until this change MTD subsystem supported handling partitions only with
-+MTD partitions parsers. That's a specific / limited API designed around
-+partitions.
-+
-+Some MTD partitions may however require different handling. They may
-+contain specific data that needs to be parsed and somehow extracted. For
-+that purpose MTD subsystem should allow binding of standard platform
-+drivers.
-+
-+An example can be U-Boot (sub)partition with environment variables.
-+There exist a "u-boot,env" DT binding for MTD (sub)partition that
-+requires an NVMEM driver.
-+
-+Ref: 5db1c2dbc04c ("dt-bindings: nvmem: add U-Boot environment variables binding")
-+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
-+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
-+Link: https://lore.kernel.org/linux-mtd/20220510131259.555-1-zajec5@gmail.com
-+---
-+ drivers/mtd/mtdpart.c | 9 +++++++++
-+ 1 file changed, 9 insertions(+)
-+
-+--- a/drivers/mtd/mtdpart.c
-++++ b/drivers/mtd/mtdpart.c
-+@@ -17,6 +17,7 @@
-+ #include <linux/mtd/partitions.h>
-+ #include <linux/err.h>
-+ #include <linux/of.h>
-++#include <linux/of_platform.h>
-+ 
-+ #include "mtdcore.h"
-+ 
-+@@ -577,10 +578,16 @@ static int mtd_part_of_parse(struct mtd_
-+ 	struct mtd_part_parser *parser;
-+ 	struct device_node *np;
-+ 	struct property *prop;
-++	struct device *dev;
-+ 	const char *compat;
-+ 	const char *fixed = "fixed-partitions";
-+ 	int ret, err = 0;
-+ 
-++	dev = &master->dev;
-++	/* Use parent device (controller) if the top level MTD is not registered */
-++	if (!IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER) && !mtd_is_partition(master))
-++		dev = master->dev.parent;
-++
-+ 	np = mtd_get_of_node(master);
-+ 	if (mtd_is_partition(master))
-+ 		of_node_get(np);
-+@@ -593,6 +600,7 @@ static int mtd_part_of_parse(struct mtd_
-+ 			continue;
-+ 		ret = mtd_part_do_parse(parser, master, pparts, NULL);
-+ 		if (ret > 0) {
-++			of_platform_populate(np, NULL, NULL, dev);
-+ 			of_node_put(np);
-+ 			return ret;
-+ 		}
-+@@ -600,6 +608,7 @@ static int mtd_part_of_parse(struct mtd_
-+ 		if (ret < 0 && !err)
-+ 			err = ret;
-+ 	}
-++	of_platform_populate(np, NULL, NULL, dev);
-+ 	of_node_put(np);
-+ 
-+ 	/*
-diff --git a/target/linux/generic/backport-5.15/401-v5.20-mtd-parsers-add-support-for-Sercomm-partitions.patch b/target/linux/generic/backport-5.15/401-v5.20-mtd-parsers-add-support-for-Sercomm-partitions.patch
-new file mode 100644
-index 0000000000..113a96ad42
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/401-v5.20-mtd-parsers-add-support-for-Sercomm-partitions.patch
-@@ -0,0 +1,302 @@
-+From 9b78ef0c7997052e9eaa0f7a4513d546fa17358c Mon Sep 17 00:00:00 2001
-+From: Mikhail Zhilkin <csharper2005@gmail.com>
-+Date: Sun, 29 May 2022 11:07:14 +0000
-+Subject: [PATCH] mtd: parsers: add support for Sercomm partitions
-+
-+This adds an MTD partition parser for the Sercomm partition table that
-+is used in some Beeline, Netgear and Sercomm routers.
-+
-+The Sercomm partition map table contains real partition offsets, which
-+may differ from device to device depending on the number and location of
-+bad blocks on NAND.
-+
-+Original patch (proposed by NOGUCHI Hiroshi):
-+Link: https://github.com/openwrt/openwrt/pull/1318#issuecomment-420607394
-+
-+Signed-off-by: NOGUCHI Hiroshi <drvlabo@gmail.com>
-+Signed-off-by: Mikhail Zhilkin <csharper2005@gmail.com>
-+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
-+Link: https://lore.kernel.org/linux-mtd/20220529110714.189732-1-csharper2005@gmail.com
-+---
-+ drivers/mtd/parsers/Kconfig  |   9 ++
-+ drivers/mtd/parsers/Makefile |   1 +
-+ drivers/mtd/parsers/scpart.c | 248 +++++++++++++++++++++++++++++++++++
-+ 3 files changed, 258 insertions(+)
-+ create mode 100644 drivers/mtd/parsers/scpart.c
-+
-+--- a/drivers/mtd/parsers/Kconfig
-++++ b/drivers/mtd/parsers/Kconfig
-+@@ -186,3 +186,12 @@ config MTD_QCOMSMEM_PARTS
-+ 	help
-+ 	  This provides support for parsing partitions from Shared Memory (SMEM)
-+ 	  for NAND and SPI flash on Qualcomm platforms.
-++
-++config MTD_SERCOMM_PARTS
-++	tristate "Sercomm partition table parser"
-++	depends on MTD && RALINK
-++	help
-++	  This provides partitions table parser for devices with Sercomm
-++	  partition map. This partition table contains real partition
-++	  offsets, which may differ from device to device depending on the
-++	  number and location of bad blocks on NAND.
-+--- a/drivers/mtd/parsers/Makefile
-++++ b/drivers/mtd/parsers/Makefile
-+@@ -10,6 +10,7 @@ ofpart-$(CONFIG_MTD_OF_PARTS_LINKSYS_NS)
-+ obj-$(CONFIG_MTD_PARSER_IMAGETAG)	+= parser_imagetag.o
-+ obj-$(CONFIG_MTD_AFS_PARTS)		+= afs.o
-+ obj-$(CONFIG_MTD_PARSER_TRX)		+= parser_trx.o
-++obj-$(CONFIG_MTD_SERCOMM_PARTS)		+= scpart.o
-+ obj-$(CONFIG_MTD_SHARPSL_PARTS)		+= sharpslpart.o
-+ obj-$(CONFIG_MTD_REDBOOT_PARTS)		+= redboot.o
-+ obj-$(CONFIG_MTD_QCOMSMEM_PARTS)	+= qcomsmempart.o
-+--- /dev/null
-++++ b/drivers/mtd/parsers/scpart.c
-+@@ -0,0 +1,248 @@
-++// SPDX-License-Identifier: GPL-2.0-or-later
-++/*
-++ *    drivers/mtd/scpart.c: Sercomm Partition Parser
-++ *
-++ *    Copyright (C) 2018 NOGUCHI Hiroshi
-++ *    Copyright (C) 2022 Mikhail Zhilkin
-++ */
-++
-++#include <linux/kernel.h>
-++#include <linux/slab.h>
-++#include <linux/mtd/mtd.h>
-++#include <linux/mtd/partitions.h>
-++#include <linux/module.h>
-++
-++#define	MOD_NAME	"scpart"
-++
-++#ifdef pr_fmt
-++#undef pr_fmt
-++#endif
-++
-++#define pr_fmt(fmt) MOD_NAME ": " fmt
-++
-++#define	ID_ALREADY_FOUND	0xffffffffUL
-++
-++#define	MAP_OFFS_IN_BLK		0x800
-++#define	MAP_MIRROR_NUM		2
-++
-++static const char sc_part_magic[] = {
-++	'S', 'C', 'F', 'L', 'M', 'A', 'P', 'O', 'K', '\0',
-++};
-++#define	PART_MAGIC_LEN		sizeof(sc_part_magic)
-++
-++/* assumes that all fields are set by CPU native endian */
-++struct sc_part_desc {
-++	uint32_t	part_id;
-++	uint32_t	part_offs;
-++	uint32_t	part_bytes;
-++};
-++
-++static uint32_t scpart_desc_is_valid(struct sc_part_desc *pdesc)
+++/* Compute the PC-relative offset to relative BPF program offset */
+++int get_offset(const struct jit_context *ctx, int off)
 ++{
-++	return ((pdesc->part_id != 0xffffffffUL) &&
-++		(pdesc->part_offs != 0xffffffffUL) &&
-++		(pdesc->part_bytes != 0xffffffffUL));
+++	return (INDEX(ctx->descriptors[ctx->bpf_index + off]) -
+++		ctx->jit_index - 1) * sizeof(u32);
 ++}
 ++
-++static int scpart_scan_partmap(struct mtd_info *master, loff_t partmap_offs,
-++			       struct sc_part_desc **ppdesc)
+++/* dst = imm (register width) */
+++void emit_mov_i(struct jit_context *ctx, u8 dst, s32 imm)
 ++{
-++	int cnt = 0;
-++	int res = 0;
-++	int res2;
-++	loff_t offs;
-++	size_t retlen;
-++	struct sc_part_desc *pdesc = NULL;
-++	struct sc_part_desc *tmpdesc;
-++	uint8_t *buf;
-++
-++	buf = kzalloc(master->erasesize, GFP_KERNEL);
-++	if (!buf) {
-++		res = -ENOMEM;
-++		goto out;
+++	if (imm >= -0x8000 && imm <= 0x7fff) {
+++		emit(ctx, addiu, dst, MIPS_R_ZERO, imm);
+++	} else {
+++		emit(ctx, lui, dst, (s16)((u32)imm >> 16));
+++		emit(ctx, ori, dst, dst, (u16)(imm & 0xffff));
 ++	}
+++	clobber_reg(ctx, dst);
+++}
 ++
-++	res2 = mtd_read(master, partmap_offs, master->erasesize, &retlen, buf);
-++	if (res2 || retlen != master->erasesize) {
-++		res = -EIO;
-++		goto free;
-++	}
+++/* dst = src (register width) */
+++void emit_mov_r(struct jit_context *ctx, u8 dst, u8 src)
+++{
+++	emit(ctx, ori, dst, src, 0);
+++	clobber_reg(ctx, dst);
+++}
 ++
-++	for (offs = MAP_OFFS_IN_BLK;
-++	     offs < master->erasesize - sizeof(*tmpdesc);
-++	     offs += sizeof(*tmpdesc)) {
-++		tmpdesc = (struct sc_part_desc *)&buf[offs];
-++		if (!scpart_desc_is_valid(tmpdesc))
-++			break;
-++		cnt++;
+++/* Validate ALU immediate range */
+++bool valid_alu_i(u8 op, s32 imm)
+++{
+++	switch (BPF_OP(op)) {
+++	case BPF_NEG:
+++	case BPF_LSH:
+++	case BPF_RSH:
+++	case BPF_ARSH:
+++		/* All legal eBPF values are valid */
+++		return true;
+++	case BPF_ADD:
+++		/* imm must be 16 bits */
+++		return imm >= -0x8000 && imm <= 0x7fff;
+++	case BPF_SUB:
+++		/* -imm must be 16 bits */
+++		return imm >= -0x7fff && imm <= 0x8000;
+++	case BPF_AND:
+++	case BPF_OR:
+++	case BPF_XOR:
+++		/* imm must be 16 bits unsigned */
+++		return imm >= 0 && imm <= 0xffff;
+++	case BPF_MUL:
+++		/* imm must be zero or a positive power of two */
+++		return imm == 0 || (imm > 0 && is_power_of_2(imm));
+++	case BPF_DIV:
+++	case BPF_MOD:
+++		/* imm must be an 17-bit power of two */
+++		return (u32)imm <= 0x10000 && is_power_of_2((u32)imm);
 ++	}
+++	return false;
+++}
 ++
-++	if (cnt > 0) {
-++		int bytes = cnt * sizeof(*pdesc);
+++/* Rewrite ALU immediate operation */
+++bool rewrite_alu_i(u8 op, s32 imm, u8 *alu, s32 *val)
+++{
+++	bool act = true;
 ++
-++		pdesc = kcalloc(cnt, sizeof(*pdesc), GFP_KERNEL);
-++		if (!pdesc) {
-++			res = -ENOMEM;
-++			goto free;
+++	switch (BPF_OP(op)) {
+++	case BPF_LSH:
+++	case BPF_RSH:
+++	case BPF_ARSH:
+++	case BPF_ADD:
+++	case BPF_SUB:
+++	case BPF_OR:
+++	case BPF_XOR:
+++		/* imm == 0 is a no-op */
+++		act = imm != 0;
+++		break;
+++	case BPF_MUL:
+++		if (imm == 1) {
+++			/* dst * 1 is a no-op */
+++			act = false;
+++		} else if (imm == 0) {
+++			/* dst * 0 is dst & 0 */
+++			op = BPF_AND;
+++		} else {
+++			/* dst * (1 << n) is dst << n */
+++			op = BPF_LSH;
+++			imm = ilog2(abs(imm));
 ++		}
-++		memcpy(pdesc, &(buf[MAP_OFFS_IN_BLK]), bytes);
-++
-++		*ppdesc = pdesc;
-++		res = cnt;
+++		break;
+++	case BPF_DIV:
+++		if (imm == 1) {
+++			/* dst / 1 is a no-op */
+++			act = false;
+++		} else {
+++			/* dst / (1 << n) is dst >> n */
+++			op = BPF_RSH;
+++			imm = ilog2(imm);
+++		}
+++		break;
+++	case BPF_MOD:
+++		/* dst % (1 << n) is dst & ((1 << n) - 1) */
+++		op = BPF_AND;
+++		imm--;
+++		break;
 ++	}
 ++
-++free:
-++	kfree(buf);
-++
-++out:
-++	return res;
+++	*alu = op;
+++	*val = imm;
+++	return act;
 ++}
 ++
-++static int scpart_find_partmap(struct mtd_info *master,
-++			       struct sc_part_desc **ppdesc)
+++/* ALU immediate operation (32-bit) */
+++void emit_alu_i(struct jit_context *ctx, u8 dst, s32 imm, u8 op)
 ++{
-++	int magic_found = 0;
-++	int res = 0;
-++	int res2;
-++	loff_t offs = 0;
-++	size_t retlen;
-++	uint8_t rdbuf[PART_MAGIC_LEN];
+++	switch (BPF_OP(op)) {
+++	/* dst = -dst */
+++	case BPF_NEG:
+++		emit(ctx, subu, dst, MIPS_R_ZERO, dst);
+++		break;
+++	/* dst = dst & imm */
+++	case BPF_AND:
+++		emit(ctx, andi, dst, dst, (u16)imm);
+++		break;
+++	/* dst = dst | imm */
+++	case BPF_OR:
+++		emit(ctx, ori, dst, dst, (u16)imm);
+++		break;
+++	/* dst = dst ^ imm */
+++	case BPF_XOR:
+++		emit(ctx, xori, dst, dst, (u16)imm);
+++		break;
+++	/* dst = dst << imm */
+++	case BPF_LSH:
+++		emit(ctx, sll, dst, dst, imm);
+++		break;
+++	/* dst = dst >> imm */
+++	case BPF_RSH:
+++		emit(ctx, srl, dst, dst, imm);
+++		break;
+++	/* dst = dst >> imm (arithmetic) */
+++	case BPF_ARSH:
+++		emit(ctx, sra, dst, dst, imm);
+++		break;
+++	/* dst = dst + imm */
+++	case BPF_ADD:
+++		emit(ctx, addiu, dst, dst, imm);
+++		break;
+++	/* dst = dst - imm */
+++	case BPF_SUB:
+++		emit(ctx, addiu, dst, dst, -imm);
+++		break;
+++	}
+++	clobber_reg(ctx, dst);
+++}
 ++
-++	while ((magic_found < MAP_MIRROR_NUM) &&
-++			(offs < master->size) &&
-++			 !mtd_block_isbad(master, offs)) {
-++		res2 = mtd_read(master, offs, PART_MAGIC_LEN, &retlen, rdbuf);
-++		if (res2 || retlen != PART_MAGIC_LEN) {
-++			res = -EIO;
-++			goto out;
+++/* ALU register operation (32-bit) */
+++void emit_alu_r(struct jit_context *ctx, u8 dst, u8 src, u8 op)
+++{
+++	switch (BPF_OP(op)) {
+++	/* dst = dst & src */
+++	case BPF_AND:
+++		emit(ctx, and, dst, dst, src);
+++		break;
+++	/* dst = dst | src */
+++	case BPF_OR:
+++		emit(ctx, or, dst, dst, src);
+++		break;
+++	/* dst = dst ^ src */
+++	case BPF_XOR:
+++		emit(ctx, xor, dst, dst, src);
+++		break;
+++	/* dst = dst << src */
+++	case BPF_LSH:
+++		emit(ctx, sllv, dst, dst, src);
+++		break;
+++	/* dst = dst >> src */
+++	case BPF_RSH:
+++		emit(ctx, srlv, dst, dst, src);
+++		break;
+++	/* dst = dst >> src (arithmetic) */
+++	case BPF_ARSH:
+++		emit(ctx, srav, dst, dst, src);
+++		break;
+++	/* dst = dst + src */
+++	case BPF_ADD:
+++		emit(ctx, addu, dst, dst, src);
+++		break;
+++	/* dst = dst - src */
+++	case BPF_SUB:
+++		emit(ctx, subu, dst, dst, src);
+++		break;
+++	/* dst = dst * src */
+++	case BPF_MUL:
+++		if (cpu_has_mips32r1 || cpu_has_mips32r6) {
+++			emit(ctx, mul, dst, dst, src);
+++		} else {
+++			emit(ctx, multu, dst, src);
+++			emit(ctx, mflo, dst);
 ++		}
-++		if (!memcmp(rdbuf, sc_part_magic, PART_MAGIC_LEN)) {
-++			pr_debug("Signature found at 0x%llx\n", offs);
-++			magic_found++;
-++			res = scpart_scan_partmap(master, offs, ppdesc);
-++			if (res > 0)
-++				goto out;
+++		break;
+++	/* dst = dst / src */
+++	case BPF_DIV:
+++		if (cpu_has_mips32r6) {
+++			emit(ctx, divu_r6, dst, dst, src);
+++		} else {
+++			emit(ctx, divu, dst, src);
+++			emit(ctx, mflo, dst);
 ++		}
-++		offs += master->erasesize;
+++		break;
+++	/* dst = dst % src */
+++	case BPF_MOD:
+++		if (cpu_has_mips32r6) {
+++			emit(ctx, modu, dst, dst, src);
+++		} else {
+++			emit(ctx, divu, dst, src);
+++			emit(ctx, mfhi, dst);
+++		}
+++		break;
 ++	}
-++
-++out:
-++	if (res > 0)
-++		pr_info("Valid 'SC PART MAP' (%d partitions) found at 0x%llx\n", res, offs);
-++	else
-++		pr_info("No valid 'SC PART MAP' was found\n");
-++
-++	return res;
+++	clobber_reg(ctx, dst);
 ++}
 ++
-++static int scpart_parse(struct mtd_info *master,
-++			const struct mtd_partition **pparts,
-++			struct mtd_part_parser_data *data)
+++/* Atomic read-modify-write (32-bit) */
+++void emit_atomic_r(struct jit_context *ctx, u8 dst, u8 src, s16 off, u8 code)
 ++{
-++	const char *partname;
-++	int n;
-++	int nr_scparts;
-++	int nr_parts = 0;
-++	int res = 0;
-++	struct sc_part_desc *scpart_map = NULL;
-++	struct mtd_partition *parts = NULL;
-++	struct device_node *mtd_node;
-++	struct device_node *ofpart_node;
-++	struct device_node *pp;
-++
-++	mtd_node = mtd_get_of_node(master);
-++	if (!mtd_node) {
-++		res = -ENOENT;
-++		goto out;
-++	}
-++
-++	ofpart_node = of_get_child_by_name(mtd_node, "partitions");
-++	if (!ofpart_node) {
-++		pr_info("%s: 'partitions' subnode not found on %pOF.\n",
-++				master->name, mtd_node);
-++		res = -ENOENT;
-++		goto out;
+++	emit(ctx, ll, MIPS_R_T9, off, dst);
+++	switch (code) {
+++	case BPF_ADD:
+++		emit(ctx, addu, MIPS_R_T8, MIPS_R_T9, src);
+++		break;
+++	case BPF_AND:
+++		emit(ctx, and, MIPS_R_T8, MIPS_R_T9, src);
+++		break;
+++	case BPF_OR:
+++		emit(ctx, or, MIPS_R_T8, MIPS_R_T9, src);
+++		break;
+++	case BPF_XOR:
+++		emit(ctx, xor, MIPS_R_T8, MIPS_R_T9, src);
+++		break;
 ++	}
+++	emit(ctx, sc, MIPS_R_T8, off, dst);
+++	emit(ctx, beqz, MIPS_R_T8, -16);
+++	emit(ctx, nop); /* Delay slot */
+++}
 ++
-++	nr_scparts = scpart_find_partmap(master, &scpart_map);
-++	if (nr_scparts <= 0) {
-++		pr_info("No any partitions was found in 'SC PART MAP'.\n");
-++		res = -ENOENT;
-++		goto free;
-++	}
+++/* Atomic compare-and-exchange (32-bit) */
+++void emit_cmpxchg_r(struct jit_context *ctx, u8 dst, u8 src, u8 res, s16 off)
+++{
+++	emit(ctx, ll, MIPS_R_T9, off, dst);
+++	emit(ctx, bne, MIPS_R_T9, res, 12);
+++	emit(ctx, move, MIPS_R_T8, src);     /* Delay slot */
+++	emit(ctx, sc, MIPS_R_T8, off, dst);
+++	emit(ctx, beqz, MIPS_R_T8, -20);
+++	emit(ctx, move, res, MIPS_R_T9);     /* Delay slot */
+++	clobber_reg(ctx, res);
+++}
 ++
-++	parts = kcalloc(of_get_child_count(ofpart_node), sizeof(*parts),
-++		GFP_KERNEL);
-++	if (!parts) {
-++		res = -ENOMEM;
-++		goto free;
-++	}
+++/* Swap bytes and truncate a register word or half word */
+++void emit_bswap_r(struct jit_context *ctx, u8 dst, u32 width)
+++{
+++	u8 tmp = MIPS_R_T8;
+++	u8 msk = MIPS_R_T9;
 ++
-++	for_each_child_of_node(ofpart_node, pp) {
-++		u32 scpart_id;
+++	switch (width) {
+++	/* Swap bytes in a word */
+++	case 32:
+++		if (cpu_has_mips32r2 || cpu_has_mips32r6) {
+++			emit(ctx, wsbh, dst, dst);
+++			emit(ctx, rotr, dst, dst, 16);
+++		} else {
+++			emit(ctx, sll, tmp, dst, 16);    /* tmp  = dst << 16 */
+++			emit(ctx, srl, dst, dst, 16);    /* dst = dst >> 16  */
+++			emit(ctx, or, dst, dst, tmp);    /* dst = dst | tmp  */
 ++
-++		if (of_property_read_u32(pp, "sercomm,scpart-id", &scpart_id))
-++			continue;
+++			emit(ctx, lui, msk, 0xff);       /* msk = 0x00ff0000 */
+++			emit(ctx, ori, msk, msk, 0xff);  /* msk = msk | 0xff */
 ++
-++		for (n = 0 ; n < nr_scparts ; n++)
-++			if ((scpart_map[n].part_id != ID_ALREADY_FOUND) &&
-++					(scpart_id == scpart_map[n].part_id))
-++				break;
-++		if (n >= nr_scparts)
-++			/* not match */
-++			continue;
+++			emit(ctx, and, tmp, dst, msk);   /* tmp = dst & msk  */
+++			emit(ctx, sll, tmp, tmp, 8);     /* tmp = tmp << 8   */
+++			emit(ctx, srl, dst, dst, 8);     /* dst = dst >> 8   */
+++			emit(ctx, and, dst, dst, msk);   /* dst = dst & msk  */
+++			emit(ctx, or, dst, dst, tmp);    /* reg = dst | tmp  */
+++		}
+++		break;
+++	/* Swap bytes in a half word */
+++	case 16:
+++		if (cpu_has_mips32r2 || cpu_has_mips32r6) {
+++			emit(ctx, wsbh, dst, dst);
+++			emit(ctx, andi, dst, dst, 0xffff);
+++		} else {
+++			emit(ctx, andi, tmp, dst, 0xff00); /* t = d & 0xff00 */
+++			emit(ctx, srl, tmp, tmp, 8);       /* t = t >> 8     */
+++			emit(ctx, andi, dst, dst, 0x00ff); /* d = d & 0x00ff */
+++			emit(ctx, sll, dst, dst, 8);       /* d = d << 8     */
+++			emit(ctx, or,  dst, dst, tmp);     /* d = d | t      */
+++		}
+++		break;
+++	}
+++	clobber_reg(ctx, dst);
+++}
 ++
-++		/* add the partition found in OF into MTD partition array */
-++		parts[nr_parts].offset = scpart_map[n].part_offs;
-++		parts[nr_parts].size = scpart_map[n].part_bytes;
-++		parts[nr_parts].of_node = pp;
+++/* Validate jump immediate range */
+++bool valid_jmp_i(u8 op, s32 imm)
+++{
+++	switch (op) {
+++	case JIT_JNOP:
+++		/* Immediate value not used */
+++		return true;
+++	case BPF_JEQ:
+++	case BPF_JNE:
+++		/* No immediate operation */
+++		return false;
+++	case BPF_JSET:
+++	case JIT_JNSET:
+++		/* imm must be 16 bits unsigned */
+++		return imm >= 0 && imm <= 0xffff;
+++	case BPF_JGE:
+++	case BPF_JLT:
+++	case BPF_JSGE:
+++	case BPF_JSLT:
+++		/* imm must be 16 bits */
+++		return imm >= -0x8000 && imm <= 0x7fff;
+++	case BPF_JGT:
+++	case BPF_JLE:
+++	case BPF_JSGT:
+++	case BPF_JSLE:
+++		/* imm + 1 must be 16 bits */
+++		return imm >= -0x8001 && imm <= 0x7ffe;
+++	}
+++	return false;
+++}
 ++
-++		if (!of_property_read_string(pp, "label", &partname))
-++			parts[nr_parts].name = partname;
-++		if (of_property_read_bool(pp, "read-only"))
-++			parts[nr_parts].mask_flags |= MTD_WRITEABLE;
-++		if (of_property_read_bool(pp, "lock"))
-++			parts[nr_parts].mask_flags |= MTD_POWERUP_LOCK;
+++/* Invert a conditional jump operation */
+++static u8 invert_jmp(u8 op)
+++{
+++	switch (op) {
+++	case BPF_JA: return JIT_JNOP;
+++	case BPF_JEQ: return BPF_JNE;
+++	case BPF_JNE: return BPF_JEQ;
+++	case BPF_JSET: return JIT_JNSET;
+++	case BPF_JGT: return BPF_JLE;
+++	case BPF_JGE: return BPF_JLT;
+++	case BPF_JLT: return BPF_JGE;
+++	case BPF_JLE: return BPF_JGT;
+++	case BPF_JSGT: return BPF_JSLE;
+++	case BPF_JSGE: return BPF_JSLT;
+++	case BPF_JSLT: return BPF_JSGE;
+++	case BPF_JSLE: return BPF_JSGT;
+++	}
+++	return 0;
+++}
 ++
-++		/* mark as 'done' */
-++		scpart_map[n].part_id = ID_ALREADY_FOUND;
+++/* Prepare a PC-relative jump operation */
+++static void setup_jmp(struct jit_context *ctx, u8 bpf_op,
+++		      s16 bpf_off, u8 *jit_op, s32 *jit_off)
+++{
+++	u32 *descp = &ctx->descriptors[ctx->bpf_index];
+++	int op = bpf_op;
+++	int offset = 0;
 ++
-++		nr_parts++;
+++	/* Do not compute offsets on the first pass */
+++	if (INDEX(*descp) == 0)
+++		goto done;
+++
+++	/* Skip jumps never taken */
+++	if (bpf_op == JIT_JNOP)
+++		goto done;
+++
+++	/* Convert jumps always taken */
+++	if (bpf_op == BPF_JA)
+++		*descp |= JIT_DESC_CONVERT;
+++
+++	/*
+++	 * Current ctx->jit_index points to the start of the branch preamble.
+++	 * Since the preamble differs among different branch conditionals,
+++	 * the current index cannot be used to compute the branch offset.
+++	 * Instead, we use the offset table value for the next instruction,
+++	 * which gives the index immediately after the branch delay slot.
+++	 */
+++	if (!CONVERTED(*descp)) {
+++		int target = ctx->bpf_index + bpf_off + 1;
+++		int origin = ctx->bpf_index + 1;
+++
+++		offset = (INDEX(ctx->descriptors[target]) -
+++			  INDEX(ctx->descriptors[origin]) + 1) * sizeof(u32);
 ++	}
 ++
-++	if (nr_parts > 0) {
-++		*pparts = parts;
-++		res = nr_parts;
-++	} else
-++		pr_info("No partition in OF matches partition ID with 'SC PART MAP'.\n");
+++	/*
+++	 * The PC-relative branch offset field on MIPS is 18 bits signed,
+++	 * so if the computed offset is larger than this we generate a an
+++	 * absolute jump that we skip with an inverted conditional branch.
+++	 */
+++	if (CONVERTED(*descp) || offset < -0x20000 || offset > 0x1ffff) {
+++		offset = 3 * sizeof(u32);
+++		op = invert_jmp(bpf_op);
+++		ctx->changes += !CONVERTED(*descp);
+++		*descp |= JIT_DESC_CONVERT;
+++	}
 ++
-++	of_node_put(pp);
+++done:
+++	*jit_off = offset;
+++	*jit_op = op;
+++}
 ++
-++free:
-++	kfree(scpart_map);
-++	if (res <= 0)
-++		kfree(parts);
+++/* Prepare a PC-relative jump operation with immediate conditional */
+++void setup_jmp_i(struct jit_context *ctx, s32 imm, u8 width,
+++		 u8 bpf_op, s16 bpf_off, u8 *jit_op, s32 *jit_off)
+++{
+++	bool always = false;
+++	bool never = false;
 ++
-++out:
-++	return res;
+++	switch (bpf_op) {
+++	case BPF_JEQ:
+++	case BPF_JNE:
+++		break;
+++	case BPF_JSET:
+++	case BPF_JLT:
+++		never = imm == 0;
+++		break;
+++	case BPF_JGE:
+++		always = imm == 0;
+++		break;
+++	case BPF_JGT:
+++		never = (u32)imm == U32_MAX;
+++		break;
+++	case BPF_JLE:
+++		always = (u32)imm == U32_MAX;
+++		break;
+++	case BPF_JSGT:
+++		never = imm == S32_MAX && width == 32;
+++		break;
+++	case BPF_JSGE:
+++		always = imm == S32_MIN && width == 32;
+++		break;
+++	case BPF_JSLT:
+++		never = imm == S32_MIN && width == 32;
+++		break;
+++	case BPF_JSLE:
+++		always = imm == S32_MAX && width == 32;
+++		break;
+++	}
+++
+++	if (never)
+++		bpf_op = JIT_JNOP;
+++	if (always)
+++		bpf_op = BPF_JA;
+++	setup_jmp(ctx, bpf_op, bpf_off, jit_op, jit_off);
 ++}
 ++
-++static const struct of_device_id scpart_parser_of_match_table[] = {
-++	{ .compatible = "sercomm,sc-partitions" },
-++	{},
-++};
-++MODULE_DEVICE_TABLE(of, scpart_parser_of_match_table);
+++/* Prepare a PC-relative jump operation with register conditional */
+++void setup_jmp_r(struct jit_context *ctx, bool same_reg,
+++		 u8 bpf_op, s16 bpf_off, u8 *jit_op, s32 *jit_off)
+++{
+++	switch (bpf_op) {
+++	case BPF_JSET:
+++		break;
+++	case BPF_JEQ:
+++	case BPF_JGE:
+++	case BPF_JLE:
+++	case BPF_JSGE:
+++	case BPF_JSLE:
+++		if (same_reg)
+++			bpf_op = BPF_JA;
+++		break;
+++	case BPF_JNE:
+++	case BPF_JLT:
+++	case BPF_JGT:
+++	case BPF_JSGT:
+++	case BPF_JSLT:
+++		if (same_reg)
+++			bpf_op = JIT_JNOP;
+++		break;
+++	}
+++	setup_jmp(ctx, bpf_op, bpf_off, jit_op, jit_off);
+++}
 ++
-++static struct mtd_part_parser scpart_parser = {
-++	.parse_fn = scpart_parse,
-++	.name = "scpart",
-++	.of_match_table = scpart_parser_of_match_table,
-++};
-++module_mtd_part_parser(scpart_parser);
+++/* Finish a PC-relative jump operation */
+++int finish_jmp(struct jit_context *ctx, u8 jit_op, s16 bpf_off)
+++{
+++	/* Emit conditional branch delay slot */
+++	if (jit_op != JIT_JNOP)
+++		emit(ctx, nop);
+++	/*
+++	 * Emit an absolute long jump with delay slot,
+++	 * if the PC-relative branch was converted.
+++	 */
+++	if (CONVERTED(ctx->descriptors[ctx->bpf_index])) {
+++		int target = get_target(ctx, ctx->bpf_index + bpf_off + 1);
 ++
-++/* mtd parsers will request the module by parser name */
-++MODULE_ALIAS("scpart");
-++MODULE_LICENSE("GPL");
-++MODULE_AUTHOR("NOGUCHI Hiroshi <drvlabo@gmail.com>");
-++MODULE_AUTHOR("Mikhail Zhilkin <csharper2005@gmail.com>");
-++MODULE_DESCRIPTION("Sercomm partition parser");
-diff --git a/target/linux/generic/backport-5.15/402-v5.20-mtd-next-mtd-core-introduce-of-support-for-dynamic-partitions.patch b/target/linux/generic/backport-5.15/402-v5.20-mtd-next-mtd-core-introduce-of-support-for-dynamic-partitions.patch
-new file mode 100644
-index 0000000000..8b8e478b00
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/402-v5.20-mtd-next-mtd-core-introduce-of-support-for-dynamic-partitions.patch
-@@ -0,0 +1,106 @@
-+From ad9b10d1eaada169bd764abcab58f08538877e26 Mon Sep 17 00:00:00 2001
-+From: Christian Marangi <ansuelsmth@gmail.com>
-+Date: Wed, 22 Jun 2022 03:06:28 +0200
-+Subject: mtd: core: introduce of support for dynamic partitions
-+
-+We have many parser that register mtd partitions at runtime. One example
-+is the cmdlinepart or the smem-part parser where the compatible is defined
-+in the dts and the partitions gets detected and registered by the
-+parser. This is problematic for the NVMEM subsystem that requires an OF
-+node to detect NVMEM cells.
-+
-+To fix this problem, introduce an additional logic that will try to
-+assign an OF node to the MTD if declared.
-+
-+On MTD addition, it will be checked if the MTD has an OF node and if
-+not declared will check if a partition with the same label / node name is
-+declared in DTS. If an exact match is found, the partition dynamically
-+allocated by the parser will have a connected OF node.
-+
-+The NVMEM subsystem will detect the OF node and register any NVMEM cells
-+declared statically in the DTS.
-+
-+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
-+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
-+Link: https://lore.kernel.org/linux-mtd/20220622010628.30414-4-ansuelsmth@gmail.com
-+---
-+ drivers/mtd/mtdcore.c | 61 +++++++++++++++++++++++++++++++++++++++++++
-+ 1 file changed, 61 insertions(+)
-+
-+--- a/drivers/mtd/mtdcore.c
-++++ b/drivers/mtd/mtdcore.c
-+@@ -564,6 +564,66 @@ static int mtd_nvmem_add(struct mtd_info
-+ 	return 0;
-+ }
-+ 
-++static void mtd_check_of_node(struct mtd_info *mtd)
+++		if (target < 0)
+++			return -1;
+++		emit(ctx, j, target);
+++		emit(ctx, nop);
+++	}
+++	return 0;
+++}
+++
+++/* Jump immediate (32-bit) */
+++void emit_jmp_i(struct jit_context *ctx, u8 dst, s32 imm, s32 off, u8 op)
 ++{
-++	struct device_node *partitions, *parent_dn, *mtd_dn = NULL;
-++	const char *pname, *prefix = "partition-";
-++	int plen, mtd_name_len, offset, prefix_len;
-++	struct mtd_info *parent;
-++	bool found = false;
+++	switch (op) {
+++	/* No-op, used internally for branch optimization */
+++	case JIT_JNOP:
+++		break;
+++	/* PC += off if dst & imm */
+++	case BPF_JSET:
+++		emit(ctx, andi, MIPS_R_T9, dst, (u16)imm);
+++		emit(ctx, bnez, MIPS_R_T9, off);
+++		break;
+++	/* PC += off if (dst & imm) == 0 (not in BPF, used for long jumps) */
+++	case JIT_JNSET:
+++		emit(ctx, andi, MIPS_R_T9, dst, (u16)imm);
+++		emit(ctx, beqz, MIPS_R_T9, off);
+++		break;
+++	/* PC += off if dst > imm */
+++	case BPF_JGT:
+++		emit(ctx, sltiu, MIPS_R_T9, dst, imm + 1);
+++		emit(ctx, beqz, MIPS_R_T9, off);
+++		break;
+++	/* PC += off if dst >= imm */
+++	case BPF_JGE:
+++		emit(ctx, sltiu, MIPS_R_T9, dst, imm);
+++		emit(ctx, beqz, MIPS_R_T9, off);
+++		break;
+++	/* PC += off if dst < imm */
+++	case BPF_JLT:
+++		emit(ctx, sltiu, MIPS_R_T9, dst, imm);
+++		emit(ctx, bnez, MIPS_R_T9, off);
+++		break;
+++	/* PC += off if dst <= imm */
+++	case BPF_JLE:
+++		emit(ctx, sltiu, MIPS_R_T9, dst, imm + 1);
+++		emit(ctx, bnez, MIPS_R_T9, off);
+++		break;
+++	/* PC += off if dst > imm (signed) */
+++	case BPF_JSGT:
+++		emit(ctx, slti, MIPS_R_T9, dst, imm + 1);
+++		emit(ctx, beqz, MIPS_R_T9, off);
+++		break;
+++	/* PC += off if dst >= imm (signed) */
+++	case BPF_JSGE:
+++		emit(ctx, slti, MIPS_R_T9, dst, imm);
+++		emit(ctx, beqz, MIPS_R_T9, off);
+++		break;
+++	/* PC += off if dst < imm (signed) */
+++	case BPF_JSLT:
+++		emit(ctx, slti, MIPS_R_T9, dst, imm);
+++		emit(ctx, bnez, MIPS_R_T9, off);
+++		break;
+++	/* PC += off if dst <= imm (signed) */
+++	case BPF_JSLE:
+++		emit(ctx, slti, MIPS_R_T9, dst, imm + 1);
+++		emit(ctx, bnez, MIPS_R_T9, off);
+++		break;
+++	}
+++}
 ++
-++	/* Check if MTD already has a device node */
-++	if (dev_of_node(&mtd->dev))
-++		return;
+++/* Jump register (32-bit) */
+++void emit_jmp_r(struct jit_context *ctx, u8 dst, u8 src, s32 off, u8 op)
+++{
+++	switch (op) {
+++	/* No-op, used internally for branch optimization */
+++	case JIT_JNOP:
+++		break;
+++	/* PC += off if dst == src */
+++	case BPF_JEQ:
+++		emit(ctx, beq, dst, src, off);
+++		break;
+++	/* PC += off if dst != src */
+++	case BPF_JNE:
+++		emit(ctx, bne, dst, src, off);
+++		break;
+++	/* PC += off if dst & src */
+++	case BPF_JSET:
+++		emit(ctx, and, MIPS_R_T9, dst, src);
+++		emit(ctx, bnez, MIPS_R_T9, off);
+++		break;
+++	/* PC += off if (dst & imm) == 0 (not in BPF, used for long jumps) */
+++	case JIT_JNSET:
+++		emit(ctx, and, MIPS_R_T9, dst, src);
+++		emit(ctx, beqz, MIPS_R_T9, off);
+++		break;
+++	/* PC += off if dst > src */
+++	case BPF_JGT:
+++		emit(ctx, sltu, MIPS_R_T9, src, dst);
+++		emit(ctx, bnez, MIPS_R_T9, off);
+++		break;
+++	/* PC += off if dst >= src */
+++	case BPF_JGE:
+++		emit(ctx, sltu, MIPS_R_T9, dst, src);
+++		emit(ctx, beqz, MIPS_R_T9, off);
+++		break;
+++	/* PC += off if dst < src */
+++	case BPF_JLT:
+++		emit(ctx, sltu, MIPS_R_T9, dst, src);
+++		emit(ctx, bnez, MIPS_R_T9, off);
+++		break;
+++	/* PC += off if dst <= src */
+++	case BPF_JLE:
+++		emit(ctx, sltu, MIPS_R_T9, src, dst);
+++		emit(ctx, beqz, MIPS_R_T9, off);
+++		break;
+++	/* PC += off if dst > src (signed) */
+++	case BPF_JSGT:
+++		emit(ctx, slt, MIPS_R_T9, src, dst);
+++		emit(ctx, bnez, MIPS_R_T9, off);
+++		break;
+++	/* PC += off if dst >= src (signed) */
+++	case BPF_JSGE:
+++		emit(ctx, slt, MIPS_R_T9, dst, src);
+++		emit(ctx, beqz, MIPS_R_T9, off);
+++		break;
+++	/* PC += off if dst < src (signed) */
+++	case BPF_JSLT:
+++		emit(ctx, slt, MIPS_R_T9, dst, src);
+++		emit(ctx, bnez, MIPS_R_T9, off);
+++		break;
+++	/* PC += off if dst <= src (signed) */
+++	case BPF_JSLE:
+++		emit(ctx, slt, MIPS_R_T9, src, dst);
+++		emit(ctx, beqz, MIPS_R_T9, off);
+++		break;
+++	}
+++}
 ++
-++	/* Check if a partitions node exist */
-++	parent = mtd->parent;
-++	parent_dn = dev_of_node(&parent->dev);
-++	if (!parent_dn)
-++		return;
+++/* Jump always */
+++int emit_ja(struct jit_context *ctx, s16 off)
+++{
+++	int target = get_target(ctx, ctx->bpf_index + off + 1);
 ++
-++	partitions = of_get_child_by_name(parent_dn, "partitions");
-++	if (!partitions)
-++		goto exit_parent;
+++	if (target < 0)
+++		return -1;
+++	emit(ctx, j, target);
+++	emit(ctx, nop);
+++	return 0;
+++}
 ++
-++	prefix_len = strlen(prefix);
-++	mtd_name_len = strlen(mtd->name);
+++/* Jump to epilogue */
+++int emit_exit(struct jit_context *ctx)
+++{
+++	int target = get_target(ctx, ctx->program->len);
 ++
-++	/* Search if a partition is defined with the same name */
-++	for_each_child_of_node(partitions, mtd_dn) {
-++		offset = 0;
+++	if (target < 0)
+++		return -1;
+++	emit(ctx, j, target);
+++	emit(ctx, nop);
+++	return 0;
+++}
 ++
-++		/* Skip partition with no/wrong prefix */
-++		if (!of_node_name_prefix(mtd_dn, "partition-"))
-++			continue;
+++/* Build the program body from eBPF bytecode */
+++static int build_body(struct jit_context *ctx)
+++{
+++	const struct bpf_prog *prog = ctx->program;
+++	unsigned int i;
 ++
-++		/* Label have priority. Check that first */
-++		if (of_property_read_string(mtd_dn, "label", &pname)) {
-++			of_property_read_string(mtd_dn, "name", &pname);
-++			offset = prefix_len;
-++		}
+++	ctx->stack_used = 0;
+++	for (i = 0; i < prog->len; i++) {
+++		const struct bpf_insn *insn = &prog->insnsi[i];
+++		u32 *descp = &ctx->descriptors[i];
+++		int ret;
 ++
-++		plen = strlen(pname) - offset;
-++		if (plen == mtd_name_len &&
-++		    !strncmp(mtd->name, pname + offset, plen)) {
-++			found = true;
-++			break;
+++		access_reg(ctx, insn->src_reg);
+++		access_reg(ctx, insn->dst_reg);
+++
+++		ctx->bpf_index = i;
+++		if (ctx->target == NULL) {
+++			ctx->changes += INDEX(*descp) != ctx->jit_index;
+++			*descp &= JIT_DESC_CONVERT;
+++			*descp |= ctx->jit_index;
 ++		}
-++	}
 ++
-++	if (!found)
-++		goto exit_partitions;
+++		ret = build_insn(insn, ctx);
+++		if (ret < 0)
+++			return ret;
 ++
-++	/* Set of_node only for nvmem */
-++	if (of_device_is_compatible(mtd_dn, "nvmem-cells"))
-++		mtd_set_of_node(mtd, mtd_dn);
+++		if (ret > 0) {
+++			i++;
+++			if (ctx->target == NULL)
+++				descp[1] = ctx->jit_index;
+++		}
+++	}
 ++
-++exit_partitions:
-++	of_node_put(partitions);
-++exit_parent:
-++	of_node_put(parent_dn);
+++	/* Store the end offset, where the epilogue begins */
+++	ctx->descriptors[prog->len] = ctx->jit_index;
+++	return 0;
 ++}
 ++
-+ /**
-+  *	add_mtd_device - register an MTD device
-+  *	@mtd: pointer to new MTD device info structure
-+@@ -669,6 +729,7 @@ int add_mtd_device(struct mtd_info *mtd)
-+ 	mtd->dev.devt = MTD_DEVT(i);
-+ 	dev_set_name(&mtd->dev, "mtd%d", i);
-+ 	dev_set_drvdata(&mtd->dev, mtd);
-++	mtd_check_of_node(mtd);
-+ 	of_node_get(mtd_get_of_node(mtd));
-+ 	error = device_register(&mtd->dev);
-+ 	if (error)
-diff --git a/target/linux/generic/backport-5.15/410-mtd-next-mtd-parsers-trx-allow-to-use-on-MediaTek-MIPS-SoCs.patch b/target/linux/generic/backport-5.15/410-mtd-next-mtd-parsers-trx-allow-to-use-on-MediaTek-MIPS-SoCs.patch
-new file mode 100644
-index 0000000000..5c49841760
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/410-mtd-next-mtd-parsers-trx-allow-to-use-on-MediaTek-MIPS-SoCs.patch
-@@ -0,0 +1,33 @@
-+From 2365f91c861cbfeef7141c69842848c7b2d3c2db Mon Sep 17 00:00:00 2001
-+From: INAGAKI Hiroshi <musashino.open@gmail.com>
-+Date: Sun, 13 Feb 2022 15:40:44 +0900
-+Subject: [PATCH] mtd: parsers: trx: allow to use on MediaTek MIPS SoCs
-+
-+Buffalo sells some router devices which have trx-formatted firmware,
-+based on MediaTek MIPS SoCs. To use parser_trx on those devices, add
-+"RALINK" to dependency and allow to compile for MediaTek MIPS SoCs.
-+
-+examples:
-+
-+- WCR-1166DS  (MT7628)
-+- WSR-1166DHP (MT7621)
-+- WSR-2533DHP (MT7621)
-+
-+Signed-off-by: INAGAKI Hiroshi <musashino.open@gmail.com>
-+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
-+Link: https://lore.kernel.org/linux-mtd/20220213064045.1781-1-musashino.open@gmail.com
-+---
-+ drivers/mtd/parsers/Kconfig | 2 +-
-+ 1 file changed, 1 insertion(+), 1 deletion(-)
-+
-+--- a/drivers/mtd/parsers/Kconfig
-++++ b/drivers/mtd/parsers/Kconfig
-+@@ -115,7 +115,7 @@ config MTD_AFS_PARTS
-+ 
-+ config MTD_PARSER_TRX
-+ 	tristate "Parser for TRX format partitions"
-+-	depends on MTD && (BCM47XX || ARCH_BCM_5301X || ARCH_MEDIATEK || COMPILE_TEST)
-++	depends on MTD && (BCM47XX || ARCH_BCM_5301X || ARCH_MEDIATEK || RALINK || COMPILE_TEST)
-+ 	help
-+ 	  TRX is a firmware format used by Broadcom on their devices. It
-+ 	  may contain up to 3/4 partitions (depending on the version).
-diff --git a/target/linux/generic/backport-5.15/420-v5.19-02-mtd-spinand-gigadevice-add-support-for-GD5FxGQ4xExxG.patch b/target/linux/generic/backport-5.15/420-v5.19-02-mtd-spinand-gigadevice-add-support-for-GD5FxGQ4xExxG.patch
-new file mode 100644
-index 0000000000..181c912fbf
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/420-v5.19-02-mtd-spinand-gigadevice-add-support-for-GD5FxGQ4xExxG.patch
-@@ -0,0 +1,58 @@
-+From 573eec222bc82fb5e724586267fbbb1aed9ffd03 Mon Sep 17 00:00:00 2001
-+From: Chuanhong Guo <gch981213@gmail.com>
-+Date: Sun, 20 Mar 2022 17:59:58 +0800
-+Subject: [PATCH 2/5] mtd: spinand: gigadevice: add support for GD5FxGQ4xExxG
-+
-+Add support for:
-+ GD5F1GQ4RExxG
-+ GD5F2GQ4{U,R}ExxG
-+
-+These chips differ from GD5F1GQ4UExxG only in chip ID, voltage
-+and capacity.
-+
-+Signed-off-by: Chuanhong Guo <gch981213@gmail.com>
-+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
-+Link: https://lore.kernel.org/linux-mtd/20220320100001.247905-3-gch981213@gmail.com
-+---
-+ drivers/mtd/nand/spi/gigadevice.c | 30 ++++++++++++++++++++++++++++++
-+ 1 file changed, 30 insertions(+)
-+
-+--- a/drivers/mtd/nand/spi/gigadevice.c
-++++ b/drivers/mtd/nand/spi/gigadevice.c
-+@@ -333,6 +333,36 @@ static const struct spinand_info gigadev
-+ 		     SPINAND_HAS_QE_BIT,
-+ 		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
-+ 				     gd5fxgq4uexxg_ecc_get_status)),
-++	SPINAND_INFO("GD5F1GQ4RExxG",
-++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xc1),
-++		     NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
-++		     NAND_ECCREQ(8, 512),
-++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
-++					      &write_cache_variants,
-++					      &update_cache_variants),
-++		     SPINAND_HAS_QE_BIT,
-++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
-++				     gd5fxgq4uexxg_ecc_get_status)),
-++	SPINAND_INFO("GD5F2GQ4UExxG",
-++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xd2),
-++		     NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
-++		     NAND_ECCREQ(8, 512),
-++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
-++					      &write_cache_variants,
-++					      &update_cache_variants),
-++		     SPINAND_HAS_QE_BIT,
-++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
-++				     gd5fxgq4uexxg_ecc_get_status)),
-++	SPINAND_INFO("GD5F2GQ4RExxG",
-++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xc2),
-++		     NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
-++		     NAND_ECCREQ(8, 512),
-++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
-++					      &write_cache_variants,
-++					      &update_cache_variants),
-++		     SPINAND_HAS_QE_BIT,
-++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
-++				     gd5fxgq4uexxg_ecc_get_status)),
-+ 	SPINAND_INFO("GD5F1GQ4UFxxG",
-+ 		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE, 0xb1, 0x48),
-+ 		     NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
-diff --git a/target/linux/generic/backport-5.15/420-v5.19-03-mtd-spinand-gigadevice-add-support-for-GD5F1GQ5RExxG.patch b/target/linux/generic/backport-5.15/420-v5.19-03-mtd-spinand-gigadevice-add-support-for-GD5F1GQ5RExxG.patch
-new file mode 100644
-index 0000000000..3a1cc9efcf
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/420-v5.19-03-mtd-spinand-gigadevice-add-support-for-GD5F1GQ5RExxG.patch
-@@ -0,0 +1,33 @@
-+From 620a988813403318023296b61228ee8f3fcdb8e0 Mon Sep 17 00:00:00 2001
-+From: Chuanhong Guo <gch981213@gmail.com>
-+Date: Sun, 20 Mar 2022 17:59:59 +0800
-+Subject: [PATCH 3/5] mtd: spinand: gigadevice: add support for GD5F1GQ5RExxG
-+
-+This chip is the 1.8v version of GD5F1GQ5UExxG.
-+
-+Signed-off-by: Chuanhong Guo <gch981213@gmail.com>
-+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
-+Link: https://lore.kernel.org/linux-mtd/20220320100001.247905-4-gch981213@gmail.com
-+---
-+ drivers/mtd/nand/spi/gigadevice.c | 10 ++++++++++
-+ 1 file changed, 10 insertions(+)
-+
-+--- a/drivers/mtd/nand/spi/gigadevice.c
-++++ b/drivers/mtd/nand/spi/gigadevice.c
-+@@ -383,6 +383,16 @@ static const struct spinand_info gigadev
-+ 		     SPINAND_HAS_QE_BIT,
-+ 		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
-+ 				     gd5fxgq5xexxg_ecc_get_status)),
-++	SPINAND_INFO("GD5F1GQ5RExxG",
-++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x41),
-++		     NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
-++		     NAND_ECCREQ(4, 512),
-++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
-++					      &write_cache_variants,
-++					      &update_cache_variants),
-++		     SPINAND_HAS_QE_BIT,
-++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
-++				     gd5fxgq5xexxg_ecc_get_status)),
-+ };
-+ 
-+ static const struct spinand_manufacturer_ops gigadevice_spinand_manuf_ops = {
-diff --git a/target/linux/generic/backport-5.15/420-v5.19-04-mtd-spinand-gigadevice-add-support-for-GD5F-2-4-GQ5x.patch b/target/linux/generic/backport-5.15/420-v5.19-04-mtd-spinand-gigadevice-add-support-for-GD5F-2-4-GQ5x.patch
-new file mode 100644
-index 0000000000..cee9d9db3e
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/420-v5.19-04-mtd-spinand-gigadevice-add-support-for-GD5F-2-4-GQ5x.patch
-@@ -0,0 +1,84 @@
-+From 194ec04b3a9e7fa97d1fbef296410631bc3cf1c8 Mon Sep 17 00:00:00 2001
-+From: Chuanhong Guo <gch981213@gmail.com>
-+Date: Sun, 20 Mar 2022 18:00:00 +0800
-+Subject: [PATCH 4/5] mtd: spinand: gigadevice: add support for GD5F{2,
-+ 4}GQ5xExxG
-+
-+Add support for:
-+ GD5F2GQ5{U,R}ExxG
-+ GD5F4GQ6{U,R}ExxG
-+
-+These chips uses 4 dummy bytes for quad io and 2 dummy bytes for dual io.
-+Besides that and memory layout, they are identical to their 1G variant.
-+
-+Signed-off-by: Chuanhong Guo <gch981213@gmail.com>
-+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
-+Link: https://lore.kernel.org/linux-mtd/20220320100001.247905-5-gch981213@gmail.com
-+---
-+ drivers/mtd/nand/spi/gigadevice.c | 48 +++++++++++++++++++++++++++++++
-+ 1 file changed, 48 insertions(+)
-+
-+--- a/drivers/mtd/nand/spi/gigadevice.c
-++++ b/drivers/mtd/nand/spi/gigadevice.c
-+@@ -47,6 +47,14 @@ static SPINAND_OP_VARIANTS(read_cache_va
-+ 		SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
-+ 		SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
-+ 
-++static SPINAND_OP_VARIANTS(read_cache_variants_2gq5,
-++		SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 4, NULL, 0),
-++		SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
-++		SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 2, NULL, 0),
-++		SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
-++		SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
-++		SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
+++/* Set the branch conversion flag on all instructions */
+++static void set_convert_flag(struct jit_context *ctx, bool enable)
+++{
+++	const struct bpf_prog *prog = ctx->program;
+++	u32 flag = enable ? JIT_DESC_CONVERT : 0;
+++	unsigned int i;
 ++
-+ static SPINAND_OP_VARIANTS(write_cache_variants,
-+ 		SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
-+ 		SPINAND_PROG_LOAD(true, 0, NULL, 0));
-+@@ -391,6 +399,46 @@ static const struct spinand_info gigadev
-+ 					      &write_cache_variants,
-+ 					      &update_cache_variants),
-+ 		     SPINAND_HAS_QE_BIT,
-++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
-++				     gd5fxgq5xexxg_ecc_get_status)),
-++	SPINAND_INFO("GD5F2GQ5UExxG",
-++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x52),
-++		     NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
-++		     NAND_ECCREQ(4, 512),
-++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5,
-++					      &write_cache_variants,
-++					      &update_cache_variants),
-++		     SPINAND_HAS_QE_BIT,
-++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
-++				     gd5fxgq5xexxg_ecc_get_status)),
-++	SPINAND_INFO("GD5F2GQ5RExxG",
-++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x42),
-++		     NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
-++		     NAND_ECCREQ(4, 512),
-++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5,
-++					      &write_cache_variants,
-++					      &update_cache_variants),
-++		     SPINAND_HAS_QE_BIT,
-++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
-++				     gd5fxgq5xexxg_ecc_get_status)),
-++	SPINAND_INFO("GD5F4GQ6UExxG",
-++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x55),
-++		     NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 2, 1),
-++		     NAND_ECCREQ(4, 512),
-++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5,
-++					      &write_cache_variants,
-++					      &update_cache_variants),
-++		     SPINAND_HAS_QE_BIT,
-++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
-++				     gd5fxgq5xexxg_ecc_get_status)),
-++	SPINAND_INFO("GD5F4GQ6RExxG",
-++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x45),
-++		     NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 2, 1),
-++		     NAND_ECCREQ(4, 512),
-++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5,
-++					      &write_cache_variants,
-++					      &update_cache_variants),
-++		     SPINAND_HAS_QE_BIT,
-+ 		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
-+ 				     gd5fxgq5xexxg_ecc_get_status)),
-+ };
-diff --git a/target/linux/generic/backport-5.15/420-v5.19-05-mtd-spinand-gigadevice-add-support-for-GD5FxGM7xExxG.patch b/target/linux/generic/backport-5.15/420-v5.19-05-mtd-spinand-gigadevice-add-support-for-GD5FxGM7xExxG.patch
-new file mode 100644
-index 0000000000..d63113e1a6
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/420-v5.19-05-mtd-spinand-gigadevice-add-support-for-GD5FxGM7xExxG.patch
-@@ -0,0 +1,91 @@
-+From 54647cd003c08b714474a5b599a147ec6a160486 Mon Sep 17 00:00:00 2001
-+From: Chuanhong Guo <gch981213@gmail.com>
-+Date: Sun, 20 Mar 2022 18:00:01 +0800
-+Subject: [PATCH 5/5] mtd: spinand: gigadevice: add support for GD5FxGM7xExxG
-+
-+Add support for:
-+ GD5F{1,2}GM7{U,R}ExxG
-+ GD5F4GM8{U,R}ExxG
-+
-+These are new 27nm counterparts for the GD5FxGQ4 chips from GigaDevice
-+with 8b/512b on-die ECC capability.
-+These chips (and currently supported GD5FxGQ5 chips) have QIO DTR
-+instruction for reading page cache. It isn't added in this patch because
-+I don't have a DTR spi controller for testing.
-+
-+Signed-off-by: Chuanhong Guo <gch981213@gmail.com>
-+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
-+Link: https://lore.kernel.org/linux-mtd/20220320100001.247905-6-gch981213@gmail.com
-+---
-+ drivers/mtd/nand/spi/gigadevice.c | 60 +++++++++++++++++++++++++++++++
-+ 1 file changed, 60 insertions(+)
-+
-+--- a/drivers/mtd/nand/spi/gigadevice.c
-++++ b/drivers/mtd/nand/spi/gigadevice.c
-+@@ -441,6 +441,66 @@ static const struct spinand_info gigadev
-+ 		     SPINAND_HAS_QE_BIT,
-+ 		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
-+ 				     gd5fxgq5xexxg_ecc_get_status)),
-++	SPINAND_INFO("GD5F1GM7UExxG",
-++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x91),
-++		     NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
-++		     NAND_ECCREQ(8, 512),
-++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
-++					      &write_cache_variants,
-++					      &update_cache_variants),
-++		     SPINAND_HAS_QE_BIT,
-++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
-++				     gd5fxgq4uexxg_ecc_get_status)),
-++	SPINAND_INFO("GD5F1GM7RExxG",
-++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x81),
-++		     NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
-++		     NAND_ECCREQ(8, 512),
-++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
-++					      &write_cache_variants,
-++					      &update_cache_variants),
-++		     SPINAND_HAS_QE_BIT,
-++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
-++				     gd5fxgq4uexxg_ecc_get_status)),
-++	SPINAND_INFO("GD5F2GM7UExxG",
-++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x92),
-++		     NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
-++		     NAND_ECCREQ(8, 512),
-++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
-++					      &write_cache_variants,
-++					      &update_cache_variants),
-++		     SPINAND_HAS_QE_BIT,
-++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
-++				     gd5fxgq4uexxg_ecc_get_status)),
-++	SPINAND_INFO("GD5F2GM7RExxG",
-++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x82),
-++		     NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
-++		     NAND_ECCREQ(8, 512),
-++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
-++					      &write_cache_variants,
-++					      &update_cache_variants),
-++		     SPINAND_HAS_QE_BIT,
-++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
-++				     gd5fxgq4uexxg_ecc_get_status)),
-++	SPINAND_INFO("GD5F4GM8UExxG",
-++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x95),
-++		     NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 1, 1, 1),
-++		     NAND_ECCREQ(8, 512),
-++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
-++					      &write_cache_variants,
-++					      &update_cache_variants),
-++		     SPINAND_HAS_QE_BIT,
-++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
-++				     gd5fxgq4uexxg_ecc_get_status)),
-++	SPINAND_INFO("GD5F4GM8RExxG",
-++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x85),
-++		     NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 1, 1, 1),
-++		     NAND_ECCREQ(8, 512),
-++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
-++					      &write_cache_variants,
-++					      &update_cache_variants),
-++		     SPINAND_HAS_QE_BIT,
-++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
-++				     gd5fxgq4uexxg_ecc_get_status)),
-+ };
-+ 
-+ static const struct spinand_manufacturer_ops gigadevice_spinand_manuf_ops = {
-diff --git a/target/linux/generic/backport-5.15/700-net-next-net-dsa-introduce-tagger-owned-storage-for-private.patch b/target/linux/generic/backport-5.15/700-net-next-net-dsa-introduce-tagger-owned-storage-for-private.patch
-new file mode 100644
-index 0000000000..fe47c175a4
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/700-net-next-net-dsa-introduce-tagger-owned-storage-for-private.patch
-@@ -0,0 +1,279 @@
-+From dc452a471dbae8aca8257c565174212620880093 Mon Sep 17 00:00:00 2001
-+From: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Date: Fri, 10 Dec 2021 01:34:37 +0200
-+Subject: net: dsa: introduce tagger-owned storage for private and shared data
-+
-+Ansuel is working on register access over Ethernet for the qca8k switch
-+family. This requires the qca8k tagging protocol driver to receive
-+frames which aren't intended for the network stack, but instead for the
-+qca8k switch driver itself.
-+
-+The dp->priv is currently the prevailing method for passing data back
-+and forth between the tagging protocol driver and the switch driver.
-+However, this method is riddled with caveats.
-+
-+The DSA design allows in principle for any switch driver to return any
-+protocol it desires in ->get_tag_protocol(). The dsa_loop driver can be
-+modified to do just that. But in the current design, the memory behind
-+dp->priv has to be allocated by the switch driver, so if the tagging
-+protocol is paired to an unexpected switch driver, we may end up in NULL
-+pointer dereferences inside the kernel, or worse (a switch driver may
-+allocate dp->priv according to the expectations of a different tagger).
-+
-+The latter possibility is even more plausible considering that DSA
-+switches can dynamically change tagging protocols in certain cases
-+(dsa <-> edsa, ocelot <-> ocelot-8021q), and the current design lends
-+itself to mistakes that are all too easy to make.
-+
-+This patch proposes that the tagging protocol driver should manage its
-+own memory, instead of relying on the switch driver to do so.
-+After analyzing the different in-tree needs, it can be observed that the
-+required tagger storage is per switch, therefore a ds->tagger_data
-+pointer is introduced. In principle, per-port storage could also be
-+introduced, although there is no need for it at the moment. Future
-+changes will replace the current usage of dp->priv with ds->tagger_data.
-+
-+We define a "binding" event between the DSA switch tree and the tagging
-+protocol. During this binding event, the tagging protocol's ->connect()
-+method is called first, and this may allocate some memory for each
-+switch of the tree. Then a cross-chip notifier is emitted for the
-+switches within that tree, and they are given the opportunity to fix up
-+the tagger's memory (for example, they might set up some function
-+pointers that represent virtual methods for consuming packets).
-+Because the memory is owned by the tagger, there exists a ->disconnect()
-+method for the tagger (which is the place to free the resources), but
-+there doesn't exist a ->disconnect() method for the switch driver.
-+This is part of the design. The switch driver should make minimal use of
-+the public part of the tagger data, and only after type-checking it
-+using the supplied "proto" argument.
-+
-+In the code there are in fact two binding events, one is the initial
-+event in dsa_switch_setup_tag_protocol(). At this stage, the cross chip
-+notifier chains aren't initialized, so we call each switch's connect()
-+method by hand. Then there is dsa_tree_bind_tag_proto() during
-+dsa_tree_change_tag_proto(), and here we have an old protocol and a new
-+one. We first connect to the new one before disconnecting from the old
-+one, to simplify error handling a bit and to ensure we remain in a valid
-+state at all times.
-+
-+Co-developed-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ include/net/dsa.h  | 12 +++++++++
-+ net/dsa/dsa2.c     | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++---
-+ net/dsa/dsa_priv.h |  1 +
-+ net/dsa/switch.c   | 14 +++++++++++
-+ 4 files changed, 96 insertions(+), 4 deletions(-)
-+
-+--- a/include/net/dsa.h
-++++ b/include/net/dsa.h
-+@@ -80,12 +80,15 @@ enum dsa_tag_protocol {
-+ };
-+ 
-+ struct dsa_switch;
-++struct dsa_switch_tree;
-+ 
-+ struct dsa_device_ops {
-+ 	struct sk_buff *(*xmit)(struct sk_buff *skb, struct net_device *dev);
-+ 	struct sk_buff *(*rcv)(struct sk_buff *skb, struct net_device *dev);
-+ 	void (*flow_dissect)(const struct sk_buff *skb, __be16 *proto,
-+ 			     int *offset);
-++	int (*connect)(struct dsa_switch_tree *dst);
-++	void (*disconnect)(struct dsa_switch_tree *dst);
-+ 	unsigned int needed_headroom;
-+ 	unsigned int needed_tailroom;
-+ 	const char *name;
-+@@ -329,6 +332,8 @@ struct dsa_switch {
-+ 	 */
-+ 	void *priv;
-+ 
-++	void *tagger_data;
+++	for (i = 0; i <= prog->len; i++)
+++		ctx->descriptors[i] = INDEX(ctx->descriptors[i]) | flag;
+++}
 ++
-+ 	/*
-+ 	 * Configuration data for this switch.
-+ 	 */
-+@@ -584,6 +589,13 @@ struct dsa_switch_ops {
-+ 						  enum dsa_tag_protocol mprot);
-+ 	int	(*change_tag_protocol)(struct dsa_switch *ds, int port,
-+ 				       enum dsa_tag_protocol proto);
-++	/*
-++	 * Method for switch drivers to connect to the tagging protocol driver
-++	 * in current use. The switch driver can provide handlers for certain
-++	 * types of packets for switch management.
-++	 */
-++	int	(*connect_tag_protocol)(struct dsa_switch *ds,
-++					enum dsa_tag_protocol proto);
-+ 
-+ 	/* Optional switch-wide initialization and destruction methods */
-+ 	int	(*setup)(struct dsa_switch *ds);
-+--- a/net/dsa/dsa2.c
-++++ b/net/dsa/dsa2.c
-+@@ -230,8 +230,12 @@ static struct dsa_switch_tree *dsa_tree_
-+ 
-+ static void dsa_tree_free(struct dsa_switch_tree *dst)
-+ {
-+-	if (dst->tag_ops)
-++	if (dst->tag_ops) {
-++		if (dst->tag_ops->disconnect)
-++			dst->tag_ops->disconnect(dst);
+++static void jit_fill_hole(void *area, unsigned int size)
+++{
+++	u32 *p;
 ++
-+ 		dsa_tag_driver_put(dst->tag_ops);
-++	}
-+ 	list_del(&dst->list);
-+ 	kfree(dst);
-+ }
-+@@ -805,7 +809,7 @@ static int dsa_switch_setup_tag_protocol
-+ 	int port, err;
-+ 
-+ 	if (tag_ops->proto == dst->default_proto)
-+-		return 0;
-++		goto connect;
-+ 
-+ 	for (port = 0; port < ds->num_ports; port++) {
-+ 		if (!dsa_is_cpu_port(ds, port))
-+@@ -821,6 +825,17 @@ static int dsa_switch_setup_tag_protocol
-+ 		}
-+ 	}
-+ 
-++connect:
-++	if (ds->ops->connect_tag_protocol) {
-++		err = ds->ops->connect_tag_protocol(ds, tag_ops->proto);
-++		if (err) {
-++			dev_err(ds->dev,
-++				"Unable to connect to tag protocol \"%s\": %pe\n",
-++				tag_ops->name, ERR_PTR(err));
-++			return err;
-++		}
-++	}
+++	/* We are guaranteed to have aligned memory. */
+++	for (p = area; size >= sizeof(u32); size -= sizeof(u32))
+++		uasm_i_break(&p, BRK_BUG); /* Increments p */
+++}
 ++
-+ 	return 0;
-+ }
-+ 
-+@@ -1132,6 +1147,46 @@ static void dsa_tree_teardown(struct dsa
-+ 	dst->setup = false;
-+ }
-+ 
-++static int dsa_tree_bind_tag_proto(struct dsa_switch_tree *dst,
-++				   const struct dsa_device_ops *tag_ops)
+++bool bpf_jit_needs_zext(void)
 ++{
-++	const struct dsa_device_ops *old_tag_ops = dst->tag_ops;
-++	struct dsa_notifier_tag_proto_info info;
-++	int err;
+++	return true;
+++}
 ++
-++	dst->tag_ops = tag_ops;
+++struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
+++{
+++	struct bpf_prog *tmp, *orig_prog = prog;
+++	struct bpf_binary_header *header = NULL;
+++	struct jit_context ctx;
+++	bool tmp_blinded = false;
+++	unsigned int tmp_idx;
+++	unsigned int image_size;
+++	u8 *image_ptr;
+++	int tries;
 ++
-++	/* Notify the new tagger about the connection to this tree */
-++	if (tag_ops->connect) {
-++		err = tag_ops->connect(dst);
-++		if (err)
-++			goto out_revert;
+++	/*
+++	 * If BPF JIT was not enabled then we must fall back to
+++	 * the interpreter.
+++	 */
+++	if (!prog->jit_requested)
+++		return orig_prog;
+++	/*
+++	 * If constant blinding was enabled and we failed during blinding
+++	 * then we must fall back to the interpreter. Otherwise, we save
+++	 * the new JITed code.
+++	 */
+++	tmp = bpf_jit_blind_constants(prog);
+++	if (IS_ERR(tmp))
+++		return orig_prog;
+++	if (tmp != prog) {
+++		tmp_blinded = true;
+++		prog = tmp;
 ++	}
 ++
-++	/* Notify the switches from this tree about the connection
-++	 * to the new tagger
-++	 */
-++	info.tag_ops = tag_ops;
-++	err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_CONNECT, &info);
-++	if (err && err != -EOPNOTSUPP)
-++		goto out_disconnect;
+++	memset(&ctx, 0, sizeof(ctx));
+++	ctx.program = prog;
 ++
-++	/* Notify the old tagger about the disconnection from this tree */
-++	if (old_tag_ops->disconnect)
-++		old_tag_ops->disconnect(dst);
+++	/*
+++	 * Not able to allocate memory for descriptors[], then
+++	 * we must fall back to the interpreter
+++	 */
+++	ctx.descriptors = kcalloc(prog->len + 1, sizeof(*ctx.descriptors),
+++				  GFP_KERNEL);
+++	if (ctx.descriptors == NULL)
+++		goto out_err;
 ++
-++	return 0;
+++	/* First pass discovers used resources */
+++	if (build_body(&ctx) < 0)
+++		goto out_err;
+++	/*
+++	 * Second pass computes instruction offsets.
+++	 * If any PC-relative branches are out of range, a sequence of
+++	 * a PC-relative branch + a jump is generated, and we have to
+++	 * try again from the beginning to generate the new offsets.
+++	 * This is done until no additional conversions are necessary.
+++	 * The last two iterations are done with all branches being
+++	 * converted, to guarantee offset table convergence within a
+++	 * fixed number of iterations.
+++	 */
+++	ctx.jit_index = 0;
+++	build_prologue(&ctx);
+++	tmp_idx = ctx.jit_index;
 ++
-++out_disconnect:
-++	/* Revert the new tagger's connection to this tree */
-++	if (tag_ops->disconnect)
-++		tag_ops->disconnect(dst);
-++out_revert:
-++	dst->tag_ops = old_tag_ops;
+++	tries = JIT_MAX_ITERATIONS;
+++	do {
+++		ctx.jit_index = tmp_idx;
+++		ctx.changes = 0;
+++		if (tries == 2)
+++			set_convert_flag(&ctx, true);
+++		if (build_body(&ctx) < 0)
+++			goto out_err;
+++	} while (ctx.changes > 0 && --tries > 0);
 ++
-++	return err;
-++}
+++	if (WARN_ONCE(ctx.changes > 0, "JIT offsets failed to converge"))
+++		goto out_err;
 ++
-+ /* Since the dsa/tagging sysfs device attribute is per master, the assumption
-+  * is that all DSA switches within a tree share the same tagger, otherwise
-+  * they would have formed disjoint trees (different "dsa,member" values).
-+@@ -1164,12 +1219,15 @@ int dsa_tree_change_tag_proto(struct dsa
-+ 			goto out_unlock;
-+ 	}
-+ 
-++	/* Notify the tag protocol change */
-+ 	info.tag_ops = tag_ops;
-+ 	err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO, &info);
-+ 	if (err)
-+-		goto out_unwind_tagger;
-++		return err;
-+ 
-+-	dst->tag_ops = tag_ops;
-++	err = dsa_tree_bind_tag_proto(dst, tag_ops);
-++	if (err)
-++		goto out_unwind_tagger;
-+ 
-+ 	rtnl_unlock();
-+ 
-+@@ -1257,6 +1315,7 @@ static int dsa_port_parse_cpu(struct dsa
-+ 	struct dsa_switch_tree *dst = ds->dst;
-+ 	const struct dsa_device_ops *tag_ops;
-+ 	enum dsa_tag_protocol default_proto;
-++	int err;
-+ 
-+ 	/* Find out which protocol the switch would prefer. */
-+ 	default_proto = dsa_get_tag_protocol(dp, master);
-+@@ -1304,6 +1363,12 @@ static int dsa_port_parse_cpu(struct dsa
-+ 		 */
-+ 		dsa_tag_driver_put(tag_ops);
-+ 	} else {
-++		if (tag_ops->connect) {
-++			err = tag_ops->connect(dst);
-++			if (err)
-++				return err;
-++		}
+++	build_epilogue(&ctx, MIPS_R_RA);
 ++
-+ 		dst->tag_ops = tag_ops;
-+ 	}
-+ 
-+--- a/net/dsa/dsa_priv.h
-++++ b/net/dsa/dsa_priv.h
-+@@ -37,6 +37,7 @@ enum {
-+ 	DSA_NOTIFIER_VLAN_DEL,
-+ 	DSA_NOTIFIER_MTU,
-+ 	DSA_NOTIFIER_TAG_PROTO,
-++	DSA_NOTIFIER_TAG_PROTO_CONNECT,
-+ 	DSA_NOTIFIER_MRP_ADD,
-+ 	DSA_NOTIFIER_MRP_DEL,
-+ 	DSA_NOTIFIER_MRP_ADD_RING_ROLE,
-+--- a/net/dsa/switch.c
-++++ b/net/dsa/switch.c
-+@@ -616,6 +616,17 @@ static int dsa_switch_change_tag_proto(s
-+ 	return 0;
-+ }
-+ 
-++static int dsa_switch_connect_tag_proto(struct dsa_switch *ds,
-++					struct dsa_notifier_tag_proto_info *info)
-++{
-++	const struct dsa_device_ops *tag_ops = info->tag_ops;
+++	/* Now we know the size of the structure to make */
+++	image_size = sizeof(u32) * ctx.jit_index;
+++	header = bpf_jit_binary_alloc(image_size, &image_ptr,
+++				      sizeof(u32), jit_fill_hole);
+++	/*
+++	 * Not able to allocate memory for the structure then
+++	 * we must fall back to the interpretation
+++	 */
+++	if (header == NULL)
+++		goto out_err;
 ++
-++	if (!ds->ops->connect_tag_protocol)
-++		return -EOPNOTSUPP;
+++	/* Actual pass to generate final JIT code */
+++	ctx.target = (u32 *)image_ptr;
+++	ctx.jit_index = 0;
 ++
-++	return ds->ops->connect_tag_protocol(ds, tag_ops->proto);
-++}
+++	/*
+++	 * If building the JITed code fails somehow,
+++	 * we fall back to the interpretation.
+++	 */
+++	build_prologue(&ctx);
+++	if (build_body(&ctx) < 0)
+++		goto out_err;
+++	build_epilogue(&ctx, MIPS_R_RA);
 ++
-+ static int dsa_switch_mrp_add(struct dsa_switch *ds,
-+ 			      struct dsa_notifier_mrp_info *info)
-+ {
-+@@ -735,6 +746,9 @@ static int dsa_switch_event(struct notif
-+ 	case DSA_NOTIFIER_TAG_PROTO:
-+ 		err = dsa_switch_change_tag_proto(ds, info);
-+ 		break;
-++	case DSA_NOTIFIER_TAG_PROTO_CONNECT:
-++		err = dsa_switch_connect_tag_proto(ds, info);
-++		break;
-+ 	case DSA_NOTIFIER_MRP_ADD:
-+ 		err = dsa_switch_mrp_add(ds, info);
-+ 		break;
-diff --git a/target/linux/generic/backport-5.15/701-net-dsa-make-tagging-protocols-connect-to-individual-switches.patch b/target/linux/generic/backport-5.15/701-net-dsa-make-tagging-protocols-connect-to-individual-switches.patch
-new file mode 100644
-index 0000000000..f682260699
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/701-net-dsa-make-tagging-protocols-connect-to-individual-switches.patch
-@@ -0,0 +1,274 @@
-+From 7f2973149c22e7a6fee4c0c9fa6b8e4108e9c208 Mon Sep 17 00:00:00 2001
-+From: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Date: Tue, 14 Dec 2021 03:45:36 +0200
-+Subject: net: dsa: make tagging protocols connect to individual switches from
-+ a tree
-+
-+On the NXP Bluebox 3 board which uses a multi-switch setup with sja1105,
-+the mechanism through which the tagger connects to the switch tree is
-+broken, due to improper DSA code design. At the time when tag_ops->connect()
-+is called in dsa_port_parse_cpu(), DSA hasn't finished "touching" all
-+the ports, so it doesn't know how large the tree is and how many ports
-+it has. It has just seen the first CPU port by this time. As a result,
-+this function will call the tagger's ->connect method too early, and the
-+tagger will connect only to the first switch from the tree.
-+
-+This could be perhaps addressed a bit more simply by just moving the
-+tag_ops->connect(dst) call a bit later (for example in dsa_tree_setup),
-+but there is already a design inconsistency at present: on the switch
-+side, the notification is on a per-switch basis, but on the tagger side,
-+it is on a per-tree basis. Furthermore, the persistent storage itself is
-+per switch (ds->tagger_data). And the tagger connect and disconnect
-+procedures (at least the ones that exist currently) could see a fair bit
-+of simplification if they didn't have to iterate through the switches of
-+a tree.
-+
-+To fix the issue, this change transforms tag_ops->connect(dst) into
-+tag_ops->connect(ds) and moves it somewhere where we already iterate
-+over all switches of a tree. That is in dsa_switch_setup_tag_protocol(),
-+which is a good placement because we already have there the connection
-+call to the switch side of things.
-+
-+As for the dsa_tree_bind_tag_proto() method (called from the code path
-+that changes the tag protocol), things are a bit more complicated
-+because we receive the tree as argument, yet when we unwind on errors,
-+it would be nice to not call tag_ops->disconnect(ds) where we didn't
-+previously call tag_ops->connect(ds). We didn't have this problem before
-+because the tag_ops connection operations passed the entire dst before,
-+and this is more fine grained now. To solve the error rewind case using
-+the new API, we have to create yet one more cross-chip notifier for
-+disconnection, and stay connected with the old tag protocol to all the
-+switches in the tree until we've succeeded to connect with the new one
-+as well. So if something fails half way, the whole tree is still
-+connected to the old tagger. But there may still be leaks if the tagger
-+fails to connect to the 2nd out of 3 switches in a tree: somebody needs
-+to tell the tagger to disconnect from the first switch. Nothing comes
-+for free, and this was previously handled privately by the tagging
-+protocol driver before, but now we need to emit a disconnect cross-chip
-+notifier for that, because DSA has to take care of the unwind path. We
-+assume that the tagging protocol has connected to a switch if it has set
-+ds->tagger_data to something, otherwise we avoid calling its
-+disconnection method in the error rewind path.
-+
-+The rest of the changes are in the tagging protocol drivers, and have to
-+do with the replacement of dst with ds. The iteration is removed and the
-+error unwind path is simplified, as mentioned above.
-+
-+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ include/net/dsa.h          |  5 ++--
-+ net/dsa/dsa2.c             | 44 +++++++++++++-----------------
-+ net/dsa/dsa_priv.h         |  1 +
-+ net/dsa/switch.c           | 52 ++++++++++++++++++++++++++++++++---
-+ net/dsa/tag_ocelot_8021q.c | 53 +++++++++++-------------------------
-+ net/dsa/tag_sja1105.c      | 67 ++++++++++++++++------------------------------
-+ 6 files changed, 109 insertions(+), 113 deletions(-)
-+
-+--- a/include/net/dsa.h
-++++ b/include/net/dsa.h
-+@@ -80,15 +80,14 @@ enum dsa_tag_protocol {
-+ };
-+ 
-+ struct dsa_switch;
-+-struct dsa_switch_tree;
-+ 
-+ struct dsa_device_ops {
-+ 	struct sk_buff *(*xmit)(struct sk_buff *skb, struct net_device *dev);
-+ 	struct sk_buff *(*rcv)(struct sk_buff *skb, struct net_device *dev);
-+ 	void (*flow_dissect)(const struct sk_buff *skb, __be16 *proto,
-+ 			     int *offset);
-+-	int (*connect)(struct dsa_switch_tree *dst);
-+-	void (*disconnect)(struct dsa_switch_tree *dst);
-++	int (*connect)(struct dsa_switch *ds);
-++	void (*disconnect)(struct dsa_switch *ds);
-+ 	unsigned int needed_headroom;
-+ 	unsigned int needed_tailroom;
-+ 	const char *name;
-+--- a/net/dsa/dsa2.c
-++++ b/net/dsa/dsa2.c
-+@@ -230,12 +230,8 @@ static struct dsa_switch_tree *dsa_tree_
-+ 
-+ static void dsa_tree_free(struct dsa_switch_tree *dst)
-+ {
-+-	if (dst->tag_ops) {
-+-		if (dst->tag_ops->disconnect)
-+-			dst->tag_ops->disconnect(dst);
-+-
-++	if (dst->tag_ops)
-+ 		dsa_tag_driver_put(dst->tag_ops);
-+-	}
-+ 	list_del(&dst->list);
-+ 	kfree(dst);
-+ }
-+@@ -826,17 +822,29 @@ static int dsa_switch_setup_tag_protocol
-+ 	}
-+ 
-+ connect:
-++	if (tag_ops->connect) {
-++		err = tag_ops->connect(ds);
-++		if (err)
-++			return err;
-++	}
+++	/* Populate line info meta data */
+++	set_convert_flag(&ctx, false);
+++	bpf_prog_fill_jited_linfo(prog, &ctx.descriptors[1]);
 ++
-+ 	if (ds->ops->connect_tag_protocol) {
-+ 		err = ds->ops->connect_tag_protocol(ds, tag_ops->proto);
-+ 		if (err) {
-+ 			dev_err(ds->dev,
-+ 				"Unable to connect to tag protocol \"%s\": %pe\n",
-+ 				tag_ops->name, ERR_PTR(err));
-+-			return err;
-++			goto disconnect;
-+ 		}
-+ 	}
-+ 
-+ 	return 0;
+++	/* Set as read-only exec and flush instruction cache */
+++	bpf_jit_binary_lock_ro(header);
+++	flush_icache_range((unsigned long)header,
+++			   (unsigned long)&ctx.target[ctx.jit_index]);
 ++
-++disconnect:
-++	if (tag_ops->disconnect)
-++		tag_ops->disconnect(ds);
+++	if (bpf_jit_enable > 1)
+++		bpf_jit_dump(prog->len, image_size, 2, ctx.target);
 ++
-++	return err;
-+ }
-+ 
-+ static int dsa_switch_setup(struct dsa_switch *ds)
-+@@ -1156,13 +1164,6 @@ static int dsa_tree_bind_tag_proto(struc
-+ 
-+ 	dst->tag_ops = tag_ops;
-+ 
-+-	/* Notify the new tagger about the connection to this tree */
-+-	if (tag_ops->connect) {
-+-		err = tag_ops->connect(dst);
-+-		if (err)
-+-			goto out_revert;
-+-	}
-+-
-+ 	/* Notify the switches from this tree about the connection
-+ 	 * to the new tagger
-+ 	 */
-+@@ -1172,16 +1173,14 @@ static int dsa_tree_bind_tag_proto(struc
-+ 		goto out_disconnect;
-+ 
-+ 	/* Notify the old tagger about the disconnection from this tree */
-+-	if (old_tag_ops->disconnect)
-+-		old_tag_ops->disconnect(dst);
-++	info.tag_ops = old_tag_ops;
-++	dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_DISCONNECT, &info);
-+ 
-+ 	return 0;
-+ 
-+ out_disconnect:
-+-	/* Revert the new tagger's connection to this tree */
-+-	if (tag_ops->disconnect)
-+-		tag_ops->disconnect(dst);
-+-out_revert:
-++	info.tag_ops = tag_ops;
-++	dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_DISCONNECT, &info);
-+ 	dst->tag_ops = old_tag_ops;
-+ 
-+ 	return err;
-+@@ -1315,7 +1314,6 @@ static int dsa_port_parse_cpu(struct dsa
-+ 	struct dsa_switch_tree *dst = ds->dst;
-+ 	const struct dsa_device_ops *tag_ops;
-+ 	enum dsa_tag_protocol default_proto;
-+-	int err;
-+ 
-+ 	/* Find out which protocol the switch would prefer. */
-+ 	default_proto = dsa_get_tag_protocol(dp, master);
-+@@ -1363,12 +1361,6 @@ static int dsa_port_parse_cpu(struct dsa
-+ 		 */
-+ 		dsa_tag_driver_put(tag_ops);
-+ 	} else {
-+-		if (tag_ops->connect) {
-+-			err = tag_ops->connect(dst);
-+-			if (err)
-+-				return err;
-+-		}
-+-
-+ 		dst->tag_ops = tag_ops;
-+ 	}
-+ 
-+--- a/net/dsa/dsa_priv.h
-++++ b/net/dsa/dsa_priv.h
-+@@ -38,6 +38,7 @@ enum {
-+ 	DSA_NOTIFIER_MTU,
-+ 	DSA_NOTIFIER_TAG_PROTO,
-+ 	DSA_NOTIFIER_TAG_PROTO_CONNECT,
-++	DSA_NOTIFIER_TAG_PROTO_DISCONNECT,
-+ 	DSA_NOTIFIER_MRP_ADD,
-+ 	DSA_NOTIFIER_MRP_DEL,
-+ 	DSA_NOTIFIER_MRP_ADD_RING_ROLE,
-+--- a/net/dsa/switch.c
-++++ b/net/dsa/switch.c
-+@@ -616,15 +616,58 @@ static int dsa_switch_change_tag_proto(s
-+ 	return 0;
-+ }
-+ 
-+-static int dsa_switch_connect_tag_proto(struct dsa_switch *ds,
-+-					struct dsa_notifier_tag_proto_info *info)
-++/* We use the same cross-chip notifiers to inform both the tagger side, as well
-++ * as the switch side, of connection and disconnection events.
-++ * Since ds->tagger_data is owned by the tagger, it isn't a hard error if the
-++ * switch side doesn't support connecting to this tagger, and therefore, the
-++ * fact that we don't disconnect the tagger side doesn't constitute a memory
-++ * leak: the tagger will still operate with persistent per-switch memory, just
-++ * with the switch side unconnected to it. What does constitute a hard error is
-++ * when the switch side supports connecting but fails.
-++ */
-++static int
-++dsa_switch_connect_tag_proto(struct dsa_switch *ds,
-++			     struct dsa_notifier_tag_proto_info *info)
-+ {
-+ 	const struct dsa_device_ops *tag_ops = info->tag_ops;
-++	int err;
+++	prog->bpf_func = (void *)ctx.target;
+++	prog->jited = 1;
+++	prog->jited_len = image_size;
 ++
-++	/* Notify the new tagger about the connection to this switch */
-++	if (tag_ops->connect) {
-++		err = tag_ops->connect(ds);
-++		if (err)
-++			return err;
-++	}
-+ 
-+ 	if (!ds->ops->connect_tag_protocol)
-+ 		return -EOPNOTSUPP;
-+ 
-+-	return ds->ops->connect_tag_protocol(ds, tag_ops->proto);
-++	/* Notify the switch about the connection to the new tagger */
-++	err = ds->ops->connect_tag_protocol(ds, tag_ops->proto);
-++	if (err) {
-++		/* Revert the new tagger's connection to this tree */
-++		if (tag_ops->disconnect)
-++			tag_ops->disconnect(ds);
-++		return err;
-++	}
+++out:
+++	if (tmp_blinded)
+++		bpf_jit_prog_release_other(prog, prog == orig_prog ?
+++					   tmp : orig_prog);
+++	kfree(ctx.descriptors);
+++	return prog;
 ++
-++	return 0;
+++out_err:
+++	prog = orig_prog;
+++	if (header)
+++		bpf_jit_binary_free(header);
+++	goto out;
 ++}
++--- /dev/null
+++++ b/arch/mips/net/bpf_jit_comp.h
++@@ -0,0 +1,211 @@
+++/* SPDX-License-Identifier: GPL-2.0-only */
+++/*
+++ * Just-In-Time compiler for eBPF bytecode on 32-bit and 64-bit MIPS.
+++ *
+++ * Copyright (c) 2021 Anyfi Networks AB.
+++ * Author: Johan Almbladh <johan.almbladh@gmail.com>
+++ *
+++ * Based on code and ideas from
+++ * Copyright (c) 2017 Cavium, Inc.
+++ * Copyright (c) 2017 Shubham Bansal <illusionist.neo@gmail.com>
+++ * Copyright (c) 2011 Mircea Gherzan <mgherzan@gmail.com>
+++ */
 ++
-++static int
-++dsa_switch_disconnect_tag_proto(struct dsa_switch *ds,
-++				struct dsa_notifier_tag_proto_info *info)
-++{
-++	const struct dsa_device_ops *tag_ops = info->tag_ops;
-++
-++	/* Notify the tagger about the disconnection from this switch */
-++	if (tag_ops->disconnect && ds->tagger_data)
-++		tag_ops->disconnect(ds);
+++#ifndef _BPF_JIT_COMP_H
+++#define _BPF_JIT_COMP_H
 ++
-++	/* No need to notify the switch, since it shouldn't have any
-++	 * resources to tear down
-++	 */
-++	return 0;
-+ }
-+ 
-+ static int dsa_switch_mrp_add(struct dsa_switch *ds,
-+@@ -749,6 +792,9 @@ static int dsa_switch_event(struct notif
-+ 	case DSA_NOTIFIER_TAG_PROTO_CONNECT:
-+ 		err = dsa_switch_connect_tag_proto(ds, info);
-+ 		break;
-++	case DSA_NOTIFIER_TAG_PROTO_DISCONNECT:
-++		err = dsa_switch_disconnect_tag_proto(ds, info);
-++		break;
-+ 	case DSA_NOTIFIER_MRP_ADD:
-+ 		err = dsa_switch_mrp_add(ds, info);
-+ 		break;
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-00-net-ethernet-mtk_eth_soc-add-support-for-coherent-DM.patch b/target/linux/generic/backport-5.15/702-v5.19-00-net-ethernet-mtk_eth_soc-add-support-for-coherent-DM.patch
-new file mode 100644
-index 0000000000..afd78fdda2
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-00-net-ethernet-mtk_eth_soc-add-support-for-coherent-DM.patch
-@@ -0,0 +1,327 @@
-+From: Felix Fietkau <nbd@nbd.name>
-+Date: Sat, 5 Feb 2022 17:59:07 +0100
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: add support for coherent
-+ DMA
-+
-+It improves performance by eliminating the need for a cache flush on rx and tx
-+In preparation for supporting WED (Wireless Ethernet Dispatch), also add a
-+function for disabling coherent DMA at runtime.
-+
-+Signed-off-by: Felix Fietkau <nbd@nbd.name>
-+---
-+
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -9,6 +9,7 @@
-+ #include <linux/of_device.h>
-+ #include <linux/of_mdio.h>
-+ #include <linux/of_net.h>
-++#include <linux/of_address.h>
-+ #include <linux/mfd/syscon.h>
-+ #include <linux/regmap.h>
-+ #include <linux/clk.h>
-+@@ -839,7 +840,7 @@ static int mtk_init_fq_dma(struct mtk_et
-+ 	dma_addr_t dma_addr;
-+ 	int i;
-+ 
-+-	eth->scratch_ring = dma_alloc_coherent(eth->dev,
-++	eth->scratch_ring = dma_alloc_coherent(eth->dma_dev,
-+ 					       cnt * sizeof(struct mtk_tx_dma),
-+ 					       &eth->phy_scratch_ring,
-+ 					       GFP_ATOMIC);
-+@@ -851,10 +852,10 @@ static int mtk_init_fq_dma(struct mtk_et
-+ 	if (unlikely(!eth->scratch_head))
-+ 		return -ENOMEM;
-+ 
-+-	dma_addr = dma_map_single(eth->dev,
-++	dma_addr = dma_map_single(eth->dma_dev,
-+ 				  eth->scratch_head, cnt * MTK_QDMA_PAGE_SIZE,
-+ 				  DMA_FROM_DEVICE);
-+-	if (unlikely(dma_mapping_error(eth->dev, dma_addr)))
-++	if (unlikely(dma_mapping_error(eth->dma_dev, dma_addr)))
-+ 		return -ENOMEM;
-+ 
-+ 	phy_ring_tail = eth->phy_scratch_ring +
-+@@ -908,26 +909,26 @@ static void mtk_tx_unmap(struct mtk_eth
-+ {
-+ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
-+ 		if (tx_buf->flags & MTK_TX_FLAGS_SINGLE0) {
-+-			dma_unmap_single(eth->dev,
-++			dma_unmap_single(eth->dma_dev,
-+ 					 dma_unmap_addr(tx_buf, dma_addr0),
-+ 					 dma_unmap_len(tx_buf, dma_len0),
-+ 					 DMA_TO_DEVICE);
-+ 		} else if (tx_buf->flags & MTK_TX_FLAGS_PAGE0) {
-+-			dma_unmap_page(eth->dev,
-++			dma_unmap_page(eth->dma_dev,
-+ 				       dma_unmap_addr(tx_buf, dma_addr0),
-+ 				       dma_unmap_len(tx_buf, dma_len0),
-+ 				       DMA_TO_DEVICE);
-+ 		}
-+ 	} else {
-+ 		if (dma_unmap_len(tx_buf, dma_len0)) {
-+-			dma_unmap_page(eth->dev,
-++			dma_unmap_page(eth->dma_dev,
-+ 				       dma_unmap_addr(tx_buf, dma_addr0),
-+ 				       dma_unmap_len(tx_buf, dma_len0),
-+ 				       DMA_TO_DEVICE);
-+ 		}
-+ 
-+ 		if (dma_unmap_len(tx_buf, dma_len1)) {
-+-			dma_unmap_page(eth->dev,
-++			dma_unmap_page(eth->dma_dev,
-+ 				       dma_unmap_addr(tx_buf, dma_addr1),
-+ 				       dma_unmap_len(tx_buf, dma_len1),
-+ 				       DMA_TO_DEVICE);
-+@@ -1005,9 +1006,9 @@ static int mtk_tx_map(struct sk_buff *sk
-+ 	if (skb_vlan_tag_present(skb))
-+ 		txd4 |= TX_DMA_INS_VLAN | skb_vlan_tag_get(skb);
-+ 
-+-	mapped_addr = dma_map_single(eth->dev, skb->data,
-++	mapped_addr = dma_map_single(eth->dma_dev, skb->data,
-+ 				     skb_headlen(skb), DMA_TO_DEVICE);
-+-	if (unlikely(dma_mapping_error(eth->dev, mapped_addr)))
-++	if (unlikely(dma_mapping_error(eth->dma_dev, mapped_addr)))
-+ 		return -ENOMEM;
-+ 
-+ 	WRITE_ONCE(itxd->txd1, mapped_addr);
-+@@ -1046,10 +1047,10 @@ static int mtk_tx_map(struct sk_buff *sk
-+ 
-+ 
-+ 			frag_map_size = min(frag_size, MTK_TX_DMA_BUF_LEN);
-+-			mapped_addr = skb_frag_dma_map(eth->dev, frag, offset,
-++			mapped_addr = skb_frag_dma_map(eth->dma_dev, frag, offset,
-+ 						       frag_map_size,
-+ 						       DMA_TO_DEVICE);
-+-			if (unlikely(dma_mapping_error(eth->dev, mapped_addr)))
-++			if (unlikely(dma_mapping_error(eth->dma_dev, mapped_addr)))
-+ 				goto err_dma;
-+ 
-+ 			if (i == nr_frags - 1 &&
-+@@ -1330,18 +1331,18 @@ static int mtk_poll_rx(struct napi_struc
-+ 			netdev->stats.rx_dropped++;
-+ 			goto release_desc;
-+ 		}
-+-		dma_addr = dma_map_single(eth->dev,
-++		dma_addr = dma_map_single(eth->dma_dev,
-+ 					  new_data + NET_SKB_PAD +
-+ 					  eth->ip_align,
-+ 					  ring->buf_size,
-+ 					  DMA_FROM_DEVICE);
-+-		if (unlikely(dma_mapping_error(eth->dev, dma_addr))) {
-++		if (unlikely(dma_mapping_error(eth->dma_dev, dma_addr))) {
-+ 			skb_free_frag(new_data);
-+ 			netdev->stats.rx_dropped++;
-+ 			goto release_desc;
-+ 		}
-+ 
-+-		dma_unmap_single(eth->dev, trxd.rxd1,
-++		dma_unmap_single(eth->dma_dev, trxd.rxd1,
-+ 				 ring->buf_size, DMA_FROM_DEVICE);
-+ 
-+ 		/* receive data */
-+@@ -1614,7 +1615,7 @@ static int mtk_tx_alloc(struct mtk_eth *
-+ 	if (!ring->buf)
-+ 		goto no_tx_mem;
-+ 
-+-	ring->dma = dma_alloc_coherent(eth->dev, MTK_DMA_SIZE * sz,
-++	ring->dma = dma_alloc_coherent(eth->dma_dev, MTK_DMA_SIZE * sz,
-+ 				       &ring->phys, GFP_ATOMIC);
-+ 	if (!ring->dma)
-+ 		goto no_tx_mem;
-+@@ -1632,7 +1633,7 @@ static int mtk_tx_alloc(struct mtk_eth *
-+ 	 * descriptors in ring->dma_pdma.
-+ 	 */
-+ 	if (!MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
-+-		ring->dma_pdma = dma_alloc_coherent(eth->dev, MTK_DMA_SIZE * sz,
-++		ring->dma_pdma = dma_alloc_coherent(eth->dma_dev, MTK_DMA_SIZE * sz,
-+ 						    &ring->phys_pdma,
-+ 						    GFP_ATOMIC);
-+ 		if (!ring->dma_pdma)
-+@@ -1691,7 +1692,7 @@ static void mtk_tx_clean(struct mtk_eth
-+ 	}
-+ 
-+ 	if (ring->dma) {
-+-		dma_free_coherent(eth->dev,
-++		dma_free_coherent(eth->dma_dev,
-+ 				  MTK_DMA_SIZE * sizeof(*ring->dma),
-+ 				  ring->dma,
-+ 				  ring->phys);
-+@@ -1699,7 +1700,7 @@ static void mtk_tx_clean(struct mtk_eth
-+ 	}
-+ 
-+ 	if (ring->dma_pdma) {
-+-		dma_free_coherent(eth->dev,
-++		dma_free_coherent(eth->dma_dev,
-+ 				  MTK_DMA_SIZE * sizeof(*ring->dma_pdma),
-+ 				  ring->dma_pdma,
-+ 				  ring->phys_pdma);
-+@@ -1747,18 +1748,18 @@ static int mtk_rx_alloc(struct mtk_eth *
-+ 			return -ENOMEM;
-+ 	}
-+ 
-+-	ring->dma = dma_alloc_coherent(eth->dev,
-++	ring->dma = dma_alloc_coherent(eth->dma_dev,
-+ 				       rx_dma_size * sizeof(*ring->dma),
-+ 				       &ring->phys, GFP_ATOMIC);
-+ 	if (!ring->dma)
-+ 		return -ENOMEM;
-+ 
-+ 	for (i = 0; i < rx_dma_size; i++) {
-+-		dma_addr_t dma_addr = dma_map_single(eth->dev,
-++		dma_addr_t dma_addr = dma_map_single(eth->dma_dev,
-+ 				ring->data[i] + NET_SKB_PAD + eth->ip_align,
-+ 				ring->buf_size,
-+ 				DMA_FROM_DEVICE);
-+-		if (unlikely(dma_mapping_error(eth->dev, dma_addr)))
-++		if (unlikely(dma_mapping_error(eth->dma_dev, dma_addr)))
-+ 			return -ENOMEM;
-+ 		ring->dma[i].rxd1 = (unsigned int)dma_addr;
-+ 
-+@@ -1794,7 +1795,7 @@ static void mtk_rx_clean(struct mtk_eth
-+ 				continue;
-+ 			if (!ring->dma[i].rxd1)
-+ 				continue;
-+-			dma_unmap_single(eth->dev,
-++			dma_unmap_single(eth->dma_dev,
-+ 					 ring->dma[i].rxd1,
-+ 					 ring->buf_size,
-+ 					 DMA_FROM_DEVICE);
-+@@ -1805,7 +1806,7 @@ static void mtk_rx_clean(struct mtk_eth
-+ 	}
-+ 
-+ 	if (ring->dma) {
-+-		dma_free_coherent(eth->dev,
-++		dma_free_coherent(eth->dma_dev,
-+ 				  ring->dma_size * sizeof(*ring->dma),
-+ 				  ring->dma,
-+ 				  ring->phys);
-+@@ -2161,7 +2162,7 @@ static void mtk_dma_free(struct mtk_eth
-+ 		if (eth->netdev[i])
-+ 			netdev_reset_queue(eth->netdev[i]);
-+ 	if (eth->scratch_ring) {
-+-		dma_free_coherent(eth->dev,
-++		dma_free_coherent(eth->dma_dev,
-+ 				  MTK_DMA_SIZE * sizeof(struct mtk_tx_dma),
-+ 				  eth->scratch_ring,
-+ 				  eth->phy_scratch_ring);
-+@@ -2511,6 +2512,8 @@ static void mtk_dim_tx(struct work_struc
-+ 
-+ static int mtk_hw_init(struct mtk_eth *eth)
-+ {
-++	u32 dma_mask = ETHSYS_DMA_AG_MAP_PDMA | ETHSYS_DMA_AG_MAP_QDMA |
-++		       ETHSYS_DMA_AG_MAP_PPE;
-+ 	int i, val, ret;
-+ 
-+ 	if (test_and_set_bit(MTK_HW_INIT, &eth->state))
-+@@ -2523,6 +2526,10 @@ static int mtk_hw_init(struct mtk_eth *e
-+ 	if (ret)
-+ 		goto err_disable_pm;
-+ 
-++	if (eth->ethsys)
-++		regmap_update_bits(eth->ethsys, ETHSYS_DMA_AG_MAP, dma_mask,
-++				   of_dma_is_coherent(eth->dma_dev->of_node) * dma_mask);
+++/* MIPS registers */
+++#define MIPS_R_ZERO	0   /* Const zero */
+++#define MIPS_R_AT	1   /* Asm temp   */
+++#define MIPS_R_V0	2   /* Result     */
+++#define MIPS_R_V1	3   /* Result     */
+++#define MIPS_R_A0	4   /* Argument   */
+++#define MIPS_R_A1	5   /* Argument   */
+++#define MIPS_R_A2	6   /* Argument   */
+++#define MIPS_R_A3	7   /* Argument   */
+++#define MIPS_R_A4	8   /* Arg (n64)  */
+++#define MIPS_R_A5	9   /* Arg (n64)  */
+++#define MIPS_R_A6	10  /* Arg (n64)  */
+++#define MIPS_R_A7	11  /* Arg (n64)  */
+++#define MIPS_R_T0	8   /* Temp (o32) */
+++#define MIPS_R_T1	9   /* Temp (o32) */
+++#define MIPS_R_T2	10  /* Temp (o32) */
+++#define MIPS_R_T3	11  /* Temp (o32) */
+++#define MIPS_R_T4	12  /* Temporary  */
+++#define MIPS_R_T5	13  /* Temporary  */
+++#define MIPS_R_T6	14  /* Temporary  */
+++#define MIPS_R_T7	15  /* Temporary  */
+++#define MIPS_R_S0	16  /* Saved      */
+++#define MIPS_R_S1	17  /* Saved      */
+++#define MIPS_R_S2	18  /* Saved      */
+++#define MIPS_R_S3	19  /* Saved      */
+++#define MIPS_R_S4	20  /* Saved      */
+++#define MIPS_R_S5	21  /* Saved      */
+++#define MIPS_R_S6	22  /* Saved      */
+++#define MIPS_R_S7	23  /* Saved      */
+++#define MIPS_R_T8	24  /* Temporary  */
+++#define MIPS_R_T9	25  /* Temporary  */
+++/*      MIPS_R_K0	26     Reserved   */
+++/*      MIPS_R_K1	27     Reserved   */
+++#define MIPS_R_GP	28  /* Global ptr */
+++#define MIPS_R_SP	29  /* Stack ptr  */
+++#define MIPS_R_FP	30  /* Frame ptr  */
+++#define MIPS_R_RA	31  /* Return     */
 ++
-+ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628)) {
-+ 		ret = device_reset(eth->dev);
-+ 		if (ret) {
-+@@ -3076,6 +3083,35 @@ free_netdev:
-+ 	return err;
-+ }
-+ 
-++void mtk_eth_set_dma_device(struct mtk_eth *eth, struct device *dma_dev)
+++/*
+++ * Jump address mask for immediate jumps. The four most significant bits
+++ * must be equal to PC.
+++ */
+++#define MIPS_JMP_MASK	0x0fffffffUL
+++
+++/* Maximum number of iterations in offset table computation */
+++#define JIT_MAX_ITERATIONS	8
+++
+++/*
+++ * Jump pseudo-instructions used internally
+++ * for branch conversion and branch optimization.
+++ */
+++#define JIT_JNSET	0xe0
+++#define JIT_JNOP	0xf0
+++
+++/* Descriptor flag for PC-relative branch conversion */
+++#define JIT_DESC_CONVERT	BIT(31)
+++
+++/* JIT context for an eBPF program */
+++struct jit_context {
+++	struct bpf_prog *program;     /* The eBPF program being JITed        */
+++	u32 *descriptors;             /* eBPF to JITed CPU insn descriptors  */
+++	u32 *target;                  /* JITed code buffer                   */
+++	u32 bpf_index;                /* Index of current BPF program insn   */
+++	u32 jit_index;                /* Index of current JIT target insn    */
+++	u32 changes;                  /* Number of PC-relative branch conv   */
+++	u32 accessed;                 /* Bit mask of read eBPF registers     */
+++	u32 clobbered;                /* Bit mask of modified CPU registers  */
+++	u32 stack_size;               /* Total allocated stack size in bytes */
+++	u32 saved_size;               /* Size of callee-saved registers      */
+++	u32 stack_used;               /* Stack size used for function calls  */
+++};
+++
+++/* Emit the instruction if the JIT memory space has been allocated */
+++#define emit(ctx, func, ...)					\
+++do {								\
+++	if ((ctx)->target != NULL) {				\
+++		u32 *p = &(ctx)->target[ctx->jit_index];	\
+++		uasm_i_##func(&p, ##__VA_ARGS__);		\
+++	}							\
+++	(ctx)->jit_index++;					\
+++} while (0)
+++
+++/*
+++ * Mark a BPF register as accessed, it needs to be
+++ * initialized by the program if expected, e.g. FP.
+++ */
+++static inline void access_reg(struct jit_context *ctx, u8 reg)
 ++{
-++	struct net_device *dev, *tmp;
-++	LIST_HEAD(dev_list);
-++	int i;
+++	ctx->accessed |= BIT(reg);
+++}
 ++
-++	rtnl_lock();
+++/*
+++ * Mark a CPU register as clobbered, it needs to be
+++ * saved/restored by the program if callee-saved.
+++ */
+++static inline void clobber_reg(struct jit_context *ctx, u8 reg)
+++{
+++	ctx->clobbered |= BIT(reg);
+++}
 ++
-++	for (i = 0; i < MTK_MAC_COUNT; i++) {
-++		dev = eth->netdev[i];
+++/*
+++ * Push registers on the stack, starting at a given depth from the stack
+++ * pointer and increasing. The next depth to be written is returned.
+++ */
+++int push_regs(struct jit_context *ctx, u32 mask, u32 excl, int depth);
 ++
-++		if (!dev || !(dev->flags & IFF_UP))
-++			continue;
+++/*
+++ * Pop registers from the stack, starting at a given depth from the stack
+++ * pointer and increasing. The next depth to be read is returned.
+++ */
+++int pop_regs(struct jit_context *ctx, u32 mask, u32 excl, int depth);
 ++
-++		list_add_tail(&dev->close_list, &dev_list);
-++	}
+++/* Compute the 28-bit jump target address from a BPF program location */
+++int get_target(struct jit_context *ctx, u32 loc);
 ++
-++	dev_close_many(&dev_list, false);
+++/* Compute the PC-relative offset to relative BPF program offset */
+++int get_offset(const struct jit_context *ctx, int off);
 ++
-++	eth->dma_dev = dma_dev;
+++/* dst = imm (32-bit) */
+++void emit_mov_i(struct jit_context *ctx, u8 dst, s32 imm);
 ++
-++	list_for_each_entry_safe(dev, tmp, &dev_list, close_list) {
-++		list_del_init(&dev->close_list);
-++		dev_open(dev, NULL);
-++	}
+++/* dst = src (32-bit) */
+++void emit_mov_r(struct jit_context *ctx, u8 dst, u8 src);
 ++
-++	rtnl_unlock();
-++}
+++/* Validate ALU/ALU64 immediate range */
+++bool valid_alu_i(u8 op, s32 imm);
 ++
-+ static int mtk_probe(struct platform_device *pdev)
-+ {
-+ 	struct device_node *mac_np;
-+@@ -3089,6 +3125,7 @@ static int mtk_probe(struct platform_dev
-+ 	eth->soc = of_device_get_match_data(&pdev->dev);
-+ 
-+ 	eth->dev = &pdev->dev;
-++	eth->dma_dev = &pdev->dev;
-+ 	eth->base = devm_platform_ioremap_resource(pdev, 0);
-+ 	if (IS_ERR(eth->base))
-+ 		return PTR_ERR(eth->base);
-+@@ -3137,6 +3174,16 @@ static int mtk_probe(struct platform_dev
-+ 		}
-+ 	}
-+ 
-++	if (of_dma_is_coherent(pdev->dev.of_node)) {
-++		struct regmap *cci;
+++/* Rewrite ALU/ALU64 immediate operation */
+++bool rewrite_alu_i(u8 op, s32 imm, u8 *alu, s32 *val);
 ++
-++		cci = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
-++						      "mediatek,cci-control");
-++		/* enable CPU/bus coherency */
-++		if (!IS_ERR(cci))
-++			regmap_write(cci, 0, 3);
-++	}
+++/* ALU immediate operation (32-bit) */
+++void emit_alu_i(struct jit_context *ctx, u8 dst, s32 imm, u8 op);
 ++
-+ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_SGMII)) {
-+ 		eth->sgmii = devm_kzalloc(eth->dev, sizeof(*eth->sgmii),
-+ 					  GFP_KERNEL);
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-+@@ -462,6 +462,12 @@
-+ #define RSTCTRL_FE		BIT(6)
-+ #define RSTCTRL_PPE		BIT(31)
-+ 
-++/* ethernet dma channel agent map */
-++#define ETHSYS_DMA_AG_MAP	0x408
-++#define ETHSYS_DMA_AG_MAP_PDMA	BIT(0)
-++#define ETHSYS_DMA_AG_MAP_QDMA	BIT(1)
-++#define ETHSYS_DMA_AG_MAP_PPE	BIT(2)
+++/* ALU register operation (32-bit) */
+++void emit_alu_r(struct jit_context *ctx, u8 dst, u8 src, u8 op);
 ++
-+ /* SGMII subsystem config registers */
-+ /* Register to auto-negotiation restart */
-+ #define SGMSYS_PCS_CONTROL_1	0x0
-+@@ -879,6 +885,7 @@ struct mtk_sgmii {
-+ /* struct mtk_eth -	This is the main datasructure for holding the state
-+  *			of the driver
-+  * @dev:		The device pointer
-++ * @dev:		The device pointer used for dma mapping/alloc
-+  * @base:		The mapped register i/o base
-+  * @page_lock:		Make sure that register operations are atomic
-+  * @tx_irq__lock:	Make sure that IRQ register operations are atomic
-+@@ -922,6 +929,7 @@ struct mtk_sgmii {
-+ 
-+ struct mtk_eth {
-+ 	struct device			*dev;
-++	struct device			*dma_dev;
-+ 	void __iomem			*base;
-+ 	spinlock_t			page_lock;
-+ 	spinlock_t			tx_irq_lock;
-+@@ -1020,6 +1028,7 @@ int mtk_gmac_rgmii_path_setup(struct mtk
-+ int mtk_eth_offload_init(struct mtk_eth *eth);
-+ int mtk_eth_setup_tc(struct net_device *dev, enum tc_setup_type type,
-+ 		     void *type_data);
-++void mtk_eth_set_dma_device(struct mtk_eth *eth, struct device *dma_dev);
-+ 
-+ 
-+ #endif /* MTK_ETH_H */
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-01-arm64-dts-mediatek-mt7622-add-support-for-coherent-D.patch b/target/linux/generic/backport-5.15/702-v5.19-01-arm64-dts-mediatek-mt7622-add-support-for-coherent-D.patch
-new file mode 100644
-index 0000000000..d9015d4805
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-01-arm64-dts-mediatek-mt7622-add-support-for-coherent-D.patch
-@@ -0,0 +1,30 @@
-+From: Felix Fietkau <nbd@nbd.name>
-+Date: Mon, 7 Feb 2022 10:27:22 +0100
-+Subject: [PATCH] arm64: dts: mediatek: mt7622: add support for coherent
-+ DMA
-+
-+It improves performance by eliminating the need for a cache flush on rx and tx
-+
-+Signed-off-by: Felix Fietkau <nbd@nbd.name>
-+---
-+
-+--- a/arch/arm64/boot/dts/mediatek/mt7622.dtsi
-++++ b/arch/arm64/boot/dts/mediatek/mt7622.dtsi
-+@@ -357,7 +357,7 @@
-+ 		};
-+ 
-+ 		cci_control2: slave-if@5000 {
-+-			compatible = "arm,cci-400-ctrl-if";
-++			compatible = "arm,cci-400-ctrl-if", "syscon";
-+ 			interface-type = "ace";
-+ 			reg = <0x5000 0x1000>;
-+ 		};
-+@@ -937,6 +937,8 @@
-+ 		power-domains = <&scpsys MT7622_POWER_DOMAIN_ETHSYS>;
-+ 		mediatek,ethsys = <&ethsys>;
-+ 		mediatek,sgmiisys = <&sgmiisys>;
-++		mediatek,cci-control = <&cci_control2>;
-++		dma-coherent;
-+ 		#address-cells = <1>;
-+ 		#size-cells = <0>;
-+ 		status = "disabled";
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-02-net-ethernet-mtk_eth_soc-add-support-for-Wireless-Et.patch b/target/linux/generic/backport-5.15/702-v5.19-02-net-ethernet-mtk_eth_soc-add-support-for-Wireless-Et.patch
-new file mode 100644
-index 0000000000..502d1d0e9b
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-02-net-ethernet-mtk_eth_soc-add-support-for-Wireless-Et.patch
-@@ -0,0 +1,1679 @@
-+From: Felix Fietkau <nbd@nbd.name>
-+Date: Sat, 5 Feb 2022 17:56:08 +0100
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: add support for Wireless
-+ Ethernet Dispatch (WED)
-+
-+The Wireless Ethernet Dispatch subsystem on the MT7622 SoC can be
-+configured to intercept and handle access to the DMA queues and
-+PCIe interrupts for a MT7615/MT7915 wireless card.
-+It can manage the internal WDMA (Wireless DMA) controller, which allows
-+ethernet packets to be passed from the packet switch engine (PSE) to the
-+wireless card, bypassing the CPU entirely.
-+This can be used to implement hardware flow offloading from ethernet to
-+WLAN.
-+
-+Signed-off-by: Felix Fietkau <nbd@nbd.name>
-+---
-+ create mode 100644 drivers/net/ethernet/mediatek/mtk_wed.c
-+ create mode 100644 drivers/net/ethernet/mediatek/mtk_wed.h
-+ create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
-+ create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_ops.c
-+ create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_regs.h
-+ create mode 100644 include/linux/soc/mediatek/mtk_wed.h
-+
-+--- a/drivers/net/ethernet/mediatek/Kconfig
-++++ b/drivers/net/ethernet/mediatek/Kconfig
-+@@ -7,6 +7,10 @@ config NET_VENDOR_MEDIATEK
-+ 
-+ if NET_VENDOR_MEDIATEK
-+ 
-++config NET_MEDIATEK_SOC_WED
-++	depends on ARCH_MEDIATEK || COMPILE_TEST
-++	def_bool NET_MEDIATEK_SOC != n
+++/* Atomic read-modify-write (32-bit) */
+++void emit_atomic_r(struct jit_context *ctx, u8 dst, u8 src, s16 off, u8 code);
 ++
-+ config NET_MEDIATEK_SOC
-+ 	tristate "MediaTek SoC Gigabit Ethernet support"
-+ 	depends on NET_DSA || !NET_DSA
-+--- a/drivers/net/ethernet/mediatek/Makefile
-++++ b/drivers/net/ethernet/mediatek/Makefile
-+@@ -5,4 +5,9 @@
-+ 
-+ obj-$(CONFIG_NET_MEDIATEK_SOC) += mtk_eth.o
-+ mtk_eth-y := mtk_eth_soc.o mtk_sgmii.o mtk_eth_path.o mtk_ppe.o mtk_ppe_debugfs.o mtk_ppe_offload.o
-++mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed.o
-++ifdef CONFIG_DEBUG_FS
-++mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed_debugfs.o
-++endif
-++obj-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed_ops.o
-+ obj-$(CONFIG_NET_MEDIATEK_STAR_EMAC) += mtk_star_emac.o
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -24,6 +24,7 @@
-+ #include <net/dsa.h>
-+ 
-+ #include "mtk_eth_soc.h"
-++#include "mtk_wed.h"
-+ 
-+ static int mtk_msg_level = -1;
-+ module_param_named(msg_level, mtk_msg_level, int, 0);
-+@@ -3206,6 +3207,22 @@ static int mtk_probe(struct platform_dev
-+ 		}
-+ 	}
-+ 
-++	for (i = 0;; i++) {
-++		struct device_node *np = of_parse_phandle(pdev->dev.of_node,
-++							  "mediatek,wed", i);
-++		static const u32 wdma_regs[] = {
-++			MTK_WDMA0_BASE,
-++			MTK_WDMA1_BASE
-++		};
-++		void __iomem *wdma;
-++
-++		if (!np || i >= ARRAY_SIZE(wdma_regs))
-++			break;
-++
-++		wdma = eth->base + wdma_regs[i];
-++		mtk_wed_add_hw(np, eth, wdma, i);
-++	}
-++
-+ 	for (i = 0; i < 3; i++) {
-+ 		if (MTK_HAS_CAPS(eth->soc->caps, MTK_SHARED_INT) && i > 0)
-+ 			eth->irq[i] = eth->irq[0];
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-+@@ -295,6 +295,9 @@
-+ #define MTK_GDM1_TX_GPCNT	0x2438
-+ #define MTK_STAT_OFFSET		0x40
-+ 
-++#define MTK_WDMA0_BASE		0x2800
-++#define MTK_WDMA1_BASE		0x2c00
-++
-+ /* QDMA descriptor txd4 */
-+ #define TX_DMA_CHKSUM		(0x7 << 29)
-+ #define TX_DMA_TSO		BIT(28)
-+--- /dev/null
-++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
-+@@ -0,0 +1,875 @@
-++// SPDX-License-Identifier: GPL-2.0-only
-++/* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name> */
-++
-++#include <linux/kernel.h>
-++#include <linux/slab.h>
-++#include <linux/module.h>
-++#include <linux/bitfield.h>
-++#include <linux/dma-mapping.h>
-++#include <linux/skbuff.h>
-++#include <linux/of_platform.h>
-++#include <linux/of_address.h>
-++#include <linux/mfd/syscon.h>
-++#include <linux/debugfs.h>
-++#include <linux/soc/mediatek/mtk_wed.h>
-++#include "mtk_eth_soc.h"
-++#include "mtk_wed_regs.h"
-++#include "mtk_wed.h"
-++#include "mtk_ppe.h"
-++
-++#define MTK_PCIE_BASE(n)		(0x1a143000 + (n) * 0x2000)
-++
-++#define MTK_WED_PKT_SIZE		1900
-++#define MTK_WED_BUF_SIZE		2048
-++#define MTK_WED_BUF_PER_PAGE		(PAGE_SIZE / 2048)
-++
-++#define MTK_WED_TX_RING_SIZE		2048
-++#define MTK_WED_WDMA_RING_SIZE		1024
-++
-++static struct mtk_wed_hw *hw_list[2];
-++static DEFINE_MUTEX(hw_lock);
-++
-++static void
-++wed_m32(struct mtk_wed_device *dev, u32 reg, u32 mask, u32 val)
-++{
-++	regmap_update_bits(dev->hw->regs, reg, mask | val, val);
-++}
+++/* Atomic compare-and-exchange (32-bit) */
+++void emit_cmpxchg_r(struct jit_context *ctx, u8 dst, u8 src, u8 res, s16 off);
 ++
-++static void
-++wed_set(struct mtk_wed_device *dev, u32 reg, u32 mask)
-++{
-++	return wed_m32(dev, reg, 0, mask);
-++}
+++/* Swap bytes and truncate a register word or half word */
+++void emit_bswap_r(struct jit_context *ctx, u8 dst, u32 width);
 ++
-++static void
-++wed_clr(struct mtk_wed_device *dev, u32 reg, u32 mask)
-++{
-++	return wed_m32(dev, reg, mask, 0);
-++}
+++/* Validate JMP/JMP32 immediate range */
+++bool valid_jmp_i(u8 op, s32 imm);
 ++
-++static void
-++wdma_m32(struct mtk_wed_device *dev, u32 reg, u32 mask, u32 val)
-++{
-++	wdma_w32(dev, reg, (wdma_r32(dev, reg) & ~mask) | val);
-++}
+++/* Prepare a PC-relative jump operation with immediate conditional */
+++void setup_jmp_i(struct jit_context *ctx, s32 imm, u8 width,
+++		 u8 bpf_op, s16 bpf_off, u8 *jit_op, s32 *jit_off);
 ++
-++static void
-++wdma_set(struct mtk_wed_device *dev, u32 reg, u32 mask)
-++{
-++	wdma_m32(dev, reg, 0, mask);
-++}
+++/* Prepare a PC-relative jump operation with register conditional */
+++void setup_jmp_r(struct jit_context *ctx, bool same_reg,
+++		 u8 bpf_op, s16 bpf_off, u8 *jit_op, s32 *jit_off);
 ++
-++static u32
-++mtk_wed_read_reset(struct mtk_wed_device *dev)
-++{
-++	return wed_r32(dev, MTK_WED_RESET);
-++}
+++/* Finish a PC-relative jump operation */
+++int finish_jmp(struct jit_context *ctx, u8 jit_op, s16 bpf_off);
 ++
-++static void
-++mtk_wed_reset(struct mtk_wed_device *dev, u32 mask)
-++{
-++	u32 status;
+++/* Conditional JMP/JMP32 immediate */
+++void emit_jmp_i(struct jit_context *ctx, u8 dst, s32 imm, s32 off, u8 op);
 ++
-++	wed_w32(dev, MTK_WED_RESET, mask);
-++	if (readx_poll_timeout(mtk_wed_read_reset, dev, status,
-++			       !(status & mask), 0, 1000))
-++		WARN_ON_ONCE(1);
-++}
+++/* Conditional JMP/JMP32 register */
+++void emit_jmp_r(struct jit_context *ctx, u8 dst, u8 src, s32 off, u8 op);
 ++
-++static struct mtk_wed_hw *
-++mtk_wed_assign(struct mtk_wed_device *dev)
-++{
-++	struct mtk_wed_hw *hw;
+++/* Jump always */
+++int emit_ja(struct jit_context *ctx, s16 off);
 ++
-++	hw = hw_list[pci_domain_nr(dev->wlan.pci_dev->bus)];
-++	if (!hw || hw->wed_dev)
-++		return NULL;
+++/* Jump to epilogue */
+++int emit_exit(struct jit_context *ctx);
 ++
-++	hw->wed_dev = dev;
-++	return hw;
-++}
+++/*
+++ * Build program prologue to set up the stack and registers.
+++ * This function is implemented separately for 32-bit and 64-bit JITs.
+++ */
+++void build_prologue(struct jit_context *ctx);
 ++
-++static int
-++mtk_wed_buffer_alloc(struct mtk_wed_device *dev)
-++{
-++	struct mtk_wdma_desc *desc;
-++	dma_addr_t desc_phys;
-++	void **page_list;
-++	int token = dev->wlan.token_start;
-++	int ring_size;
-++	int n_pages;
-++	int i, page_idx;
+++/*
+++ * Build the program epilogue to restore the stack and registers.
+++ * This function is implemented separately for 32-bit and 64-bit JITs.
+++ */
+++void build_epilogue(struct jit_context *ctx, int dest_reg);
 ++
-++	ring_size = dev->wlan.nbuf & ~(MTK_WED_BUF_PER_PAGE - 1);
-++	n_pages = ring_size / MTK_WED_BUF_PER_PAGE;
+++/*
+++ * Convert an eBPF instruction to native instruction, i.e
+++ * JITs an eBPF instruction.
+++ * Returns :
+++ *	0  - Successfully JITed an 8-byte eBPF instruction
+++ *	>0 - Successfully JITed a 16-byte eBPF instruction
+++ *	<0 - Failed to JIT.
+++ * This function is implemented separately for 32-bit and 64-bit JITs.
+++ */
+++int build_insn(const struct bpf_insn *insn, struct jit_context *ctx);
 ++
-++	page_list = kcalloc(n_pages, sizeof(*page_list), GFP_KERNEL);
-++	if (!page_list)
-++		return -ENOMEM;
+++#endif /* _BPF_JIT_COMP_H */
++--- /dev/null
+++++ b/arch/mips/net/bpf_jit_comp32.c
++@@ -0,0 +1,1741 @@
+++// SPDX-License-Identifier: GPL-2.0-only
+++/*
+++ * Just-In-Time compiler for eBPF bytecode on MIPS.
+++ * Implementation of JIT functions for 32-bit CPUs.
+++ *
+++ * Copyright (c) 2021 Anyfi Networks AB.
+++ * Author: Johan Almbladh <johan.almbladh@gmail.com>
+++ *
+++ * Based on code and ideas from
+++ * Copyright (c) 2017 Cavium, Inc.
+++ * Copyright (c) 2017 Shubham Bansal <illusionist.neo@gmail.com>
+++ * Copyright (c) 2011 Mircea Gherzan <mgherzan@gmail.com>
+++ */
 ++
-++	dev->buf_ring.size = ring_size;
-++	dev->buf_ring.pages = page_list;
+++#include <linux/math64.h>
+++#include <linux/errno.h>
+++#include <linux/filter.h>
+++#include <linux/bpf.h>
+++#include <asm/cpu-features.h>
+++#include <asm/isa-rev.h>
+++#include <asm/uasm.h>
 ++
-++	desc = dma_alloc_coherent(dev->hw->dev, ring_size * sizeof(*desc),
-++				  &desc_phys, GFP_KERNEL);
-++	if (!desc)
-++		return -ENOMEM;
+++#include "bpf_jit_comp.h"
 ++
-++	dev->buf_ring.desc = desc;
-++	dev->buf_ring.desc_phys = desc_phys;
+++/* MIPS a4-a7 are not available in the o32 ABI */
+++#undef MIPS_R_A4
+++#undef MIPS_R_A5
+++#undef MIPS_R_A6
+++#undef MIPS_R_A7
 ++
-++	for (i = 0, page_idx = 0; i < ring_size; i += MTK_WED_BUF_PER_PAGE) {
-++		dma_addr_t page_phys, buf_phys;
-++		struct page *page;
-++		void *buf;
-++		int s;
+++/* Stack is 8-byte aligned in o32 ABI */
+++#define MIPS_STACK_ALIGNMENT 8
 ++
-++		page = __dev_alloc_pages(GFP_KERNEL, 0);
-++		if (!page)
-++			return -ENOMEM;
+++/*
+++ * The top 16 bytes of a stack frame is reserved for the callee in O32 ABI.
+++ * This corresponds to stack space for register arguments a0-a3.
+++ */
+++#define JIT_RESERVED_STACK 16
 ++
-++		page_phys = dma_map_page(dev->hw->dev, page, 0, PAGE_SIZE,
-++					 DMA_BIDIRECTIONAL);
-++		if (dma_mapping_error(dev->hw->dev, page_phys)) {
-++			__free_page(page);
-++			return -ENOMEM;
-++		}
+++/* Temporary 64-bit register used by JIT */
+++#define JIT_REG_TMP MAX_BPF_JIT_REG
 ++
-++		page_list[page_idx++] = page;
-++		dma_sync_single_for_cpu(dev->hw->dev, page_phys, PAGE_SIZE,
-++					DMA_BIDIRECTIONAL);
+++/*
+++ * Number of prologue bytes to skip when doing a tail call.
+++ * Tail call count (TCC) initialization (8 bytes) always, plus
+++ * R0-to-v0 assignment (4 bytes) if big endian.
+++ */
+++#ifdef __BIG_ENDIAN
+++#define JIT_TCALL_SKIP 12
+++#else
+++#define JIT_TCALL_SKIP 8
+++#endif
 ++
-++		buf = page_to_virt(page);
-++		buf_phys = page_phys;
+++/* CPU registers holding the callee return value */
+++#define JIT_RETURN_REGS	  \
+++	(BIT(MIPS_R_V0) | \
+++	 BIT(MIPS_R_V1))
 ++
-++		for (s = 0; s < MTK_WED_BUF_PER_PAGE; s++) {
-++			u32 txd_size;
+++/* CPU registers arguments passed to callee directly */
+++#define JIT_ARG_REGS      \
+++	(BIT(MIPS_R_A0) | \
+++	 BIT(MIPS_R_A1) | \
+++	 BIT(MIPS_R_A2) | \
+++	 BIT(MIPS_R_A3))
 ++
-++			txd_size = dev->wlan.init_buf(buf, buf_phys, token++);
+++/* CPU register arguments passed to callee on stack */
+++#define JIT_STACK_REGS    \
+++	(BIT(MIPS_R_T0) | \
+++	 BIT(MIPS_R_T1) | \
+++	 BIT(MIPS_R_T2) | \
+++	 BIT(MIPS_R_T3) | \
+++	 BIT(MIPS_R_T4) | \
+++	 BIT(MIPS_R_T5))
 ++
-++			desc->buf0 = buf_phys;
-++			desc->buf1 = buf_phys + txd_size;
-++			desc->ctrl = FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN0,
-++						txd_size) |
-++				     FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN1,
-++						MTK_WED_BUF_SIZE - txd_size) |
-++				     MTK_WDMA_DESC_CTRL_LAST_SEG1;
-++			desc->info = 0;
-++			desc++;
+++/* Caller-saved CPU registers */
+++#define JIT_CALLER_REGS    \
+++	(JIT_RETURN_REGS | \
+++	 JIT_ARG_REGS    | \
+++	 JIT_STACK_REGS)
 ++
-++			buf += MTK_WED_BUF_SIZE;
-++			buf_phys += MTK_WED_BUF_SIZE;
-++		}
+++/* Callee-saved CPU registers */
+++#define JIT_CALLEE_REGS   \
+++	(BIT(MIPS_R_S0) | \
+++	 BIT(MIPS_R_S1) | \
+++	 BIT(MIPS_R_S2) | \
+++	 BIT(MIPS_R_S3) | \
+++	 BIT(MIPS_R_S4) | \
+++	 BIT(MIPS_R_S5) | \
+++	 BIT(MIPS_R_S6) | \
+++	 BIT(MIPS_R_S7) | \
+++	 BIT(MIPS_R_GP) | \
+++	 BIT(MIPS_R_FP) | \
+++	 BIT(MIPS_R_RA))
 ++
-++		dma_sync_single_for_device(dev->hw->dev, page_phys, PAGE_SIZE,
-++					   DMA_BIDIRECTIONAL);
-++	}
+++/*
+++ * Mapping of 64-bit eBPF registers to 32-bit native MIPS registers.
+++ *
+++ * 1) Native register pairs are ordered according to CPU endiannes, following
+++ *    the MIPS convention for passing 64-bit arguments and return values.
+++ * 2) The eBPF return value, arguments and callee-saved registers are mapped
+++ *    to their native MIPS equivalents.
+++ * 3) Since the 32 highest bits in the eBPF FP register are always zero,
+++ *    only one general-purpose register is actually needed for the mapping.
+++ *    We use the fp register for this purpose, and map the highest bits to
+++ *    the MIPS register r0 (zero).
+++ * 4) We use the MIPS gp and at registers as internal temporary registers
+++ *    for constant blinding. The gp register is callee-saved.
+++ * 5) One 64-bit temporary register is mapped for use when sign-extending
+++ *    immediate operands. MIPS registers t6-t9 are available to the JIT
+++ *    for as temporaries when implementing complex 64-bit operations.
+++ *
+++ * With this scheme all eBPF registers are being mapped to native MIPS
+++ * registers without having to use any stack scratch space. The direct
+++ * register mapping (2) simplifies the handling of function calls.
+++ */
+++static const u8 bpf2mips32[][2] = {
+++	/* Return value from in-kernel function, and exit value from eBPF */
+++	[BPF_REG_0] = {MIPS_R_V1, MIPS_R_V0},
+++	/* Arguments from eBPF program to in-kernel function */
+++	[BPF_REG_1] = {MIPS_R_A1, MIPS_R_A0},
+++	[BPF_REG_2] = {MIPS_R_A3, MIPS_R_A2},
+++	/* Remaining arguments, to be passed on the stack per O32 ABI */
+++	[BPF_REG_3] = {MIPS_R_T1, MIPS_R_T0},
+++	[BPF_REG_4] = {MIPS_R_T3, MIPS_R_T2},
+++	[BPF_REG_5] = {MIPS_R_T5, MIPS_R_T4},
+++	/* Callee-saved registers that in-kernel function will preserve */
+++	[BPF_REG_6] = {MIPS_R_S1, MIPS_R_S0},
+++	[BPF_REG_7] = {MIPS_R_S3, MIPS_R_S2},
+++	[BPF_REG_8] = {MIPS_R_S5, MIPS_R_S4},
+++	[BPF_REG_9] = {MIPS_R_S7, MIPS_R_S6},
+++	/* Read-only frame pointer to access the eBPF stack */
+++#ifdef __BIG_ENDIAN
+++	[BPF_REG_FP] = {MIPS_R_FP, MIPS_R_ZERO},
+++#else
+++	[BPF_REG_FP] = {MIPS_R_ZERO, MIPS_R_FP},
+++#endif
+++	/* Temporary register for blinding constants */
+++	[BPF_REG_AX] = {MIPS_R_GP, MIPS_R_AT},
+++	/* Temporary register for internal JIT use */
+++	[JIT_REG_TMP] = {MIPS_R_T7, MIPS_R_T6},
+++};
 ++
-++	return 0;
+++/* Get low CPU register for a 64-bit eBPF register mapping */
+++static inline u8 lo(const u8 reg[])
+++{
+++#ifdef __BIG_ENDIAN
+++	return reg[0];
+++#else
+++	return reg[1];
+++#endif
 ++}
 ++
-++static void
-++mtk_wed_free_buffer(struct mtk_wed_device *dev)
+++/* Get high CPU register for a 64-bit eBPF register mapping */
+++static inline u8 hi(const u8 reg[])
 ++{
-++	struct mtk_wdma_desc *desc = dev->buf_ring.desc;
-++	void **page_list = dev->buf_ring.pages;
-++	int page_idx;
-++	int i;
-++
-++	if (!page_list)
-++		return;
-++
-++	if (!desc)
-++		goto free_pagelist;
-++
-++	for (i = 0, page_idx = 0; i < dev->buf_ring.size; i += MTK_WED_BUF_PER_PAGE) {
-++		void *page = page_list[page_idx++];
-++
-++		if (!page)
-++			break;
-++
-++		dma_unmap_page(dev->hw->dev, desc[i].buf0,
-++			       PAGE_SIZE, DMA_BIDIRECTIONAL);
-++		__free_page(page);
-++	}
-++
-++	dma_free_coherent(dev->hw->dev, dev->buf_ring.size * sizeof(*desc),
-++			  desc, dev->buf_ring.desc_phys);
-++
-++free_pagelist:
-++	kfree(page_list);
+++#ifdef __BIG_ENDIAN
+++	return reg[1];
+++#else
+++	return reg[0];
+++#endif
 ++}
 ++
-++static void
-++mtk_wed_free_ring(struct mtk_wed_device *dev, struct mtk_wed_ring *ring)
+++/*
+++ * Mark a 64-bit CPU register pair as clobbered, it needs to be
+++ * saved/restored by the program if callee-saved.
+++ */
+++static void clobber_reg64(struct jit_context *ctx, const u8 reg[])
 ++{
-++	if (!ring->desc)
-++		return;
-++
-++	dma_free_coherent(dev->hw->dev, ring->size * sizeof(*ring->desc),
-++			  ring->desc, ring->desc_phys);
+++	clobber_reg(ctx, reg[0]);
+++	clobber_reg(ctx, reg[1]);
 ++}
 ++
-++static void
-++mtk_wed_free_tx_rings(struct mtk_wed_device *dev)
+++/* dst = imm (sign-extended) */
+++static void emit_mov_se_i64(struct jit_context *ctx, const u8 dst[], s32 imm)
 ++{
-++	int i;
-++
-++	for (i = 0; i < ARRAY_SIZE(dev->tx_ring); i++)
-++		mtk_wed_free_ring(dev, &dev->tx_ring[i]);
-++	for (i = 0; i < ARRAY_SIZE(dev->tx_wdma); i++)
-++		mtk_wed_free_ring(dev, &dev->tx_wdma[i]);
+++	emit_mov_i(ctx, lo(dst), imm);
+++	if (imm < 0)
+++		emit(ctx, addiu, hi(dst), MIPS_R_ZERO, -1);
+++	else
+++		emit(ctx, move, hi(dst), MIPS_R_ZERO);
+++	clobber_reg64(ctx, dst);
 ++}
 ++
-++static void
-++mtk_wed_set_ext_int(struct mtk_wed_device *dev, bool en)
+++/* Zero extension, if verifier does not do it for us  */
+++static void emit_zext_ver(struct jit_context *ctx, const u8 dst[])
 ++{
-++	u32 mask = MTK_WED_EXT_INT_STATUS_ERROR_MASK;
-++
-++	if (!dev->hw->num_flows)
-++		mask &= ~MTK_WED_EXT_INT_STATUS_TKID_WO_PYLD;
-++
-++	wed_w32(dev, MTK_WED_EXT_INT_MASK, en ? mask : 0);
-++	wed_r32(dev, MTK_WED_EXT_INT_MASK);
+++	if (!ctx->program->aux->verifier_zext) {
+++		emit(ctx, move, hi(dst), MIPS_R_ZERO);
+++		clobber_reg(ctx, hi(dst));
+++	}
 ++}
 ++
-++static void
-++mtk_wed_stop(struct mtk_wed_device *dev)
+++/* Load delay slot, if ISA mandates it */
+++static void emit_load_delay(struct jit_context *ctx)
 ++{
-++	regmap_write(dev->hw->mirror, dev->hw->index * 4, 0);
-++	mtk_wed_set_ext_int(dev, false);
-++
-++	wed_clr(dev, MTK_WED_CTRL,
-++		MTK_WED_CTRL_WDMA_INT_AGENT_EN |
-++		MTK_WED_CTRL_WPDMA_INT_AGENT_EN |
-++		MTK_WED_CTRL_WED_TX_BM_EN |
-++		MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
-++	wed_w32(dev, MTK_WED_WPDMA_INT_TRIGGER, 0);
-++	wed_w32(dev, MTK_WED_WDMA_INT_TRIGGER, 0);
-++	wdma_w32(dev, MTK_WDMA_INT_MASK, 0);
-++	wdma_w32(dev, MTK_WDMA_INT_GRP2, 0);
-++	wed_w32(dev, MTK_WED_WPDMA_INT_MASK, 0);
-++
-++	wed_clr(dev, MTK_WED_GLO_CFG,
-++		MTK_WED_GLO_CFG_TX_DMA_EN |
-++		MTK_WED_GLO_CFG_RX_DMA_EN);
-++	wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
-++		MTK_WED_WPDMA_GLO_CFG_TX_DRV_EN |
-++		MTK_WED_WPDMA_GLO_CFG_RX_DRV_EN);
-++	wed_clr(dev, MTK_WED_WDMA_GLO_CFG,
-++		MTK_WED_WDMA_GLO_CFG_RX_DRV_EN);
+++	if (!cpu_has_mips_2_3_4_5_r)
+++		emit(ctx, nop);
 ++}
 ++
-++static void
-++mtk_wed_detach(struct mtk_wed_device *dev)
+++/* ALU immediate operation (64-bit) */
+++static void emit_alu_i64(struct jit_context *ctx,
+++			 const u8 dst[], s32 imm, u8 op)
 ++{
-++	struct device_node *wlan_node = dev->wlan.pci_dev->dev.of_node;
-++	struct mtk_wed_hw *hw = dev->hw;
+++	u8 src = MIPS_R_T6;
 ++
-++	mutex_lock(&hw_lock);
+++	/*
+++	 * ADD/SUB with all but the max negative imm can be handled by
+++	 * inverting the operation and the imm value, saving one insn.
+++	 */
+++	if (imm > S32_MIN && imm < 0)
+++		switch (op) {
+++		case BPF_ADD:
+++			op = BPF_SUB;
+++			imm = -imm;
+++			break;
+++		case BPF_SUB:
+++			op = BPF_ADD;
+++			imm = -imm;
+++			break;
+++		}
 ++
-++	mtk_wed_stop(dev);
+++	/* Move immediate to temporary register */
+++	emit_mov_i(ctx, src, imm);
 ++
-++	wdma_w32(dev, MTK_WDMA_RESET_IDX, MTK_WDMA_RESET_IDX_RX);
-++	wdma_w32(dev, MTK_WDMA_RESET_IDX, 0);
-++
-++	mtk_wed_reset(dev, MTK_WED_RESET_WED);
-++
-++	mtk_wed_free_buffer(dev);
-++	mtk_wed_free_tx_rings(dev);
-++
-++	if (of_dma_is_coherent(wlan_node))
-++		regmap_update_bits(hw->hifsys, HIFSYS_DMA_AG_MAP,
-++				   BIT(hw->index), BIT(hw->index));
-++
-++	if (!hw_list[!hw->index]->wed_dev &&
-++	    hw->eth->dma_dev != hw->eth->dev)
-++		mtk_eth_set_dma_device(hw->eth, hw->eth->dev);
-++
-++	memset(dev, 0, sizeof(*dev));
-++	module_put(THIS_MODULE);
-++
-++	hw->wed_dev = NULL;
-++	mutex_unlock(&hw_lock);
+++	switch (op) {
+++	/* dst = dst + imm */
+++	case BPF_ADD:
+++		emit(ctx, addu, lo(dst), lo(dst), src);
+++		emit(ctx, sltu, MIPS_R_T9, lo(dst), src);
+++		emit(ctx, addu, hi(dst), hi(dst), MIPS_R_T9);
+++		if (imm < 0)
+++			emit(ctx, addiu, hi(dst), hi(dst), -1);
+++		break;
+++	/* dst = dst - imm */
+++	case BPF_SUB:
+++		emit(ctx, sltu, MIPS_R_T9, lo(dst), src);
+++		emit(ctx, subu, lo(dst), lo(dst), src);
+++		emit(ctx, subu, hi(dst), hi(dst), MIPS_R_T9);
+++		if (imm < 0)
+++			emit(ctx, addiu, hi(dst), hi(dst), 1);
+++		break;
+++	/* dst = dst | imm */
+++	case BPF_OR:
+++		emit(ctx, or, lo(dst), lo(dst), src);
+++		if (imm < 0)
+++			emit(ctx, addiu, hi(dst), MIPS_R_ZERO, -1);
+++		break;
+++	/* dst = dst & imm */
+++	case BPF_AND:
+++		emit(ctx, and, lo(dst), lo(dst), src);
+++		if (imm >= 0)
+++			emit(ctx, move, hi(dst), MIPS_R_ZERO);
+++		break;
+++	/* dst = dst ^ imm */
+++	case BPF_XOR:
+++		emit(ctx, xor, lo(dst), lo(dst), src);
+++		if (imm < 0) {
+++			emit(ctx, subu, hi(dst), MIPS_R_ZERO, hi(dst));
+++			emit(ctx, addiu, hi(dst), hi(dst), -1);
+++		}
+++		break;
+++	}
+++	clobber_reg64(ctx, dst);
 ++}
 ++
-++static void
-++mtk_wed_hw_init_early(struct mtk_wed_device *dev)
+++/* ALU register operation (64-bit) */
+++static void emit_alu_r64(struct jit_context *ctx,
+++			 const u8 dst[], const u8 src[], u8 op)
 ++{
-++	u32 mask, set;
-++	u32 offset;
-++
-++	mtk_wed_stop(dev);
-++	mtk_wed_reset(dev, MTK_WED_RESET_WED);
-++
-++	mask = MTK_WED_WDMA_GLO_CFG_BT_SIZE |
-++	       MTK_WED_WDMA_GLO_CFG_DYNAMIC_DMAD_RECYCLE |
-++	       MTK_WED_WDMA_GLO_CFG_RX_DIS_FSM_AUTO_IDLE;
-++	set = FIELD_PREP(MTK_WED_WDMA_GLO_CFG_BT_SIZE, 2) |
-++	      MTK_WED_WDMA_GLO_CFG_DYNAMIC_SKIP_DMAD_PREP |
-++	      MTK_WED_WDMA_GLO_CFG_IDLE_DMAD_SUPPLY;
-++	wed_m32(dev, MTK_WED_WDMA_GLO_CFG, mask, set);
-++
-++	wdma_set(dev, MTK_WDMA_GLO_CFG, MTK_WDMA_GLO_CFG_RX_INFO_PRERES);
-++
-++	offset = dev->hw->index ? 0x04000400 : 0;
-++	wed_w32(dev, MTK_WED_WDMA_OFFSET0, 0x2a042a20 + offset);
-++	wed_w32(dev, MTK_WED_WDMA_OFFSET1, 0x29002800 + offset);
-++
-++	wed_w32(dev, MTK_WED_PCIE_CFG_BASE, MTK_PCIE_BASE(dev->hw->index));
-++	wed_w32(dev, MTK_WED_WPDMA_CFG_BASE, dev->wlan.wpdma_phys);
+++	switch (BPF_OP(op)) {
+++	/* dst = dst + src */
+++	case BPF_ADD:
+++		if (src == dst) {
+++			emit(ctx, srl, MIPS_R_T9, lo(dst), 31);
+++			emit(ctx, addu, lo(dst), lo(dst), lo(dst));
+++		} else {
+++			emit(ctx, addu, lo(dst), lo(dst), lo(src));
+++			emit(ctx, sltu, MIPS_R_T9, lo(dst), lo(src));
+++		}
+++		emit(ctx, addu, hi(dst), hi(dst), hi(src));
+++		emit(ctx, addu, hi(dst), hi(dst), MIPS_R_T9);
+++		break;
+++	/* dst = dst - src */
+++	case BPF_SUB:
+++		emit(ctx, sltu, MIPS_R_T9, lo(dst), lo(src));
+++		emit(ctx, subu, lo(dst), lo(dst), lo(src));
+++		emit(ctx, subu, hi(dst), hi(dst), hi(src));
+++		emit(ctx, subu, hi(dst), hi(dst), MIPS_R_T9);
+++		break;
+++	/* dst = dst | src */
+++	case BPF_OR:
+++		emit(ctx, or, lo(dst), lo(dst), lo(src));
+++		emit(ctx, or, hi(dst), hi(dst), hi(src));
+++		break;
+++	/* dst = dst & src */
+++	case BPF_AND:
+++		emit(ctx, and, lo(dst), lo(dst), lo(src));
+++		emit(ctx, and, hi(dst), hi(dst), hi(src));
+++		break;
+++	/* dst = dst ^ src */
+++	case BPF_XOR:
+++		emit(ctx, xor, lo(dst), lo(dst), lo(src));
+++		emit(ctx, xor, hi(dst), hi(dst), hi(src));
+++		break;
+++	}
+++	clobber_reg64(ctx, dst);
 ++}
 ++
-++static void
-++mtk_wed_hw_init(struct mtk_wed_device *dev)
+++/* ALU invert (64-bit) */
+++static void emit_neg_i64(struct jit_context *ctx, const u8 dst[])
 ++{
-++	if (dev->init_done)
-++		return;
-++
-++	dev->init_done = true;
-++	mtk_wed_set_ext_int(dev, false);
-++	wed_w32(dev, MTK_WED_TX_BM_CTRL,
-++		MTK_WED_TX_BM_CTRL_PAUSE |
-++		FIELD_PREP(MTK_WED_TX_BM_CTRL_VLD_GRP_NUM,
-++			   dev->buf_ring.size / 128) |
-++		FIELD_PREP(MTK_WED_TX_BM_CTRL_RSV_GRP_NUM,
-++			   MTK_WED_TX_RING_SIZE / 256));
-++
-++	wed_w32(dev, MTK_WED_TX_BM_BASE, dev->buf_ring.desc_phys);
-++
-++	wed_w32(dev, MTK_WED_TX_BM_TKID,
-++		FIELD_PREP(MTK_WED_TX_BM_TKID_START,
-++			   dev->wlan.token_start) |
-++		FIELD_PREP(MTK_WED_TX_BM_TKID_END,
-++			   dev->wlan.token_start + dev->wlan.nbuf - 1));
-++
-++	wed_w32(dev, MTK_WED_TX_BM_BUF_LEN, MTK_WED_PKT_SIZE);
-++
-++	wed_w32(dev, MTK_WED_TX_BM_DYN_THR,
-++		FIELD_PREP(MTK_WED_TX_BM_DYN_THR_LO, 1) |
-++		MTK_WED_TX_BM_DYN_THR_HI);
-++
-++	mtk_wed_reset(dev, MTK_WED_RESET_TX_BM);
-++
-++	wed_set(dev, MTK_WED_CTRL,
-++		MTK_WED_CTRL_WED_TX_BM_EN |
-++		MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
+++	emit(ctx, sltu, MIPS_R_T9, MIPS_R_ZERO, lo(dst));
+++	emit(ctx, subu, lo(dst), MIPS_R_ZERO, lo(dst));
+++	emit(ctx, subu, hi(dst), MIPS_R_ZERO, hi(dst));
+++	emit(ctx, subu, hi(dst), hi(dst), MIPS_R_T9);
 ++
-++	wed_clr(dev, MTK_WED_TX_BM_CTRL, MTK_WED_TX_BM_CTRL_PAUSE);
+++	clobber_reg64(ctx, dst);
 ++}
 ++
-++static void
-++mtk_wed_ring_reset(struct mtk_wdma_desc *desc, int size)
+++/* ALU shift immediate (64-bit) */
+++static void emit_shift_i64(struct jit_context *ctx,
+++			   const u8 dst[], u32 imm, u8 op)
 ++{
-++	int i;
-++
-++	for (i = 0; i < size; i++) {
-++		desc[i].buf0 = 0;
-++		desc[i].ctrl = cpu_to_le32(MTK_WDMA_DESC_CTRL_DMA_DONE);
-++		desc[i].buf1 = 0;
-++		desc[i].info = 0;
+++	switch (BPF_OP(op)) {
+++	/* dst = dst << imm */
+++	case BPF_LSH:
+++		if (imm < 32) {
+++			emit(ctx, srl, MIPS_R_T9, lo(dst), 32 - imm);
+++			emit(ctx, sll, lo(dst), lo(dst), imm);
+++			emit(ctx, sll, hi(dst), hi(dst), imm);
+++			emit(ctx, or, hi(dst), hi(dst), MIPS_R_T9);
+++		} else {
+++			emit(ctx, sll, hi(dst), lo(dst), imm - 32);
+++			emit(ctx, move, lo(dst), MIPS_R_ZERO);
+++		}
+++		break;
+++	/* dst = dst >> imm */
+++	case BPF_RSH:
+++		if (imm < 32) {
+++			emit(ctx, sll, MIPS_R_T9, hi(dst), 32 - imm);
+++			emit(ctx, srl, lo(dst), lo(dst), imm);
+++			emit(ctx, srl, hi(dst), hi(dst), imm);
+++			emit(ctx, or, lo(dst), lo(dst), MIPS_R_T9);
+++		} else {
+++			emit(ctx, srl, lo(dst), hi(dst), imm - 32);
+++			emit(ctx, move, hi(dst), MIPS_R_ZERO);
+++		}
+++		break;
+++	/* dst = dst >> imm (arithmetic) */
+++	case BPF_ARSH:
+++		if (imm < 32) {
+++			emit(ctx, sll, MIPS_R_T9, hi(dst), 32 - imm);
+++			emit(ctx, srl, lo(dst), lo(dst), imm);
+++			emit(ctx, sra, hi(dst), hi(dst), imm);
+++			emit(ctx, or, lo(dst), lo(dst), MIPS_R_T9);
+++		} else {
+++			emit(ctx, sra, lo(dst), hi(dst), imm - 32);
+++			emit(ctx, sra, hi(dst), hi(dst), 31);
+++		}
+++		break;
 ++	}
+++	clobber_reg64(ctx, dst);
 ++}
 ++
-++static u32
-++mtk_wed_check_busy(struct mtk_wed_device *dev)
+++/* ALU shift register (64-bit) */
+++static void emit_shift_r64(struct jit_context *ctx,
+++			   const u8 dst[], u8 src, u8 op)
 ++{
-++	if (wed_r32(dev, MTK_WED_GLO_CFG) & MTK_WED_GLO_CFG_TX_DMA_BUSY)
-++		return true;
-++
-++	if (wed_r32(dev, MTK_WED_WPDMA_GLO_CFG) &
-++	    MTK_WED_WPDMA_GLO_CFG_TX_DRV_BUSY)
-++		return true;
-++
-++	if (wed_r32(dev, MTK_WED_CTRL) & MTK_WED_CTRL_WDMA_INT_AGENT_BUSY)
-++		return true;
-++
-++	if (wed_r32(dev, MTK_WED_WDMA_GLO_CFG) &
-++	    MTK_WED_WDMA_GLO_CFG_RX_DRV_BUSY)
-++		return true;
+++	u8 t1 = MIPS_R_T8;
+++	u8 t2 = MIPS_R_T9;
 ++
-++	if (wdma_r32(dev, MTK_WDMA_GLO_CFG) &
-++	    MTK_WED_WDMA_GLO_CFG_RX_DRV_BUSY)
-++		return true;
+++	emit(ctx, andi, t1, src, 32);              /* t1 = src & 32          */
+++	emit(ctx, beqz, t1, 16);                   /* PC += 16 if t1 == 0    */
+++	emit(ctx, nor, t2, src, MIPS_R_ZERO);      /* t2 = ~src (delay slot) */
 ++
-++	if (wed_r32(dev, MTK_WED_CTRL) &
-++	    (MTK_WED_CTRL_WED_TX_BM_BUSY | MTK_WED_CTRL_WED_TX_FREE_AGENT_BUSY))
-++		return true;
+++	switch (BPF_OP(op)) {
+++	/* dst = dst << src */
+++	case BPF_LSH:
+++		/* Next: shift >= 32 */
+++		emit(ctx, sllv, hi(dst), lo(dst), src);    /* dh = dl << src */
+++		emit(ctx, move, lo(dst), MIPS_R_ZERO);     /* dl = 0         */
+++		emit(ctx, b, 20);                          /* PC += 20       */
+++		/* +16: shift < 32 */
+++		emit(ctx, srl, t1, lo(dst), 1);            /* t1 = dl >> 1   */
+++		emit(ctx, srlv, t1, t1, t2);               /* t1 = t1 >> t2  */
+++		emit(ctx, sllv, lo(dst), lo(dst), src);    /* dl = dl << src */
+++		emit(ctx, sllv, hi(dst), hi(dst), src);    /* dh = dh << src */
+++		emit(ctx, or, hi(dst), hi(dst), t1);       /* dh = dh | t1   */
+++		break;
+++	/* dst = dst >> src */
+++	case BPF_RSH:
+++		/* Next: shift >= 32 */
+++		emit(ctx, srlv, lo(dst), hi(dst), src);    /* dl = dh >> src */
+++		emit(ctx, move, hi(dst), MIPS_R_ZERO);     /* dh = 0         */
+++		emit(ctx, b, 20);                          /* PC += 20       */
+++		/* +16: shift < 32 */
+++		emit(ctx, sll, t1, hi(dst), 1);            /* t1 = dl << 1   */
+++		emit(ctx, sllv, t1, t1, t2);               /* t1 = t1 << t2  */
+++		emit(ctx, srlv, lo(dst), lo(dst), src);    /* dl = dl >> src */
+++		emit(ctx, srlv, hi(dst), hi(dst), src);    /* dh = dh >> src */
+++		emit(ctx, or, lo(dst), lo(dst), t1);       /* dl = dl | t1   */
+++		break;
+++	/* dst = dst >> src (arithmetic) */
+++	case BPF_ARSH:
+++		/* Next: shift >= 32 */
+++		emit(ctx, srav, lo(dst), hi(dst), src);   /* dl = dh >>a src */
+++		emit(ctx, sra, hi(dst), hi(dst), 31);     /* dh = dh >>a 31  */
+++		emit(ctx, b, 20);                         /* PC += 20        */
+++		/* +16: shift < 32 */
+++		emit(ctx, sll, t1, hi(dst), 1);           /* t1 = dl << 1    */
+++		emit(ctx, sllv, t1, t1, t2);              /* t1 = t1 << t2   */
+++		emit(ctx, srlv, lo(dst), lo(dst), src);   /* dl = dl >>a src */
+++		emit(ctx, srav, hi(dst), hi(dst), src);   /* dh = dh >> src  */
+++		emit(ctx, or, lo(dst), lo(dst), t1);      /* dl = dl | t1    */
+++		break;
+++	}
 ++
-++	return false;
+++	/* +20: Done */
+++	clobber_reg64(ctx, dst);
 ++}
 ++
-++static int
-++mtk_wed_poll_busy(struct mtk_wed_device *dev)
+++/* ALU mul immediate (64x32-bit) */
+++static void emit_mul_i64(struct jit_context *ctx, const u8 dst[], s32 imm)
 ++{
-++	int sleep = 15000;
-++	int timeout = 100 * sleep;
-++	u32 val;
-++
-++	return read_poll_timeout(mtk_wed_check_busy, val, !val, sleep,
-++				 timeout, false, dev);
-++}
+++	u8 src = MIPS_R_T6;
+++	u8 tmp = MIPS_R_T9;
 ++
-++static void
-++mtk_wed_reset_dma(struct mtk_wed_device *dev)
-++{
-++	bool busy = false;
-++	u32 val;
-++	int i;
+++	switch (imm) {
+++	/* dst = dst * 1 is a no-op */
+++	case 1:
+++		break;
+++	/* dst = dst * -1 */
+++	case -1:
+++		emit_neg_i64(ctx, dst);
+++		break;
+++	case 0:
+++		emit_mov_r(ctx, lo(dst), MIPS_R_ZERO);
+++		emit_mov_r(ctx, hi(dst), MIPS_R_ZERO);
+++		break;
+++	/* Full 64x32 multiply */
+++	default:
+++		/* hi(dst) = hi(dst) * src(imm) */
+++		emit_mov_i(ctx, src, imm);
+++		if (cpu_has_mips32r1 || cpu_has_mips32r6) {
+++			emit(ctx, mul, hi(dst), hi(dst), src);
+++		} else {
+++			emit(ctx, multu, hi(dst), src);
+++			emit(ctx, mflo, hi(dst));
+++		}
 ++
-++	for (i = 0; i < ARRAY_SIZE(dev->tx_ring); i++) {
-++		struct mtk_wdma_desc *desc = dev->tx_ring[i].desc;
+++		/* hi(dst) = hi(dst) - lo(dst) */
+++		if (imm < 0)
+++			emit(ctx, subu, hi(dst), hi(dst), lo(dst));
 ++
-++		if (!desc)
-++			continue;
+++		/* tmp = lo(dst) * src(imm) >> 32 */
+++		/* lo(dst) = lo(dst) * src(imm) */
+++		if (cpu_has_mips32r6) {
+++			emit(ctx, muhu, tmp, lo(dst), src);
+++			emit(ctx, mulu, lo(dst), lo(dst), src);
+++		} else {
+++			emit(ctx, multu, lo(dst), src);
+++			emit(ctx, mflo, lo(dst));
+++			emit(ctx, mfhi, tmp);
+++		}
 ++
-++		mtk_wed_ring_reset(desc, MTK_WED_TX_RING_SIZE);
+++		/* hi(dst) += tmp */
+++		emit(ctx, addu, hi(dst), hi(dst), tmp);
+++		clobber_reg64(ctx, dst);
+++		break;
 ++	}
+++}
 ++
-++	if (mtk_wed_poll_busy(dev))
-++		busy = mtk_wed_check_busy(dev);
+++/* ALU mul register (64x64-bit) */
+++static void emit_mul_r64(struct jit_context *ctx,
+++			 const u8 dst[], const u8 src[])
+++{
+++	u8 acc = MIPS_R_T8;
+++	u8 tmp = MIPS_R_T9;
 ++
-++	if (busy) {
-++		mtk_wed_reset(dev, MTK_WED_RESET_WED_TX_DMA);
+++	/* acc = hi(dst) * lo(src) */
+++	if (cpu_has_mips32r1 || cpu_has_mips32r6) {
+++		emit(ctx, mul, acc, hi(dst), lo(src));
 ++	} else {
-++		wed_w32(dev, MTK_WED_RESET_IDX,
-++			MTK_WED_RESET_IDX_TX |
-++			MTK_WED_RESET_IDX_RX);
-++		wed_w32(dev, MTK_WED_RESET_IDX, 0);
+++		emit(ctx, multu, hi(dst), lo(src));
+++		emit(ctx, mflo, acc);
 ++	}
 ++
-++	wdma_w32(dev, MTK_WDMA_RESET_IDX, MTK_WDMA_RESET_IDX_RX);
-++	wdma_w32(dev, MTK_WDMA_RESET_IDX, 0);
-++
-++	if (busy) {
-++		mtk_wed_reset(dev, MTK_WED_RESET_WDMA_INT_AGENT);
-++		mtk_wed_reset(dev, MTK_WED_RESET_WDMA_RX_DRV);
+++	/* tmp = lo(dst) * hi(src) */
+++	if (cpu_has_mips32r1 || cpu_has_mips32r6) {
+++		emit(ctx, mul, tmp, lo(dst), hi(src));
 ++	} else {
-++		wed_w32(dev, MTK_WED_WDMA_RESET_IDX,
-++			MTK_WED_WDMA_RESET_IDX_RX | MTK_WED_WDMA_RESET_IDX_DRV);
-++		wed_w32(dev, MTK_WED_WDMA_RESET_IDX, 0);
-++
-++		wed_set(dev, MTK_WED_WDMA_GLO_CFG,
-++			MTK_WED_WDMA_GLO_CFG_RST_INIT_COMPLETE);
-++
-++		wed_clr(dev, MTK_WED_WDMA_GLO_CFG,
-++			MTK_WED_WDMA_GLO_CFG_RST_INIT_COMPLETE);
-++	}
-++
-++	for (i = 0; i < 100; i++) {
-++		val = wed_r32(dev, MTK_WED_TX_BM_INTF);
-++		if (FIELD_GET(MTK_WED_TX_BM_INTF_TKFIFO_FDEP, val) == 0x40)
-++			break;
+++		emit(ctx, multu, lo(dst), hi(src));
+++		emit(ctx, mflo, tmp);
 ++	}
 ++
-++	mtk_wed_reset(dev, MTK_WED_RESET_TX_FREE_AGENT);
-++	mtk_wed_reset(dev, MTK_WED_RESET_TX_BM);
+++	/* acc += tmp */
+++	emit(ctx, addu, acc, acc, tmp);
 ++
-++	if (busy) {
-++		mtk_wed_reset(dev, MTK_WED_RESET_WPDMA_INT_AGENT);
-++		mtk_wed_reset(dev, MTK_WED_RESET_WPDMA_TX_DRV);
-++		mtk_wed_reset(dev, MTK_WED_RESET_WPDMA_RX_DRV);
+++	/* tmp = lo(dst) * lo(src) >> 32 */
+++	/* lo(dst) = lo(dst) * lo(src) */
+++	if (cpu_has_mips32r6) {
+++		emit(ctx, muhu, tmp, lo(dst), lo(src));
+++		emit(ctx, mulu, lo(dst), lo(dst), lo(src));
 ++	} else {
-++		wed_w32(dev, MTK_WED_WPDMA_RESET_IDX,
-++			MTK_WED_WPDMA_RESET_IDX_TX |
-++			MTK_WED_WPDMA_RESET_IDX_RX);
-++		wed_w32(dev, MTK_WED_WPDMA_RESET_IDX, 0);
+++		emit(ctx, multu, lo(dst), lo(src));
+++		emit(ctx, mflo, lo(dst));
+++		emit(ctx, mfhi, tmp);
 ++	}
 ++
+++	/* hi(dst) = acc + tmp */
+++	emit(ctx, addu, hi(dst), acc, tmp);
+++	clobber_reg64(ctx, dst);
 ++}
 ++
-++static int
-++mtk_wed_ring_alloc(struct mtk_wed_device *dev, struct mtk_wed_ring *ring,
-++		   int size)
+++/* Helper function for 64-bit modulo */
+++static u64 jit_mod64(u64 a, u64 b)
 ++{
-++	ring->desc = dma_alloc_coherent(dev->hw->dev,
-++					size * sizeof(*ring->desc),
-++					&ring->desc_phys, GFP_KERNEL);
-++	if (!ring->desc)
-++		return -ENOMEM;
-++
-++	ring->size = size;
-++	mtk_wed_ring_reset(ring->desc, size);
+++	u64 rem;
 ++
-++	return 0;
+++	div64_u64_rem(a, b, &rem);
+++	return rem;
 ++}
 ++
-++static int
-++mtk_wed_wdma_ring_setup(struct mtk_wed_device *dev, int idx, int size)
+++/* ALU div/mod register (64-bit) */
+++static void emit_divmod_r64(struct jit_context *ctx,
+++			    const u8 dst[], const u8 src[], u8 op)
 ++{
-++	struct mtk_wed_ring *wdma = &dev->tx_wdma[idx];
+++	const u8 *r0 = bpf2mips32[BPF_REG_0]; /* Mapped to v0-v1 */
+++	const u8 *r1 = bpf2mips32[BPF_REG_1]; /* Mapped to a0-a1 */
+++	const u8 *r2 = bpf2mips32[BPF_REG_2]; /* Mapped to a2-a3 */
+++	int exclude, k;
+++	u32 addr = 0;
 ++
-++	if (mtk_wed_ring_alloc(dev, wdma, MTK_WED_WDMA_RING_SIZE))
-++		return -ENOMEM;
+++	/* Push caller-saved registers on stack */
+++	push_regs(ctx, ctx->clobbered & JIT_CALLER_REGS,
+++		  0, JIT_RESERVED_STACK);
 ++
-++	wdma_w32(dev, MTK_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_BASE,
-++		 wdma->desc_phys);
-++	wdma_w32(dev, MTK_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_COUNT,
-++		 size);
-++	wdma_w32(dev, MTK_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_CPU_IDX, 0);
+++	/* Put 64-bit arguments 1 and 2 in registers a0-a3 */
+++	for (k = 0; k < 2; k++) {
+++		emit(ctx, move, MIPS_R_T9, src[k]);
+++		emit(ctx, move, r1[k], dst[k]);
+++		emit(ctx, move, r2[k], MIPS_R_T9);
+++	}
 ++
-++	wed_w32(dev, MTK_WED_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_BASE,
-++		wdma->desc_phys);
-++	wed_w32(dev, MTK_WED_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_COUNT,
-++		size);
+++	/* Emit function call */
+++	switch (BPF_OP(op)) {
+++	/* dst = dst / src */
+++	case BPF_DIV:
+++		addr = (u32)&div64_u64;
+++		break;
+++	/* dst = dst % src */
+++	case BPF_MOD:
+++		addr = (u32)&jit_mod64;
+++		break;
+++	}
+++	emit_mov_i(ctx, MIPS_R_T9, addr);
+++	emit(ctx, jalr, MIPS_R_RA, MIPS_R_T9);
+++	emit(ctx, nop); /* Delay slot */
 ++
-++	return 0;
-++}
+++	/* Store the 64-bit result in dst */
+++	emit(ctx, move, dst[0], r0[0]);
+++	emit(ctx, move, dst[1], r0[1]);
 ++
-++static void
-++mtk_wed_start(struct mtk_wed_device *dev, u32 irq_mask)
-++{
-++	u32 wdma_mask;
-++	u32 val;
-++	int i;
+++	/* Restore caller-saved registers, excluding the computed result */
+++	exclude = BIT(lo(dst)) | BIT(hi(dst));
+++	pop_regs(ctx, ctx->clobbered & JIT_CALLER_REGS,
+++		 exclude, JIT_RESERVED_STACK);
+++	emit_load_delay(ctx);
 ++
-++	for (i = 0; i < ARRAY_SIZE(dev->tx_wdma); i++)
-++		if (!dev->tx_wdma[i].desc)
-++			mtk_wed_wdma_ring_setup(dev, i, 16);
+++	clobber_reg64(ctx, dst);
+++	clobber_reg(ctx, MIPS_R_V0);
+++	clobber_reg(ctx, MIPS_R_V1);
+++	clobber_reg(ctx, MIPS_R_RA);
+++}
 ++
-++	wdma_mask = FIELD_PREP(MTK_WDMA_INT_MASK_RX_DONE, GENMASK(1, 0));
+++/* Swap bytes in a register word */
+++static void emit_swap8_r(struct jit_context *ctx, u8 dst, u8 src, u8 mask)
+++{
+++	u8 tmp = MIPS_R_T9;
 ++
-++	mtk_wed_hw_init(dev);
-++
-++	wed_set(dev, MTK_WED_CTRL,
-++		MTK_WED_CTRL_WDMA_INT_AGENT_EN |
-++		MTK_WED_CTRL_WPDMA_INT_AGENT_EN |
-++		MTK_WED_CTRL_WED_TX_BM_EN |
-++		MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
-++
-++	wed_w32(dev, MTK_WED_PCIE_INT_TRIGGER, MTK_WED_PCIE_INT_TRIGGER_STATUS);
-++
-++	wed_w32(dev, MTK_WED_WPDMA_INT_TRIGGER,
-++		MTK_WED_WPDMA_INT_TRIGGER_RX_DONE |
-++		MTK_WED_WPDMA_INT_TRIGGER_TX_DONE);
-++
-++	wed_set(dev, MTK_WED_WPDMA_INT_CTRL,
-++		MTK_WED_WPDMA_INT_CTRL_SUBRT_ADV);
-++
-++	wed_w32(dev, MTK_WED_WDMA_INT_TRIGGER, wdma_mask);
-++	wed_clr(dev, MTK_WED_WDMA_INT_CTRL, wdma_mask);
-++
-++	wdma_w32(dev, MTK_WDMA_INT_MASK, wdma_mask);
-++	wdma_w32(dev, MTK_WDMA_INT_GRP2, wdma_mask);
-++
-++	wed_w32(dev, MTK_WED_WPDMA_INT_MASK, irq_mask);
-++	wed_w32(dev, MTK_WED_INT_MASK, irq_mask);
-++
-++	wed_set(dev, MTK_WED_GLO_CFG,
-++		MTK_WED_GLO_CFG_TX_DMA_EN |
-++		MTK_WED_GLO_CFG_RX_DMA_EN);
-++	wed_set(dev, MTK_WED_WPDMA_GLO_CFG,
-++		MTK_WED_WPDMA_GLO_CFG_TX_DRV_EN |
-++		MTK_WED_WPDMA_GLO_CFG_RX_DRV_EN);
-++	wed_set(dev, MTK_WED_WDMA_GLO_CFG,
-++		MTK_WED_WDMA_GLO_CFG_RX_DRV_EN);
-++
-++	mtk_wed_set_ext_int(dev, true);
-++	val = dev->wlan.wpdma_phys |
-++	      MTK_PCIE_MIRROR_MAP_EN |
-++	      FIELD_PREP(MTK_PCIE_MIRROR_MAP_WED_ID, dev->hw->index);
-++
-++	if (dev->hw->index)
-++		val |= BIT(1);
-++	val |= BIT(0);
-++	regmap_write(dev->hw->mirror, dev->hw->index * 4, val);
-++
-++	dev->running = true;
+++	emit(ctx, and, tmp, src, mask); /* tmp = src & 0x00ff00ff */
+++	emit(ctx, sll, tmp, tmp, 8);    /* tmp = tmp << 8         */
+++	emit(ctx, srl, dst, src, 8);    /* dst = src >> 8         */
+++	emit(ctx, and, dst, dst, mask); /* dst = dst & 0x00ff00ff */
+++	emit(ctx, or,  dst, dst, tmp);  /* dst = dst | tmp        */
 ++}
 ++
-++static int
-++mtk_wed_attach(struct mtk_wed_device *dev)
-++	__releases(RCU)
+++/* Swap half words in a register word */
+++static void emit_swap16_r(struct jit_context *ctx, u8 dst, u8 src)
 ++{
-++	struct mtk_wed_hw *hw;
-++	int ret = 0;
-++
-++	RCU_LOCKDEP_WARN(!rcu_read_lock_held(),
-++			 "mtk_wed_attach without holding the RCU read lock");
-++
-++	if (pci_domain_nr(dev->wlan.pci_dev->bus) > 1 ||
-++	    !try_module_get(THIS_MODULE))
-++		ret = -ENODEV;
+++	u8 tmp = MIPS_R_T9;
 ++
-++	rcu_read_unlock();
+++	emit(ctx, sll, tmp, src, 16);  /* tmp = src << 16 */
+++	emit(ctx, srl, dst, src, 16);  /* dst = src >> 16 */
+++	emit(ctx, or,  dst, dst, tmp); /* dst = dst | tmp */
+++}
 ++
-++	if (ret)
-++		return ret;
+++/* Swap bytes and truncate a register double word, word or half word */
+++static void emit_bswap_r64(struct jit_context *ctx, const u8 dst[], u32 width)
+++{
+++	u8 tmp = MIPS_R_T8;
 ++
-++	mutex_lock(&hw_lock);
+++	switch (width) {
+++	/* Swap bytes in a double word */
+++	case 64:
+++		if (cpu_has_mips32r2 || cpu_has_mips32r6) {
+++			emit(ctx, rotr, tmp, hi(dst), 16);
+++			emit(ctx, rotr, hi(dst), lo(dst), 16);
+++			emit(ctx, wsbh, lo(dst), tmp);
+++			emit(ctx, wsbh, hi(dst), hi(dst));
+++		} else {
+++			emit_swap16_r(ctx, tmp, lo(dst));
+++			emit_swap16_r(ctx, lo(dst), hi(dst));
+++			emit(ctx, move, hi(dst), tmp);
 ++
-++	hw = mtk_wed_assign(dev);
-++	if (!hw) {
-++		module_put(THIS_MODULE);
-++		ret = -ENODEV;
-++		goto out;
+++			emit(ctx, lui, tmp, 0xff);      /* tmp = 0x00ff0000 */
+++			emit(ctx, ori, tmp, tmp, 0xff); /* tmp = 0x00ff00ff */
+++			emit_swap8_r(ctx, lo(dst), lo(dst), tmp);
+++			emit_swap8_r(ctx, hi(dst), hi(dst), tmp);
+++		}
+++		break;
+++	/* Swap bytes in a word */
+++	/* Swap bytes in a half word */
+++	case 32:
+++	case 16:
+++		emit_bswap_r(ctx, lo(dst), width);
+++		emit(ctx, move, hi(dst), MIPS_R_ZERO);
+++		break;
 ++	}
+++	clobber_reg64(ctx, dst);
+++}
 ++
-++	dev_info(&dev->wlan.pci_dev->dev, "attaching wed device %d\n", hw->index);
-++
-++	dev->hw = hw;
-++	dev->dev = hw->dev;
-++	dev->irq = hw->irq;
-++	dev->wdma_idx = hw->index;
-++
-++	if (hw->eth->dma_dev == hw->eth->dev &&
-++	    of_dma_is_coherent(hw->eth->dev->of_node))
-++		mtk_eth_set_dma_device(hw->eth, hw->dev);
-++
-++	ret = mtk_wed_buffer_alloc(dev);
-++	if (ret) {
-++		mtk_wed_detach(dev);
-++		goto out;
+++/* Truncate a register double word, word or half word */
+++static void emit_trunc_r64(struct jit_context *ctx, const u8 dst[], u32 width)
+++{
+++	switch (width) {
+++	case 64:
+++		break;
+++	/* Zero-extend a word */
+++	case 32:
+++		emit(ctx, move, hi(dst), MIPS_R_ZERO);
+++		clobber_reg(ctx, hi(dst));
+++		break;
+++	/* Zero-extend a half word */
+++	case 16:
+++		emit(ctx, move, hi(dst), MIPS_R_ZERO);
+++		emit(ctx, andi, lo(dst), lo(dst), 0xffff);
+++		clobber_reg64(ctx, dst);
+++		break;
 ++	}
+++}
 ++
-++	mtk_wed_hw_init_early(dev);
-++	regmap_update_bits(hw->hifsys, HIFSYS_DMA_AG_MAP, BIT(hw->index), 0);
-++
-++out:
-++	mutex_unlock(&hw_lock);
+++/* Load operation: dst = *(size*)(src + off) */
+++static void emit_ldx(struct jit_context *ctx,
+++		     const u8 dst[], u8 src, s16 off, u8 size)
+++{
+++	switch (size) {
+++	/* Load a byte */
+++	case BPF_B:
+++		emit(ctx, lbu, lo(dst), off, src);
+++		emit(ctx, move, hi(dst), MIPS_R_ZERO);
+++		break;
+++	/* Load a half word */
+++	case BPF_H:
+++		emit(ctx, lhu, lo(dst), off, src);
+++		emit(ctx, move, hi(dst), MIPS_R_ZERO);
+++		break;
+++	/* Load a word */
+++	case BPF_W:
+++		emit(ctx, lw, lo(dst), off, src);
+++		emit(ctx, move, hi(dst), MIPS_R_ZERO);
+++		break;
+++	/* Load a double word */
+++	case BPF_DW:
+++		if (dst[1] == src) {
+++			emit(ctx, lw, dst[0], off + 4, src);
+++			emit(ctx, lw, dst[1], off, src);
+++		} else {
+++			emit(ctx, lw, dst[1], off, src);
+++			emit(ctx, lw, dst[0], off + 4, src);
+++		}
+++		emit_load_delay(ctx);
+++		break;
+++	}
+++	clobber_reg64(ctx, dst);
+++}
 ++
-++	return ret;
+++/* Store operation: *(size *)(dst + off) = src */
+++static void emit_stx(struct jit_context *ctx,
+++		     const u8 dst, const u8 src[], s16 off, u8 size)
+++{
+++	switch (size) {
+++	/* Store a byte */
+++	case BPF_B:
+++		emit(ctx, sb, lo(src), off, dst);
+++		break;
+++	/* Store a half word */
+++	case BPF_H:
+++		emit(ctx, sh, lo(src), off, dst);
+++		break;
+++	/* Store a word */
+++	case BPF_W:
+++		emit(ctx, sw, lo(src), off, dst);
+++		break;
+++	/* Store a double word */
+++	case BPF_DW:
+++		emit(ctx, sw, src[1], off, dst);
+++		emit(ctx, sw, src[0], off + 4, dst);
+++		break;
+++	}
 ++}
 ++
-++static int
-++mtk_wed_tx_ring_setup(struct mtk_wed_device *dev, int idx, void __iomem *regs)
+++/* Atomic read-modify-write (32-bit, non-ll/sc fallback) */
+++static void emit_atomic_r32(struct jit_context *ctx,
+++			    u8 dst, u8 src, s16 off, u8 code)
 ++{
-++	struct mtk_wed_ring *ring = &dev->tx_ring[idx];
+++	u32 exclude = 0;
+++	u32 addr = 0;
 ++
+++	/* Push caller-saved registers on stack */
+++	push_regs(ctx, ctx->clobbered & JIT_CALLER_REGS,
+++		  0, JIT_RESERVED_STACK);
 ++	/*
-++	 * Tx ring redirection:
-++	 * Instead of configuring the WLAN PDMA TX ring directly, the WLAN
-++	 * driver allocated DMA ring gets configured into WED MTK_WED_RING_TX(n)
-++	 * registers.
-++	 *
-++	 * WED driver posts its own DMA ring as WLAN PDMA TX and configures it
-++	 * into MTK_WED_WPDMA_RING_TX(n) registers.
-++	 * It gets filled with packets picked up from WED TX ring and from
-++	 * WDMA RX.
+++	 * Argument 1: dst+off if xchg, otherwise src, passed in register a0
+++	 * Argument 2: src if xchg, othersize dst+off, passed in register a1
 ++	 */
+++	emit(ctx, move, MIPS_R_T9, dst);
+++	emit(ctx, move, MIPS_R_A0, src);
+++	emit(ctx, addiu, MIPS_R_A1, MIPS_R_T9, off);
 ++
-++	BUG_ON(idx > ARRAY_SIZE(dev->tx_ring));
-++
-++	if (mtk_wed_ring_alloc(dev, ring, MTK_WED_TX_RING_SIZE))
-++		return -ENOMEM;
-++
-++	if (mtk_wed_wdma_ring_setup(dev, idx, MTK_WED_WDMA_RING_SIZE))
-++		return -ENOMEM;
-++
-++	ring->reg_base = MTK_WED_RING_TX(idx);
-++	ring->wpdma = regs;
-++
-++	/* WED -> WPDMA */
-++	wpdma_tx_w32(dev, idx, MTK_WED_RING_OFS_BASE, ring->desc_phys);
-++	wpdma_tx_w32(dev, idx, MTK_WED_RING_OFS_COUNT, MTK_WED_TX_RING_SIZE);
-++	wpdma_tx_w32(dev, idx, MTK_WED_RING_OFS_CPU_IDX, 0);
-++
-++	wed_w32(dev, MTK_WED_WPDMA_RING_TX(idx) + MTK_WED_RING_OFS_BASE,
-++		ring->desc_phys);
-++	wed_w32(dev, MTK_WED_WPDMA_RING_TX(idx) + MTK_WED_RING_OFS_COUNT,
-++		MTK_WED_TX_RING_SIZE);
-++	wed_w32(dev, MTK_WED_WPDMA_RING_TX(idx) + MTK_WED_RING_OFS_CPU_IDX, 0);
+++	/* Emit function call */
+++	switch (code) {
+++	case BPF_ADD:
+++		addr = (u32)&atomic_add;
+++		break;
+++	case BPF_SUB:
+++		addr = (u32)&atomic_sub;
+++		break;
+++	case BPF_OR:
+++		addr = (u32)&atomic_or;
+++		break;
+++	case BPF_AND:
+++		addr = (u32)&atomic_and;
+++		break;
+++	case BPF_XOR:
+++		addr = (u32)&atomic_xor;
+++		break;
+++	}
+++	emit_mov_i(ctx, MIPS_R_T9, addr);
+++	emit(ctx, jalr, MIPS_R_RA, MIPS_R_T9);
+++	emit(ctx, nop); /* Delay slot */
 ++
-++	return 0;
+++	/* Restore caller-saved registers, except any fetched value */
+++	pop_regs(ctx, ctx->clobbered & JIT_CALLER_REGS,
+++		 exclude, JIT_RESERVED_STACK);
+++	emit_load_delay(ctx);
+++	clobber_reg(ctx, MIPS_R_RA);
 ++}
 ++
-++static int
-++mtk_wed_txfree_ring_setup(struct mtk_wed_device *dev, void __iomem *regs)
+++/* Atomic read-modify-write (64-bit) */
+++static void emit_atomic_r64(struct jit_context *ctx,
+++			    u8 dst, const u8 src[], s16 off, u8 code)
 ++{
-++	struct mtk_wed_ring *ring = &dev->txfree_ring;
-++	int i;
+++	const u8 *r1 = bpf2mips32[BPF_REG_1]; /* Mapped to a0-a1 */
+++	u32 exclude = 0;
+++	u32 addr = 0;
 ++
+++	/* Push caller-saved registers on stack */
+++	push_regs(ctx, ctx->clobbered & JIT_CALLER_REGS,
+++		  0, JIT_RESERVED_STACK);
 ++	/*
-++	 * For txfree event handling, the same DMA ring is shared between WED
-++	 * and WLAN. The WLAN driver accesses the ring index registers through
-++	 * WED
+++	 * Argument 1: 64-bit src, passed in registers a0-a1
+++	 * Argument 2: 32-bit dst+off, passed in register a2
 ++	 */
-++	ring->reg_base = MTK_WED_RING_RX(1);
-++	ring->wpdma = regs;
-++
-++	for (i = 0; i < 12; i += 4) {
-++		u32 val = readl(regs + i);
+++	emit(ctx, move, MIPS_R_T9, dst);
+++	emit(ctx, move, r1[0], src[0]);
+++	emit(ctx, move, r1[1], src[1]);
+++	emit(ctx, addiu, MIPS_R_A2, MIPS_R_T9, off);
 ++
-++		wed_w32(dev, MTK_WED_RING_RX(1) + i, val);
-++		wed_w32(dev, MTK_WED_WPDMA_RING_RX(1) + i, val);
+++	/* Emit function call */
+++	switch (code) {
+++	case BPF_ADD:
+++		addr = (u32)&atomic64_add;
+++		break;
+++	case BPF_SUB:
+++		addr = (u32)&atomic64_sub;
+++		break;
+++	case BPF_OR:
+++		addr = (u32)&atomic64_or;
+++		break;
+++	case BPF_AND:
+++		addr = (u32)&atomic64_and;
+++		break;
+++	case BPF_XOR:
+++		addr = (u32)&atomic64_xor;
+++		break;
 ++	}
+++	emit_mov_i(ctx, MIPS_R_T9, addr);
+++	emit(ctx, jalr, MIPS_R_RA, MIPS_R_T9);
+++	emit(ctx, nop); /* Delay slot */
 ++
-++	return 0;
+++	/* Restore caller-saved registers, except any fetched value */
+++	pop_regs(ctx, ctx->clobbered & JIT_CALLER_REGS,
+++		 exclude, JIT_RESERVED_STACK);
+++	emit_load_delay(ctx);
+++	clobber_reg(ctx, MIPS_R_RA);
 ++}
 ++
-++static u32
-++mtk_wed_irq_get(struct mtk_wed_device *dev, u32 mask)
+++/*
+++ * Conditional movz or an emulated equivalent.
+++ * Note that the rs register may be modified.
+++ */
+++static void emit_movz_r(struct jit_context *ctx, u8 rd, u8 rs, u8 rt)
 ++{
-++	u32 val;
+++	if (cpu_has_mips_2) {
+++		emit(ctx, movz, rd, rs, rt);           /* rd = rt ? rd : rs  */
+++	} else if (cpu_has_mips32r6) {
+++		if (rs != MIPS_R_ZERO)
+++			emit(ctx, seleqz, rs, rs, rt); /* rs = 0 if rt == 0  */
+++		emit(ctx, selnez, rd, rd, rt);         /* rd = 0 if rt != 0  */
+++		if (rs != MIPS_R_ZERO)
+++			emit(ctx, or, rd, rd, rs);     /* rd = rd | rs       */
+++	} else {
+++		emit(ctx, bnez, rt, 8);                /* PC += 8 if rd != 0 */
+++		emit(ctx, nop);                        /* +0: delay slot     */
+++		emit(ctx, or, rd, rs, MIPS_R_ZERO);    /* +4: rd = rs        */
+++	}
+++	clobber_reg(ctx, rd);
+++	clobber_reg(ctx, rs);
+++}
 ++
-++	val = wed_r32(dev, MTK_WED_EXT_INT_STATUS);
-++	wed_w32(dev, MTK_WED_EXT_INT_STATUS, val);
-++	val &= MTK_WED_EXT_INT_STATUS_ERROR_MASK;
-++	if (!dev->hw->num_flows)
-++		val &= ~MTK_WED_EXT_INT_STATUS_TKID_WO_PYLD;
-++	if (val && net_ratelimit())
-++		pr_err("mtk_wed%d: error status=%08x\n", dev->hw->index, val);
+++/*
+++ * Conditional movn or an emulated equivalent.
+++ * Note that the rs register may be modified.
+++ */
+++static void emit_movn_r(struct jit_context *ctx, u8 rd, u8 rs, u8 rt)
+++{
+++	if (cpu_has_mips_2) {
+++		emit(ctx, movn, rd, rs, rt);           /* rd = rt ? rs : rd  */
+++	} else if (cpu_has_mips32r6) {
+++		if (rs != MIPS_R_ZERO)
+++			emit(ctx, selnez, rs, rs, rt); /* rs = 0 if rt == 0  */
+++		emit(ctx, seleqz, rd, rd, rt);         /* rd = 0 if rt != 0  */
+++		if (rs != MIPS_R_ZERO)
+++			emit(ctx, or, rd, rd, rs);     /* rd = rd | rs       */
+++	} else {
+++		emit(ctx, beqz, rt, 8);                /* PC += 8 if rd == 0 */
+++		emit(ctx, nop);                        /* +0: delay slot     */
+++		emit(ctx, or, rd, rs, MIPS_R_ZERO);    /* +4: rd = rs        */
+++	}
+++	clobber_reg(ctx, rd);
+++	clobber_reg(ctx, rs);
+++}
 ++
-++	val = wed_r32(dev, MTK_WED_INT_STATUS);
-++	val &= mask;
-++	wed_w32(dev, MTK_WED_INT_STATUS, val); /* ACK */
+++/* Emulation of 64-bit sltiu rd, rs, imm, where imm may be S32_MAX + 1 */
+++static void emit_sltiu_r64(struct jit_context *ctx, u8 rd,
+++			   const u8 rs[], s64 imm)
+++{
+++	u8 tmp = MIPS_R_T9;
 ++
-++	return val;
+++	if (imm < 0) {
+++		emit_mov_i(ctx, rd, imm);                 /* rd = imm        */
+++		emit(ctx, sltu, rd, lo(rs), rd);          /* rd = rsl < rd   */
+++		emit(ctx, sltiu, tmp, hi(rs), -1);        /* tmp = rsh < ~0U */
+++		emit(ctx, or, rd, rd, tmp);               /* rd = rd | tmp   */
+++	} else { /* imm >= 0 */
+++		if (imm > 0x7fff) {
+++			emit_mov_i(ctx, rd, (s32)imm);     /* rd = imm       */
+++			emit(ctx, sltu, rd, lo(rs), rd);   /* rd = rsl < rd  */
+++		} else {
+++			emit(ctx, sltiu, rd, lo(rs), imm); /* rd = rsl < imm */
+++		}
+++		emit_movn_r(ctx, rd, MIPS_R_ZERO, hi(rs)); /* rd = 0 if rsh  */
+++	}
 ++}
 ++
-++static void
-++mtk_wed_irq_set_mask(struct mtk_wed_device *dev, u32 mask)
+++/* Emulation of 64-bit sltu rd, rs, rt */
+++static void emit_sltu_r64(struct jit_context *ctx, u8 rd,
+++			  const u8 rs[], const u8 rt[])
 ++{
-++	if (!dev->running)
-++		return;
+++	u8 tmp = MIPS_R_T9;
 ++
-++	mtk_wed_set_ext_int(dev, !!mask);
-++	wed_w32(dev, MTK_WED_INT_MASK, mask);
+++	emit(ctx, sltu, rd, lo(rs), lo(rt));           /* rd = rsl < rtl     */
+++	emit(ctx, subu, tmp, hi(rs), hi(rt));          /* tmp = rsh - rth    */
+++	emit_movn_r(ctx, rd, MIPS_R_ZERO, tmp);        /* rd = 0 if tmp != 0 */
+++	emit(ctx, sltu, tmp, hi(rs), hi(rt));          /* tmp = rsh < rth    */
+++	emit(ctx, or, rd, rd, tmp);                    /* rd = rd | tmp      */
 ++}
 ++
-++int mtk_wed_flow_add(int index)
+++/* Emulation of 64-bit slti rd, rs, imm, where imm may be S32_MAX + 1 */
+++static void emit_slti_r64(struct jit_context *ctx, u8 rd,
+++			  const u8 rs[], s64 imm)
 ++{
-++	struct mtk_wed_hw *hw = hw_list[index];
-++	int ret;
-++
-++	if (!hw || !hw->wed_dev)
-++		return -ENODEV;
+++	u8 t1 = MIPS_R_T8;
+++	u8 t2 = MIPS_R_T9;
+++	u8 cmp;
 ++
-++	if (hw->num_flows) {
-++		hw->num_flows++;
-++		return 0;
+++	/*
+++	 * if ((rs < 0) ^ (imm < 0)) t1 = imm >u rsl
+++	 * else                      t1 = rsl <u imm
+++	 */
+++	emit_mov_i(ctx, rd, (s32)imm);
+++	emit(ctx, sltu, t1, lo(rs), rd);               /* t1 = rsl <u imm   */
+++	emit(ctx, sltu, t2, rd, lo(rs));               /* t2 = imm <u rsl   */
+++	emit(ctx, srl, rd, hi(rs), 31);                /* rd = rsh >> 31    */
+++	if (imm < 0)
+++		emit_movz_r(ctx, t1, t2, rd);          /* t1 = rd ? t1 : t2 */
+++	else
+++		emit_movn_r(ctx, t1, t2, rd);          /* t1 = rd ? t2 : t1 */
+++	/*
+++	 * if ((imm < 0 && rsh != 0xffffffff) ||
+++	 *     (imm >= 0 && rsh != 0))
+++	 *      t1 = 0
+++	 */
+++	if (imm < 0) {
+++		emit(ctx, addiu, rd, hi(rs), 1);       /* rd = rsh + 1 */
+++		cmp = rd;
+++	} else { /* imm >= 0 */
+++		cmp = hi(rs);
 ++	}
+++	emit_movn_r(ctx, t1, MIPS_R_ZERO, cmp);        /* t1 = 0 if cmp != 0 */
 ++
-++	mutex_lock(&hw_lock);
-++	if (!hw->wed_dev) {
-++		ret = -ENODEV;
-++		goto out;
-++	}
+++	/*
+++	 * if (imm < 0) rd = rsh < -1
+++	 * else         rd = rsh != 0
+++	 * rd = rd | t1
+++	 */
+++	emit(ctx, slti, rd, hi(rs), imm < 0 ? -1 : 0); /* rd = rsh < hi(imm) */
+++	emit(ctx, or, rd, rd, t1);                     /* rd = rd | t1       */
+++}
 ++
-++	ret = hw->wed_dev->wlan.offload_enable(hw->wed_dev);
-++	if (!ret)
-++		hw->num_flows++;
-++	mtk_wed_set_ext_int(hw->wed_dev, true);
+++/* Emulation of 64-bit(slt rd, rs, rt) */
+++static void emit_slt_r64(struct jit_context *ctx, u8 rd,
+++			 const u8 rs[], const u8 rt[])
+++{
+++	u8 t1 = MIPS_R_T7;
+++	u8 t2 = MIPS_R_T8;
+++	u8 t3 = MIPS_R_T9;
 ++
-++out:
-++	mutex_unlock(&hw_lock);
+++	/*
+++	 * if ((rs < 0) ^ (rt < 0)) t1 = rtl <u rsl
+++	 * else                     t1 = rsl <u rtl
+++	 * if (rsh == rth)          t1 = 0
+++	 */
+++	emit(ctx, sltu, t1, lo(rs), lo(rt));           /* t1 = rsl <u rtl   */
+++	emit(ctx, sltu, t2, lo(rt), lo(rs));           /* t2 = rtl <u rsl   */
+++	emit(ctx, xor, t3, hi(rs), hi(rt));            /* t3 = rlh ^ rth    */
+++	emit(ctx, srl, rd, t3, 31);                    /* rd = t3 >> 31     */
+++	emit_movn_r(ctx, t1, t2, rd);                  /* t1 = rd ? t2 : t1 */
+++	emit_movn_r(ctx, t1, MIPS_R_ZERO, t3);         /* t1 = 0 if t3 != 0 */
 ++
-++	return ret;
+++	/* rd = (rsh < rth) | t1 */
+++	emit(ctx, slt, rd, hi(rs), hi(rt));            /* rd = rsh <s rth   */
+++	emit(ctx, or, rd, rd, t1);                     /* rd = rd | t1      */
 ++}
 ++
-++void mtk_wed_flow_remove(int index)
+++/* Jump immediate (64-bit) */
+++static void emit_jmp_i64(struct jit_context *ctx,
+++			 const u8 dst[], s32 imm, s32 off, u8 op)
 ++{
-++	struct mtk_wed_hw *hw = hw_list[index];
-++
-++	if (!hw)
-++		return;
+++	u8 tmp = MIPS_R_T6;
 ++
-++	if (--hw->num_flows)
-++		return;
-++
-++	mutex_lock(&hw_lock);
-++	if (!hw->wed_dev)
-++		goto out;
-++
-++	hw->wed_dev->wlan.offload_disable(hw->wed_dev);
-++	mtk_wed_set_ext_int(hw->wed_dev, true);
-++
-++out:
-++	mutex_unlock(&hw_lock);
+++	switch (op) {
+++	/* No-op, used internally for branch optimization */
+++	case JIT_JNOP:
+++		break;
+++	/* PC += off if dst == imm */
+++	/* PC += off if dst != imm */
+++	case BPF_JEQ:
+++	case BPF_JNE:
+++		if (imm >= -0x7fff && imm <= 0x8000) {
+++			emit(ctx, addiu, tmp, lo(dst), -imm);
+++		} else if ((u32)imm <= 0xffff) {
+++			emit(ctx, xori, tmp, lo(dst), imm);
+++		} else {       /* Register fallback */
+++			emit_mov_i(ctx, tmp, imm);
+++			emit(ctx, xor, tmp, lo(dst), tmp);
+++		}
+++		if (imm < 0) { /* Compare sign extension */
+++			emit(ctx, addu, MIPS_R_T9, hi(dst), 1);
+++			emit(ctx, or, tmp, tmp, MIPS_R_T9);
+++		} else {       /* Compare zero extension */
+++			emit(ctx, or, tmp, tmp, hi(dst));
+++		}
+++		if (op == BPF_JEQ)
+++			emit(ctx, beqz, tmp, off);
+++		else   /* BPF_JNE */
+++			emit(ctx, bnez, tmp, off);
+++		break;
+++	/* PC += off if dst & imm */
+++	/* PC += off if (dst & imm) == 0 (not in BPF, used for long jumps) */
+++	case BPF_JSET:
+++	case JIT_JNSET:
+++		if ((u32)imm <= 0xffff) {
+++			emit(ctx, andi, tmp, lo(dst), imm);
+++		} else {     /* Register fallback */
+++			emit_mov_i(ctx, tmp, imm);
+++			emit(ctx, and, tmp, lo(dst), tmp);
+++		}
+++		if (imm < 0) /* Sign-extension pulls in high word */
+++			emit(ctx, or, tmp, tmp, hi(dst));
+++		if (op == BPF_JSET)
+++			emit(ctx, bnez, tmp, off);
+++		else   /* JIT_JNSET */
+++			emit(ctx, beqz, tmp, off);
+++		break;
+++	/* PC += off if dst > imm */
+++	case BPF_JGT:
+++		emit_sltiu_r64(ctx, tmp, dst, (s64)imm + 1);
+++		emit(ctx, beqz, tmp, off);
+++		break;
+++	/* PC += off if dst >= imm */
+++	case BPF_JGE:
+++		emit_sltiu_r64(ctx, tmp, dst, imm);
+++		emit(ctx, beqz, tmp, off);
+++		break;
+++	/* PC += off if dst < imm */
+++	case BPF_JLT:
+++		emit_sltiu_r64(ctx, tmp, dst, imm);
+++		emit(ctx, bnez, tmp, off);
+++		break;
+++	/* PC += off if dst <= imm */
+++	case BPF_JLE:
+++		emit_sltiu_r64(ctx, tmp, dst, (s64)imm + 1);
+++		emit(ctx, bnez, tmp, off);
+++		break;
+++	/* PC += off if dst > imm (signed) */
+++	case BPF_JSGT:
+++		emit_slti_r64(ctx, tmp, dst, (s64)imm + 1);
+++		emit(ctx, beqz, tmp, off);
+++		break;
+++	/* PC += off if dst >= imm (signed) */
+++	case BPF_JSGE:
+++		emit_slti_r64(ctx, tmp, dst, imm);
+++		emit(ctx, beqz, tmp, off);
+++		break;
+++	/* PC += off if dst < imm (signed) */
+++	case BPF_JSLT:
+++		emit_slti_r64(ctx, tmp, dst, imm);
+++		emit(ctx, bnez, tmp, off);
+++		break;
+++	/* PC += off if dst <= imm (signed) */
+++	case BPF_JSLE:
+++		emit_slti_r64(ctx, tmp, dst, (s64)imm + 1);
+++		emit(ctx, bnez, tmp, off);
+++		break;
+++	}
 ++}
 ++
-++void mtk_wed_add_hw(struct device_node *np, struct mtk_eth *eth,
-++		    void __iomem *wdma, int index)
+++/* Jump register (64-bit) */
+++static void emit_jmp_r64(struct jit_context *ctx,
+++			 const u8 dst[], const u8 src[], s32 off, u8 op)
 ++{
-++	static const struct mtk_wed_ops wed_ops = {
-++		.attach = mtk_wed_attach,
-++		.tx_ring_setup = mtk_wed_tx_ring_setup,
-++		.txfree_ring_setup = mtk_wed_txfree_ring_setup,
-++		.start = mtk_wed_start,
-++		.stop = mtk_wed_stop,
-++		.reset_dma = mtk_wed_reset_dma,
-++		.reg_read = wed_r32,
-++		.reg_write = wed_w32,
-++		.irq_get = mtk_wed_irq_get,
-++		.irq_set_mask = mtk_wed_irq_set_mask,
-++		.detach = mtk_wed_detach,
-++	};
-++	struct device_node *eth_np = eth->dev->of_node;
-++	struct platform_device *pdev;
-++	struct mtk_wed_hw *hw;
-++	struct regmap *regs;
-++	int irq;
-++
-++	if (!np)
-++		return;
-++
-++	pdev = of_find_device_by_node(np);
-++	if (!pdev)
-++		return;
-++
-++	get_device(&pdev->dev);
-++	irq = platform_get_irq(pdev, 0);
-++	if (irq < 0)
-++		return;
-++
-++	regs = syscon_regmap_lookup_by_phandle(np, NULL);
-++	if (!regs)
-++		return;
-++
-++	rcu_assign_pointer(mtk_soc_wed_ops, &wed_ops);
+++	u8 t1 = MIPS_R_T6;
+++	u8 t2 = MIPS_R_T7;
 ++
-++	mutex_lock(&hw_lock);
+++	switch (op) {
+++	/* No-op, used internally for branch optimization */
+++	case JIT_JNOP:
+++		break;
+++	/* PC += off if dst == src */
+++	/* PC += off if dst != src */
+++	case BPF_JEQ:
+++	case BPF_JNE:
+++		emit(ctx, subu, t1, lo(dst), lo(src));
+++		emit(ctx, subu, t2, hi(dst), hi(src));
+++		emit(ctx, or, t1, t1, t2);
+++		if (op == BPF_JEQ)
+++			emit(ctx, beqz, t1, off);
+++		else   /* BPF_JNE */
+++			emit(ctx, bnez, t1, off);
+++		break;
+++	/* PC += off if dst & src */
+++	/* PC += off if (dst & imm) == 0 (not in BPF, used for long jumps) */
+++	case BPF_JSET:
+++	case JIT_JNSET:
+++		emit(ctx, and, t1, lo(dst), lo(src));
+++		emit(ctx, and, t2, hi(dst), hi(src));
+++		emit(ctx, or, t1, t1, t2);
+++		if (op == BPF_JSET)
+++			emit(ctx, bnez, t1, off);
+++		else   /* JIT_JNSET */
+++			emit(ctx, beqz, t1, off);
+++		break;
+++	/* PC += off if dst > src */
+++	case BPF_JGT:
+++		emit_sltu_r64(ctx, t1, src, dst);
+++		emit(ctx, bnez, t1, off);
+++		break;
+++	/* PC += off if dst >= src */
+++	case BPF_JGE:
+++		emit_sltu_r64(ctx, t1, dst, src);
+++		emit(ctx, beqz, t1, off);
+++		break;
+++	/* PC += off if dst < src */
+++	case BPF_JLT:
+++		emit_sltu_r64(ctx, t1, dst, src);
+++		emit(ctx, bnez, t1, off);
+++		break;
+++	/* PC += off if dst <= src */
+++	case BPF_JLE:
+++		emit_sltu_r64(ctx, t1, src, dst);
+++		emit(ctx, beqz, t1, off);
+++		break;
+++	/* PC += off if dst > src (signed) */
+++	case BPF_JSGT:
+++		emit_slt_r64(ctx, t1, src, dst);
+++		emit(ctx, bnez, t1, off);
+++		break;
+++	/* PC += off if dst >= src (signed) */
+++	case BPF_JSGE:
+++		emit_slt_r64(ctx, t1, dst, src);
+++		emit(ctx, beqz, t1, off);
+++		break;
+++	/* PC += off if dst < src (signed) */
+++	case BPF_JSLT:
+++		emit_slt_r64(ctx, t1, dst, src);
+++		emit(ctx, bnez, t1, off);
+++		break;
+++	/* PC += off if dst <= src (signed) */
+++	case BPF_JSLE:
+++		emit_slt_r64(ctx, t1, src, dst);
+++		emit(ctx, beqz, t1, off);
+++		break;
+++	}
+++}
 ++
-++	if (WARN_ON(hw_list[index]))
-++		goto unlock;
+++/* Function call */
+++static int emit_call(struct jit_context *ctx, const struct bpf_insn *insn)
+++{
+++	bool fixed;
+++	u64 addr;
 ++
-++	hw = kzalloc(sizeof(*hw), GFP_KERNEL);
-++	hw->node = np;
-++	hw->regs = regs;
-++	hw->eth = eth;
-++	hw->dev = &pdev->dev;
-++	hw->wdma = wdma;
-++	hw->index = index;
-++	hw->irq = irq;
-++	hw->mirror = syscon_regmap_lookup_by_phandle(eth_np,
-++						     "mediatek,pcie-mirror");
-++	hw->hifsys = syscon_regmap_lookup_by_phandle(eth_np,
-++						     "mediatek,hifsys");
-++	if (IS_ERR(hw->mirror) || IS_ERR(hw->hifsys)) {
-++		kfree(hw);
-++		goto unlock;
-++	}
+++	/* Decode the call address */
+++	if (bpf_jit_get_func_addr(ctx->program, insn, false,
+++				  &addr, &fixed) < 0)
+++		return -1;
+++	if (!fixed)
+++		return -1;
 ++
-++	if (!index) {
-++		regmap_write(hw->mirror, 0, 0);
-++		regmap_write(hw->mirror, 4, 0);
-++	}
-++	mtk_wed_hw_add_debugfs(hw);
+++	/* Push stack arguments */
+++	push_regs(ctx, JIT_STACK_REGS, 0, JIT_RESERVED_STACK);
 ++
-++	hw_list[index] = hw;
+++	/* Emit function call */
+++	emit_mov_i(ctx, MIPS_R_T9, addr);
+++	emit(ctx, jalr, MIPS_R_RA, MIPS_R_T9);
+++	emit(ctx, nop); /* Delay slot */
 ++
-++unlock:
-++	mutex_unlock(&hw_lock);
+++	clobber_reg(ctx, MIPS_R_RA);
+++	clobber_reg(ctx, MIPS_R_V0);
+++	clobber_reg(ctx, MIPS_R_V1);
+++	return 0;
 ++}
 ++
-++void mtk_wed_exit(void)
+++/* Function tail call */
+++static int emit_tail_call(struct jit_context *ctx)
 ++{
-++	int i;
+++	u8 ary = lo(bpf2mips32[BPF_REG_2]);
+++	u8 ind = lo(bpf2mips32[BPF_REG_3]);
+++	u8 t1 = MIPS_R_T8;
+++	u8 t2 = MIPS_R_T9;
+++	int off;
 ++
-++	rcu_assign_pointer(mtk_soc_wed_ops, NULL);
+++	/*
+++	 * Tail call:
+++	 * eBPF R1   - function argument (context ptr), passed in a0-a1
+++	 * eBPF R2   - ptr to object with array of function entry points
+++	 * eBPF R3   - array index of function to be called
+++	 * stack[sz] - remaining tail call count, initialized in prologue
+++	 */
 ++
-++	synchronize_rcu();
+++	/* if (ind >= ary->map.max_entries) goto out */
+++	off = offsetof(struct bpf_array, map.max_entries);
+++	if (off > 0x7fff)
+++		return -1;
+++	emit(ctx, lw, t1, off, ary);             /* t1 = ary->map.max_entries*/
+++	emit_load_delay(ctx);                    /* Load delay slot          */
+++	emit(ctx, sltu, t1, ind, t1);            /* t1 = ind < t1            */
+++	emit(ctx, beqz, t1, get_offset(ctx, 1)); /* PC += off(1) if t1 == 0  */
+++						 /* (next insn delay slot)   */
+++	/* if (TCC-- <= 0) goto out */
+++	emit(ctx, lw, t2, ctx->stack_size, MIPS_R_SP);  /* t2 = *(SP + size) */
+++	emit_load_delay(ctx);                     /* Load delay slot         */
+++	emit(ctx, blez, t2, get_offset(ctx, 1));  /* PC += off(1) if t2 < 0  */
+++	emit(ctx, addiu, t2, t2, -1);             /* t2-- (delay slot)       */
+++	emit(ctx, sw, t2, ctx->stack_size, MIPS_R_SP);  /* *(SP + size) = t2 */
 ++
-++	for (i = 0; i < ARRAY_SIZE(hw_list); i++) {
-++		struct mtk_wed_hw *hw;
+++	/* prog = ary->ptrs[ind] */
+++	off = offsetof(struct bpf_array, ptrs);
+++	if (off > 0x7fff)
+++		return -1;
+++	emit(ctx, sll, t1, ind, 2);               /* t1 = ind << 2           */
+++	emit(ctx, addu, t1, t1, ary);             /* t1 += ary               */
+++	emit(ctx, lw, t2, off, t1);               /* t2 = *(t1 + off)        */
+++	emit_load_delay(ctx);                     /* Load delay slot         */
 ++
-++		hw = hw_list[i];
-++		if (!hw)
-++			continue;
+++	/* if (prog == 0) goto out */
+++	emit(ctx, beqz, t2, get_offset(ctx, 1));  /* PC += off(1) if t2 == 0 */
+++	emit(ctx, nop);                           /* Delay slot              */
 ++
-++		hw_list[i] = NULL;
-++		debugfs_remove(hw->debugfs_dir);
-++		put_device(hw->dev);
-++		kfree(hw);
-++	}
-++}
-+--- /dev/null
-++++ b/drivers/net/ethernet/mediatek/mtk_wed.h
-+@@ -0,0 +1,128 @@
-++// SPDX-License-Identifier: GPL-2.0-only
-++/* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name> */
+++	/* func = prog->bpf_func + 8 (prologue skip offset) */
+++	off = offsetof(struct bpf_prog, bpf_func);
+++	if (off > 0x7fff)
+++		return -1;
+++	emit(ctx, lw, t1, off, t2);                /* t1 = *(t2 + off)       */
+++	emit_load_delay(ctx);                      /* Load delay slot        */
+++	emit(ctx, addiu, t1, t1, JIT_TCALL_SKIP);  /* t1 += skip (8 or 12)   */
 ++
-++#ifndef __MTK_WED_PRIV_H
-++#define __MTK_WED_PRIV_H
+++	/* goto func */
+++	build_epilogue(ctx, t1);
+++	return 0;
+++}
 ++
-++#include <linux/soc/mediatek/mtk_wed.h>
-++#include <linux/debugfs.h>
-++#include <linux/regmap.h>
+++/*
+++ * Stack frame layout for a JITed program (stack grows down).
+++ *
+++ * Higher address  : Caller's stack frame       :
+++ *                 :----------------------------:
+++ *                 : 64-bit eBPF args r3-r5     :
+++ *                 :----------------------------:
+++ *                 : Reserved / tail call count :
+++ *                 +============================+  <--- MIPS sp before call
+++ *                 | Callee-saved registers,    |
+++ *                 | including RA and FP        |
+++ *                 +----------------------------+  <--- eBPF FP (MIPS zero,fp)
+++ *                 | Local eBPF variables       |
+++ *                 | allocated by program       |
+++ *                 +----------------------------+
+++ *                 | Reserved for caller-saved  |
+++ *                 | registers                  |
+++ *                 +----------------------------+
+++ *                 | Reserved for 64-bit eBPF   |
+++ *                 | args r3-r5 & args passed   |
+++ *                 | on stack in kernel calls   |
+++ * Lower address   +============================+  <--- MIPS sp
+++ */
 ++
-++struct mtk_eth;
+++/* Build program prologue to set up the stack and registers */
+++void build_prologue(struct jit_context *ctx)
+++{
+++	const u8 *r1 = bpf2mips32[BPF_REG_1];
+++	const u8 *fp = bpf2mips32[BPF_REG_FP];
+++	int stack, saved, locals, reserved;
 ++
-++struct mtk_wed_hw {
-++	struct device_node *node;
-++	struct mtk_eth *eth;
-++	struct regmap *regs;
-++	struct regmap *hifsys;
-++	struct device *dev;
-++	void __iomem *wdma;
-++	struct regmap *mirror;
-++	struct dentry *debugfs_dir;
-++	struct mtk_wed_device *wed_dev;
-++	u32 debugfs_reg;
-++	u32 num_flows;
-++	char dirname[5];
-++	int irq;
-++	int index;
-++};
+++	/*
+++	 * The first two instructions initialize TCC in the reserved (for us)
+++	 * 16-byte area in the parent's stack frame. On a tail call, the
+++	 * calling function jumps into the prologue after these instructions.
+++	 */
+++	emit(ctx, ori, MIPS_R_T9, MIPS_R_ZERO,
+++	     min(MAX_TAIL_CALL_CNT + 1, 0xffff));
+++	emit(ctx, sw, MIPS_R_T9, 0, MIPS_R_SP);
 ++
+++	/*
+++	 * Register eBPF R1 contains the 32-bit context pointer argument.
+++	 * A 32-bit argument is always passed in MIPS register a0, regardless
+++	 * of CPU endianness. Initialize R1 accordingly and zero-extend.
+++	 */
+++#ifdef __BIG_ENDIAN
+++	emit(ctx, move, lo(r1), MIPS_R_A0);
+++#endif
 ++
-++#ifdef CONFIG_NET_MEDIATEK_SOC_WED
-++static inline void
-++wed_w32(struct mtk_wed_device *dev, u32 reg, u32 val)
-++{
-++	regmap_write(dev->hw->regs, reg, val);
-++}
+++	/* === Entry-point for tail calls === */
 ++
-++static inline u32
-++wed_r32(struct mtk_wed_device *dev, u32 reg)
-++{
-++	unsigned int val;
+++	/* Zero-extend the 32-bit argument */
+++	emit(ctx, move, hi(r1), MIPS_R_ZERO);
 ++
-++	regmap_read(dev->hw->regs, reg, &val);
+++	/* If the eBPF frame pointer was accessed it must be saved */
+++	if (ctx->accessed & BIT(BPF_REG_FP))
+++		clobber_reg64(ctx, fp);
 ++
-++	return val;
-++}
+++	/* Compute the stack space needed for callee-saved registers */
+++	saved = hweight32(ctx->clobbered & JIT_CALLEE_REGS) * sizeof(u32);
+++	saved = ALIGN(saved, MIPS_STACK_ALIGNMENT);
 ++
-++static inline void
-++wdma_w32(struct mtk_wed_device *dev, u32 reg, u32 val)
-++{
-++	writel(val, dev->hw->wdma + reg);
-++}
+++	/* Stack space used by eBPF program local data */
+++	locals = ALIGN(ctx->program->aux->stack_depth, MIPS_STACK_ALIGNMENT);
 ++
-++static inline u32
-++wdma_r32(struct mtk_wed_device *dev, u32 reg)
-++{
-++	return readl(dev->hw->wdma + reg);
-++}
+++	/*
+++	 * If we are emitting function calls, reserve extra stack space for
+++	 * caller-saved registers and function arguments passed on the stack.
+++	 * The required space is computed automatically during resource
+++	 * usage discovery (pass 1).
+++	 */
+++	reserved = ctx->stack_used;
 ++
-++static inline u32
-++wpdma_tx_r32(struct mtk_wed_device *dev, int ring, u32 reg)
-++{
-++	if (!dev->tx_ring[ring].wpdma)
-++		return 0;
+++	/* Allocate the stack frame */
+++	stack = ALIGN(saved + locals + reserved, MIPS_STACK_ALIGNMENT);
+++	emit(ctx, addiu, MIPS_R_SP, MIPS_R_SP, -stack);
 ++
-++	return readl(dev->tx_ring[ring].wpdma + reg);
-++}
+++	/* Store callee-saved registers on stack */
+++	push_regs(ctx, ctx->clobbered & JIT_CALLEE_REGS, 0, stack - saved);
 ++
-++static inline void
-++wpdma_tx_w32(struct mtk_wed_device *dev, int ring, u32 reg, u32 val)
-++{
-++	if (!dev->tx_ring[ring].wpdma)
-++		return;
+++	/* Initialize the eBPF frame pointer if accessed */
+++	if (ctx->accessed & BIT(BPF_REG_FP))
+++		emit(ctx, addiu, lo(fp), MIPS_R_SP, stack - saved);
 ++
-++	writel(val, dev->tx_ring[ring].wpdma + reg);
+++	ctx->saved_size = saved;
+++	ctx->stack_size = stack;
 ++}
 ++
-++static inline u32
-++wpdma_txfree_r32(struct mtk_wed_device *dev, u32 reg)
+++/* Build the program epilogue to restore the stack and registers */
+++void build_epilogue(struct jit_context *ctx, int dest_reg)
 ++{
-++	if (!dev->txfree_ring.wpdma)
-++		return 0;
+++	/* Restore callee-saved registers from stack */
+++	pop_regs(ctx, ctx->clobbered & JIT_CALLEE_REGS, 0,
+++		 ctx->stack_size - ctx->saved_size);
+++	/*
+++	 * A 32-bit return value is always passed in MIPS register v0,
+++	 * but on big-endian targets the low part of R0 is mapped to v1.
+++	 */
+++#ifdef __BIG_ENDIAN
+++	emit(ctx, move, MIPS_R_V0, MIPS_R_V1);
+++#endif
 ++
-++	return readl(dev->txfree_ring.wpdma + reg);
+++	/* Jump to the return address and adjust the stack pointer */
+++	emit(ctx, jr, dest_reg);
+++	emit(ctx, addiu, MIPS_R_SP, MIPS_R_SP, ctx->stack_size);
 ++}
 ++
-++static inline void
-++wpdma_txfree_w32(struct mtk_wed_device *dev, u32 reg, u32 val)
+++/* Build one eBPF instruction */
+++int build_insn(const struct bpf_insn *insn, struct jit_context *ctx)
 ++{
-++	if (!dev->txfree_ring.wpdma)
-++		return;
-++
-++	writel(val, dev->txfree_ring.wpdma + reg);
-++}
+++	const u8 *dst = bpf2mips32[insn->dst_reg];
+++	const u8 *src = bpf2mips32[insn->src_reg];
+++	const u8 *tmp = bpf2mips32[JIT_REG_TMP];
+++	u8 code = insn->code;
+++	s16 off = insn->off;
+++	s32 imm = insn->imm;
+++	s32 val, rel;
+++	u8 alu, jmp;
 ++
-++void mtk_wed_add_hw(struct device_node *np, struct mtk_eth *eth,
-++		    void __iomem *wdma, int index);
-++void mtk_wed_exit(void);
-++int mtk_wed_flow_add(int index);
-++void mtk_wed_flow_remove(int index);
-++#else
-++static inline void
-++mtk_wed_add_hw(struct device_node *np, struct mtk_eth *eth,
-++	       void __iomem *wdma, int index)
-++{
-++}
-++static inline void
-++mtk_wed_exit(void)
-++{
-++}
-++static inline int mtk_wed_flow_add(int index)
-++{
-++	return -EINVAL;
-++}
-++static inline void mtk_wed_flow_remove(int index)
-++{
-++}
-++#endif
-++
-++#ifdef CONFIG_DEBUG_FS
-++void mtk_wed_hw_add_debugfs(struct mtk_wed_hw *hw);
+++	switch (code) {
+++	/* ALU operations */
+++	/* dst = imm */
+++	case BPF_ALU | BPF_MOV | BPF_K:
+++		emit_mov_i(ctx, lo(dst), imm);
+++		emit_zext_ver(ctx, dst);
+++		break;
+++	/* dst = src */
+++	case BPF_ALU | BPF_MOV | BPF_X:
+++		if (imm == 1) {
+++			/* Special mov32 for zext */
+++			emit_mov_i(ctx, hi(dst), 0);
+++		} else {
+++			emit_mov_r(ctx, lo(dst), lo(src));
+++			emit_zext_ver(ctx, dst);
+++		}
+++		break;
+++	/* dst = -dst */
+++	case BPF_ALU | BPF_NEG:
+++		emit_alu_i(ctx, lo(dst), 0, BPF_NEG);
+++		emit_zext_ver(ctx, dst);
+++		break;
+++	/* dst = dst & imm */
+++	/* dst = dst | imm */
+++	/* dst = dst ^ imm */
+++	/* dst = dst << imm */
+++	/* dst = dst >> imm */
+++	/* dst = dst >> imm (arithmetic) */
+++	/* dst = dst + imm */
+++	/* dst = dst - imm */
+++	/* dst = dst * imm */
+++	/* dst = dst / imm */
+++	/* dst = dst % imm */
+++	case BPF_ALU | BPF_OR | BPF_K:
+++	case BPF_ALU | BPF_AND | BPF_K:
+++	case BPF_ALU | BPF_XOR | BPF_K:
+++	case BPF_ALU | BPF_LSH | BPF_K:
+++	case BPF_ALU | BPF_RSH | BPF_K:
+++	case BPF_ALU | BPF_ARSH | BPF_K:
+++	case BPF_ALU | BPF_ADD | BPF_K:
+++	case BPF_ALU | BPF_SUB | BPF_K:
+++	case BPF_ALU | BPF_MUL | BPF_K:
+++	case BPF_ALU | BPF_DIV | BPF_K:
+++	case BPF_ALU | BPF_MOD | BPF_K:
+++		if (!valid_alu_i(BPF_OP(code), imm)) {
+++			emit_mov_i(ctx, MIPS_R_T6, imm);
+++			emit_alu_r(ctx, lo(dst), MIPS_R_T6, BPF_OP(code));
+++		} else if (rewrite_alu_i(BPF_OP(code), imm, &alu, &val)) {
+++			emit_alu_i(ctx, lo(dst), val, alu);
+++		}
+++		emit_zext_ver(ctx, dst);
+++		break;
+++	/* dst = dst & src */
+++	/* dst = dst | src */
+++	/* dst = dst ^ src */
+++	/* dst = dst << src */
+++	/* dst = dst >> src */
+++	/* dst = dst >> src (arithmetic) */
+++	/* dst = dst + src */
+++	/* dst = dst - src */
+++	/* dst = dst * src */
+++	/* dst = dst / src */
+++	/* dst = dst % src */
+++	case BPF_ALU | BPF_AND | BPF_X:
+++	case BPF_ALU | BPF_OR | BPF_X:
+++	case BPF_ALU | BPF_XOR | BPF_X:
+++	case BPF_ALU | BPF_LSH | BPF_X:
+++	case BPF_ALU | BPF_RSH | BPF_X:
+++	case BPF_ALU | BPF_ARSH | BPF_X:
+++	case BPF_ALU | BPF_ADD | BPF_X:
+++	case BPF_ALU | BPF_SUB | BPF_X:
+++	case BPF_ALU | BPF_MUL | BPF_X:
+++	case BPF_ALU | BPF_DIV | BPF_X:
+++	case BPF_ALU | BPF_MOD | BPF_X:
+++		emit_alu_r(ctx, lo(dst), lo(src), BPF_OP(code));
+++		emit_zext_ver(ctx, dst);
+++		break;
+++	/* dst = imm (64-bit) */
+++	case BPF_ALU64 | BPF_MOV | BPF_K:
+++		emit_mov_se_i64(ctx, dst, imm);
+++		break;
+++	/* dst = src (64-bit) */
+++	case BPF_ALU64 | BPF_MOV | BPF_X:
+++		emit_mov_r(ctx, lo(dst), lo(src));
+++		emit_mov_r(ctx, hi(dst), hi(src));
+++		break;
+++	/* dst = -dst (64-bit) */
+++	case BPF_ALU64 | BPF_NEG:
+++		emit_neg_i64(ctx, dst);
+++		break;
+++	/* dst = dst & imm (64-bit) */
+++	case BPF_ALU64 | BPF_AND | BPF_K:
+++		emit_alu_i64(ctx, dst, imm, BPF_OP(code));
+++		break;
+++	/* dst = dst | imm (64-bit) */
+++	/* dst = dst ^ imm (64-bit) */
+++	/* dst = dst + imm (64-bit) */
+++	/* dst = dst - imm (64-bit) */
+++	case BPF_ALU64 | BPF_OR | BPF_K:
+++	case BPF_ALU64 | BPF_XOR | BPF_K:
+++	case BPF_ALU64 | BPF_ADD | BPF_K:
+++	case BPF_ALU64 | BPF_SUB | BPF_K:
+++		if (imm)
+++			emit_alu_i64(ctx, dst, imm, BPF_OP(code));
+++		break;
+++	/* dst = dst << imm (64-bit) */
+++	/* dst = dst >> imm (64-bit) */
+++	/* dst = dst >> imm (64-bit, arithmetic) */
+++	case BPF_ALU64 | BPF_LSH | BPF_K:
+++	case BPF_ALU64 | BPF_RSH | BPF_K:
+++	case BPF_ALU64 | BPF_ARSH | BPF_K:
+++		if (imm)
+++			emit_shift_i64(ctx, dst, imm, BPF_OP(code));
+++		break;
+++	/* dst = dst * imm (64-bit) */
+++	case BPF_ALU64 | BPF_MUL | BPF_K:
+++		emit_mul_i64(ctx, dst, imm);
+++		break;
+++	/* dst = dst / imm (64-bit) */
+++	/* dst = dst % imm (64-bit) */
+++	case BPF_ALU64 | BPF_DIV | BPF_K:
+++	case BPF_ALU64 | BPF_MOD | BPF_K:
+++		/*
+++		 * Sign-extend the immediate value into a temporary register,
+++		 * and then do the operation on this register.
+++		 */
+++		emit_mov_se_i64(ctx, tmp, imm);
+++		emit_divmod_r64(ctx, dst, tmp, BPF_OP(code));
+++		break;
+++	/* dst = dst & src (64-bit) */
+++	/* dst = dst | src (64-bit) */
+++	/* dst = dst ^ src (64-bit) */
+++	/* dst = dst + src (64-bit) */
+++	/* dst = dst - src (64-bit) */
+++	case BPF_ALU64 | BPF_AND | BPF_X:
+++	case BPF_ALU64 | BPF_OR | BPF_X:
+++	case BPF_ALU64 | BPF_XOR | BPF_X:
+++	case BPF_ALU64 | BPF_ADD | BPF_X:
+++	case BPF_ALU64 | BPF_SUB | BPF_X:
+++		emit_alu_r64(ctx, dst, src, BPF_OP(code));
+++		break;
+++	/* dst = dst << src (64-bit) */
+++	/* dst = dst >> src (64-bit) */
+++	/* dst = dst >> src (64-bit, arithmetic) */
+++	case BPF_ALU64 | BPF_LSH | BPF_X:
+++	case BPF_ALU64 | BPF_RSH | BPF_X:
+++	case BPF_ALU64 | BPF_ARSH | BPF_X:
+++		emit_shift_r64(ctx, dst, lo(src), BPF_OP(code));
+++		break;
+++	/* dst = dst * src (64-bit) */
+++	case BPF_ALU64 | BPF_MUL | BPF_X:
+++		emit_mul_r64(ctx, dst, src);
+++		break;
+++	/* dst = dst / src (64-bit) */
+++	/* dst = dst % src (64-bit) */
+++	case BPF_ALU64 | BPF_DIV | BPF_X:
+++	case BPF_ALU64 | BPF_MOD | BPF_X:
+++		emit_divmod_r64(ctx, dst, src, BPF_OP(code));
+++		break;
+++	/* dst = htole(dst) */
+++	/* dst = htobe(dst) */
+++	case BPF_ALU | BPF_END | BPF_FROM_LE:
+++	case BPF_ALU | BPF_END | BPF_FROM_BE:
+++		if (BPF_SRC(code) ==
+++#ifdef __BIG_ENDIAN
+++		    BPF_FROM_LE
 ++#else
-++static inline void mtk_wed_hw_add_debugfs(struct mtk_wed_hw *hw)
-++{
-++}
+++		    BPF_FROM_BE
 ++#endif
+++		    )
+++			emit_bswap_r64(ctx, dst, imm);
+++		else
+++			emit_trunc_r64(ctx, dst, imm);
+++		break;
+++	/* dst = imm64 */
+++	case BPF_LD | BPF_IMM | BPF_DW:
+++		emit_mov_i(ctx, lo(dst), imm);
+++		emit_mov_i(ctx, hi(dst), insn[1].imm);
+++		return 1;
+++	/* LDX: dst = *(size *)(src + off) */
+++	case BPF_LDX | BPF_MEM | BPF_W:
+++	case BPF_LDX | BPF_MEM | BPF_H:
+++	case BPF_LDX | BPF_MEM | BPF_B:
+++	case BPF_LDX | BPF_MEM | BPF_DW:
+++		emit_ldx(ctx, dst, lo(src), off, BPF_SIZE(code));
+++		break;
+++	/* ST: *(size *)(dst + off) = imm */
+++	case BPF_ST | BPF_MEM | BPF_W:
+++	case BPF_ST | BPF_MEM | BPF_H:
+++	case BPF_ST | BPF_MEM | BPF_B:
+++	case BPF_ST | BPF_MEM | BPF_DW:
+++		switch (BPF_SIZE(code)) {
+++		case BPF_DW:
+++			/* Sign-extend immediate value into temporary reg */
+++			emit_mov_se_i64(ctx, tmp, imm);
+++			break;
+++		case BPF_W:
+++		case BPF_H:
+++		case BPF_B:
+++			emit_mov_i(ctx, lo(tmp), imm);
+++			break;
+++		}
+++		emit_stx(ctx, lo(dst), tmp, off, BPF_SIZE(code));
+++		break;
+++	/* STX: *(size *)(dst + off) = src */
+++	case BPF_STX | BPF_MEM | BPF_W:
+++	case BPF_STX | BPF_MEM | BPF_H:
+++	case BPF_STX | BPF_MEM | BPF_B:
+++	case BPF_STX | BPF_MEM | BPF_DW:
+++		emit_stx(ctx, lo(dst), src, off, BPF_SIZE(code));
+++		break;
+++	/* Speculation barrier */
+++	case BPF_ST | BPF_NOSPEC:
+++		break;
+++	/* Atomics */
+++	case BPF_STX | BPF_XADD | BPF_W:
+++		switch (imm) {
+++		case BPF_ADD:
+++		case BPF_AND:
+++		case BPF_OR:
+++		case BPF_XOR:
+++			if (cpu_has_llsc)
+++				emit_atomic_r(ctx, lo(dst), lo(src), off, imm);
+++			else /* Non-ll/sc fallback */
+++				emit_atomic_r32(ctx, lo(dst), lo(src),
+++						off, imm);
+++			break;
+++		default:
+++			goto notyet;
+++		}
+++		break;
+++	/* Atomics (64-bit) */
+++	case BPF_STX | BPF_XADD | BPF_DW:
+++		switch (imm) {
+++		case BPF_ADD:
+++		case BPF_AND:
+++		case BPF_OR:
+++		case BPF_XOR:
+++			emit_atomic_r64(ctx, lo(dst), src, off, imm);
+++			break;
+++		default:
+++			goto notyet;
+++		}
+++		break;
+++	/* PC += off if dst == src */
+++	/* PC += off if dst != src */
+++	/* PC += off if dst & src */
+++	/* PC += off if dst > src */
+++	/* PC += off if dst >= src */
+++	/* PC += off if dst < src */
+++	/* PC += off if dst <= src */
+++	/* PC += off if dst > src (signed) */
+++	/* PC += off if dst >= src (signed) */
+++	/* PC += off if dst < src (signed) */
+++	/* PC += off if dst <= src (signed) */
+++	case BPF_JMP32 | BPF_JEQ | BPF_X:
+++	case BPF_JMP32 | BPF_JNE | BPF_X:
+++	case BPF_JMP32 | BPF_JSET | BPF_X:
+++	case BPF_JMP32 | BPF_JGT | BPF_X:
+++	case BPF_JMP32 | BPF_JGE | BPF_X:
+++	case BPF_JMP32 | BPF_JLT | BPF_X:
+++	case BPF_JMP32 | BPF_JLE | BPF_X:
+++	case BPF_JMP32 | BPF_JSGT | BPF_X:
+++	case BPF_JMP32 | BPF_JSGE | BPF_X:
+++	case BPF_JMP32 | BPF_JSLT | BPF_X:
+++	case BPF_JMP32 | BPF_JSLE | BPF_X:
+++		if (off == 0)
+++			break;
+++		setup_jmp_r(ctx, dst == src, BPF_OP(code), off, &jmp, &rel);
+++		emit_jmp_r(ctx, lo(dst), lo(src), rel, jmp);
+++		if (finish_jmp(ctx, jmp, off) < 0)
+++			goto toofar;
+++		break;
+++	/* PC += off if dst == imm */
+++	/* PC += off if dst != imm */
+++	/* PC += off if dst & imm */
+++	/* PC += off if dst > imm */
+++	/* PC += off if dst >= imm */
+++	/* PC += off if dst < imm */
+++	/* PC += off if dst <= imm */
+++	/* PC += off if dst > imm (signed) */
+++	/* PC += off if dst >= imm (signed) */
+++	/* PC += off if dst < imm (signed) */
+++	/* PC += off if dst <= imm (signed) */
+++	case BPF_JMP32 | BPF_JEQ | BPF_K:
+++	case BPF_JMP32 | BPF_JNE | BPF_K:
+++	case BPF_JMP32 | BPF_JSET | BPF_K:
+++	case BPF_JMP32 | BPF_JGT | BPF_K:
+++	case BPF_JMP32 | BPF_JGE | BPF_K:
+++	case BPF_JMP32 | BPF_JLT | BPF_K:
+++	case BPF_JMP32 | BPF_JLE | BPF_K:
+++	case BPF_JMP32 | BPF_JSGT | BPF_K:
+++	case BPF_JMP32 | BPF_JSGE | BPF_K:
+++	case BPF_JMP32 | BPF_JSLT | BPF_K:
+++	case BPF_JMP32 | BPF_JSLE | BPF_K:
+++		if (off == 0)
+++			break;
+++		setup_jmp_i(ctx, imm, 32, BPF_OP(code), off, &jmp, &rel);
+++		if (valid_jmp_i(jmp, imm)) {
+++			emit_jmp_i(ctx, lo(dst), imm, rel, jmp);
+++		} else {
+++			/* Move large immediate to register */
+++			emit_mov_i(ctx, MIPS_R_T6, imm);
+++			emit_jmp_r(ctx, lo(dst), MIPS_R_T6, rel, jmp);
+++		}
+++		if (finish_jmp(ctx, jmp, off) < 0)
+++			goto toofar;
+++		break;
+++	/* PC += off if dst == src */
+++	/* PC += off if dst != src */
+++	/* PC += off if dst & src */
+++	/* PC += off if dst > src */
+++	/* PC += off if dst >= src */
+++	/* PC += off if dst < src */
+++	/* PC += off if dst <= src */
+++	/* PC += off if dst > src (signed) */
+++	/* PC += off if dst >= src (signed) */
+++	/* PC += off if dst < src (signed) */
+++	/* PC += off if dst <= src (signed) */
+++	case BPF_JMP | BPF_JEQ | BPF_X:
+++	case BPF_JMP | BPF_JNE | BPF_X:
+++	case BPF_JMP | BPF_JSET | BPF_X:
+++	case BPF_JMP | BPF_JGT | BPF_X:
+++	case BPF_JMP | BPF_JGE | BPF_X:
+++	case BPF_JMP | BPF_JLT | BPF_X:
+++	case BPF_JMP | BPF_JLE | BPF_X:
+++	case BPF_JMP | BPF_JSGT | BPF_X:
+++	case BPF_JMP | BPF_JSGE | BPF_X:
+++	case BPF_JMP | BPF_JSLT | BPF_X:
+++	case BPF_JMP | BPF_JSLE | BPF_X:
+++		if (off == 0)
+++			break;
+++		setup_jmp_r(ctx, dst == src, BPF_OP(code), off, &jmp, &rel);
+++		emit_jmp_r64(ctx, dst, src, rel, jmp);
+++		if (finish_jmp(ctx, jmp, off) < 0)
+++			goto toofar;
+++		break;
+++	/* PC += off if dst == imm */
+++	/* PC += off if dst != imm */
+++	/* PC += off if dst & imm */
+++	/* PC += off if dst > imm */
+++	/* PC += off if dst >= imm */
+++	/* PC += off if dst < imm */
+++	/* PC += off if dst <= imm */
+++	/* PC += off if dst > imm (signed) */
+++	/* PC += off if dst >= imm (signed) */
+++	/* PC += off if dst < imm (signed) */
+++	/* PC += off if dst <= imm (signed) */
+++	case BPF_JMP | BPF_JEQ | BPF_K:
+++	case BPF_JMP | BPF_JNE | BPF_K:
+++	case BPF_JMP | BPF_JSET | BPF_K:
+++	case BPF_JMP | BPF_JGT | BPF_K:
+++	case BPF_JMP | BPF_JGE | BPF_K:
+++	case BPF_JMP | BPF_JLT | BPF_K:
+++	case BPF_JMP | BPF_JLE | BPF_K:
+++	case BPF_JMP | BPF_JSGT | BPF_K:
+++	case BPF_JMP | BPF_JSGE | BPF_K:
+++	case BPF_JMP | BPF_JSLT | BPF_K:
+++	case BPF_JMP | BPF_JSLE | BPF_K:
+++		if (off == 0)
+++			break;
+++		setup_jmp_i(ctx, imm, 64, BPF_OP(code), off, &jmp, &rel);
+++		emit_jmp_i64(ctx, dst, imm, rel, jmp);
+++		if (finish_jmp(ctx, jmp, off) < 0)
+++			goto toofar;
+++		break;
+++	/* PC += off */
+++	case BPF_JMP | BPF_JA:
+++		if (off == 0)
+++			break;
+++		if (emit_ja(ctx, off) < 0)
+++			goto toofar;
+++		break;
+++	/* Tail call */
+++	case BPF_JMP | BPF_TAIL_CALL:
+++		if (emit_tail_call(ctx) < 0)
+++			goto invalid;
+++		break;
+++	/* Function call */
+++	case BPF_JMP | BPF_CALL:
+++		if (emit_call(ctx, insn) < 0)
+++			goto invalid;
+++		break;
+++	/* Function return */
+++	case BPF_JMP | BPF_EXIT:
+++		/*
+++		 * Optimization: when last instruction is EXIT
+++		 * simply continue to epilogue.
+++		 */
+++		if (ctx->bpf_index == ctx->program->len - 1)
+++			break;
+++		if (emit_exit(ctx) < 0)
+++			goto toofar;
+++		break;
 ++
-++#endif
+++	default:
+++invalid:
+++		pr_err_once("unknown opcode %02x\n", code);
+++		return -EINVAL;
+++notyet:
+++		pr_info_once("*** NOT YET: opcode %02x ***\n", code);
+++		return -EFAULT;
+++toofar:
+++		pr_info_once("*** TOO FAR: jump at %u opcode %02x ***\n",
+++			     ctx->bpf_index, code);
+++		return -E2BIG;
+++	}
+++	return 0;
+++}
+diff --git a/target/linux/generic/backport-5.15/050-v5.16-03-mips-bpf-Add-new-eBPF-JIT-for-64-bit-MIPS.patch b/target/linux/generic/backport-5.15/050-v5.16-03-mips-bpf-Add-new-eBPF-JIT-for-64-bit-MIPS.patch
+new file mode 100644
+index 0000000000..38b46c0b76
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/050-v5.16-03-mips-bpf-Add-new-eBPF-JIT-for-64-bit-MIPS.patch
+@@ -0,0 +1,1005 @@
++From: Johan Almbladh <johan.almbladh@anyfinetworks.com>
++Date: Tue, 5 Oct 2021 18:54:05 +0200
++Subject: [PATCH] mips: bpf: Add new eBPF JIT for 64-bit MIPS
++
++This is an implementation on of an eBPF JIT for 64-bit MIPS III-V and
++MIPS64r1-r6. It uses the same framework introduced by the 32-bit JIT.
++
++Signed-off-by: Johan Almbladh <johan.almbladh@anyfinetworks.com>
++---
++ create mode 100644 arch/mips/net/bpf_jit_comp64.c
++
 +--- /dev/null
-++++ b/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
-+@@ -0,0 +1,175 @@
+++++ b/arch/mips/net/bpf_jit_comp64.c
++@@ -0,0 +1,991 @@
 ++// SPDX-License-Identifier: GPL-2.0-only
-++/* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name> */
-++
-++#include <linux/seq_file.h>
-++#include "mtk_wed.h"
-++#include "mtk_wed_regs.h"
-++
-++struct reg_dump {
-++	const char *name;
-++	u16 offset;
-++	u8 type;
-++	u8 base;
-++};
+++/*
+++ * Just-In-Time compiler for eBPF bytecode on MIPS.
+++ * Implementation of JIT functions for 64-bit CPUs.
+++ *
+++ * Copyright (c) 2021 Anyfi Networks AB.
+++ * Author: Johan Almbladh <johan.almbladh@gmail.com>
+++ *
+++ * Based on code and ideas from
+++ * Copyright (c) 2017 Cavium, Inc.
+++ * Copyright (c) 2017 Shubham Bansal <illusionist.neo@gmail.com>
+++ * Copyright (c) 2011 Mircea Gherzan <mgherzan@gmail.com>
+++ */
 ++
-++enum {
-++	DUMP_TYPE_STRING,
-++	DUMP_TYPE_WED,
-++	DUMP_TYPE_WDMA,
-++	DUMP_TYPE_WPDMA_TX,
-++	DUMP_TYPE_WPDMA_TXFREE,
-++};
+++#include <linux/errno.h>
+++#include <linux/filter.h>
+++#include <linux/bpf.h>
+++#include <asm/cpu-features.h>
+++#include <asm/isa-rev.h>
+++#include <asm/uasm.h>
 ++
-++#define DUMP_STR(_str) { _str, 0, DUMP_TYPE_STRING }
-++#define DUMP_REG(_reg, ...) { #_reg, MTK_##_reg, __VA_ARGS__ }
-++#define DUMP_RING(_prefix, _base, ...)				\
-++	{ _prefix " BASE", _base, __VA_ARGS__ },		\
-++	{ _prefix " CNT",  _base + 0x4, __VA_ARGS__ },	\
-++	{ _prefix " CIDX", _base + 0x8, __VA_ARGS__ },	\
-++	{ _prefix " DIDX", _base + 0xc, __VA_ARGS__ }
+++#include "bpf_jit_comp.h"
 ++
-++#define DUMP_WED(_reg) DUMP_REG(_reg, DUMP_TYPE_WED)
-++#define DUMP_WED_RING(_base) DUMP_RING(#_base, MTK_##_base, DUMP_TYPE_WED)
+++/* MIPS t0-t3 are not available in the n64 ABI */
+++#undef MIPS_R_T0
+++#undef MIPS_R_T1
+++#undef MIPS_R_T2
+++#undef MIPS_R_T3
 ++
-++#define DUMP_WDMA(_reg) DUMP_REG(_reg, DUMP_TYPE_WDMA)
-++#define DUMP_WDMA_RING(_base) DUMP_RING(#_base, MTK_##_base, DUMP_TYPE_WDMA)
+++/* Stack is 16-byte aligned in n64 ABI */
+++#define MIPS_STACK_ALIGNMENT 16
 ++
-++#define DUMP_WPDMA_TX_RING(_n) DUMP_RING("WPDMA_TX" #_n, 0, DUMP_TYPE_WPDMA_TX, _n)
-++#define DUMP_WPDMA_TXFREE_RING DUMP_RING("WPDMA_RX1", 0, DUMP_TYPE_WPDMA_TXFREE)
+++/* Extra 64-bit eBPF registers used by JIT */
+++#define JIT_REG_TC (MAX_BPF_JIT_REG + 0)
+++#define JIT_REG_ZX (MAX_BPF_JIT_REG + 1)
 ++
-++static void
-++print_reg_val(struct seq_file *s, const char *name, u32 val)
-++{
-++	seq_printf(s, "%-32s %08x\n", name, val);
-++}
+++/* Number of prologue bytes to skip when doing a tail call */
+++#define JIT_TCALL_SKIP 4
 ++
-++static void
-++dump_wed_regs(struct seq_file *s, struct mtk_wed_device *dev,
-++	      const struct reg_dump *regs, int n_regs)
+++/* Callee-saved CPU registers that the JIT must preserve */
+++#define JIT_CALLEE_REGS   \
+++	(BIT(MIPS_R_S0) | \
+++	 BIT(MIPS_R_S1) | \
+++	 BIT(MIPS_R_S2) | \
+++	 BIT(MIPS_R_S3) | \
+++	 BIT(MIPS_R_S4) | \
+++	 BIT(MIPS_R_S5) | \
+++	 BIT(MIPS_R_S6) | \
+++	 BIT(MIPS_R_S7) | \
+++	 BIT(MIPS_R_GP) | \
+++	 BIT(MIPS_R_FP) | \
+++	 BIT(MIPS_R_RA))
+++
+++/* Caller-saved CPU registers available for JIT use */
+++#define JIT_CALLER_REGS	  \
+++	(BIT(MIPS_R_A5) | \
+++	 BIT(MIPS_R_A6) | \
+++	 BIT(MIPS_R_A7))
+++/*
+++ * Mapping of 64-bit eBPF registers to 64-bit native MIPS registers.
+++ * MIPS registers t4 - t7 may be used by the JIT as temporary registers.
+++ * MIPS registers t8 - t9 are reserved for single-register common functions.
+++ */
+++static const u8 bpf2mips64[] = {
+++	/* Return value from in-kernel function, and exit value from eBPF */
+++	[BPF_REG_0] = MIPS_R_V0,
+++	/* Arguments from eBPF program to in-kernel function */
+++	[BPF_REG_1] = MIPS_R_A0,
+++	[BPF_REG_2] = MIPS_R_A1,
+++	[BPF_REG_3] = MIPS_R_A2,
+++	[BPF_REG_4] = MIPS_R_A3,
+++	[BPF_REG_5] = MIPS_R_A4,
+++	/* Callee-saved registers that in-kernel function will preserve */
+++	[BPF_REG_6] = MIPS_R_S0,
+++	[BPF_REG_7] = MIPS_R_S1,
+++	[BPF_REG_8] = MIPS_R_S2,
+++	[BPF_REG_9] = MIPS_R_S3,
+++	/* Read-only frame pointer to access the eBPF stack */
+++	[BPF_REG_FP] = MIPS_R_FP,
+++	/* Temporary register for blinding constants */
+++	[BPF_REG_AX] = MIPS_R_AT,
+++	/* Tail call count register, caller-saved */
+++	[JIT_REG_TC] = MIPS_R_A5,
+++	/* Constant for register zero-extension */
+++	[JIT_REG_ZX] = MIPS_R_V1,
+++};
+++
+++/*
+++ * MIPS 32-bit operations on 64-bit registers generate a sign-extended
+++ * result. However, the eBPF ISA mandates zero-extension, so we rely on the
+++ * verifier to add that for us (emit_zext_ver). In addition, ALU arithmetic
+++ * operations, right shift and byte swap require properly sign-extended
+++ * operands or the result is unpredictable. We emit explicit sign-extensions
+++ * in those cases.
+++ */
+++
+++/* Sign extension */
+++static void emit_sext(struct jit_context *ctx, u8 dst, u8 src)
 ++{
-++	const struct reg_dump *cur;
-++	u32 val;
+++	emit(ctx, sll, dst, src, 0);
+++	clobber_reg(ctx, dst);
+++}
 ++
-++	for (cur = regs; cur < &regs[n_regs]; cur++) {
-++		switch (cur->type) {
-++		case DUMP_TYPE_STRING:
-++			seq_printf(s, "%s======== %s:\n",
-++				   cur > regs ? "\n" : "",
-++				   cur->name);
-++			continue;
-++		case DUMP_TYPE_WED:
-++			val = wed_r32(dev, cur->offset);
-++			break;
-++		case DUMP_TYPE_WDMA:
-++			val = wdma_r32(dev, cur->offset);
-++			break;
-++		case DUMP_TYPE_WPDMA_TX:
-++			val = wpdma_tx_r32(dev, cur->base, cur->offset);
-++			break;
-++		case DUMP_TYPE_WPDMA_TXFREE:
-++			val = wpdma_txfree_r32(dev, cur->offset);
-++			break;
-++		}
-++		print_reg_val(s, cur->name, val);
+++/* Zero extension */
+++static void emit_zext(struct jit_context *ctx, u8 dst)
+++{
+++	if (cpu_has_mips64r2 || cpu_has_mips64r6) {
+++		emit(ctx, dinsu, dst, MIPS_R_ZERO, 32, 32);
+++	} else {
+++		emit(ctx, and, dst, dst, bpf2mips64[JIT_REG_ZX]);
+++		access_reg(ctx, JIT_REG_ZX); /* We need the ZX register */
 ++	}
+++	clobber_reg(ctx, dst);
 ++}
 ++
+++/* Zero extension, if verifier does not do it for us  */
+++static void emit_zext_ver(struct jit_context *ctx, u8 dst)
+++{
+++	if (!ctx->program->aux->verifier_zext)
+++		emit_zext(ctx, dst);
+++}
 ++
-++static int
-++wed_txinfo_show(struct seq_file *s, void *data)
+++/* dst = imm (64-bit) */
+++static void emit_mov_i64(struct jit_context *ctx, u8 dst, u64 imm64)
 ++{
-++	static const struct reg_dump regs[] = {
-++		DUMP_STR("WED TX"),
-++		DUMP_WED(WED_TX_MIB(0)),
-++		DUMP_WED_RING(WED_RING_TX(0)),
+++	if (imm64 >= 0xffffffffffff8000ULL || imm64 < 0x8000ULL) {
+++		emit(ctx, daddiu, dst, MIPS_R_ZERO, (s16)imm64);
+++	} else if (imm64 >= 0xffffffff80000000ULL ||
+++		   (imm64 < 0x80000000 && imm64 > 0xffff)) {
+++		emit(ctx, lui, dst, (s16)(imm64 >> 16));
+++		emit(ctx, ori, dst, dst, (u16)imm64 & 0xffff);
+++	} else {
+++		u8 acc = MIPS_R_ZERO;
+++		int k;
 ++
-++		DUMP_WED(WED_TX_MIB(1)),
-++		DUMP_WED_RING(WED_RING_TX(1)),
+++		for (k = 0; k < 4; k++) {
+++			u16 half = imm64 >> (48 - 16 * k);
 ++
-++		DUMP_STR("WPDMA TX"),
-++		DUMP_WED(WED_WPDMA_TX_MIB(0)),
-++		DUMP_WED_RING(WED_WPDMA_RING_TX(0)),
-++		DUMP_WED(WED_WPDMA_TX_COHERENT_MIB(0)),
+++			if (acc == dst)
+++				emit(ctx, dsll, dst, dst, 16);
 ++
-++		DUMP_WED(WED_WPDMA_TX_MIB(1)),
-++		DUMP_WED_RING(WED_WPDMA_RING_TX(1)),
-++		DUMP_WED(WED_WPDMA_TX_COHERENT_MIB(1)),
+++			if (half) {
+++				emit(ctx, ori, dst, acc, half);
+++				acc = dst;
+++			}
+++		}
+++	}
+++	clobber_reg(ctx, dst);
+++}
 ++
-++		DUMP_STR("WPDMA TX"),
-++		DUMP_WPDMA_TX_RING(0),
-++		DUMP_WPDMA_TX_RING(1),
+++/* ALU immediate operation (64-bit) */
+++static void emit_alu_i64(struct jit_context *ctx, u8 dst, s32 imm, u8 op)
+++{
+++	switch (BPF_OP(op)) {
+++	/* dst = dst | imm */
+++	case BPF_OR:
+++		emit(ctx, ori, dst, dst, (u16)imm);
+++		break;
+++	/* dst = dst ^ imm */
+++	case BPF_XOR:
+++		emit(ctx, xori, dst, dst, (u16)imm);
+++		break;
+++	/* dst = -dst */
+++	case BPF_NEG:
+++		emit(ctx, dsubu, dst, MIPS_R_ZERO, dst);
+++		break;
+++	/* dst = dst << imm */
+++	case BPF_LSH:
+++		emit(ctx, dsll_safe, dst, dst, imm);
+++		break;
+++	/* dst = dst >> imm */
+++	case BPF_RSH:
+++		emit(ctx, dsrl_safe, dst, dst, imm);
+++		break;
+++	/* dst = dst >> imm (arithmetic) */
+++	case BPF_ARSH:
+++		emit(ctx, dsra_safe, dst, dst, imm);
+++		break;
+++	/* dst = dst + imm */
+++	case BPF_ADD:
+++		emit(ctx, daddiu, dst, dst, imm);
+++		break;
+++	/* dst = dst - imm */
+++	case BPF_SUB:
+++		emit(ctx, daddiu, dst, dst, -imm);
+++		break;
+++	default:
+++		/* Width-generic operations */
+++		emit_alu_i(ctx, dst, imm, op);
+++	}
+++	clobber_reg(ctx, dst);
+++}
 ++
-++		DUMP_STR("WED WDMA RX"),
-++		DUMP_WED(WED_WDMA_RX_MIB(0)),
-++		DUMP_WED_RING(WED_WDMA_RING_RX(0)),
-++		DUMP_WED(WED_WDMA_RX_THRES(0)),
-++		DUMP_WED(WED_WDMA_RX_RECYCLE_MIB(0)),
-++		DUMP_WED(WED_WDMA_RX_PROCESSED_MIB(0)),
+++/* ALU register operation (64-bit) */
+++static void emit_alu_r64(struct jit_context *ctx, u8 dst, u8 src, u8 op)
+++{
+++	switch (BPF_OP(op)) {
+++	/* dst = dst << src */
+++	case BPF_LSH:
+++		emit(ctx, dsllv, dst, dst, src);
+++		break;
+++	/* dst = dst >> src */
+++	case BPF_RSH:
+++		emit(ctx, dsrlv, dst, dst, src);
+++		break;
+++	/* dst = dst >> src (arithmetic) */
+++	case BPF_ARSH:
+++		emit(ctx, dsrav, dst, dst, src);
+++		break;
+++	/* dst = dst + src */
+++	case BPF_ADD:
+++		emit(ctx, daddu, dst, dst, src);
+++		break;
+++	/* dst = dst - src */
+++	case BPF_SUB:
+++		emit(ctx, dsubu, dst, dst, src);
+++		break;
+++	/* dst = dst * src */
+++	case BPF_MUL:
+++		if (cpu_has_mips64r6) {
+++			emit(ctx, dmulu, dst, dst, src);
+++		} else {
+++			emit(ctx, dmultu, dst, src);
+++			emit(ctx, mflo, dst);
+++		}
+++		break;
+++	/* dst = dst / src */
+++	case BPF_DIV:
+++		if (cpu_has_mips64r6) {
+++			emit(ctx, ddivu_r6, dst, dst, src);
+++		} else {
+++			emit(ctx, ddivu, dst, src);
+++			emit(ctx, mflo, dst);
+++		}
+++		break;
+++	/* dst = dst % src */
+++	case BPF_MOD:
+++		if (cpu_has_mips64r6) {
+++			emit(ctx, dmodu, dst, dst, src);
+++		} else {
+++			emit(ctx, ddivu, dst, src);
+++			emit(ctx, mfhi, dst);
+++		}
+++		break;
+++	default:
+++		/* Width-generic operations */
+++		emit_alu_r(ctx, dst, src, op);
+++	}
+++	clobber_reg(ctx, dst);
+++}
 ++
-++		DUMP_WED(WED_WDMA_RX_MIB(1)),
-++		DUMP_WED_RING(WED_WDMA_RING_RX(1)),
-++		DUMP_WED(WED_WDMA_RX_THRES(1)),
-++		DUMP_WED(WED_WDMA_RX_RECYCLE_MIB(1)),
-++		DUMP_WED(WED_WDMA_RX_PROCESSED_MIB(1)),
+++/* Swap sub words in a register double word */
+++static void emit_swap_r64(struct jit_context *ctx, u8 dst, u8 mask, u32 bits)
+++{
+++	u8 tmp = MIPS_R_T9;
 ++
-++		DUMP_STR("WDMA RX"),
-++		DUMP_WDMA(WDMA_GLO_CFG),
-++		DUMP_WDMA_RING(WDMA_RING_RX(0)),
-++		DUMP_WDMA_RING(WDMA_RING_RX(1)),
-++	};
-++	struct mtk_wed_hw *hw = s->private;
-++	struct mtk_wed_device *dev = hw->wed_dev;
+++	emit(ctx, and, tmp, dst, mask);  /* tmp = dst & mask  */
+++	emit(ctx, dsll, tmp, tmp, bits); /* tmp = tmp << bits */
+++	emit(ctx, dsrl, dst, dst, bits); /* dst = dst >> bits */
+++	emit(ctx, and, dst, dst, mask);  /* dst = dst & mask  */
+++	emit(ctx, or, dst, dst, tmp);    /* dst = dst | tmp   */
+++}
 ++
-++	if (!dev)
-++		return 0;
+++/* Swap bytes and truncate a register double word, word or half word */
+++static void emit_bswap_r64(struct jit_context *ctx, u8 dst, u32 width)
+++{
+++	switch (width) {
+++	/* Swap bytes in a double word */
+++	case 64:
+++		if (cpu_has_mips64r2 || cpu_has_mips64r6) {
+++			emit(ctx, dsbh, dst, dst);
+++			emit(ctx, dshd, dst, dst);
+++		} else {
+++			u8 t1 = MIPS_R_T6;
+++			u8 t2 = MIPS_R_T7;
 ++
-++	dump_wed_regs(s, dev, regs, ARRAY_SIZE(regs));
+++			emit(ctx, dsll32, t2, dst, 0);  /* t2 = dst << 32    */
+++			emit(ctx, dsrl32, dst, dst, 0); /* dst = dst >> 32   */
+++			emit(ctx, or, dst, dst, t2);    /* dst = dst | t2    */
 ++
-++	return 0;
-++}
-++DEFINE_SHOW_ATTRIBUTE(wed_txinfo);
+++			emit(ctx, ori, t2, MIPS_R_ZERO, 0xffff);
+++			emit(ctx, dsll32, t1, t2, 0);   /* t1 = t2 << 32     */
+++			emit(ctx, or, t1, t1, t2);      /* t1 = t1 | t2      */
+++			emit_swap_r64(ctx, dst, t1, 16);/* dst = swap16(dst) */
 ++
+++			emit(ctx, lui, t2, 0xff);       /* t2 = 0x00ff0000   */
+++			emit(ctx, ori, t2, t2, 0xff);   /* t2 = t2 | 0x00ff  */
+++			emit(ctx, dsll32, t1, t2, 0);   /* t1 = t2 << 32     */
+++			emit(ctx, or, t1, t1, t2);      /* t1 = t1 | t2      */
+++			emit_swap_r64(ctx, dst, t1, 8); /* dst = swap8(dst)  */
+++		}
+++		break;
+++	/* Swap bytes in a half word */
+++	/* Swap bytes in a word */
+++	case 32:
+++	case 16:
+++		emit_sext(ctx, dst, dst);
+++		emit_bswap_r(ctx, dst, width);
+++		if (cpu_has_mips64r2 || cpu_has_mips64r6)
+++			emit_zext(ctx, dst);
+++		break;
+++	}
+++	clobber_reg(ctx, dst);
+++}
 ++
-++static int
-++mtk_wed_reg_set(void *data, u64 val)
+++/* Truncate a register double word, word or half word */
+++static void emit_trunc_r64(struct jit_context *ctx, u8 dst, u32 width)
 ++{
-++	struct mtk_wed_hw *hw = data;
-++
-++	regmap_write(hw->regs, hw->debugfs_reg, val);
-++
-++	return 0;
+++	switch (width) {
+++	case 64:
+++		break;
+++	/* Zero-extend a word */
+++	case 32:
+++		emit_zext(ctx, dst);
+++		break;
+++	/* Zero-extend a half word */
+++	case 16:
+++		emit(ctx, andi, dst, dst, 0xffff);
+++		break;
+++	}
+++	clobber_reg(ctx, dst);
 ++}
 ++
-++static int
-++mtk_wed_reg_get(void *data, u64 *val)
+++/* Load operation: dst = *(size*)(src + off) */
+++static void emit_ldx(struct jit_context *ctx, u8 dst, u8 src, s16 off, u8 size)
 ++{
-++	struct mtk_wed_hw *hw = data;
-++	unsigned int regval;
-++	int ret;
+++	switch (size) {
+++	/* Load a byte */
+++	case BPF_B:
+++		emit(ctx, lbu, dst, off, src);
+++		break;
+++	/* Load a half word */
+++	case BPF_H:
+++		emit(ctx, lhu, dst, off, src);
+++		break;
+++	/* Load a word */
+++	case BPF_W:
+++		emit(ctx, lwu, dst, off, src);
+++		break;
+++	/* Load a double word */
+++	case BPF_DW:
+++		emit(ctx, ld, dst, off, src);
+++		break;
+++	}
+++	clobber_reg(ctx, dst);
+++}
 ++
-++	ret = regmap_read(hw->regs, hw->debugfs_reg, &regval);
-++	if (ret)
-++		return ret;
+++/* Store operation: *(size *)(dst + off) = src */
+++static void emit_stx(struct jit_context *ctx, u8 dst, u8 src, s16 off, u8 size)
+++{
+++	switch (size) {
+++	/* Store a byte */
+++	case BPF_B:
+++		emit(ctx, sb, src, off, dst);
+++		break;
+++	/* Store a half word */
+++	case BPF_H:
+++		emit(ctx, sh, src, off, dst);
+++		break;
+++	/* Store a word */
+++	case BPF_W:
+++		emit(ctx, sw, src, off, dst);
+++		break;
+++	/* Store a double word */
+++	case BPF_DW:
+++		emit(ctx, sd, src, off, dst);
+++		break;
+++	}
+++}
 ++
-++	*val = regval;
+++/* Atomic read-modify-write */
+++static void emit_atomic_r64(struct jit_context *ctx,
+++			    u8 dst, u8 src, s16 off, u8 code)
+++{
+++	u8 t1 = MIPS_R_T6;
+++	u8 t2 = MIPS_R_T7;
 ++
-++	return 0;
+++	emit(ctx, lld, t1, off, dst);
+++	switch (code) {
+++	case BPF_ADD:
+++		emit(ctx, daddu, t2, t1, src);
+++		break;
+++	case BPF_AND:
+++		emit(ctx, and, t2, t1, src);
+++		break;
+++	case BPF_OR:
+++		emit(ctx, or, t2, t1, src);
+++		break;
+++	case BPF_XOR:
+++		emit(ctx, xor, t2, t1, src);
+++		break;
+++	}
+++	emit(ctx, scd, t2, off, dst);
+++	emit(ctx, beqz, t2, -16);
+++	emit(ctx, nop); /* Delay slot */
 ++}
 ++
-++DEFINE_DEBUGFS_ATTRIBUTE(fops_regval, mtk_wed_reg_get, mtk_wed_reg_set,
-++             "0x%08llx\n");
-++
-++void mtk_wed_hw_add_debugfs(struct mtk_wed_hw *hw)
+++/* Function call */
+++static int emit_call(struct jit_context *ctx, const struct bpf_insn *insn)
 ++{
-++	struct dentry *dir;
+++	u8 zx = bpf2mips64[JIT_REG_ZX];
+++	u8 tmp = MIPS_R_T6;
+++	bool fixed;
+++	u64 addr;
 ++
-++	snprintf(hw->dirname, sizeof(hw->dirname), "wed%d", hw->index);
-++	dir = debugfs_create_dir(hw->dirname, NULL);
-++	if (!dir)
-++		return;
+++	/* Decode the call address */
+++	if (bpf_jit_get_func_addr(ctx->program, insn, false,
+++				  &addr, &fixed) < 0)
+++		return -1;
+++	if (!fixed)
+++		return -1;
 ++
-++	hw->debugfs_dir = dir;
-++	debugfs_create_u32("regidx", 0600, dir, &hw->debugfs_reg);
-++	debugfs_create_file_unsafe("regval", 0600, dir, hw, &fops_regval);
-++	debugfs_create_file_unsafe("txinfo", 0400, dir, hw, &wed_txinfo_fops);
-++}
-+--- /dev/null
-++++ b/drivers/net/ethernet/mediatek/mtk_wed_ops.c
-+@@ -0,0 +1,8 @@
-++// SPDX-License-Identifier: GPL-2.0-only
-++/* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name> */
+++	/* Push caller-saved registers on stack */
+++	push_regs(ctx, ctx->clobbered & JIT_CALLER_REGS, 0, 0);
 ++
-++#include <linux/kernel.h>
-++#include <linux/soc/mediatek/mtk_wed.h>
+++	/* Emit function call */
+++	emit_mov_i64(ctx, tmp, addr);
+++	emit(ctx, jalr, MIPS_R_RA, tmp);
+++	emit(ctx, nop); /* Delay slot */
 ++
-++const struct mtk_wed_ops __rcu *mtk_soc_wed_ops;
-++EXPORT_SYMBOL_GPL(mtk_soc_wed_ops);
-+--- /dev/null
-++++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
-+@@ -0,0 +1,251 @@
-++// SPDX-License-Identifier: GPL-2.0-only
-++/* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name> */
+++	/* Restore caller-saved registers */
+++	pop_regs(ctx, ctx->clobbered & JIT_CALLER_REGS, 0, 0);
 ++
-++#ifndef __MTK_WED_REGS_H
-++#define __MTK_WED_REGS_H
+++	/* Re-initialize the JIT zero-extension register if accessed */
+++	if (ctx->accessed & BIT(JIT_REG_ZX)) {
+++		emit(ctx, daddiu, zx, MIPS_R_ZERO, -1);
+++		emit(ctx, dsrl32, zx, zx, 0);
+++	}
 ++
-++#define MTK_WDMA_DESC_CTRL_LEN1			GENMASK(14, 0)
-++#define MTK_WDMA_DESC_CTRL_LAST_SEG1		BIT(15)
-++#define MTK_WDMA_DESC_CTRL_BURST		BIT(16)
-++#define MTK_WDMA_DESC_CTRL_LEN0			GENMASK(29, 16)
-++#define MTK_WDMA_DESC_CTRL_LAST_SEG0		BIT(30)
-++#define MTK_WDMA_DESC_CTRL_DMA_DONE		BIT(31)
+++	clobber_reg(ctx, MIPS_R_RA);
+++	clobber_reg(ctx, MIPS_R_V0);
+++	clobber_reg(ctx, MIPS_R_V1);
+++	return 0;
+++}
 ++
-++struct mtk_wdma_desc {
-++	__le32 buf0;
-++	__le32 ctrl;
-++	__le32 buf1;
-++	__le32 info;
-++} __packed __aligned(4);
+++/* Function tail call */
+++static int emit_tail_call(struct jit_context *ctx)
+++{
+++	u8 ary = bpf2mips64[BPF_REG_2];
+++	u8 ind = bpf2mips64[BPF_REG_3];
+++	u8 tcc = bpf2mips64[JIT_REG_TC];
+++	u8 tmp = MIPS_R_T6;
+++	int off;
 ++
-++#define MTK_WED_RESET					0x008
-++#define MTK_WED_RESET_TX_BM				BIT(0)
-++#define MTK_WED_RESET_TX_FREE_AGENT			BIT(4)
-++#define MTK_WED_RESET_WPDMA_TX_DRV			BIT(8)
-++#define MTK_WED_RESET_WPDMA_RX_DRV			BIT(9)
-++#define MTK_WED_RESET_WPDMA_INT_AGENT			BIT(11)
-++#define MTK_WED_RESET_WED_TX_DMA			BIT(12)
-++#define MTK_WED_RESET_WDMA_RX_DRV			BIT(17)
-++#define MTK_WED_RESET_WDMA_INT_AGENT			BIT(19)
-++#define MTK_WED_RESET_WED				BIT(31)
+++	/*
+++	 * Tail call:
+++	 * eBPF R1 - function argument (context ptr), passed in a0-a1
+++	 * eBPF R2 - ptr to object with array of function entry points
+++	 * eBPF R3 - array index of function to be called
+++	 */
 ++
-++#define MTK_WED_CTRL					0x00c
-++#define MTK_WED_CTRL_WPDMA_INT_AGENT_EN			BIT(0)
-++#define MTK_WED_CTRL_WPDMA_INT_AGENT_BUSY		BIT(1)
-++#define MTK_WED_CTRL_WDMA_INT_AGENT_EN			BIT(2)
-++#define MTK_WED_CTRL_WDMA_INT_AGENT_BUSY		BIT(3)
-++#define MTK_WED_CTRL_WED_TX_BM_EN			BIT(8)
-++#define MTK_WED_CTRL_WED_TX_BM_BUSY			BIT(9)
-++#define MTK_WED_CTRL_WED_TX_FREE_AGENT_EN		BIT(10)
-++#define MTK_WED_CTRL_WED_TX_FREE_AGENT_BUSY		BIT(11)
-++#define MTK_WED_CTRL_RESERVE_EN				BIT(12)
-++#define MTK_WED_CTRL_RESERVE_BUSY			BIT(13)
-++#define MTK_WED_CTRL_FINAL_DIDX_READ			BIT(24)
-++#define MTK_WED_CTRL_MIB_READ_CLEAR			BIT(28)
-++
-++#define MTK_WED_EXT_INT_STATUS				0x020
-++#define MTK_WED_EXT_INT_STATUS_TF_LEN_ERR		BIT(0)
-++#define MTK_WED_EXT_INT_STATUS_TKID_WO_PYLD		BIT(1)
-++#define MTK_WED_EXT_INT_STATUS_TKID_TITO_INVALID	BIT(4)
-++#define MTK_WED_EXT_INT_STATUS_TX_FBUF_LO_TH		BIT(8)
-++#define MTK_WED_EXT_INT_STATUS_TX_FBUF_HI_TH		BIT(9)
-++#define MTK_WED_EXT_INT_STATUS_RX_FBUF_LO_TH		BIT(12)
-++#define MTK_WED_EXT_INT_STATUS_RX_FBUF_HI_TH		BIT(13)
-++#define MTK_WED_EXT_INT_STATUS_RX_DRV_R_RESP_ERR	BIT(16)
-++#define MTK_WED_EXT_INT_STATUS_RX_DRV_W_RESP_ERR	BIT(17)
-++#define MTK_WED_EXT_INT_STATUS_RX_DRV_COHERENT		BIT(18)
-++#define MTK_WED_EXT_INT_STATUS_RX_DRV_INIT_WDMA_EN	BIT(19)
-++#define MTK_WED_EXT_INT_STATUS_RX_DRV_BM_DMAD_COHERENT	BIT(20)
-++#define MTK_WED_EXT_INT_STATUS_TX_DRV_R_RESP_ERR	BIT(21)
-++#define MTK_WED_EXT_INT_STATUS_TX_DRV_W_RESP_ERR	BIT(22)
-++#define MTK_WED_EXT_INT_STATUS_RX_DRV_DMA_RECYCLE	BIT(24)
-++#define MTK_WED_EXT_INT_STATUS_ERROR_MASK		(MTK_WED_EXT_INT_STATUS_TF_LEN_ERR | \
-++							 MTK_WED_EXT_INT_STATUS_TKID_WO_PYLD | \
-++							 MTK_WED_EXT_INT_STATUS_TKID_TITO_INVALID | \
-++							 MTK_WED_EXT_INT_STATUS_RX_DRV_R_RESP_ERR | \
-++							 MTK_WED_EXT_INT_STATUS_RX_DRV_W_RESP_ERR | \
-++							 MTK_WED_EXT_INT_STATUS_RX_DRV_INIT_WDMA_EN | \
-++							 MTK_WED_EXT_INT_STATUS_TX_DRV_R_RESP_ERR | \
-++							 MTK_WED_EXT_INT_STATUS_TX_DRV_W_RESP_ERR)
-++
-++#define MTK_WED_EXT_INT_MASK				0x028
-++
-++#define MTK_WED_STATUS					0x060
-++#define MTK_WED_STATUS_TX				GENMASK(15, 8)
-++
-++#define MTK_WED_TX_BM_CTRL				0x080
-++#define MTK_WED_TX_BM_CTRL_VLD_GRP_NUM			GENMASK(6, 0)
-++#define MTK_WED_TX_BM_CTRL_RSV_GRP_NUM			GENMASK(22, 16)
-++#define MTK_WED_TX_BM_CTRL_PAUSE			BIT(28)
-++
-++#define MTK_WED_TX_BM_BASE				0x084
-++
-++#define MTK_WED_TX_BM_TKID				0x088
-++#define MTK_WED_TX_BM_TKID_START			GENMASK(15, 0)
-++#define MTK_WED_TX_BM_TKID_END				GENMASK(31, 16)
-++
-++#define MTK_WED_TX_BM_BUF_LEN				0x08c
-++
-++#define MTK_WED_TX_BM_INTF				0x09c
-++#define MTK_WED_TX_BM_INTF_TKID				GENMASK(15, 0)
-++#define MTK_WED_TX_BM_INTF_TKFIFO_FDEP			GENMASK(23, 16)
-++#define MTK_WED_TX_BM_INTF_TKID_VALID			BIT(28)
-++#define MTK_WED_TX_BM_INTF_TKID_READ			BIT(29)
-++
-++#define MTK_WED_TX_BM_DYN_THR				0x0a0
-++#define MTK_WED_TX_BM_DYN_THR_LO			GENMASK(6, 0)
-++#define MTK_WED_TX_BM_DYN_THR_HI			GENMASK(22, 16)
-++
-++#define MTK_WED_INT_STATUS				0x200
-++#define MTK_WED_INT_MASK				0x204
-++
-++#define MTK_WED_GLO_CFG					0x208
-++#define MTK_WED_GLO_CFG_TX_DMA_EN			BIT(0)
-++#define MTK_WED_GLO_CFG_TX_DMA_BUSY			BIT(1)
-++#define MTK_WED_GLO_CFG_RX_DMA_EN			BIT(2)
-++#define MTK_WED_GLO_CFG_RX_DMA_BUSY			BIT(3)
-++#define MTK_WED_GLO_CFG_RX_BT_SIZE			GENMASK(5, 4)
-++#define MTK_WED_GLO_CFG_TX_WB_DDONE			BIT(6)
-++#define MTK_WED_GLO_CFG_BIG_ENDIAN			BIT(7)
-++#define MTK_WED_GLO_CFG_DIS_BT_SIZE_ALIGN		BIT(8)
-++#define MTK_WED_GLO_CFG_TX_BT_SIZE_LO			BIT(9)
-++#define MTK_WED_GLO_CFG_MULTI_DMA_EN			GENMASK(11, 10)
-++#define MTK_WED_GLO_CFG_FIFO_LITTLE_ENDIAN		BIT(12)
-++#define MTK_WED_GLO_CFG_MI_DEPTH_RD			GENMASK(21, 13)
-++#define MTK_WED_GLO_CFG_TX_BT_SIZE_HI			GENMASK(23, 22)
-++#define MTK_WED_GLO_CFG_SW_RESET			BIT(24)
-++#define MTK_WED_GLO_CFG_FIRST_TOKEN_ONLY		BIT(26)
-++#define MTK_WED_GLO_CFG_OMIT_RX_INFO			BIT(27)
-++#define MTK_WED_GLO_CFG_OMIT_TX_INFO			BIT(28)
-++#define MTK_WED_GLO_CFG_BYTE_SWAP			BIT(29)
-++#define MTK_WED_GLO_CFG_RX_2B_OFFSET			BIT(31)
-++
-++#define MTK_WED_RESET_IDX				0x20c
-++#define MTK_WED_RESET_IDX_TX				GENMASK(3, 0)
-++#define MTK_WED_RESET_IDX_RX				GENMASK(17, 16)
-++
-++#define MTK_WED_TX_MIB(_n)				(0x2a0 + (_n) * 4)
-++
-++#define MTK_WED_RING_TX(_n)				(0x300 + (_n) * 0x10)
-++
-++#define MTK_WED_RING_RX(_n)				(0x400 + (_n) * 0x10)
-++
-++#define MTK_WED_WPDMA_INT_TRIGGER			0x504
-++#define MTK_WED_WPDMA_INT_TRIGGER_RX_DONE		BIT(1)
-++#define MTK_WED_WPDMA_INT_TRIGGER_TX_DONE		GENMASK(5, 4)
-++
-++#define MTK_WED_WPDMA_GLO_CFG				0x508
-++#define MTK_WED_WPDMA_GLO_CFG_TX_DRV_EN			BIT(0)
-++#define MTK_WED_WPDMA_GLO_CFG_TX_DRV_BUSY		BIT(1)
-++#define MTK_WED_WPDMA_GLO_CFG_RX_DRV_EN			BIT(2)
-++#define MTK_WED_WPDMA_GLO_CFG_RX_DRV_BUSY		BIT(3)
-++#define MTK_WED_WPDMA_GLO_CFG_RX_BT_SIZE		GENMASK(5, 4)
-++#define MTK_WED_WPDMA_GLO_CFG_TX_WB_DDONE		BIT(6)
-++#define MTK_WED_WPDMA_GLO_CFG_BIG_ENDIAN		BIT(7)
-++#define MTK_WED_WPDMA_GLO_CFG_DIS_BT_SIZE_ALIGN		BIT(8)
-++#define MTK_WED_WPDMA_GLO_CFG_TX_BT_SIZE_LO		BIT(9)
-++#define MTK_WED_WPDMA_GLO_CFG_MULTI_DMA_EN		GENMASK(11, 10)
-++#define MTK_WED_WPDMA_GLO_CFG_FIFO_LITTLE_ENDIAN	BIT(12)
-++#define MTK_WED_WPDMA_GLO_CFG_MI_DEPTH_RD		GENMASK(21, 13)
-++#define MTK_WED_WPDMA_GLO_CFG_TX_BT_SIZE_HI		GENMASK(23, 22)
-++#define MTK_WED_WPDMA_GLO_CFG_SW_RESET			BIT(24)
-++#define MTK_WED_WPDMA_GLO_CFG_FIRST_TOKEN_ONLY		BIT(26)
-++#define MTK_WED_WPDMA_GLO_CFG_OMIT_RX_INFO		BIT(27)
-++#define MTK_WED_WPDMA_GLO_CFG_OMIT_TX_INFO		BIT(28)
-++#define MTK_WED_WPDMA_GLO_CFG_BYTE_SWAP			BIT(29)
-++#define MTK_WED_WPDMA_GLO_CFG_RX_2B_OFFSET		BIT(31)
-++
-++#define MTK_WED_WPDMA_RESET_IDX				0x50c
-++#define MTK_WED_WPDMA_RESET_IDX_TX			GENMASK(3, 0)
-++#define MTK_WED_WPDMA_RESET_IDX_RX			GENMASK(17, 16)
-++
-++#define MTK_WED_WPDMA_INT_CTRL				0x520
-++#define MTK_WED_WPDMA_INT_CTRL_SUBRT_ADV		BIT(21)
-++
-++#define MTK_WED_WPDMA_INT_MASK				0x524
-++
-++#define MTK_WED_PCIE_CFG_BASE				0x560
-++
-++#define MTK_WED_PCIE_INT_TRIGGER			0x570
-++#define MTK_WED_PCIE_INT_TRIGGER_STATUS			BIT(16)
-++
-++#define MTK_WED_WPDMA_CFG_BASE				0x580
-++
-++#define MTK_WED_WPDMA_TX_MIB(_n)			(0x5a0 + (_n) * 4)
-++#define MTK_WED_WPDMA_TX_COHERENT_MIB(_n)		(0x5d0 + (_n) * 4)
-++
-++#define MTK_WED_WPDMA_RING_TX(_n)			(0x600 + (_n) * 0x10)
-++#define MTK_WED_WPDMA_RING_RX(_n)			(0x700 + (_n) * 0x10)
-++#define MTK_WED_WDMA_RING_RX(_n)			(0x900 + (_n) * 0x10)
-++#define MTK_WED_WDMA_RX_THRES(_n)			(0x940 + (_n) * 0x4)
-++
-++#define MTK_WED_WDMA_GLO_CFG				0xa04
-++#define MTK_WED_WDMA_GLO_CFG_TX_DRV_EN			BIT(0)
-++#define MTK_WED_WDMA_GLO_CFG_RX_DRV_EN			BIT(2)
-++#define MTK_WED_WDMA_GLO_CFG_RX_DRV_BUSY		BIT(3)
-++#define MTK_WED_WDMA_GLO_CFG_BT_SIZE			GENMASK(5, 4)
-++#define MTK_WED_WDMA_GLO_CFG_TX_WB_DDONE		BIT(6)
-++#define MTK_WED_WDMA_GLO_CFG_RX_DIS_FSM_AUTO_IDLE	BIT(13)
-++#define MTK_WED_WDMA_GLO_CFG_WCOMPLETE_SEL		BIT(16)
-++#define MTK_WED_WDMA_GLO_CFG_INIT_PHASE_RXDMA_BYPASS	BIT(17)
-++#define MTK_WED_WDMA_GLO_CFG_INIT_PHASE_BYPASS		BIT(18)
-++#define MTK_WED_WDMA_GLO_CFG_FSM_RETURN_IDLE		BIT(19)
-++#define MTK_WED_WDMA_GLO_CFG_WAIT_COHERENT		BIT(20)
-++#define MTK_WED_WDMA_GLO_CFG_AXI_W_AFTER_AW		BIT(21)
-++#define MTK_WED_WDMA_GLO_CFG_IDLE_DMAD_SUPPLY_SINGLE_W	BIT(22)
-++#define MTK_WED_WDMA_GLO_CFG_IDLE_DMAD_SUPPLY		BIT(23)
-++#define MTK_WED_WDMA_GLO_CFG_DYNAMIC_SKIP_DMAD_PREP	BIT(24)
-++#define MTK_WED_WDMA_GLO_CFG_DYNAMIC_DMAD_RECYCLE	BIT(25)
-++#define MTK_WED_WDMA_GLO_CFG_RST_INIT_COMPLETE		BIT(26)
-++#define MTK_WED_WDMA_GLO_CFG_RXDRV_CLKGATE_BYPASS	BIT(30)
-++
-++#define MTK_WED_WDMA_RESET_IDX				0xa08
-++#define MTK_WED_WDMA_RESET_IDX_RX			GENMASK(17, 16)
-++#define MTK_WED_WDMA_RESET_IDX_DRV			GENMASK(25, 24)
-++
-++#define MTK_WED_WDMA_INT_TRIGGER			0xa28
-++#define MTK_WED_WDMA_INT_TRIGGER_RX_DONE		GENMASK(17, 16)
-++
-++#define MTK_WED_WDMA_INT_CTRL				0xa2c
-++#define MTK_WED_WDMA_INT_CTRL_POLL_SRC_SEL		GENMASK(17, 16)
-++
-++#define MTK_WED_WDMA_OFFSET0				0xaa4
-++#define MTK_WED_WDMA_OFFSET1				0xaa8
-++
-++#define MTK_WED_WDMA_RX_MIB(_n)				(0xae0 + (_n) * 4)
-++#define MTK_WED_WDMA_RX_RECYCLE_MIB(_n)			(0xae8 + (_n) * 4)
-++#define MTK_WED_WDMA_RX_PROCESSED_MIB(_n)		(0xaf0 + (_n) * 4)
-++
-++#define MTK_WED_RING_OFS_BASE				0x00
-++#define MTK_WED_RING_OFS_COUNT				0x04
-++#define MTK_WED_RING_OFS_CPU_IDX			0x08
-++#define MTK_WED_RING_OFS_DMA_IDX			0x0c
-++
-++#define MTK_WDMA_RING_RX(_n)				(0x100 + (_n) * 0x10)
-++
-++#define MTK_WDMA_GLO_CFG				0x204
-++#define MTK_WDMA_GLO_CFG_RX_INFO_PRERES			GENMASK(28, 26)
-++
-++#define MTK_WDMA_RESET_IDX				0x208
-++#define MTK_WDMA_RESET_IDX_TX				GENMASK(3, 0)
-++#define MTK_WDMA_RESET_IDX_RX				GENMASK(17, 16)
-++
-++#define MTK_WDMA_INT_MASK				0x228
-++#define MTK_WDMA_INT_MASK_TX_DONE			GENMASK(3, 0)
-++#define MTK_WDMA_INT_MASK_RX_DONE			GENMASK(17, 16)
-++#define MTK_WDMA_INT_MASK_TX_DELAY			BIT(28)
-++#define MTK_WDMA_INT_MASK_TX_COHERENT			BIT(29)
-++#define MTK_WDMA_INT_MASK_RX_DELAY			BIT(30)
-++#define MTK_WDMA_INT_MASK_RX_COHERENT			BIT(31)
-++
-++#define MTK_WDMA_INT_GRP1				0x250
-++#define MTK_WDMA_INT_GRP2				0x254
-++
-++#define MTK_PCIE_MIRROR_MAP(n)				((n) ? 0x4 : 0x0)
-++#define MTK_PCIE_MIRROR_MAP_EN				BIT(0)
-++#define MTK_PCIE_MIRROR_MAP_WED_ID			BIT(1)
-++
-++/* DMA channel mapping */
-++#define HIFSYS_DMA_AG_MAP				0x008
-++
-++#endif
-+--- /dev/null
-++++ b/include/linux/soc/mediatek/mtk_wed.h
-+@@ -0,0 +1,131 @@
-++#ifndef __MTK_WED_H
-++#define __MTK_WED_H
+++	/* if (ind >= ary->map.max_entries) goto out */
+++	off = offsetof(struct bpf_array, map.max_entries);
+++	if (off > 0x7fff)
+++		return -1;
+++	emit(ctx, lwu, tmp, off, ary);            /* tmp = ary->map.max_entrs*/
+++	emit(ctx, sltu, tmp, ind, tmp);           /* tmp = ind < t1          */
+++	emit(ctx, beqz, tmp, get_offset(ctx, 1)); /* PC += off(1) if tmp == 0*/
 ++
-++#include <linux/kernel.h>
-++#include <linux/rcupdate.h>
-++#include <linux/regmap.h>
-++#include <linux/pci.h>
+++	/* if (--TCC < 0) goto out */
+++	emit(ctx, daddiu, tcc, tcc, -1);          /* tcc-- (delay slot)      */
+++	emit(ctx, bltz, tcc, get_offset(ctx, 1)); /* PC += off(1) if tcc < 0 */
+++						  /* (next insn delay slot)  */
+++	/* prog = ary->ptrs[ind] */
+++	off = offsetof(struct bpf_array, ptrs);
+++	if (off > 0x7fff)
+++		return -1;
+++	emit(ctx, dsll, tmp, ind, 3);             /* tmp = ind << 3          */
+++	emit(ctx, daddu, tmp, tmp, ary);          /* tmp += ary              */
+++	emit(ctx, ld, tmp, off, tmp);             /* tmp = *(tmp + off)      */
 ++
-++#define MTK_WED_TX_QUEUES		2
+++	/* if (prog == 0) goto out */
+++	emit(ctx, beqz, tmp, get_offset(ctx, 1)); /* PC += off(1) if tmp == 0*/
+++	emit(ctx, nop);                           /* Delay slot              */
 ++
-++struct mtk_wed_hw;
-++struct mtk_wdma_desc;
+++	/* func = prog->bpf_func + 8 (prologue skip offset) */
+++	off = offsetof(struct bpf_prog, bpf_func);
+++	if (off > 0x7fff)
+++		return -1;
+++	emit(ctx, ld, tmp, off, tmp);                /* tmp = *(tmp + off)   */
+++	emit(ctx, daddiu, tmp, tmp, JIT_TCALL_SKIP); /* tmp += skip (4)      */
 ++
-++struct mtk_wed_ring {
-++	struct mtk_wdma_desc *desc;
-++	dma_addr_t desc_phys;
-++	int size;
+++	/* goto func */
+++	build_epilogue(ctx, tmp);
+++	access_reg(ctx, JIT_REG_TC);
+++	return 0;
+++}
 ++
-++	u32 reg_base;
-++	void __iomem *wpdma;
-++};
+++/*
+++ * Stack frame layout for a JITed program (stack grows down).
+++ *
+++ * Higher address  : Previous stack frame      :
+++ *                 +===========================+  <--- MIPS sp before call
+++ *                 | Callee-saved registers,   |
+++ *                 | including RA and FP       |
+++ *                 +---------------------------+  <--- eBPF FP (MIPS fp)
+++ *                 | Local eBPF variables      |
+++ *                 | allocated by program      |
+++ *                 +---------------------------+
+++ *                 | Reserved for caller-saved |
+++ *                 | registers                 |
+++ * Lower address   +===========================+  <--- MIPS sp
+++ */
 ++
-++struct mtk_wed_device {
-++#ifdef CONFIG_NET_MEDIATEK_SOC_WED
-++	const struct mtk_wed_ops *ops;
-++	struct device *dev;
-++	struct mtk_wed_hw *hw;
-++	bool init_done, running;
-++	int wdma_idx;
-++	int irq;
+++/* Build program prologue to set up the stack and registers */
+++void build_prologue(struct jit_context *ctx)
+++{
+++	u8 fp = bpf2mips64[BPF_REG_FP];
+++	u8 tc = bpf2mips64[JIT_REG_TC];
+++	u8 zx = bpf2mips64[JIT_REG_ZX];
+++	int stack, saved, locals, reserved;
 ++
-++	struct mtk_wed_ring tx_ring[MTK_WED_TX_QUEUES];
-++	struct mtk_wed_ring txfree_ring;
-++	struct mtk_wed_ring tx_wdma[MTK_WED_TX_QUEUES];
+++	/*
+++	 * The first instruction initializes the tail call count register.
+++	 * On a tail call, the calling function jumps into the prologue
+++	 * after this instruction.
+++	 */
+++	emit(ctx, addiu, tc, MIPS_R_ZERO, min(MAX_TAIL_CALL_CNT + 1, 0xffff));
 ++
-++	struct {
-++		int size;
-++		void **pages;
-++		struct mtk_wdma_desc *desc;
-++		dma_addr_t desc_phys;
-++	} buf_ring;
+++	/* === Entry-point for tail calls === */
 ++
-++	/* filled by driver: */
-++	struct {
-++		struct pci_dev *pci_dev;
+++	/*
+++	 * If the eBPF frame pointer and tail call count registers were
+++	 * accessed they must be preserved. Mark them as clobbered here
+++	 * to save and restore them on the stack as needed.
+++	 */
+++	if (ctx->accessed & BIT(BPF_REG_FP))
+++		clobber_reg(ctx, fp);
+++	if (ctx->accessed & BIT(JIT_REG_TC))
+++		clobber_reg(ctx, tc);
+++	if (ctx->accessed & BIT(JIT_REG_ZX))
+++		clobber_reg(ctx, zx);
 ++
-++		u32 wpdma_phys;
+++	/* Compute the stack space needed for callee-saved registers */
+++	saved = hweight32(ctx->clobbered & JIT_CALLEE_REGS) * sizeof(u64);
+++	saved = ALIGN(saved, MIPS_STACK_ALIGNMENT);
 ++
-++		u16 token_start;
-++		unsigned int nbuf;
+++	/* Stack space used by eBPF program local data */
+++	locals = ALIGN(ctx->program->aux->stack_depth, MIPS_STACK_ALIGNMENT);
 ++
-++		u32 (*init_buf)(void *ptr, dma_addr_t phys, int token_id);
-++		int (*offload_enable)(struct mtk_wed_device *wed);
-++		void (*offload_disable)(struct mtk_wed_device *wed);
-++	} wlan;
-++#endif
-++};
+++	/*
+++	 * If we are emitting function calls, reserve extra stack space for
+++	 * caller-saved registers needed by the JIT. The required space is
+++	 * computed automatically during resource usage discovery (pass 1).
+++	 */
+++	reserved = ctx->stack_used;
 ++
-++struct mtk_wed_ops {
-++	int (*attach)(struct mtk_wed_device *dev);
-++	int (*tx_ring_setup)(struct mtk_wed_device *dev, int ring,
-++			     void __iomem *regs);
-++	int (*txfree_ring_setup)(struct mtk_wed_device *dev,
-++				 void __iomem *regs);
-++	void (*detach)(struct mtk_wed_device *dev);
+++	/* Allocate the stack frame */
+++	stack = ALIGN(saved + locals + reserved, MIPS_STACK_ALIGNMENT);
+++	if (stack)
+++		emit(ctx, daddiu, MIPS_R_SP, MIPS_R_SP, -stack);
 ++
-++	void (*stop)(struct mtk_wed_device *dev);
-++	void (*start)(struct mtk_wed_device *dev, u32 irq_mask);
-++	void (*reset_dma)(struct mtk_wed_device *dev);
+++	/* Store callee-saved registers on stack */
+++	push_regs(ctx, ctx->clobbered & JIT_CALLEE_REGS, 0, stack - saved);
 ++
-++	u32 (*reg_read)(struct mtk_wed_device *dev, u32 reg);
-++	void (*reg_write)(struct mtk_wed_device *dev, u32 reg, u32 val);
+++	/* Initialize the eBPF frame pointer if accessed */
+++	if (ctx->accessed & BIT(BPF_REG_FP))
+++		emit(ctx, daddiu, fp, MIPS_R_SP, stack - saved);
 ++
-++	u32 (*irq_get)(struct mtk_wed_device *dev, u32 mask);
-++	void (*irq_set_mask)(struct mtk_wed_device *dev, u32 mask);
-++};
+++	/* Initialize the ePF JIT zero-extension register if accessed */
+++	if (ctx->accessed & BIT(JIT_REG_ZX)) {
+++		emit(ctx, daddiu, zx, MIPS_R_ZERO, -1);
+++		emit(ctx, dsrl32, zx, zx, 0);
+++	}
 ++
-++extern const struct mtk_wed_ops __rcu *mtk_soc_wed_ops;
+++	ctx->saved_size = saved;
+++	ctx->stack_size = stack;
+++}
 ++
-++static inline int
-++mtk_wed_device_attach(struct mtk_wed_device *dev)
+++/* Build the program epilogue to restore the stack and registers */
+++void build_epilogue(struct jit_context *ctx, int dest_reg)
 ++{
-++	int ret = -ENODEV;
-++
-++#ifdef CONFIG_NET_MEDIATEK_SOC_WED
-++	rcu_read_lock();
-++	dev->ops = rcu_dereference(mtk_soc_wed_ops);
-++	if (dev->ops)
-++		ret = dev->ops->attach(dev);
-++	else
-++		rcu_read_unlock();
-++
-++	if (ret)
-++		dev->ops = NULL;
-++#endif
+++	/* Restore callee-saved registers from stack */
+++	pop_regs(ctx, ctx->clobbered & JIT_CALLEE_REGS, 0,
+++		 ctx->stack_size - ctx->saved_size);
 ++
-++	return ret;
-++}
+++	/* Release the stack frame */
+++	if (ctx->stack_size)
+++		emit(ctx, daddiu, MIPS_R_SP, MIPS_R_SP, ctx->stack_size);
 ++
-++#ifdef CONFIG_NET_MEDIATEK_SOC_WED
-++#define mtk_wed_device_active(_dev) !!(_dev)->ops
-++#define mtk_wed_device_detach(_dev) (_dev)->ops->detach(_dev)
-++#define mtk_wed_device_start(_dev, _mask) (_dev)->ops->start(_dev, _mask)
-++#define mtk_wed_device_tx_ring_setup(_dev, _ring, _regs) \
-++	(_dev)->ops->tx_ring_setup(_dev, _ring, _regs)
-++#define mtk_wed_device_txfree_ring_setup(_dev, _regs) \
-++	(_dev)->ops->txfree_ring_setup(_dev, _regs)
-++#define mtk_wed_device_reg_read(_dev, _reg) \
-++	(_dev)->ops->reg_read(_dev, _reg)
-++#define mtk_wed_device_reg_write(_dev, _reg, _val) \
-++	(_dev)->ops->reg_write(_dev, _reg, _val)
-++#define mtk_wed_device_irq_get(_dev, _mask) \
-++	(_dev)->ops->irq_get(_dev, _mask)
-++#define mtk_wed_device_irq_set_mask(_dev, _mask) \
-++	(_dev)->ops->irq_set_mask(_dev, _mask)
-++#else
-++static inline bool mtk_wed_device_active(struct mtk_wed_device *dev)
-++{
-++	return false;
+++	/* Jump to return address and sign-extend the 32-bit return value */
+++	emit(ctx, jr, dest_reg);
+++	emit(ctx, sll, MIPS_R_V0, MIPS_R_V0, 0); /* Delay slot */
 ++}
-++#define mtk_wed_device_detach(_dev) do {} while (0)
-++#define mtk_wed_device_start(_dev, _mask) do {} while (0)
-++#define mtk_wed_device_tx_ring_setup(_dev, _ring, _regs) -ENODEV
-++#define mtk_wed_device_txfree_ring_setup(_dev, _ring, _regs) -ENODEV
-++#define mtk_wed_device_reg_read(_dev, _reg) 0
-++#define mtk_wed_device_reg_write(_dev, _reg, _val) do {} while (0)
-++#define mtk_wed_device_irq_get(_dev, _mask) 0
-++#define mtk_wed_device_irq_set_mask(_dev, _mask) do {} while (0)
-++#endif
 ++
-++#endif
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-03-net-ethernet-mtk_eth_soc-implement-flow-offloading-t.patch b/target/linux/generic/backport-5.15/702-v5.19-03-net-ethernet-mtk_eth_soc-implement-flow-offloading-t.patch
-new file mode 100644
-index 0000000000..28b3252104
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-03-net-ethernet-mtk_eth_soc-implement-flow-offloading-t.patch
-@@ -0,0 +1,269 @@
-+From: Felix Fietkau <nbd@nbd.name>
-+Date: Sat, 5 Feb 2022 18:29:22 +0100
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: implement flow offloading
-+ to WED devices
-+
-+This allows hardware flow offloading from Ethernet to WLAN on MT7622 SoC
-+
-+Co-developed-by: Lorenzo Bianconi <lorenzo@kernel.org>
-+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
-+Signed-off-by: Felix Fietkau <nbd@nbd.name>
-+---
-+
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
-+@@ -329,6 +329,24 @@ int mtk_foe_entry_set_pppoe(struct mtk_f
-+ 	return 0;
-+ }
-+ 
-++int mtk_foe_entry_set_wdma(struct mtk_foe_entry *entry, int wdma_idx, int txq,
-++			   int bss, int wcid)
+++/* Build one eBPF instruction */
+++int build_insn(const struct bpf_insn *insn, struct jit_context *ctx)
 ++{
-++	struct mtk_foe_mac_info *l2 = mtk_foe_entry_l2(entry);
-++	u32 *ib2 = mtk_foe_entry_ib2(entry);
-++
-++	*ib2 &= ~MTK_FOE_IB2_PORT_MG;
-++	*ib2 |= MTK_FOE_IB2_WDMA_WINFO;
-++	if (wdma_idx)
-++		*ib2 |= MTK_FOE_IB2_WDMA_DEVIDX;
-++
-++	l2->vlan2 = FIELD_PREP(MTK_FOE_VLAN2_WINFO_BSS, bss) |
-++		    FIELD_PREP(MTK_FOE_VLAN2_WINFO_WCID, wcid) |
-++		    FIELD_PREP(MTK_FOE_VLAN2_WINFO_RING, txq);
-++
-++	return 0;
-++}
+++	u8 dst = bpf2mips64[insn->dst_reg];
+++	u8 src = bpf2mips64[insn->src_reg];
+++	u8 code = insn->code;
+++	s16 off = insn->off;
+++	s32 imm = insn->imm;
+++	s32 val, rel;
+++	u8 alu, jmp;
 ++
-+ static inline bool mtk_foe_entry_usable(struct mtk_foe_entry *entry)
-+ {
-+ 	return !(entry->ib1 & MTK_FOE_IB1_STATIC) &&
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
-+@@ -48,9 +48,9 @@ enum {
-+ #define MTK_FOE_IB2_DEST_PORT		GENMASK(7, 5)
-+ #define MTK_FOE_IB2_MULTICAST		BIT(8)
-+ 
-+-#define MTK_FOE_IB2_WHNAT_QID2		GENMASK(13, 12)
-+-#define MTK_FOE_IB2_WHNAT_DEVIDX	BIT(16)
-+-#define MTK_FOE_IB2_WHNAT_NAT		BIT(17)
-++#define MTK_FOE_IB2_WDMA_QID2		GENMASK(13, 12)
-++#define MTK_FOE_IB2_WDMA_DEVIDX		BIT(16)
-++#define MTK_FOE_IB2_WDMA_WINFO		BIT(17)
-+ 
-+ #define MTK_FOE_IB2_PORT_MG		GENMASK(17, 12)
-+ 
-+@@ -58,9 +58,9 @@ enum {
-+ 
-+ #define MTK_FOE_IB2_DSCP		GENMASK(31, 24)
-+ 
-+-#define MTK_FOE_VLAN2_WHNAT_BSS		GEMMASK(5, 0)
-+-#define MTK_FOE_VLAN2_WHNAT_WCID	GENMASK(13, 6)
-+-#define MTK_FOE_VLAN2_WHNAT_RING	GENMASK(15, 14)
-++#define MTK_FOE_VLAN2_WINFO_BSS		GENMASK(5, 0)
-++#define MTK_FOE_VLAN2_WINFO_WCID	GENMASK(13, 6)
-++#define MTK_FOE_VLAN2_WINFO_RING	GENMASK(15, 14)
-+ 
-+ enum {
-+ 	MTK_FOE_STATE_INVALID,
-+@@ -281,6 +281,8 @@ int mtk_foe_entry_set_ipv6_tuple(struct
-+ int mtk_foe_entry_set_dsa(struct mtk_foe_entry *entry, int port);
-+ int mtk_foe_entry_set_vlan(struct mtk_foe_entry *entry, int vid);
-+ int mtk_foe_entry_set_pppoe(struct mtk_foe_entry *entry, int sid);
-++int mtk_foe_entry_set_wdma(struct mtk_foe_entry *entry, int wdma_idx, int txq,
-++			   int bss, int wcid);
-+ int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_foe_entry *entry,
-+ 			 u16 timestamp);
-+ int mtk_ppe_debugfs_init(struct mtk_ppe *ppe);
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
-+@@ -10,6 +10,7 @@
-+ #include <net/pkt_cls.h>
-+ #include <net/dsa.h>
-+ #include "mtk_eth_soc.h"
-++#include "mtk_wed.h"
-+ 
-+ struct mtk_flow_data {
-+ 	struct ethhdr eth;
-+@@ -39,6 +40,7 @@ struct mtk_flow_entry {
-+ 	struct rhash_head node;
-+ 	unsigned long cookie;
-+ 	u16 hash;
-++	s8 wed_index;
-+ };
-+ 
-+ static const struct rhashtable_params mtk_flow_ht_params = {
-+@@ -80,6 +82,35 @@ mtk_flow_offload_mangle_eth(const struct
-+ 	memcpy(dest, src, act->mangle.mask ? 2 : 4);
-+ }
-+ 
-++static int
-++mtk_flow_get_wdma_info(struct net_device *dev, const u8 *addr, struct mtk_wdma_info *info)
-++{
-++	struct net_device_path_ctx ctx = {
-++		.dev = dev,
-++		.daddr = addr,
-++	};
-++	struct net_device_path path = {};
-++
-++	if (!IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED))
-++		return -1;
-++
-++	if (!dev->netdev_ops->ndo_fill_forward_path)
-++		return -1;
-++
-++	if (dev->netdev_ops->ndo_fill_forward_path(&ctx, &path))
-++		return -1;
-++
-++	if (path.type != DEV_PATH_MTK_WDMA)
-++		return -1;
-++
-++	info->wdma_idx = path.mtk_wdma.wdma_idx;
-++	info->queue = path.mtk_wdma.queue;
-++	info->bss = path.mtk_wdma.bss;
-++	info->wcid = path.mtk_wdma.wcid;
+++	switch (code) {
+++	/* ALU operations */
+++	/* dst = imm */
+++	case BPF_ALU | BPF_MOV | BPF_K:
+++		emit_mov_i(ctx, dst, imm);
+++		emit_zext_ver(ctx, dst);
+++		break;
+++	/* dst = src */
+++	case BPF_ALU | BPF_MOV | BPF_X:
+++		if (imm == 1) {
+++			/* Special mov32 for zext */
+++			emit_zext(ctx, dst);
+++		} else {
+++			emit_mov_r(ctx, dst, src);
+++			emit_zext_ver(ctx, dst);
+++		}
+++		break;
+++	/* dst = -dst */
+++	case BPF_ALU | BPF_NEG:
+++		emit_sext(ctx, dst, dst);
+++		emit_alu_i(ctx, dst, 0, BPF_NEG);
+++		emit_zext_ver(ctx, dst);
+++		break;
+++	/* dst = dst & imm */
+++	/* dst = dst | imm */
+++	/* dst = dst ^ imm */
+++	/* dst = dst << imm */
+++	case BPF_ALU | BPF_OR | BPF_K:
+++	case BPF_ALU | BPF_AND | BPF_K:
+++	case BPF_ALU | BPF_XOR | BPF_K:
+++	case BPF_ALU | BPF_LSH | BPF_K:
+++		if (!valid_alu_i(BPF_OP(code), imm)) {
+++			emit_mov_i(ctx, MIPS_R_T4, imm);
+++			emit_alu_r(ctx, dst, MIPS_R_T4, BPF_OP(code));
+++		} else if (rewrite_alu_i(BPF_OP(code), imm, &alu, &val)) {
+++			emit_alu_i(ctx, dst, val, alu);
+++		}
+++		emit_zext_ver(ctx, dst);
+++		break;
+++	/* dst = dst >> imm */
+++	/* dst = dst >> imm (arithmetic) */
+++	/* dst = dst + imm */
+++	/* dst = dst - imm */
+++	/* dst = dst * imm */
+++	/* dst = dst / imm */
+++	/* dst = dst % imm */
+++	case BPF_ALU | BPF_RSH | BPF_K:
+++	case BPF_ALU | BPF_ARSH | BPF_K:
+++	case BPF_ALU | BPF_ADD | BPF_K:
+++	case BPF_ALU | BPF_SUB | BPF_K:
+++	case BPF_ALU | BPF_MUL | BPF_K:
+++	case BPF_ALU | BPF_DIV | BPF_K:
+++	case BPF_ALU | BPF_MOD | BPF_K:
+++		if (!valid_alu_i(BPF_OP(code), imm)) {
+++			emit_sext(ctx, dst, dst);
+++			emit_mov_i(ctx, MIPS_R_T4, imm);
+++			emit_alu_r(ctx, dst, MIPS_R_T4, BPF_OP(code));
+++		} else if (rewrite_alu_i(BPF_OP(code), imm, &alu, &val)) {
+++			emit_sext(ctx, dst, dst);
+++			emit_alu_i(ctx, dst, val, alu);
+++		}
+++		emit_zext_ver(ctx, dst);
+++		break;
+++	/* dst = dst & src */
+++	/* dst = dst | src */
+++	/* dst = dst ^ src */
+++	/* dst = dst << src */
+++	case BPF_ALU | BPF_AND | BPF_X:
+++	case BPF_ALU | BPF_OR | BPF_X:
+++	case BPF_ALU | BPF_XOR | BPF_X:
+++	case BPF_ALU | BPF_LSH | BPF_X:
+++		emit_alu_r(ctx, dst, src, BPF_OP(code));
+++		emit_zext_ver(ctx, dst);
+++		break;
+++	/* dst = dst >> src */
+++	/* dst = dst >> src (arithmetic) */
+++	/* dst = dst + src */
+++	/* dst = dst - src */
+++	/* dst = dst * src */
+++	/* dst = dst / src */
+++	/* dst = dst % src */
+++	case BPF_ALU | BPF_RSH | BPF_X:
+++	case BPF_ALU | BPF_ARSH | BPF_X:
+++	case BPF_ALU | BPF_ADD | BPF_X:
+++	case BPF_ALU | BPF_SUB | BPF_X:
+++	case BPF_ALU | BPF_MUL | BPF_X:
+++	case BPF_ALU | BPF_DIV | BPF_X:
+++	case BPF_ALU | BPF_MOD | BPF_X:
+++		emit_sext(ctx, dst, dst);
+++		emit_sext(ctx, MIPS_R_T4, src);
+++		emit_alu_r(ctx, dst, MIPS_R_T4, BPF_OP(code));
+++		emit_zext_ver(ctx, dst);
+++		break;
+++	/* dst = imm (64-bit) */
+++	case BPF_ALU64 | BPF_MOV | BPF_K:
+++		emit_mov_i(ctx, dst, imm);
+++		break;
+++	/* dst = src (64-bit) */
+++	case BPF_ALU64 | BPF_MOV | BPF_X:
+++		emit_mov_r(ctx, dst, src);
+++		break;
+++	/* dst = -dst (64-bit) */
+++	case BPF_ALU64 | BPF_NEG:
+++		emit_alu_i64(ctx, dst, 0, BPF_NEG);
+++		break;
+++	/* dst = dst & imm (64-bit) */
+++	/* dst = dst | imm (64-bit) */
+++	/* dst = dst ^ imm (64-bit) */
+++	/* dst = dst << imm (64-bit) */
+++	/* dst = dst >> imm (64-bit) */
+++	/* dst = dst >> imm ((64-bit, arithmetic) */
+++	/* dst = dst + imm (64-bit) */
+++	/* dst = dst - imm (64-bit) */
+++	/* dst = dst * imm (64-bit) */
+++	/* dst = dst / imm (64-bit) */
+++	/* dst = dst % imm (64-bit) */
+++	case BPF_ALU64 | BPF_AND | BPF_K:
+++	case BPF_ALU64 | BPF_OR | BPF_K:
+++	case BPF_ALU64 | BPF_XOR | BPF_K:
+++	case BPF_ALU64 | BPF_LSH | BPF_K:
+++	case BPF_ALU64 | BPF_RSH | BPF_K:
+++	case BPF_ALU64 | BPF_ARSH | BPF_K:
+++	case BPF_ALU64 | BPF_ADD | BPF_K:
+++	case BPF_ALU64 | BPF_SUB | BPF_K:
+++	case BPF_ALU64 | BPF_MUL | BPF_K:
+++	case BPF_ALU64 | BPF_DIV | BPF_K:
+++	case BPF_ALU64 | BPF_MOD | BPF_K:
+++		if (!valid_alu_i(BPF_OP(code), imm)) {
+++			emit_mov_i(ctx, MIPS_R_T4, imm);
+++			emit_alu_r64(ctx, dst, MIPS_R_T4, BPF_OP(code));
+++		} else if (rewrite_alu_i(BPF_OP(code), imm, &alu, &val)) {
+++			emit_alu_i64(ctx, dst, val, alu);
+++		}
+++		break;
+++	/* dst = dst & src (64-bit) */
+++	/* dst = dst | src (64-bit) */
+++	/* dst = dst ^ src (64-bit) */
+++	/* dst = dst << src (64-bit) */
+++	/* dst = dst >> src (64-bit) */
+++	/* dst = dst >> src (64-bit, arithmetic) */
+++	/* dst = dst + src (64-bit) */
+++	/* dst = dst - src (64-bit) */
+++	/* dst = dst * src (64-bit) */
+++	/* dst = dst / src (64-bit) */
+++	/* dst = dst % src (64-bit) */
+++	case BPF_ALU64 | BPF_AND | BPF_X:
+++	case BPF_ALU64 | BPF_OR | BPF_X:
+++	case BPF_ALU64 | BPF_XOR | BPF_X:
+++	case BPF_ALU64 | BPF_LSH | BPF_X:
+++	case BPF_ALU64 | BPF_RSH | BPF_X:
+++	case BPF_ALU64 | BPF_ARSH | BPF_X:
+++	case BPF_ALU64 | BPF_ADD | BPF_X:
+++	case BPF_ALU64 | BPF_SUB | BPF_X:
+++	case BPF_ALU64 | BPF_MUL | BPF_X:
+++	case BPF_ALU64 | BPF_DIV | BPF_X:
+++	case BPF_ALU64 | BPF_MOD | BPF_X:
+++		emit_alu_r64(ctx, dst, src, BPF_OP(code));
+++		break;
+++	/* dst = htole(dst) */
+++	/* dst = htobe(dst) */
+++	case BPF_ALU | BPF_END | BPF_FROM_LE:
+++	case BPF_ALU | BPF_END | BPF_FROM_BE:
+++		if (BPF_SRC(code) ==
+++#ifdef __BIG_ENDIAN
+++		    BPF_FROM_LE
+++#else
+++		    BPF_FROM_BE
+++#endif
+++		    )
+++			emit_bswap_r64(ctx, dst, imm);
+++		else
+++			emit_trunc_r64(ctx, dst, imm);
+++		break;
+++	/* dst = imm64 */
+++	case BPF_LD | BPF_IMM | BPF_DW:
+++		emit_mov_i64(ctx, dst, (u32)imm | ((u64)insn[1].imm << 32));
+++		return 1;
+++	/* LDX: dst = *(size *)(src + off) */
+++	case BPF_LDX | BPF_MEM | BPF_W:
+++	case BPF_LDX | BPF_MEM | BPF_H:
+++	case BPF_LDX | BPF_MEM | BPF_B:
+++	case BPF_LDX | BPF_MEM | BPF_DW:
+++		emit_ldx(ctx, dst, src, off, BPF_SIZE(code));
+++		break;
+++	/* ST: *(size *)(dst + off) = imm */
+++	case BPF_ST | BPF_MEM | BPF_W:
+++	case BPF_ST | BPF_MEM | BPF_H:
+++	case BPF_ST | BPF_MEM | BPF_B:
+++	case BPF_ST | BPF_MEM | BPF_DW:
+++		emit_mov_i(ctx, MIPS_R_T4, imm);
+++		emit_stx(ctx, dst, MIPS_R_T4, off, BPF_SIZE(code));
+++		break;
+++	/* STX: *(size *)(dst + off) = src */
+++	case BPF_STX | BPF_MEM | BPF_W:
+++	case BPF_STX | BPF_MEM | BPF_H:
+++	case BPF_STX | BPF_MEM | BPF_B:
+++	case BPF_STX | BPF_MEM | BPF_DW:
+++		emit_stx(ctx, dst, src, off, BPF_SIZE(code));
+++		break;
+++	/* Speculation barrier */
+++	case BPF_ST | BPF_NOSPEC:
+++		break;
+++	/* Atomics */
+++	case BPF_STX | BPF_XADD | BPF_W:
+++	case BPF_STX | BPF_XADD | BPF_DW:
+++		switch (imm) {
+++		case BPF_ADD:
+++		case BPF_AND:
+++		case BPF_OR:
+++		case BPF_XOR:
+++			if (BPF_SIZE(code) == BPF_DW) {
+++				emit_atomic_r64(ctx, dst, src, off, imm);
+++			} else { /* 32-bit, no fetch */
+++				emit_sext(ctx, MIPS_R_T4, src);
+++				emit_atomic_r(ctx, dst, MIPS_R_T4, off, imm);
+++			}
+++			break;
+++		default:
+++			goto notyet;
+++		}
+++		break;
+++	/* PC += off if dst == src */
+++	/* PC += off if dst != src */
+++	/* PC += off if dst & src */
+++	/* PC += off if dst > src */
+++	/* PC += off if dst >= src */
+++	/* PC += off if dst < src */
+++	/* PC += off if dst <= src */
+++	/* PC += off if dst > src (signed) */
+++	/* PC += off if dst >= src (signed) */
+++	/* PC += off if dst < src (signed) */
+++	/* PC += off if dst <= src (signed) */
+++	case BPF_JMP32 | BPF_JEQ | BPF_X:
+++	case BPF_JMP32 | BPF_JNE | BPF_X:
+++	case BPF_JMP32 | BPF_JSET | BPF_X:
+++	case BPF_JMP32 | BPF_JGT | BPF_X:
+++	case BPF_JMP32 | BPF_JGE | BPF_X:
+++	case BPF_JMP32 | BPF_JLT | BPF_X:
+++	case BPF_JMP32 | BPF_JLE | BPF_X:
+++	case BPF_JMP32 | BPF_JSGT | BPF_X:
+++	case BPF_JMP32 | BPF_JSGE | BPF_X:
+++	case BPF_JMP32 | BPF_JSLT | BPF_X:
+++	case BPF_JMP32 | BPF_JSLE | BPF_X:
+++		if (off == 0)
+++			break;
+++		setup_jmp_r(ctx, dst == src, BPF_OP(code), off, &jmp, &rel);
+++		emit_sext(ctx, MIPS_R_T4, dst); /* Sign-extended dst */
+++		emit_sext(ctx, MIPS_R_T5, src); /* Sign-extended src */
+++		emit_jmp_r(ctx, MIPS_R_T4, MIPS_R_T5, rel, jmp);
+++		if (finish_jmp(ctx, jmp, off) < 0)
+++			goto toofar;
+++		break;
+++	/* PC += off if dst == imm */
+++	/* PC += off if dst != imm */
+++	/* PC += off if dst & imm */
+++	/* PC += off if dst > imm */
+++	/* PC += off if dst >= imm */
+++	/* PC += off if dst < imm */
+++	/* PC += off if dst <= imm */
+++	/* PC += off if dst > imm (signed) */
+++	/* PC += off if dst >= imm (signed) */
+++	/* PC += off if dst < imm (signed) */
+++	/* PC += off if dst <= imm (signed) */
+++	case BPF_JMP32 | BPF_JEQ | BPF_K:
+++	case BPF_JMP32 | BPF_JNE | BPF_K:
+++	case BPF_JMP32 | BPF_JSET | BPF_K:
+++	case BPF_JMP32 | BPF_JGT | BPF_K:
+++	case BPF_JMP32 | BPF_JGE | BPF_K:
+++	case BPF_JMP32 | BPF_JLT | BPF_K:
+++	case BPF_JMP32 | BPF_JLE | BPF_K:
+++	case BPF_JMP32 | BPF_JSGT | BPF_K:
+++	case BPF_JMP32 | BPF_JSGE | BPF_K:
+++	case BPF_JMP32 | BPF_JSLT | BPF_K:
+++	case BPF_JMP32 | BPF_JSLE | BPF_K:
+++		if (off == 0)
+++			break;
+++		setup_jmp_i(ctx, imm, 32, BPF_OP(code), off, &jmp, &rel);
+++		emit_sext(ctx, MIPS_R_T4, dst); /* Sign-extended dst */
+++		if (valid_jmp_i(jmp, imm)) {
+++			emit_jmp_i(ctx, MIPS_R_T4, imm, rel, jmp);
+++		} else {
+++			/* Move large immediate to register, sign-extended */
+++			emit_mov_i(ctx, MIPS_R_T5, imm);
+++			emit_jmp_r(ctx, MIPS_R_T4, MIPS_R_T5, rel, jmp);
+++		}
+++		if (finish_jmp(ctx, jmp, off) < 0)
+++			goto toofar;
+++		break;
+++	/* PC += off if dst == src */
+++	/* PC += off if dst != src */
+++	/* PC += off if dst & src */
+++	/* PC += off if dst > src */
+++	/* PC += off if dst >= src */
+++	/* PC += off if dst < src */
+++	/* PC += off if dst <= src */
+++	/* PC += off if dst > src (signed) */
+++	/* PC += off if dst >= src (signed) */
+++	/* PC += off if dst < src (signed) */
+++	/* PC += off if dst <= src (signed) */
+++	case BPF_JMP | BPF_JEQ | BPF_X:
+++	case BPF_JMP | BPF_JNE | BPF_X:
+++	case BPF_JMP | BPF_JSET | BPF_X:
+++	case BPF_JMP | BPF_JGT | BPF_X:
+++	case BPF_JMP | BPF_JGE | BPF_X:
+++	case BPF_JMP | BPF_JLT | BPF_X:
+++	case BPF_JMP | BPF_JLE | BPF_X:
+++	case BPF_JMP | BPF_JSGT | BPF_X:
+++	case BPF_JMP | BPF_JSGE | BPF_X:
+++	case BPF_JMP | BPF_JSLT | BPF_X:
+++	case BPF_JMP | BPF_JSLE | BPF_X:
+++		if (off == 0)
+++			break;
+++		setup_jmp_r(ctx, dst == src, BPF_OP(code), off, &jmp, &rel);
+++		emit_jmp_r(ctx, dst, src, rel, jmp);
+++		if (finish_jmp(ctx, jmp, off) < 0)
+++			goto toofar;
+++		break;
+++	/* PC += off if dst == imm */
+++	/* PC += off if dst != imm */
+++	/* PC += off if dst & imm */
+++	/* PC += off if dst > imm */
+++	/* PC += off if dst >= imm */
+++	/* PC += off if dst < imm */
+++	/* PC += off if dst <= imm */
+++	/* PC += off if dst > imm (signed) */
+++	/* PC += off if dst >= imm (signed) */
+++	/* PC += off if dst < imm (signed) */
+++	/* PC += off if dst <= imm (signed) */
+++	case BPF_JMP | BPF_JEQ | BPF_K:
+++	case BPF_JMP | BPF_JNE | BPF_K:
+++	case BPF_JMP | BPF_JSET | BPF_K:
+++	case BPF_JMP | BPF_JGT | BPF_K:
+++	case BPF_JMP | BPF_JGE | BPF_K:
+++	case BPF_JMP | BPF_JLT | BPF_K:
+++	case BPF_JMP | BPF_JLE | BPF_K:
+++	case BPF_JMP | BPF_JSGT | BPF_K:
+++	case BPF_JMP | BPF_JSGE | BPF_K:
+++	case BPF_JMP | BPF_JSLT | BPF_K:
+++	case BPF_JMP | BPF_JSLE | BPF_K:
+++		if (off == 0)
+++			break;
+++		setup_jmp_i(ctx, imm, 64, BPF_OP(code), off, &jmp, &rel);
+++		if (valid_jmp_i(jmp, imm)) {
+++			emit_jmp_i(ctx, dst, imm, rel, jmp);
+++		} else {
+++			/* Move large immediate to register */
+++			emit_mov_i(ctx, MIPS_R_T4, imm);
+++			emit_jmp_r(ctx, dst, MIPS_R_T4, rel, jmp);
+++		}
+++		if (finish_jmp(ctx, jmp, off) < 0)
+++			goto toofar;
+++		break;
+++	/* PC += off */
+++	case BPF_JMP | BPF_JA:
+++		if (off == 0)
+++			break;
+++		if (emit_ja(ctx, off) < 0)
+++			goto toofar;
+++		break;
+++	/* Tail call */
+++	case BPF_JMP | BPF_TAIL_CALL:
+++		if (emit_tail_call(ctx) < 0)
+++			goto invalid;
+++		break;
+++	/* Function call */
+++	case BPF_JMP | BPF_CALL:
+++		if (emit_call(ctx, insn) < 0)
+++			goto invalid;
+++		break;
+++	/* Function return */
+++	case BPF_JMP | BPF_EXIT:
+++		/*
+++		 * Optimization: when last instruction is EXIT
+++		 * simply continue to epilogue.
+++		 */
+++		if (ctx->bpf_index == ctx->program->len - 1)
+++			break;
+++		if (emit_exit(ctx) < 0)
+++			goto toofar;
+++		break;
 ++
+++	default:
+++invalid:
+++		pr_err_once("unknown opcode %02x\n", code);
+++		return -EINVAL;
+++notyet:
+++		pr_info_once("*** NOT YET: opcode %02x ***\n", code);
+++		return -EFAULT;
+++toofar:
+++		pr_info_once("*** TOO FAR: jump at %u opcode %02x ***\n",
+++			     ctx->bpf_index, code);
+++		return -E2BIG;
+++	}
 ++	return 0;
 ++}
-++
-+ 
-+ static int
-+ mtk_flow_mangle_ports(const struct flow_action_entry *act,
-+@@ -149,10 +180,20 @@ mtk_flow_get_dsa_port(struct net_device
-+ 
-+ static int
-+ mtk_flow_set_output_device(struct mtk_eth *eth, struct mtk_foe_entry *foe,
-+-			   struct net_device *dev)
-++			   struct net_device *dev, const u8 *dest_mac,
-++			   int *wed_index)
+diff --git a/target/linux/generic/backport-5.15/050-v5.16-04-mips-bpf-Add-JIT-workarounds-for-CPU-errata.patch b/target/linux/generic/backport-5.15/050-v5.16-04-mips-bpf-Add-JIT-workarounds-for-CPU-errata.patch
+new file mode 100644
+index 0000000000..63553ebe58
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/050-v5.16-04-mips-bpf-Add-JIT-workarounds-for-CPU-errata.patch
+@@ -0,0 +1,120 @@
++From: Johan Almbladh <johan.almbladh@anyfinetworks.com>
++Date: Tue, 5 Oct 2021 18:54:06 +0200
++Subject: [PATCH] mips: bpf: Add JIT workarounds for CPU errata
++
++This patch adds workarounds for the following CPU errata to the MIPS
++eBPF JIT, if enabled in the kernel configuration.
++
++  - R10000 ll/sc weak ordering
++  - Loongson-3 ll/sc weak ordering
++  - Loongson-2F jump hang
++
++The Loongson-2F nop errata is implemented in uasm, which the JIT uses,
++so no additional mitigations are needed for that.
++
++Signed-off-by: Johan Almbladh <johan.almbladh@anyfinetworks.com>
++Reviewed-by: Jiaxun Yang <jiaxun.yang@flygoat.com>
++---
++
++--- a/arch/mips/net/bpf_jit_comp.c
+++++ b/arch/mips/net/bpf_jit_comp.c
++@@ -404,6 +404,7 @@ void emit_alu_r(struct jit_context *ctx,
++ /* Atomic read-modify-write (32-bit) */
++ void emit_atomic_r(struct jit_context *ctx, u8 dst, u8 src, s16 off, u8 code)
 + {
-++	struct mtk_wdma_info info = {};
-+ 	int pse_port, dsa_port;
+++	LLSC_sync(ctx);
++ 	emit(ctx, ll, MIPS_R_T9, off, dst);
++ 	switch (code) {
++ 	case BPF_ADD:
++@@ -420,18 +421,19 @@ void emit_atomic_r(struct jit_context *c
++ 		break;
++ 	}
++ 	emit(ctx, sc, MIPS_R_T8, off, dst);
++-	emit(ctx, beqz, MIPS_R_T8, -16);
+++	emit(ctx, LLSC_beqz, MIPS_R_T8, -16 - LLSC_offset);
++ 	emit(ctx, nop); /* Delay slot */
++ }
 + 
-++	if (mtk_flow_get_wdma_info(dev, dest_mac, &info) == 0) {
-++		mtk_foe_entry_set_wdma(foe, info.wdma_idx, info.queue, info.bss,
-++				       info.wcid);
-++		pse_port = 3;
-++		*wed_index = info.wdma_idx;
-++		goto out;
-++	}
-++
-+ 	dsa_port = mtk_flow_get_dsa_port(&dev);
-+ 	if (dsa_port >= 0)
-+ 		mtk_foe_entry_set_dsa(foe, dsa_port);
-+@@ -164,6 +205,7 @@ mtk_flow_set_output_device(struct mtk_et
-+ 	else
-+ 		return -EOPNOTSUPP;
-+ 
-++out:
-+ 	mtk_foe_entry_set_pse_port(foe, pse_port);
++ /* Atomic compare-and-exchange (32-bit) */
++ void emit_cmpxchg_r(struct jit_context *ctx, u8 dst, u8 src, u8 res, s16 off)
++ {
+++	LLSC_sync(ctx);
++ 	emit(ctx, ll, MIPS_R_T9, off, dst);
++ 	emit(ctx, bne, MIPS_R_T9, res, 12);
++ 	emit(ctx, move, MIPS_R_T8, src);     /* Delay slot */
++ 	emit(ctx, sc, MIPS_R_T8, off, dst);
++-	emit(ctx, beqz, MIPS_R_T8, -20);
+++	emit(ctx, LLSC_beqz, MIPS_R_T8, -20 - LLSC_offset);
++ 	emit(ctx, move, res, MIPS_R_T9);     /* Delay slot */
++ 	clobber_reg(ctx, res);
++ }
++--- a/arch/mips/net/bpf_jit_comp.h
+++++ b/arch/mips/net/bpf_jit_comp.h
++@@ -87,7 +87,7 @@ struct jit_context {
++ };
 + 
-+ 	return 0;
-+@@ -179,6 +221,7 @@ mtk_flow_offload_replace(struct mtk_eth
-+ 	struct net_device *odev = NULL;
-+ 	struct mtk_flow_entry *entry;
-+ 	int offload_type = 0;
-++	int wed_index = -1;
-+ 	u16 addr_type = 0;
-+ 	u32 timestamp;
-+ 	u8 l4proto = 0;
-+@@ -326,10 +369,14 @@ mtk_flow_offload_replace(struct mtk_eth
-+ 	if (data.pppoe.num == 1)
-+ 		mtk_foe_entry_set_pppoe(&foe, data.pppoe.sid);
++ /* Emit the instruction if the JIT memory space has been allocated */
++-#define emit(ctx, func, ...)					\
+++#define __emit(ctx, func, ...)					\
++ do {								\
++ 	if ((ctx)->target != NULL) {				\
++ 		u32 *p = &(ctx)->target[ctx->jit_index];	\
++@@ -95,6 +95,30 @@ do {								\
++ 	}							\
++ 	(ctx)->jit_index++;					\
++ } while (0)
+++#define emit(...) __emit(__VA_ARGS__)
+++
+++/* Workaround for R10000 ll/sc errata */
+++#ifdef CONFIG_WAR_R10000
+++#define LLSC_beqz	beqzl
+++#else
+++#define LLSC_beqz	beqz
+++#endif
+++
+++/* Workaround for Loongson-3 ll/sc errata */
+++#ifdef CONFIG_CPU_LOONGSON3_WORKAROUNDS
+++#define LLSC_sync(ctx)	emit(ctx, sync, 0)
+++#define LLSC_offset	4
+++#else
+++#define LLSC_sync(ctx)
+++#define LLSC_offset	0
+++#endif
+++
+++/* Workaround for Loongson-2F jump errata */
+++#ifdef CONFIG_CPU_JUMP_WORKAROUNDS
+++#define JALR_MASK	0xffffffffcfffffffULL
+++#else
+++#define JALR_MASK	(~0ULL)
+++#endif
 + 
-+-	err = mtk_flow_set_output_device(eth, &foe, odev);
-++	err = mtk_flow_set_output_device(eth, &foe, odev, data.eth.h_dest,
-++					 &wed_index);
-+ 	if (err)
-+ 		return err;
++ /*
++  * Mark a BPF register as accessed, it needs to be
++--- a/arch/mips/net/bpf_jit_comp64.c
+++++ b/arch/mips/net/bpf_jit_comp64.c
++@@ -375,6 +375,7 @@ static void emit_atomic_r64(struct jit_c
++ 	u8 t1 = MIPS_R_T6;
++ 	u8 t2 = MIPS_R_T7;
 + 
-++	if (wed_index >= 0 && (err = mtk_wed_flow_add(wed_index)) < 0)
-++		return err;
-++
-+ 	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
-+ 	if (!entry)
-+ 		return -ENOMEM;
-+@@ -343,6 +390,7 @@ mtk_flow_offload_replace(struct mtk_eth
+++	LLSC_sync(ctx);
++ 	emit(ctx, lld, t1, off, dst);
++ 	switch (code) {
++ 	case BPF_ADD:
++@@ -391,7 +392,7 @@ static void emit_atomic_r64(struct jit_c
++ 		break;
 + 	}
-+ 
-+ 	entry->hash = hash;
-++	entry->wed_index = wed_index;
-+ 	err = rhashtable_insert_fast(&eth->flow_table, &entry->node,
-+ 				     mtk_flow_ht_params);
-+ 	if (err < 0)
-+@@ -353,6 +401,8 @@ clear_flow:
-+ 	mtk_foe_entry_clear(&eth->ppe, hash);
-+ free:
-+ 	kfree(entry);
-++	if (wed_index >= 0)
-++	    mtk_wed_flow_remove(wed_index);
-+ 	return err;
++ 	emit(ctx, scd, t2, off, dst);
++-	emit(ctx, beqz, t2, -16);
+++	emit(ctx, LLSC_beqz, t2, -16 - LLSC_offset);
++ 	emit(ctx, nop); /* Delay slot */
 + }
 + 
-+@@ -369,6 +419,8 @@ mtk_flow_offload_destroy(struct mtk_eth
-+ 	mtk_foe_entry_clear(&eth->ppe, entry->hash);
-+ 	rhashtable_remove_fast(&eth->flow_table, &entry->node,
-+ 			       mtk_flow_ht_params);
-++	if (entry->wed_index >= 0)
-++		mtk_wed_flow_remove(entry->wed_index);
-+ 	kfree(entry);
-+ 
-+ 	return 0;
-+--- a/drivers/net/ethernet/mediatek/mtk_wed.h
-++++ b/drivers/net/ethernet/mediatek/mtk_wed.h
-+@@ -7,6 +7,7 @@
-+ #include <linux/soc/mediatek/mtk_wed.h>
-+ #include <linux/debugfs.h>
-+ #include <linux/regmap.h>
-++#include <linux/netdevice.h>
-+ 
-+ struct mtk_eth;
-+ 
-+@@ -27,6 +28,12 @@ struct mtk_wed_hw {
-+ 	int index;
-+ };
-+ 
-++struct mtk_wdma_info {
-++	u8 wdma_idx;
-++	u8 queue;
-++	u16 wcid;
-++	u8 bss;
-++};
-+ 
-+ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
-+ static inline void
-+--- a/include/linux/netdevice.h
-++++ b/include/linux/netdevice.h
-+@@ -849,6 +849,7 @@ enum net_device_path_type {
-+ 	DEV_PATH_BRIDGE,
-+ 	DEV_PATH_PPPOE,
-+ 	DEV_PATH_DSA,
-++	DEV_PATH_MTK_WDMA,
-+ };
++@@ -414,7 +415,7 @@ static int emit_call(struct jit_context
++ 	push_regs(ctx, ctx->clobbered & JIT_CALLER_REGS, 0, 0);
 + 
-+ struct net_device_path {
-+@@ -874,6 +875,12 @@ struct net_device_path {
-+ 			int port;
-+ 			u16 proto;
-+ 		} dsa;
-++		struct {
-++			u8 wdma_idx;
-++			u8 queue;
-++			u16 wcid;
-++			u8 bss;
-++		} mtk_wdma;
-+ 	};
-+ };
++ 	/* Emit function call */
++-	emit_mov_i64(ctx, tmp, addr);
+++	emit_mov_i64(ctx, tmp, addr & JALR_MASK);
++ 	emit(ctx, jalr, MIPS_R_RA, tmp);
++ 	emit(ctx, nop); /* Delay slot */
 + 
-+--- a/net/core/dev.c
-++++ b/net/core/dev.c
-+@@ -763,6 +763,10 @@ int dev_fill_forward_path(const struct n
-+ 		if (WARN_ON_ONCE(last_dev == ctx.dev))
-+ 			return -1;
-+ 	}
-++
-++	if (!ctx.dev)
-++		return ret;
-++
-+ 	path = dev_fwd_path(stack);
-+ 	if (!path)
-+ 		return -1;
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-04-arm64-dts-mediatek-mt7622-introduce-nodes-for-Wirele.patch b/target/linux/generic/backport-5.15/702-v5.19-04-arm64-dts-mediatek-mt7622-introduce-nodes-for-Wirele.patch
+diff --git a/target/linux/generic/backport-5.15/050-v5.16-05-mips-bpf-Enable-eBPF-JITs.patch b/target/linux/generic/backport-5.15/050-v5.16-05-mips-bpf-Enable-eBPF-JITs.patch
 new file mode 100644
-index 0000000000..f59a364a73
+index 0000000000..41b3d8139f
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-04-arm64-dts-mediatek-mt7622-introduce-nodes-for-Wirele.patch
-@@ -0,0 +1,62 @@
-+From: Felix Fietkau <nbd@nbd.name>
-+Date: Sat, 5 Feb 2022 18:36:36 +0100
-+Subject: [PATCH] arm64: dts: mediatek: mt7622: introduce nodes for
-+ Wireless Ethernet Dispatch
-+
-+Introduce wed0 and wed1 nodes in order to enable offloading forwarding
-+between ethernet and wireless devices on the mt7622 chipset.
-+
-+Signed-off-by: Felix Fietkau <nbd@nbd.name>
-+---
++++ b/target/linux/generic/backport-5.15/050-v5.16-05-mips-bpf-Enable-eBPF-JITs.patch
+@@ -0,0 +1,61 @@
++From: Johan Almbladh <johan.almbladh@anyfinetworks.com>
++Date: Tue, 5 Oct 2021 18:54:07 +0200
++Subject: [PATCH] mips: bpf: Enable eBPF JITs
 +
-+--- a/arch/arm64/boot/dts/mediatek/mt7622.dtsi
-++++ b/arch/arm64/boot/dts/mediatek/mt7622.dtsi
-+@@ -893,6 +893,11 @@
-+ 		};
-+ 	};
-+ 
-++	hifsys: syscon@1af00000 {
-++		compatible = "mediatek,mt7622-hifsys", "syscon";
-++		reg = <0 0x1af00000 0 0x70>;
-++	};
-++
-+ 	ethsys: syscon@1b000000 {
-+ 		compatible = "mediatek,mt7622-ethsys",
-+ 			     "syscon";
-+@@ -911,6 +916,26 @@
-+ 		#dma-cells = <1>;
-+ 	};
-+ 
-++	pcie_mirror: pcie-mirror@10000400 {
-++		compatible = "mediatek,mt7622-pcie-mirror",
-++			     "syscon";
-++		reg = <0 0x10000400 0 0x10>;
-++	};
-++
-++	wed0: wed@1020a000 {
-++		compatible = "mediatek,mt7622-wed",
-++			     "syscon";
-++		reg = <0 0x1020a000 0 0x1000>;
-++		interrupts = <GIC_SPI 214 IRQ_TYPE_LEVEL_LOW>;
-++	};
-++
-++	wed1: wed@1020b000 {
-++		compatible = "mediatek,mt7622-wed",
-++			     "syscon";
-++		reg = <0 0x1020b000 0 0x1000>;
-++		interrupts = <GIC_SPI 215 IRQ_TYPE_LEVEL_LOW>;
-++	};
-++
-+ 	eth: ethernet@1b100000 {
-+ 		compatible = "mediatek,mt7622-eth",
-+ 			     "mediatek,mt2701-eth",
-+@@ -938,6 +963,9 @@
-+ 		mediatek,ethsys = <&ethsys>;
-+ 		mediatek,sgmiisys = <&sgmiisys>;
-+ 		mediatek,cci-control = <&cci_control2>;
-++		mediatek,wed = <&wed0>, <&wed1>;
-++		mediatek,pcie-mirror = <&pcie_mirror>;
-++		mediatek,hifsys = <&hifsys>;
-+ 		dma-coherent;
-+ 		#address-cells = <1>;
-+ 		#size-cells = <0>;
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-05-net-ethernet-mtk_eth_soc-add-ipv6-flow-offload-suppo.patch b/target/linux/generic/backport-5.15/702-v5.19-05-net-ethernet-mtk_eth_soc-add-ipv6-flow-offload-suppo.patch
-new file mode 100644
-index 0000000000..9adb067015
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-05-net-ethernet-mtk_eth_soc-add-ipv6-flow-offload-suppo.patch
-@@ -0,0 +1,79 @@
-+From: David Bentham <db260179@gmail.com>
-+Date: Mon, 21 Feb 2022 15:36:16 +0100
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: add ipv6 flow offload
-+ support
++This patch enables the new eBPF JITs for 32-bit and 64-bit MIPS. It also
++disables the old cBPF JIT to so cBPF programs are converted to use the
++new JIT.
 +
-+Add the missing IPv6 flow offloading support for routing only.
-+Hardware flow offloading is done by the packet processing engine (PPE)
-+of the Ethernet MAC and as it doesn't support mangling of IPv6 packets,
-+IPv6 NAT cannot be supported.
++Workarounds for R4000 CPU errata are not implemented by the JIT, so the
++JIT is disabled if any of those workarounds are configured.
 +
-+Signed-off-by: David Bentham <db260179@gmail.com>
-+Signed-off-by: Felix Fietkau <nbd@nbd.name>
++Signed-off-by: Johan Almbladh <johan.almbladh@anyfinetworks.com>
 +---
 +
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
-+@@ -6,6 +6,7 @@
-+ #include <linux/if_ether.h>
-+ #include <linux/rhashtable.h>
-+ #include <linux/ip.h>
-++#include <linux/ipv6.h>
-+ #include <net/flow_offload.h>
-+ #include <net/pkt_cls.h>
-+ #include <net/dsa.h>
-+@@ -20,6 +21,11 @@ struct mtk_flow_data {
-+ 			__be32 src_addr;
-+ 			__be32 dst_addr;
-+ 		} v4;
-++
-++		struct {
-++			struct in6_addr src_addr;
-++			struct in6_addr dst_addr;
-++		} v6;
-+ 	};
++--- a/MAINTAINERS
+++++ b/MAINTAINERS
++@@ -3430,6 +3430,7 @@ S:	Supported
++ F:	arch/arm64/net/
 + 
-+ 	__be16 src_port;
-+@@ -65,6 +71,14 @@ mtk_flow_set_ipv4_addr(struct mtk_foe_en
-+ 					    data->v4.dst_addr, data->dst_port);
-+ }
++ BPF JIT for MIPS (32-BIT AND 64-BIT)
+++M:	Johan Almbladh <johan.almbladh@anyfinetworks.com>
++ M:	Paul Burton <paulburton@kernel.org>
++ L:	netdev@vger.kernel.org
++ L:	bpf@vger.kernel.org
++--- a/arch/mips/Kconfig
+++++ b/arch/mips/Kconfig
++@@ -57,7 +57,6 @@ config MIPS
++ 	select HAVE_ARCH_TRACEHOOK
++ 	select HAVE_ARCH_TRANSPARENT_HUGEPAGE if CPU_SUPPORTS_HUGEPAGES
++ 	select HAVE_ASM_MODVERSIONS
++-	select HAVE_CBPF_JIT if !64BIT && !CPU_MICROMIPS
++ 	select HAVE_CONTEXT_TRACKING
++ 	select HAVE_TIF_NOHZ
++ 	select HAVE_C_RECORDMCOUNT
++@@ -65,7 +64,10 @@ config MIPS
++ 	select HAVE_DEBUG_STACKOVERFLOW
++ 	select HAVE_DMA_CONTIGUOUS
++ 	select HAVE_DYNAMIC_FTRACE
++-	select HAVE_EBPF_JIT if 64BIT && !CPU_MICROMIPS && TARGET_ISA_REV >= 2
+++	select HAVE_EBPF_JIT if !CPU_MICROMIPS && \
+++				!CPU_DADDI_WORKAROUNDS && \
+++				!CPU_R4000_WORKAROUNDS && \
+++				!CPU_R4400_WORKAROUNDS
++ 	select HAVE_EXIT_THREAD
++ 	select HAVE_FAST_GUP
++ 	select HAVE_FTRACE_MCOUNT_RECORD
++--- a/arch/mips/net/Makefile
+++++ b/arch/mips/net/Makefile
++@@ -2,9 +2,10 @@
++ # MIPS networking code
 + 
-++static int
-++mtk_flow_set_ipv6_addr(struct mtk_foe_entry *foe, struct mtk_flow_data *data)
-++{
-++	return mtk_foe_entry_set_ipv6_tuple(foe,
-++					    data->v6.src_addr.s6_addr32, data->src_port,
-++					    data->v6.dst_addr.s6_addr32, data->dst_port);
-++}
-++
-+ static void
-+ mtk_flow_offload_mangle_eth(const struct flow_action_entry *act, void *eth)
-+ {
-+@@ -296,6 +310,9 @@ mtk_flow_offload_replace(struct mtk_eth
-+ 	case FLOW_DISSECTOR_KEY_IPV4_ADDRS:
-+ 		offload_type = MTK_PPE_PKT_TYPE_IPV4_HNAPT;
-+ 		break;
-++	case FLOW_DISSECTOR_KEY_IPV6_ADDRS:
-++		offload_type = MTK_PPE_PKT_TYPE_IPV6_ROUTE_5T;
-++		break;
-+ 	default:
-+ 		return -EOPNOTSUPP;
-+ 	}
-+@@ -331,6 +348,17 @@ mtk_flow_offload_replace(struct mtk_eth
-+ 		mtk_flow_set_ipv4_addr(&foe, &data, false);
-+ 	}
++ obj-$(CONFIG_MIPS_CBPF_JIT) += bpf_jit.o bpf_jit_asm.o
+++obj-$(CONFIG_MIPS_EBPF_JIT) += bpf_jit_comp.o
 + 
-++	if (addr_type == FLOW_DISSECTOR_KEY_IPV6_ADDRS) {
-++		struct flow_match_ipv6_addrs addrs;
-++
-++		flow_rule_match_ipv6_addrs(rule, &addrs);
-++
-++		data.v6.src_addr = addrs.key->src;
-++		data.v6.dst_addr = addrs.key->dst;
-++
-++		mtk_flow_set_ipv6_addr(&foe, &data);
-++	}
-++
-+ 	flow_action_for_each(i, act, &rule->action) {
-+ 		if (act->id != FLOW_ACTION_MANGLE)
-+ 			continue;
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-06-net-ethernet-mtk_eth_soc-support-TC_SETUP_BLOCK-for-.patch b/target/linux/generic/backport-5.15/702-v5.19-06-net-ethernet-mtk_eth_soc-support-TC_SETUP_BLOCK-for-.patch
++ ifeq ($(CONFIG_32BIT),y)
++-        obj-$(CONFIG_MIPS_EBPF_JIT) += bpf_jit_comp.o bpf_jit_comp32.o
+++        obj-$(CONFIG_MIPS_EBPF_JIT) += bpf_jit_comp32.o
++ else
++-        obj-$(CONFIG_MIPS_EBPF_JIT) += ebpf_jit.o
+++        obj-$(CONFIG_MIPS_EBPF_JIT) += bpf_jit_comp64.o
++ endif
+diff --git a/target/linux/generic/backport-5.15/050-v5.16-06-mips-bpf-Remove-old-BPF-JIT-implementations.patch b/target/linux/generic/backport-5.15/050-v5.16-06-mips-bpf-Remove-old-BPF-JIT-implementations.patch
 new file mode 100644
-index 0000000000..72c6d28172
+index 0000000000..e25c336831
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-06-net-ethernet-mtk_eth_soc-support-TC_SETUP_BLOCK-for-.patch
-@@ -0,0 +1,29 @@
-+From: Felix Fietkau <nbd@nbd.name>
-+Date: Mon, 21 Feb 2022 15:37:21 +0100
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: support TC_SETUP_BLOCK for
-+ PPE offload
++++ b/target/linux/generic/backport-5.15/050-v5.16-06-mips-bpf-Remove-old-BPF-JIT-implementations.patch
+@@ -0,0 +1,387 @@
++From: Johan Almbladh <johan.almbladh@anyfinetworks.com>
++Date: Tue, 5 Oct 2021 18:54:08 +0200
++Subject: [PATCH] mips: bpf: Remove old BPF JIT implementations
 +
-+This allows offload entries to be created from user space
++This patch removes the old 32-bit cBPF and 64-bit eBPF JIT implementations.
++They are replaced by a new eBPF implementation that supports both 32-bit
++and 64-bit MIPS CPUs.
 +
-+Signed-off-by: Felix Fietkau <nbd@nbd.name>
++Signed-off-by: Johan Almbladh <johan.almbladh@anyfinetworks.com>
 +---
++ delete mode 100644 arch/mips/net/bpf_jit.c
++ delete mode 100644 arch/mips/net/bpf_jit.h
++ delete mode 100644 arch/mips/net/bpf_jit_asm.S
++ delete mode 100644 arch/mips/net/ebpf_jit.c
 +
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
-+@@ -563,10 +563,13 @@ mtk_eth_setup_tc_block(struct net_device
-+ int mtk_eth_setup_tc(struct net_device *dev, enum tc_setup_type type,
-+ 		     void *type_data)
-+ {
-+-	if (type == TC_SETUP_FT)
-++	switch (type) {
-++	case TC_SETUP_BLOCK:
-++	case TC_SETUP_FT:
-+ 		return mtk_eth_setup_tc_block(dev, type_data);
++--- a/arch/mips/net/bpf_jit.h
+++++ /dev/null
++@@ -1,81 +0,0 @@
++-/* SPDX-License-Identifier: GPL-2.0-only */
++-/*
++- * Just-In-Time compiler for BPF filters on MIPS
++- *
++- * Copyright (c) 2014 Imagination Technologies Ltd.
++- * Author: Markos Chandras <markos.chandras@imgtec.com>
++- */
 +-
-+-	return -EOPNOTSUPP;
-++	default:
-++		return -EOPNOTSUPP;
-++	}
-+ }
-+ 
-+ int mtk_eth_offload_init(struct mtk_eth *eth)
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-07-net-ethernet-mtk_eth_soc-allocate-struct-mtk_ppe-sep.patch b/target/linux/generic/backport-5.15/702-v5.19-07-net-ethernet-mtk_eth_soc-allocate-struct-mtk_ppe-sep.patch
-new file mode 100644
-index 0000000000..a17d49cac1
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-07-net-ethernet-mtk_eth_soc-allocate-struct-mtk_ppe-sep.patch
-@@ -0,0 +1,159 @@
-+From: Felix Fietkau <nbd@nbd.name>
-+Date: Mon, 21 Feb 2022 15:38:20 +0100
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: allocate struct mtk_ppe
-+ separately
-+
-+Preparation for adding more data to it, which will increase its size.
-+
-+Signed-off-by: Felix Fietkau <nbd@nbd.name>
-+---
-+
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -2332,7 +2332,7 @@ static int mtk_open(struct net_device *d
-+ 		if (err)
-+ 			return err;
-+ 
-+-		if (eth->soc->offload_version && mtk_ppe_start(&eth->ppe) == 0)
-++		if (eth->soc->offload_version && mtk_ppe_start(eth->ppe) == 0)
-+ 			gdm_config = MTK_GDMA_TO_PPE;
-+ 
-+ 		mtk_gdm_config(eth, gdm_config);
-+@@ -2406,7 +2406,7 @@ static int mtk_stop(struct net_device *d
-+ 	mtk_dma_free(eth);
-+ 
-+ 	if (eth->soc->offload_version)
-+-		mtk_ppe_stop(&eth->ppe);
-++		mtk_ppe_stop(eth->ppe);
-+ 
-+ 	return 0;
-+ }
-+@@ -3298,10 +3298,11 @@ static int mtk_probe(struct platform_dev
-+ 	}
-+ 
-+ 	if (eth->soc->offload_version) {
-+-		err = mtk_ppe_init(&eth->ppe, eth->dev,
-+-				   eth->base + MTK_ETH_PPE_BASE, 2);
-+-		if (err)
-++		eth->ppe = mtk_ppe_init(eth->dev, eth->base + MTK_ETH_PPE_BASE, 2);
-++		if (!eth->ppe) {
-++			err = -ENOMEM;
-+ 			goto err_free_dev;
-++		}
-+ 
-+ 		err = mtk_eth_offload_init(eth);
-+ 		if (err)
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-+@@ -982,7 +982,7 @@ struct mtk_eth {
-+ 	u32				rx_dma_l4_valid;
-+ 	int				ip_align;
-+ 
-+-	struct mtk_ppe			ppe;
-++	struct mtk_ppe			*ppe;
-+ 	struct rhashtable		flow_table;
-+ };
-+ 
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
-+@@ -384,10 +384,15 @@ int mtk_foe_entry_commit(struct mtk_ppe
-+ 	return hash;
-+ }
-+ 
-+-int mtk_ppe_init(struct mtk_ppe *ppe, struct device *dev, void __iomem *base,
-++struct mtk_ppe *mtk_ppe_init(struct device *dev, void __iomem *base,
-+ 		 int version)
-+ {
-+ 	struct mtk_foe_entry *foe;
-++	struct mtk_ppe *ppe;
-++
-++	ppe = devm_kzalloc(dev, sizeof(*ppe), GFP_KERNEL);
-++	if (!ppe)
-++		return NULL;
-+ 
-+ 	/* need to allocate a separate device, since it PPE DMA access is
-+ 	 * not coherent.
-+@@ -399,13 +404,13 @@ int mtk_ppe_init(struct mtk_ppe *ppe, st
-+ 	foe = dmam_alloc_coherent(ppe->dev, MTK_PPE_ENTRIES * sizeof(*foe),
-+ 				  &ppe->foe_phys, GFP_KERNEL);
-+ 	if (!foe)
-+-		return -ENOMEM;
-++		return NULL;
-+ 
-+ 	ppe->foe_table = foe;
-+ 
-+ 	mtk_ppe_debugfs_init(ppe);
-+ 
-+-	return 0;
-++	return ppe;
-+ }
-+ 
-+ static void mtk_ppe_init_foe_table(struct mtk_ppe *ppe)
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
-+@@ -246,8 +246,7 @@ struct mtk_ppe {
-+ 	void *acct_table;
-+ };
-+ 
-+-int mtk_ppe_init(struct mtk_ppe *ppe, struct device *dev, void __iomem *base,
-+-		 int version);
-++struct mtk_ppe *mtk_ppe_init(struct device *dev, void __iomem *base, int version);
-+ int mtk_ppe_start(struct mtk_ppe *ppe);
-+ int mtk_ppe_stop(struct mtk_ppe *ppe);
-+ 
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
-+@@ -411,7 +411,7 @@ mtk_flow_offload_replace(struct mtk_eth
-+ 
-+ 	entry->cookie = f->cookie;
-+ 	timestamp = mtk_eth_timestamp(eth);
-+-	hash = mtk_foe_entry_commit(&eth->ppe, &foe, timestamp);
-++	hash = mtk_foe_entry_commit(eth->ppe, &foe, timestamp);
-+ 	if (hash < 0) {
-+ 		err = hash;
-+ 		goto free;
-+@@ -426,7 +426,7 @@ mtk_flow_offload_replace(struct mtk_eth
-+ 
-+ 	return 0;
-+ clear_flow:
-+-	mtk_foe_entry_clear(&eth->ppe, hash);
-++	mtk_foe_entry_clear(eth->ppe, hash);
-+ free:
-+ 	kfree(entry);
-+ 	if (wed_index >= 0)
-+@@ -444,7 +444,7 @@ mtk_flow_offload_destroy(struct mtk_eth
-+ 	if (!entry)
-+ 		return -ENOENT;
-+ 
-+-	mtk_foe_entry_clear(&eth->ppe, entry->hash);
-++	mtk_foe_entry_clear(eth->ppe, entry->hash);
-+ 	rhashtable_remove_fast(&eth->flow_table, &entry->node,
-+ 			       mtk_flow_ht_params);
-+ 	if (entry->wed_index >= 0)
-+@@ -466,7 +466,7 @@ mtk_flow_offload_stats(struct mtk_eth *e
-+ 	if (!entry)
-+ 		return -ENOENT;
-+ 
-+-	timestamp = mtk_foe_entry_timestamp(&eth->ppe, entry->hash);
-++	timestamp = mtk_foe_entry_timestamp(eth->ppe, entry->hash);
-+ 	if (timestamp < 0)
-+ 		return -ETIMEDOUT;
-+ 
-+@@ -522,7 +522,7 @@ mtk_eth_setup_tc_block(struct net_device
-+ 	struct flow_block_cb *block_cb;
-+ 	flow_setup_cb_t *cb;
-+ 
-+-	if (!eth->ppe.foe_table)
-++	if (!eth->ppe || !eth->ppe->foe_table)
-+ 		return -EOPNOTSUPP;
-+ 
-+ 	if (f->binder_type != FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS)
-+@@ -574,7 +574,7 @@ int mtk_eth_setup_tc(struct net_device *
-+ 
-+ int mtk_eth_offload_init(struct mtk_eth *eth)
-+ {
-+-	if (!eth->ppe.foe_table)
-++	if (!eth->ppe || !eth->ppe->foe_table)
-+ 		return 0;
-+ 
-+ 	return rhashtable_init(&eth->flow_table, &mtk_flow_ht_params);
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-08-net-ethernet-mtk_eth_soc-rework-hardware-flow-table-.patch b/target/linux/generic/backport-5.15/702-v5.19-08-net-ethernet-mtk_eth_soc-rework-hardware-flow-table-.patch
++-#ifndef BPF_JIT_MIPS_OP_H
++-#define BPF_JIT_MIPS_OP_H
++-
++-/* Registers used by JIT */
++-#define MIPS_R_ZERO	0
++-#define MIPS_R_V0	2
++-#define MIPS_R_A0	4
++-#define MIPS_R_A1	5
++-#define MIPS_R_T4	12
++-#define MIPS_R_T5	13
++-#define MIPS_R_T6	14
++-#define MIPS_R_T7	15
++-#define MIPS_R_S0	16
++-#define MIPS_R_S1	17
++-#define MIPS_R_S2	18
++-#define MIPS_R_S3	19
++-#define MIPS_R_S4	20
++-#define MIPS_R_S5	21
++-#define MIPS_R_S6	22
++-#define MIPS_R_S7	23
++-#define MIPS_R_SP	29
++-#define MIPS_R_RA	31
++-
++-/* Conditional codes */
++-#define MIPS_COND_EQ	0x1
++-#define MIPS_COND_GE	(0x1 << 1)
++-#define MIPS_COND_GT	(0x1 << 2)
++-#define MIPS_COND_NE	(0x1 << 3)
++-#define MIPS_COND_ALL	(0x1 << 4)
++-/* Conditionals on X register or K immediate */
++-#define MIPS_COND_X	(0x1 << 5)
++-#define MIPS_COND_K	(0x1 << 6)
++-
++-#define r_ret	MIPS_R_V0
++-
++-/*
++- * Use 2 scratch registers to avoid pipeline interlocks.
++- * There is no overhead during epilogue and prologue since
++- * any of the $s0-$s6 registers will only be preserved if
++- * they are going to actually be used.
++- */
++-#define r_skb_hl	MIPS_R_S0 /* skb header length */
++-#define r_skb_data	MIPS_R_S1 /* skb actual data */
++-#define r_off		MIPS_R_S2
++-#define r_A		MIPS_R_S3
++-#define r_X		MIPS_R_S4
++-#define r_skb		MIPS_R_S5
++-#define r_M		MIPS_R_S6
++-#define r_skb_len	MIPS_R_S7
++-#define r_s0		MIPS_R_T4 /* scratch reg 1 */
++-#define r_s1		MIPS_R_T5 /* scratch reg 2 */
++-#define r_tmp_imm	MIPS_R_T6 /* No need to preserve this */
++-#define r_tmp		MIPS_R_T7 /* No need to preserve this */
++-#define r_zero		MIPS_R_ZERO
++-#define r_sp		MIPS_R_SP
++-#define r_ra		MIPS_R_RA
++-
++-#ifndef __ASSEMBLY__
++-
++-/* Declare ASM helpers */
++-
++-#define DECLARE_LOAD_FUNC(func) \
++-	extern u8 func(unsigned long *skb, int offset); \
++-	extern u8 func##_negative(unsigned long *skb, int offset); \
++-	extern u8 func##_positive(unsigned long *skb, int offset)
++-
++-DECLARE_LOAD_FUNC(sk_load_word);
++-DECLARE_LOAD_FUNC(sk_load_half);
++-DECLARE_LOAD_FUNC(sk_load_byte);
++-
++-#endif
++-
++-#endif /* BPF_JIT_MIPS_OP_H */
++--- a/arch/mips/net/bpf_jit_asm.S
+++++ /dev/null
++@@ -1,285 +0,0 @@
++-/*
++- * bpf_jib_asm.S: Packet/header access helper functions for MIPS/MIPS64 BPF
++- * compiler.
++- *
++- * Copyright (C) 2015 Imagination Technologies Ltd.
++- * Author: Markos Chandras <markos.chandras@imgtec.com>
++- *
++- * This program is free software; you can redistribute it and/or modify it
++- * under the terms of the GNU General Public License as published by the
++- * Free Software Foundation; version 2 of the License.
++- */
++-
++-#include <asm/asm.h>
++-#include <asm/isa-rev.h>
++-#include <asm/regdef.h>
++-#include "bpf_jit.h"
++-
++-/* ABI
++- *
++- * r_skb_hl	skb header length
++- * r_skb_data	skb data
++- * r_off(a1)	offset register
++- * r_A		BPF register A
++- * r_X		PF register X
++- * r_skb(a0)	*skb
++- * r_M		*scratch memory
++- * r_skb_le	skb length
++- * r_s0		Scratch register 0
++- * r_s1		Scratch register 1
++- *
++- * On entry:
++- * a0: *skb
++- * a1: offset (imm or imm + X)
++- *
++- * All non-BPF-ABI registers are free for use. On return, we only
++- * care about r_ret. The BPF-ABI registers are assumed to remain
++- * unmodified during the entire filter operation.
++- */
++-
++-#define skb	a0
++-#define offset	a1
++-#define SKF_LL_OFF  (-0x200000) /* Can't include linux/filter.h in assembly */
++-
++-	/* We know better :) so prevent assembler reordering etc */
++-	.set 	noreorder
++-
++-#define is_offset_negative(TYPE)				\
++-	/* If offset is negative we have more work to do */	\
++-	slti	t0, offset, 0;					\
++-	bgtz	t0, bpf_slow_path_##TYPE##_neg;			\
++-	/* Be careful what follows in DS. */
++-
++-#define is_offset_in_header(SIZE, TYPE)				\
++-	/* Reading from header? */				\
++-	addiu	$r_s0, $r_skb_hl, -SIZE;			\
++-	slt	t0, $r_s0, offset;				\
++-	bgtz	t0, bpf_slow_path_##TYPE;			\
++-
++-LEAF(sk_load_word)
++-	is_offset_negative(word)
++-FEXPORT(sk_load_word_positive)
++-	is_offset_in_header(4, word)
++-	/* Offset within header boundaries */
++-	PTR_ADDU t1, $r_skb_data, offset
++-	.set	reorder
++-	lw	$r_A, 0(t1)
++-	.set	noreorder
++-#ifdef CONFIG_CPU_LITTLE_ENDIAN
++-# if MIPS_ISA_REV >= 2
++-	wsbh	t0, $r_A
++-	rotr	$r_A, t0, 16
++-# else
++-	sll	t0, $r_A, 24
++-	srl	t1, $r_A, 24
++-	srl	t2, $r_A, 8
++-	or	t0, t0, t1
++-	andi	t2, t2, 0xff00
++-	andi	t1, $r_A, 0xff00
++-	or	t0, t0, t2
++-	sll	t1, t1, 8
++-	or	$r_A, t0, t1
++-# endif
++-#endif
++-	jr	$r_ra
++-	 move	$r_ret, zero
++-	END(sk_load_word)
++-
++-LEAF(sk_load_half)
++-	is_offset_negative(half)
++-FEXPORT(sk_load_half_positive)
++-	is_offset_in_header(2, half)
++-	/* Offset within header boundaries */
++-	PTR_ADDU t1, $r_skb_data, offset
++-	lhu	$r_A, 0(t1)
++-#ifdef CONFIG_CPU_LITTLE_ENDIAN
++-# if MIPS_ISA_REV >= 2
++-	wsbh	$r_A, $r_A
++-# else
++-	sll	t0, $r_A, 8
++-	srl	t1, $r_A, 8
++-	andi	t0, t0, 0xff00
++-	or	$r_A, t0, t1
++-# endif
++-#endif
++-	jr	$r_ra
++-	 move	$r_ret, zero
++-	END(sk_load_half)
++-
++-LEAF(sk_load_byte)
++-	is_offset_negative(byte)
++-FEXPORT(sk_load_byte_positive)
++-	is_offset_in_header(1, byte)
++-	/* Offset within header boundaries */
++-	PTR_ADDU t1, $r_skb_data, offset
++-	lbu	$r_A, 0(t1)
++-	jr	$r_ra
++-	 move	$r_ret, zero
++-	END(sk_load_byte)
++-
++-/*
++- * call skb_copy_bits:
++- * (prototype in linux/skbuff.h)
++- *
++- * int skb_copy_bits(sk_buff *skb, int offset, void *to, int len)
++- *
++- * o32 mandates we leave 4 spaces for argument registers in case
++- * the callee needs to use them. Even though we don't care about
++- * the argument registers ourselves, we need to allocate that space
++- * to remain ABI compliant since the callee may want to use that space.
++- * We also allocate 2 more spaces for $r_ra and our return register (*to).
++- *
++- * n64 is a bit different. The *caller* will allocate the space to preserve
++- * the arguments. So in 64-bit kernels, we allocate the 4-arg space for no
++- * good reason but it does not matter that much really.
++- *
++- * (void *to) is returned in r_s0
++- *
++- */
++-#ifdef CONFIG_CPU_LITTLE_ENDIAN
++-#define DS_OFFSET(SIZE) (4 * SZREG)
++-#else
++-#define DS_OFFSET(SIZE) ((4 * SZREG) + (4 - SIZE))
++-#endif
++-#define bpf_slow_path_common(SIZE)				\
++-	/* Quick check. Are we within reasonable boundaries? */ \
++-	LONG_ADDIU	$r_s1, $r_skb_len, -SIZE;		\
++-	sltu		$r_s0, offset, $r_s1;			\
++-	beqz		$r_s0, fault;				\
++-	/* Load 4th argument in DS */				\
++-	 LONG_ADDIU	a3, zero, SIZE;				\
++-	PTR_ADDIU	$r_sp, $r_sp, -(6 * SZREG);		\
++-	PTR_LA		t0, skb_copy_bits;			\
++-	PTR_S		$r_ra, (5 * SZREG)($r_sp);		\
++-	/* Assign low slot to a2 */				\
++-	PTR_ADDIU	a2, $r_sp, DS_OFFSET(SIZE);		\
++-	jalr		t0;					\
++-	/* Reset our destination slot (DS but it's ok) */	\
++-	 INT_S		zero, (4 * SZREG)($r_sp);		\
++-	/*							\
++-	 * skb_copy_bits returns 0 on success and -EFAULT	\
++-	 * on error. Our data live in a2. Do not bother with	\
++-	 * our data if an error has been returned.		\
++-	 */							\
++-	/* Restore our frame */					\
++-	PTR_L		$r_ra, (5 * SZREG)($r_sp);		\
++-	INT_L		$r_s0, (4 * SZREG)($r_sp);		\
++-	bltz		v0, fault;				\
++-	 PTR_ADDIU	$r_sp, $r_sp, 6 * SZREG;		\
++-	move		$r_ret, zero;				\
++-
++-NESTED(bpf_slow_path_word, (6 * SZREG), $r_sp)
++-	bpf_slow_path_common(4)
++-#ifdef CONFIG_CPU_LITTLE_ENDIAN
++-# if MIPS_ISA_REV >= 2
++-	wsbh	t0, $r_s0
++-	jr	$r_ra
++-	 rotr	$r_A, t0, 16
++-# else
++-	sll	t0, $r_s0, 24
++-	srl	t1, $r_s0, 24
++-	srl	t2, $r_s0, 8
++-	or	t0, t0, t1
++-	andi	t2, t2, 0xff00
++-	andi	t1, $r_s0, 0xff00
++-	or	t0, t0, t2
++-	sll	t1, t1, 8
++-	jr	$r_ra
++-	 or	$r_A, t0, t1
++-# endif
++-#else
++-	jr	$r_ra
++-	 move	$r_A, $r_s0
++-#endif
++-
++-	END(bpf_slow_path_word)
++-
++-NESTED(bpf_slow_path_half, (6 * SZREG), $r_sp)
++-	bpf_slow_path_common(2)
++-#ifdef CONFIG_CPU_LITTLE_ENDIAN
++-# if MIPS_ISA_REV >= 2
++-	jr	$r_ra
++-	 wsbh	$r_A, $r_s0
++-# else
++-	sll	t0, $r_s0, 8
++-	andi	t1, $r_s0, 0xff00
++-	andi	t0, t0, 0xff00
++-	srl	t1, t1, 8
++-	jr	$r_ra
++-	 or	$r_A, t0, t1
++-# endif
++-#else
++-	jr	$r_ra
++-	 move	$r_A, $r_s0
++-#endif
++-
++-	END(bpf_slow_path_half)
++-
++-NESTED(bpf_slow_path_byte, (6 * SZREG), $r_sp)
++-	bpf_slow_path_common(1)
++-	jr	$r_ra
++-	 move	$r_A, $r_s0
++-
++-	END(bpf_slow_path_byte)
++-
++-/*
++- * Negative entry points
++- */
++-	.macro bpf_is_end_of_data
++-	li	t0, SKF_LL_OFF
++-	/* Reading link layer data? */
++-	slt	t1, offset, t0
++-	bgtz	t1, fault
++-	/* Be careful what follows in DS. */
++-	.endm
++-/*
++- * call skb_copy_bits:
++- * (prototype in linux/filter.h)
++- *
++- * void *bpf_internal_load_pointer_neg_helper(const struct sk_buff *skb,
++- *                                            int k, unsigned int size)
++- *
++- * see above (bpf_slow_path_common) for ABI restrictions
++- */
++-#define bpf_negative_common(SIZE)					\
++-	PTR_ADDIU	$r_sp, $r_sp, -(6 * SZREG);			\
++-	PTR_LA		t0, bpf_internal_load_pointer_neg_helper;	\
++-	PTR_S		$r_ra, (5 * SZREG)($r_sp);			\
++-	jalr		t0;						\
++-	 li		a2, SIZE;					\
++-	PTR_L		$r_ra, (5 * SZREG)($r_sp);			\
++-	/* Check return pointer */					\
++-	beqz		v0, fault;					\
++-	 PTR_ADDIU	$r_sp, $r_sp, 6 * SZREG;			\
++-	/* Preserve our pointer */					\
++-	move		$r_s0, v0;					\
++-	/* Set return value */						\
++-	move		$r_ret, zero;					\
++-
++-bpf_slow_path_word_neg:
++-	bpf_is_end_of_data
++-NESTED(sk_load_word_negative, (6 * SZREG), $r_sp)
++-	bpf_negative_common(4)
++-	jr	$r_ra
++-	 lw	$r_A, 0($r_s0)
++-	END(sk_load_word_negative)
++-
++-bpf_slow_path_half_neg:
++-	bpf_is_end_of_data
++-NESTED(sk_load_half_negative, (6 * SZREG), $r_sp)
++-	bpf_negative_common(2)
++-	jr	$r_ra
++-	 lhu	$r_A, 0($r_s0)
++-	END(sk_load_half_negative)
++-
++-bpf_slow_path_byte_neg:
++-	bpf_is_end_of_data
++-NESTED(sk_load_byte_negative, (6 * SZREG), $r_sp)
++-	bpf_negative_common(1)
++-	jr	$r_ra
++-	 lbu	$r_A, 0($r_s0)
++-	END(sk_load_byte_negative)
++-
++-fault:
++-	jr	$r_ra
++-	 addiu $r_ret, zero, 1
+diff --git a/target/linux/generic/backport-5.15/081-v5.17-regmap-allow-to-define-reg_update_bits-for-no-bus.patch b/target/linux/generic/backport-5.15/081-v5.17-regmap-allow-to-define-reg_update_bits-for-no-bus.patch
 new file mode 100644
-index 0000000000..76e8eb11fd
+index 0000000000..e4c0833ae7
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-08-net-ethernet-mtk_eth_soc-rework-hardware-flow-table-.patch
-@@ -0,0 +1,424 @@
-+From: Felix Fietkau <nbd@nbd.name>
-+Date: Mon, 21 Feb 2022 15:39:18 +0100
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: rework hardware flow table
-+ management
-+
-+The hardware was designed to handle flow detection and creation of flow entries
-+by itself, relying on the software primarily for filling in egress routing
-+information.
-+When there is a hash collision between multiple flows, this allows the hardware
-+to maintain the entry for the most active flow.
-+Additionally, the hardware only keeps offloading active for entries with at
-+least 30 packets per second.
-+
-+With this rework, the code no longer creates a hardware entries directly.
-+Instead, the hardware entry is only created when the PPE reports a matching
-+unbound flow with the minimum target rate.
-+In order to reduce CPU overhead, looking for flows belonging to a hash entry
-+is rate limited to once every 100ms.
++++ b/target/linux/generic/backport-5.15/081-v5.17-regmap-allow-to-define-reg_update_bits-for-no-bus.patch
+@@ -0,0 +1,52 @@
++From 02d6fdecb9c38de19065f6bed8d5214556fd061d Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Thu, 4 Nov 2021 16:00:40 +0100
++Subject: regmap: allow to define reg_update_bits for no bus configuration
 +
-+This rework is also used as preparation for emulating bridge offload by
-+managing L4 offload entries on demand.
++Some device requires a special handling for reg_update_bits and can't use
++the normal regmap read write logic. An example is when locking is
++handled by the device and rmw operations requires to do atomic operations.
++Allow to declare a dedicated function in regmap_config for
++reg_update_bits in no bus configuration.
 +
-+Signed-off-by: Felix Fietkau <nbd@nbd.name>
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Link: https://lore.kernel.org/r/20211104150040.1260-1-ansuelsmth@gmail.com
++Signed-off-by: Mark Brown <broonie@kernel.org>
 +---
++ drivers/base/regmap/regmap.c | 1 +
++ include/linux/regmap.h       | 7 +++++++
++ 2 files changed, 8 insertions(+)
 +
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -21,6 +21,7 @@
-+ #include <linux/pinctrl/devinfo.h>
-+ #include <linux/phylink.h>
-+ #include <linux/jhash.h>
-++#include <linux/bitfield.h>
-+ #include <net/dsa.h>
-+ 
-+ #include "mtk_eth_soc.h"
-+@@ -1292,7 +1293,7 @@ static int mtk_poll_rx(struct napi_struc
-+ 		struct net_device *netdev;
-+ 		unsigned int pktlen;
-+ 		dma_addr_t dma_addr;
-+-		u32 hash;
-++		u32 hash, reason;
-+ 		int mac;
++--- a/drivers/base/regmap/regmap.c
+++++ b/drivers/base/regmap/regmap.c
++@@ -877,6 +877,7 @@ struct regmap *__regmap_init(struct devi
++ 	if (!bus) {
++ 		map->reg_read  = config->reg_read;
++ 		map->reg_write = config->reg_write;
+++		map->reg_update_bits = config->reg_update_bits;
 + 
-+ 		ring = mtk_get_rx_ring(eth);
-+@@ -1371,6 +1372,11 @@ static int mtk_poll_rx(struct napi_struc
-+ 			skb_set_hash(skb, hash, PKT_HASH_TYPE_L4);
-+ 		}
++ 		map->defer_caching = false;
++ 		goto skip_format_initialization;
++--- a/include/linux/regmap.h
+++++ b/include/linux/regmap.h
++@@ -290,6 +290,11 @@ typedef void (*regmap_unlock)(void *);
++  *		  read operation on a bus such as SPI, I2C, etc. Most of the
++  *		  devices do not need this.
++  * @reg_write:	  Same as above for writing.
+++ * @reg_update_bits: Optional callback that if filled will be used to perform
+++ *		     all the update_bits(rmw) operation. Should only be provided
+++ *		     if the function require special handling with lock and reg
+++ *		     handling and the operation cannot be represented as a simple
+++ *		     update_bits operation on a bus such as SPI, I2C, etc.
++  * @fast_io:	  Register IO is fast. Use a spinlock instead of a mutex
++  *	     	  to perform locking. This field is ignored if custom lock/unlock
++  *	     	  functions are used (see fields lock/unlock of struct regmap_config).
++@@ -372,6 +377,8 @@ struct regmap_config {
 + 
-++		reason = FIELD_GET(MTK_RXD4_PPE_CPU_REASON, trxd.rxd4);
-++		if (reason == MTK_PPE_CPU_REASON_HIT_UNBIND_RATE_REACHED)
-++			mtk_ppe_check_skb(eth->ppe, skb,
-++					  trxd.rxd4 & MTK_RXD4_FOE_ENTRY);
-++
-+ 		if (netdev->features & NETIF_F_HW_VLAN_CTAG_RX &&
-+ 		    (trxd.rxd2 & RX_DMA_VTAG))
-+ 			__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q),
-+@@ -3298,7 +3304,7 @@ static int mtk_probe(struct platform_dev
-+ 	}
++ 	int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
++ 	int (*reg_write)(void *context, unsigned int reg, unsigned int val);
+++	int (*reg_update_bits)(void *context, unsigned int reg,
+++			       unsigned int mask, unsigned int val);
 + 
-+ 	if (eth->soc->offload_version) {
-+-		eth->ppe = mtk_ppe_init(eth->dev, eth->base + MTK_ETH_PPE_BASE, 2);
-++		eth->ppe = mtk_ppe_init(eth, eth->base + MTK_ETH_PPE_BASE, 2);
-+ 		if (!eth->ppe) {
-+ 			err = -ENOMEM;
-+ 			goto err_free_dev;
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
-+@@ -6,9 +6,12 @@
-+ #include <linux/iopoll.h>
-+ #include <linux/etherdevice.h>
-+ #include <linux/platform_device.h>
-++#include "mtk_eth_soc.h"
-+ #include "mtk_ppe.h"
-+ #include "mtk_ppe_regs.h"
++ 	bool fast_io;
 + 
-++static DEFINE_SPINLOCK(ppe_lock);
-++
-+ static void ppe_w32(struct mtk_ppe *ppe, u32 reg, u32 val)
-+ {
-+ 	writel(val, ppe->base + reg);
-+@@ -41,6 +44,11 @@ static u32 ppe_clear(struct mtk_ppe *ppe
-+ 	return ppe_m32(ppe, reg, val, 0);
+diff --git a/target/linux/generic/backport-5.15/100-v5.18-tty-serial-bcm63xx-use-more-precise-Kconfig-symbol.patch b/target/linux/generic/backport-5.15/100-v5.18-tty-serial-bcm63xx-use-more-precise-Kconfig-symbol.patch
+new file mode 100644
+index 0000000000..7de3cbbda0
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/100-v5.18-tty-serial-bcm63xx-use-more-precise-Kconfig-symbol.patch
+@@ -0,0 +1,37 @@
++From 0dc0da881b4574d1e04a079ab2ea75da61f5ad2e Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Fri, 11 Mar 2022 10:32:33 +0100
++Subject: [PATCH] tty: serial: bcm63xx: use more precise Kconfig symbol
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++Patches lowering SERIAL_BCM63XX dependencies led to a discussion and
++documentation change regarding "depends" usage. Adjust Kconfig entry to
++match current guidelines. Make this symbol available for relevant
++architectures only.
++
++Cc: Geert Uytterhoeven <geert@linux-m68k.org>
++Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
++Acked-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Ref: f35a07f92616 ("tty: serial: bcm63xx: lower driver dependencies")
++Ref: 18084e435ff6 ("Documentation/kbuild: Document platform dependency practises")
++Link: https://lore.kernel.org/r/20220311093233.10012-1-zajec5@gmail.com
++Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
++---
++ drivers/tty/serial/Kconfig | 3 ++-
++ 1 file changed, 2 insertions(+), 1 deletion(-)
++
++--- a/drivers/tty/serial/Kconfig
+++++ b/drivers/tty/serial/Kconfig
++@@ -1098,7 +1098,8 @@ config SERIAL_TIMBERDALE
++ config SERIAL_BCM63XX
++ 	tristate "Broadcom BCM63xx/BCM33xx UART support"
++ 	select SERIAL_CORE
++-	depends on COMMON_CLK
+++	depends on ARCH_BCM4908 || ARCH_BCM_63XX || BCM63XX || BMIPS_GENERIC || COMPILE_TEST
+++	default ARCH_BCM4908 || ARCH_BCM_63XX || BCM63XX || BMIPS_GENERIC
++ 	help
++ 	  This enables the driver for the onchip UART core found on
++ 	  the following chipsets:
+diff --git a/target/linux/generic/backport-5.15/200-v5.18-tools-resolve_btfids-Build-with-host-flags.patch b/target/linux/generic/backport-5.15/200-v5.18-tools-resolve_btfids-Build-with-host-flags.patch
+new file mode 100644
+index 0000000000..caec8db5d6
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/200-v5.18-tools-resolve_btfids-Build-with-host-flags.patch
+@@ -0,0 +1,49 @@
++From cdbc4e3399ed8cdcf234a85f7a2482b622379e82 Mon Sep 17 00:00:00 2001
++From: Connor O'Brien <connoro@google.com>
++Date: Wed, 12 Jan 2022 00:25:03 +0000
++Subject: [PATCH] tools/resolve_btfids: Build with host flags
++
++resolve_btfids is built using $(HOSTCC) and $(HOSTLD) but does not
++pick up the corresponding flags. As a result, host-specific settings
++(such as a sysroot specified via HOSTCFLAGS=--sysroot=..., or a linker
++specified via HOSTLDFLAGS=-fuse-ld=...) will not be respected.
++
++Fix this by setting CFLAGS to KBUILD_HOSTCFLAGS and LDFLAGS to
++KBUILD_HOSTLDFLAGS.
++
++Also pass the cflags through to libbpf via EXTRA_CFLAGS to ensure that
++the host libbpf is built with flags consistent with resolve_btfids.
++
++Signed-off-by: Connor O'Brien <connoro@google.com>
++Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
++Acked-by: Song Liu <songliubraving@fb.com>
++Link: https://lore.kernel.org/bpf/20220112002503.115968-1-connoro@google.com
++(cherry picked from commit 0e3a1c902ffb56e9fe4416f0cd382c97b09ecbf6)
++Signed-off-by: Stijn Tintel <stijn@linux-ipv6.be>
++---
++ tools/bpf/resolve_btfids/Makefile | 6 ++++--
++ 1 file changed, 4 insertions(+), 2 deletions(-)
++
++--- a/tools/bpf/resolve_btfids/Makefile
+++++ b/tools/bpf/resolve_btfids/Makefile
++@@ -23,6 +23,8 @@ CC       = $(HOSTCC)
++ LD       = $(HOSTLD)
++ ARCH     = $(HOSTARCH)
++ RM      ?= rm
+++CFLAGS  := $(KBUILD_HOSTCFLAGS)
+++LDFLAGS := $(KBUILD_HOSTLDFLAGS)
++ 
++ OUTPUT ?= $(srctree)/tools/bpf/resolve_btfids/
++ 
++@@ -45,9 +47,9 @@ $(SUBCMDOBJ): fixdep FORCE | $(OUTPUT)/l
++ 	$(Q)$(MAKE) -C $(SUBCMD_SRC) OUTPUT=$(abspath $(dir $@))/ $(abspath $@)
++ 
++ $(BPFOBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf
++-	$(Q)$(MAKE) $(submake_extras) -C $(LIBBPF_SRC)  OUTPUT=$(abspath $(dir $@))/ $(abspath $@)
+++	$(Q)$(MAKE) $(submake_extras) -C $(LIBBPF_SRC)  OUTPUT=$(abspath $(dir $@))/ EXTRA_CFLAGS="$(CFLAGS)" $(abspath $@)
++ 
++-CFLAGS := -g \
+++CFLAGS += -g \
++           -I$(srctree)/tools/include \
++           -I$(srctree)/tools/include/uapi \
++           -I$(LIBBPF_SRC) \
+diff --git a/target/linux/generic/backport-5.15/201-v5.16-scripts-dtc-Update-to-upstream-version-v1.6.1-19-g0a.patch b/target/linux/generic/backport-5.15/201-v5.16-scripts-dtc-Update-to-upstream-version-v1.6.1-19-g0a.patch
+new file mode 100644
+index 0000000000..d1bef74f32
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/201-v5.16-scripts-dtc-Update-to-upstream-version-v1.6.1-19-g0a.patch
+@@ -0,0 +1,997 @@
++From a77725a9a3c5924e2fd4cd5b3557dd92a8e46f87 Mon Sep 17 00:00:00 2001
++From: Rob Herring <robh@kernel.org>
++Date: Mon, 25 Oct 2021 11:05:45 -0500
++Subject: [PATCH 1/1] scripts/dtc: Update to upstream version
++ v1.6.1-19-g0a3a9d3449c8
++
++This adds the following commits from upstream:
++
++0a3a9d3449c8 checks: Add an interrupt-map check
++8fd24744e361 checks: Ensure '#interrupt-cells' only exists in interrupt providers
++d8d1a9a77863 checks: Drop interrupt provider '#address-cells' check
++52a16fd72824 checks: Make interrupt_provider check dependent on interrupts_extended_is_cell
++37fd700685da treesource: Maintain phandle label/path on output
++e33ce1d6a8c7 flattree: Use '\n', not ';' to separate asm pseudo-ops
++d24cc189dca6 asm: Use assembler macros instead of cpp macros
++ff3a30c115ad asm: Use .asciz and .ascii instead of .string
++5eb5927d81ee fdtdump: fix -Werror=int-to-pointer-cast
++0869f8269161 libfdt: Add ALIGNMENT error string
++69595a167f06 checks: Fix bus-range check
++72d09e2682a4 Makefile: add -Wsign-compare to warning options
++b587787ef388 checks: Fix signedness comparisons warnings
++69bed6c2418f dtc: Wrap phandle validity check
++910221185560 fdtget: Fix signedness comparisons warnings
++d966f08fcd21 tests: Fix signedness comparisons warnings
++ecfb438c07fa dtc: Fix signedness comparisons warnings: pointer diff
++5bec74a6d135 dtc: Fix signedness comparisons warnings: reservednum
++24e7f511fd4a fdtdump: Fix signedness comparisons warnings
++b6910bec1161 Bump version to v1.6.1
++21d61d18f968 Fix CID 1461557
++4c2ef8f4d14c checks: Introduce is_multiple_of()
++e59ca36fb70e Make handling of cpp line information more tolerant
++0c3fd9b6aceb checks: Drop interrupt_cells_is_cell check
++6b3081abc4ac checks: Add check_is_cell() for all phandle+arg properties
++2dffc192a77f yamltree: Remove marker ordering dependency
++61e513439e40 pylibfdt: Rework "avoid unused variable warning" lines
++c8bddd106095 tests: add a positive gpio test case
++ad4abfadb687 checks: replace strstr and strrchr with strends
++09c6a6e88718 dtc.h: add strends for suffix matching
++9bb9b8d0b4a0 checks: tigthen up nr-gpios prop exception
++b07b62ee3342 libfdt: Add FDT alignment check to fdt_check_header()
++a2def5479950 libfdt: Check that the root-node name is empty
++4ca61f84dc21 libfdt: Check that there is only one root node
++34d708249a91 dtc: Remove -O dtbo support
++8e7ff260f755 libfdt: Fix a possible "unchecked return value" warning
++88875268c05c checks: Warn on node-name and property name being the same
++9d2279e7e6ee checks: Change node-name check to match devicetree spec
++f527c867a8c6 util: limit gnu_printf format attribute to gcc >= 4.4.0
++
++Reviewed-by: Frank Rowand <frank.rowand@sony.com>
++Tested-by: Frank Rowand <frank.rowand@sony.com>
++Signed-off-by: Rob Herring <robh@kernel.org>
++---
++ scripts/dtc/checks.c              | 222 ++++++++++++++++++++++--------
++ scripts/dtc/dtc-lexer.l           |   2 +-
++ scripts/dtc/dtc.c                 |   6 +-
++ scripts/dtc/dtc.h                 |  40 +++++-
++ scripts/dtc/flattree.c            |  11 +-
++ scripts/dtc/libfdt/fdt.c          |   4 +
++ scripts/dtc/libfdt/fdt_rw.c       |  18 ++-
++ scripts/dtc/libfdt/fdt_strerror.c |   1 +
++ scripts/dtc/libfdt/libfdt.h       |   7 +
++ scripts/dtc/livetree.c            |   6 +-
++ scripts/dtc/treesource.c          |  48 +++----
++ scripts/dtc/util.h                |   6 +-
++ scripts/dtc/version_gen.h         |   2 +-
++ scripts/dtc/yamltree.c            |  16 ++-
++ 14 files changed, 275 insertions(+), 114 deletions(-)
++
++--- a/scripts/dtc/checks.c
+++++ b/scripts/dtc/checks.c
++@@ -143,6 +143,14 @@ static void check_nodes_props(struct che
++ 		check_nodes_props(c, dti, child);
 + }
 + 
-++static u32 mtk_eth_timestamp(struct mtk_eth *eth)
+++static bool is_multiple_of(int multiple, int divisor)
 ++{
-++	return mtk_r32(eth, 0x0010) & MTK_FOE_IB1_BIND_TIMESTAMP;
+++	if (divisor == 0)
+++		return multiple == 0;
+++	else
+++		return (multiple % divisor) == 0;
 ++}
 ++
-+ static int mtk_ppe_wait_busy(struct mtk_ppe *ppe)
++ static bool run_check(struct check *c, struct dt_info *dti)
 + {
-+ 	int ret;
-+@@ -353,26 +361,59 @@ static inline bool mtk_foe_entry_usable(
-+ 	       FIELD_GET(MTK_FOE_IB1_STATE, entry->ib1) != MTK_FOE_STATE_BIND;
++ 	struct node *dt = dti->dt;
++@@ -297,19 +305,20 @@ ERROR(duplicate_property_names, check_du
++ #define LOWERCASE	"abcdefghijklmnopqrstuvwxyz"
++ #define UPPERCASE	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
++ #define DIGITS		"0123456789"
++-#define PROPNODECHARS	LOWERCASE UPPERCASE DIGITS ",._+*#?-"
+++#define NODECHARS	LOWERCASE UPPERCASE DIGITS ",._+-@"
+++#define PROPCHARS	LOWERCASE UPPERCASE DIGITS ",._+*#?-"
++ #define PROPNODECHARSSTRICT	LOWERCASE UPPERCASE DIGITS ",-"
++ 
++ static void check_node_name_chars(struct check *c, struct dt_info *dti,
++ 				  struct node *node)
++ {
++-	int n = strspn(node->name, c->data);
+++	size_t n = strspn(node->name, c->data);
++ 
++ 	if (n < strlen(node->name))
++ 		FAIL(c, dti, node, "Bad character '%c' in node name",
++ 		     node->name[n]);
 + }
++-ERROR(node_name_chars, check_node_name_chars, PROPNODECHARS "@");
+++ERROR(node_name_chars, check_node_name_chars, NODECHARS);
 + 
-+-int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_foe_entry *entry,
-+-			 u16 timestamp)
-++static bool
-++mtk_flow_entry_match(struct mtk_flow_entry *entry, struct mtk_foe_entry *data)
++ static void check_node_name_chars_strict(struct check *c, struct dt_info *dti,
++ 					 struct node *node)
++@@ -330,6 +339,20 @@ static void check_node_name_format(struc
++ }
++ ERROR(node_name_format, check_node_name_format, NULL, &node_name_chars);
++ 
+++static void check_node_name_vs_property_name(struct check *c,
+++					     struct dt_info *dti,
+++					     struct node *node)
 ++{
-++	int type, len;
-++
-++	if ((data->ib1 ^ entry->data.ib1) & MTK_FOE_IB1_UDP)
-++		return false;
-++
-++	type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->data.ib1);
-++	if (type > MTK_PPE_PKT_TYPE_IPV4_DSLITE)
-++		len = offsetof(struct mtk_foe_entry, ipv6._rsv);
-++	else
-++		len = offsetof(struct mtk_foe_entry, ipv4.ib2);
+++	if (!node->parent)
+++		return;
 ++
-++	return !memcmp(&entry->data.data, &data->data, len - 4);
+++	if (get_property(node->parent, node->name)) {
+++		FAIL(c, dti, node, "node name and property name conflict");
+++	}
 ++}
+++WARNING(node_name_vs_property_name, check_node_name_vs_property_name,
+++	NULL, &node_name_chars);
 ++
-++static void
-++mtk_flow_entry_update(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
++ static void check_unit_address_vs_reg(struct check *c, struct dt_info *dti,
++ 				      struct node *node)
 + {
-+ 	struct mtk_foe_entry *hwe;
-+-	u32 hash;
-++	struct mtk_foe_entry foe;
++@@ -363,14 +386,14 @@ static void check_property_name_chars(st
++ 	struct property *prop;
 + 
-++	spin_lock_bh(&ppe_lock);
-++	if (entry->hash == 0xffff)
-++		goto out;
-++
-++	hwe = &ppe->foe_table[entry->hash];
-++	memcpy(&foe, hwe, sizeof(foe));
-++	if (!mtk_flow_entry_match(entry, &foe)) {
-++		entry->hash = 0xffff;
-++		goto out;
-++	}
-++
-++	entry->data.ib1 = foe.ib1;
-++
-++out:
-++	spin_unlock_bh(&ppe_lock);
-++}
-++
-++static void
-++__mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_foe_entry *entry,
-++		       u16 hash)
-++{
-++	struct mtk_foe_entry *hwe;
-++	u16 timestamp;
-++
-++	timestamp = mtk_eth_timestamp(ppe->eth);
-+ 	timestamp &= MTK_FOE_IB1_BIND_TIMESTAMP;
-+ 	entry->ib1 &= ~MTK_FOE_IB1_BIND_TIMESTAMP;
-+ 	entry->ib1 |= FIELD_PREP(MTK_FOE_IB1_BIND_TIMESTAMP, timestamp);
++ 	for_each_property(node, prop) {
++-		int n = strspn(prop->name, c->data);
+++		size_t n = strspn(prop->name, c->data);
 + 
-+-	hash = mtk_ppe_hash_entry(entry);
-+ 	hwe = &ppe->foe_table[hash];
-+-	if (!mtk_foe_entry_usable(hwe)) {
-+-		hwe++;
-+-		hash++;
++ 		if (n < strlen(prop->name))
++ 			FAIL_PROP(c, dti, node, prop, "Bad character '%c' in property name",
++ 				  prop->name[n]);
++ 	}
++ }
++-ERROR(property_name_chars, check_property_name_chars, PROPNODECHARS);
+++ERROR(property_name_chars, check_property_name_chars, PROPCHARS);
++ 
++ static void check_property_name_chars_strict(struct check *c,
++ 					     struct dt_info *dti,
++@@ -380,7 +403,7 @@ static void check_property_name_chars_st
++ 
++ 	for_each_property(node, prop) {
++ 		const char *name = prop->name;
++-		int n = strspn(name, c->data);
+++		size_t n = strspn(name, c->data);
++ 
++ 		if (n == strlen(prop->name))
++ 			continue;
++@@ -497,7 +520,7 @@ static cell_t check_phandle_prop(struct
++ 
++ 	phandle = propval_cell(prop);
++ 
++-	if ((phandle == 0) || (phandle == -1)) {
+++	if (!phandle_is_valid(phandle)) {
++ 		FAIL_PROP(c, dti, node, prop, "bad value (0x%x) in %s property",
++ 		     phandle, prop->name);
++ 		return 0;
++@@ -556,7 +579,7 @@ static void check_name_properties(struct
++ 	if (!prop)
++ 		return; /* No name property, that's fine */
++ 
++-	if ((prop->val.len != node->basenamelen+1)
+++	if ((prop->val.len != node->basenamelen + 1U)
++ 	    || (memcmp(prop->val.val, node->name, node->basenamelen) != 0)) {
++ 		FAIL(c, dti, node, "\"name\" property is incorrect (\"%s\" instead"
++ 		     " of base node name)", prop->val.val);
++@@ -657,7 +680,6 @@ ERROR(omit_unused_nodes, fixup_omit_unus
++  */
++ WARNING_IF_NOT_CELL(address_cells_is_cell, "#address-cells");
++ WARNING_IF_NOT_CELL(size_cells_is_cell, "#size-cells");
++-WARNING_IF_NOT_CELL(interrupt_cells_is_cell, "#interrupt-cells");
++ 
++ WARNING_IF_NOT_STRING(device_type_is_string, "device_type");
++ WARNING_IF_NOT_STRING(model_is_string, "model");
++@@ -672,8 +694,7 @@ static void check_names_is_string_list(s
++ 	struct property *prop;
++ 
++ 	for_each_property(node, prop) {
++-		const char *s = strrchr(prop->name, '-');
++-		if (!s || !streq(s, "-names"))
+++		if (!strends(prop->name, "-names"))
++ 			continue;
++ 
++ 		c->data = prop->name;
++@@ -753,7 +774,7 @@ static void check_reg_format(struct chec
++ 	size_cells = node_size_cells(node->parent);
++ 	entrylen = (addr_cells + size_cells) * sizeof(cell_t);
++ 
++-	if (!entrylen || (prop->val.len % entrylen) != 0)
+++	if (!is_multiple_of(prop->val.len, entrylen))
++ 		FAIL_PROP(c, dti, node, prop, "property has invalid length (%d bytes) "
++ 			  "(#address-cells == %d, #size-cells == %d)",
++ 			  prop->val.len, addr_cells, size_cells);
++@@ -794,7 +815,7 @@ static void check_ranges_format(struct c
++ 				  "#size-cells (%d) differs from %s (%d)",
++ 				  ranges, c_size_cells, node->parent->fullpath,
++ 				  p_size_cells);
++-	} else if ((prop->val.len % entrylen) != 0) {
+++	} else if (!is_multiple_of(prop->val.len, entrylen)) {
++ 		FAIL_PROP(c, dti, node, prop, "\"%s\" property has invalid length (%d bytes) "
++ 			  "(parent #address-cells == %d, child #address-cells == %d, "
++ 			  "#size-cells == %d)", ranges, prop->val.len,
++@@ -871,7 +892,7 @@ static void check_pci_device_bus_num(str
++ 	} else {
++ 		cells = (cell_t *)prop->val.val;
++ 		min_bus = fdt32_to_cpu(cells[0]);
++-		max_bus = fdt32_to_cpu(cells[0]);
+++		max_bus = fdt32_to_cpu(cells[1]);
++ 	}
++ 	if ((bus_num < min_bus) || (bus_num > max_bus))
++ 		FAIL_PROP(c, dti, node, prop, "PCI bus number %d out of range, expected (%d - %d)",
++@@ -1367,9 +1388,9 @@ static void check_property_phandle_args(
++ 				          const struct provider *provider)
++ {
++ 	struct node *root = dti->dt;
++-	int cell, cellsize = 0;
+++	unsigned int cell, cellsize = 0;
++ 
++-	if (prop->val.len % sizeof(cell_t)) {
+++	if (!is_multiple_of(prop->val.len, sizeof(cell_t))) {
++ 		FAIL_PROP(c, dti, node, prop,
++ 			  "property size (%d) is invalid, expected multiple of %zu",
++ 			  prop->val.len, sizeof(cell_t));
++@@ -1379,14 +1400,14 @@ static void check_property_phandle_args(
++ 	for (cell = 0; cell < prop->val.len / sizeof(cell_t); cell += cellsize + 1) {
++ 		struct node *provider_node;
++ 		struct property *cellprop;
++-		int phandle;
+++		cell_t phandle;
++ 
++ 		phandle = propval_cell_n(prop, cell);
++ 		/*
++ 		 * Some bindings use a cell value 0 or -1 to skip over optional
++ 		 * entries when each index position has a specific definition.
++ 		 */
++-		if (phandle == 0 || phandle == -1) {
+++		if (!phandle_is_valid(phandle)) {
++ 			/* Give up if this is an overlay with external references */
++ 			if (dti->dtsflags & DTSF_PLUGIN)
++ 				break;
++@@ -1452,7 +1473,8 @@ static void check_provider_cells_propert
++ }
++ #define WARNING_PROPERTY_PHANDLE_CELLS(nm, propname, cells_name, ...) \
++ 	static struct provider nm##_provider = { (propname), (cells_name), __VA_ARGS__ }; \
++-	WARNING(nm##_property, check_provider_cells_property, &nm##_provider, &phandle_references);
+++	WARNING_IF_NOT_CELL(nm##_is_cell, cells_name); \
+++	WARNING(nm##_property, check_provider_cells_property, &nm##_provider, &nm##_is_cell, &phandle_references);
++ 
++ WARNING_PROPERTY_PHANDLE_CELLS(clocks, "clocks", "#clock-cells");
++ WARNING_PROPERTY_PHANDLE_CELLS(cooling_device, "cooling-device", "#cooling-cells");
++@@ -1473,24 +1495,17 @@ WARNING_PROPERTY_PHANDLE_CELLS(thermal_s
++ 
++ static bool prop_is_gpio(struct property *prop)
++ {
++-	char *str;
 +-
-+-		if (!mtk_foe_entry_usable(hwe))
-+-			return -ENOSPC;
-+-	}
++ 	/*
++ 	 * *-gpios and *-gpio can appear in property names,
++ 	 * so skip over any false matches (only one known ATM)
++ 	 */
++-	if (strstr(prop->name, "nr-gpio"))
+++	if (strends(prop->name, ",nr-gpios"))
++ 		return false;
++ 
++-	str = strrchr(prop->name, '-');
++-	if (str)
++-		str++;
++-	else
++-		str = prop->name;
++-	if (!(streq(str, "gpios") || streq(str, "gpio")))
++-		return false;
 +-
-+ 	memcpy(&hwe->data, &entry->data, sizeof(hwe->data));
-+ 	wmb();
-+ 	hwe->ib1 = entry->ib1;
-+@@ -380,13 +421,77 @@ int mtk_foe_entry_commit(struct mtk_ppe
-+ 	dma_wmb();
++-	return true;
+++	return strends(prop->name, "-gpios") ||
+++		streq(prop->name, "gpios") ||
+++		strends(prop->name, "-gpio") ||
+++		streq(prop->name, "gpio");
++ }
 + 
-+ 	mtk_ppe_cache_clear(ppe);
-++}
++ static void check_gpios_property(struct check *c,
++@@ -1525,13 +1540,10 @@ static void check_deprecated_gpio_proper
++ 	struct property *prop;
 + 
-+-	return hash;
-++void mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
-++{
-++	spin_lock_bh(&ppe_lock);
-++	hlist_del_init(&entry->list);
-++	if (entry->hash != 0xffff) {
-++		ppe->foe_table[entry->hash].ib1 &= ~MTK_FOE_IB1_STATE;
-++		ppe->foe_table[entry->hash].ib1 |= FIELD_PREP(MTK_FOE_IB1_STATE,
-++							      MTK_FOE_STATE_BIND);
-++		dma_wmb();
++ 	for_each_property(node, prop) {
++-		char *str;
++-
++ 		if (!prop_is_gpio(prop))
++ 			continue;
++ 
++-		str = strstr(prop->name, "gpio");
++-		if (!streq(str, "gpio"))
+++		if (!strends(prop->name, "gpio"))
++ 			continue;
++ 
++ 		FAIL_PROP(c, dti, node, prop,
++@@ -1561,21 +1573,106 @@ static void check_interrupt_provider(str
++ 				     struct node *node)
++ {
++ 	struct property *prop;
+++	bool irq_provider = node_is_interrupt_provider(node);
++ 
++-	if (!node_is_interrupt_provider(node))
+++	prop = get_property(node, "#interrupt-cells");
+++	if (irq_provider && !prop) {
+++		FAIL(c, dti, node,
+++		     "Missing '#interrupt-cells' in interrupt provider");
++ 		return;
+++	}
++ 
++-	prop = get_property(node, "#interrupt-cells");
++-	if (!prop)
+++	if (!irq_provider && prop) {
++ 		FAIL(c, dti, node,
++-		     "Missing #interrupt-cells in interrupt provider");
+++		     "'#interrupt-cells' found, but node is not an interrupt provider");
+++		return;
 ++	}
-++	entry->hash = 0xffff;
-++	spin_unlock_bh(&ppe_lock);
 ++}
-++
-++int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
+++WARNING(interrupt_provider, check_interrupt_provider, NULL, &interrupts_extended_is_cell);
++ 
++-	prop = get_property(node, "#address-cells");
++-	if (!prop)
+++static void check_interrupt_map(struct check *c,
+++				struct dt_info *dti,
+++				struct node *node)
 ++{
-++	u32 hash = mtk_ppe_hash_entry(&entry->data);
-++
-++	entry->hash = 0xffff;
-++	spin_lock_bh(&ppe_lock);
-++	hlist_add_head(&entry->list, &ppe->foe_flow[hash / 2]);
-++	spin_unlock_bh(&ppe_lock);
-++
-++	return 0;
-++}
+++	struct node *root = dti->dt;
+++	struct property *prop, *irq_map_prop;
+++	size_t cellsize, cell, map_cells;
 ++
-++void __mtk_ppe_check_skb(struct mtk_ppe *ppe, struct sk_buff *skb, u16 hash)
-++{
-++	struct hlist_head *head = &ppe->foe_flow[hash / 2];
-++	struct mtk_flow_entry *entry;
-++	struct mtk_foe_entry *hwe = &ppe->foe_table[hash];
-++	bool found = false;
+++	irq_map_prop = get_property(node, "interrupt-map");
+++	if (!irq_map_prop)
+++		return;
 ++
-++	if (hlist_empty(head))
+++	if (node->addr_cells < 0) {
++ 		FAIL(c, dti, node,
++-		     "Missing #address-cells in interrupt provider");
+++		     "Missing '#address-cells' in interrupt-map provider");
+++		return;
+++	}
+++	cellsize = node_addr_cells(node);
+++	cellsize += propval_cell(get_property(node, "#interrupt-cells"));
+++
+++	prop = get_property(node, "interrupt-map-mask");
+++	if (prop && (prop->val.len != (cellsize * sizeof(cell_t))))
+++		FAIL_PROP(c, dti, node, prop,
+++			  "property size (%d) is invalid, expected %zu",
+++			  prop->val.len, cellsize * sizeof(cell_t));
+++
+++	if (!is_multiple_of(irq_map_prop->val.len, sizeof(cell_t))) {
+++		FAIL_PROP(c, dti, node, irq_map_prop,
+++			  "property size (%d) is invalid, expected multiple of %zu",
+++			  irq_map_prop->val.len, sizeof(cell_t));
 ++		return;
+++	}
 ++
-++	spin_lock_bh(&ppe_lock);
-++	hlist_for_each_entry(entry, head, list) {
-++		if (found || !mtk_flow_entry_match(entry, hwe)) {
-++			if (entry->hash != 0xffff)
-++				entry->hash = 0xffff;
-++			continue;
+++	map_cells = irq_map_prop->val.len / sizeof(cell_t);
+++	for (cell = 0; cell < map_cells; ) {
+++		struct node *provider_node;
+++		struct property *cellprop;
+++		int phandle;
+++		size_t parent_cellsize;
+++
+++		if ((cell + cellsize) >= map_cells) {
+++			FAIL_PROP(c, dti, node, irq_map_prop,
+++				  "property size (%d) too small, expected > %zu",
+++				  irq_map_prop->val.len, (cell + cellsize) * sizeof(cell_t));
+++			break;
+++		}
+++		cell += cellsize;
+++
+++		phandle = propval_cell_n(irq_map_prop, cell);
+++		if (!phandle_is_valid(phandle)) {
+++			/* Give up if this is an overlay with external references */
+++			if (!(dti->dtsflags & DTSF_PLUGIN))
+++				FAIL_PROP(c, dti, node, irq_map_prop,
+++					  "Cell %zu is not a phandle(%d)",
+++					  cell, phandle);
+++			break;
 ++		}
 ++
-++		entry->hash = hash;
-++		__mtk_foe_entry_commit(ppe, &entry->data, hash);
-++		found = true;
-++	}
-++	spin_unlock_bh(&ppe_lock);
-++}
+++		provider_node = get_node_by_phandle(root, phandle);
+++		if (!provider_node) {
+++			FAIL_PROP(c, dti, node, irq_map_prop,
+++				  "Could not get phandle(%d) node for (cell %zu)",
+++				  phandle, cell);
+++			break;
+++		}
 ++
-++int mtk_foe_entry_idle_time(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
-++{
-++	u16 now = mtk_eth_timestamp(ppe->eth) & MTK_FOE_IB1_BIND_TIMESTAMP;
-++	u16 timestamp;
+++		cellprop = get_property(provider_node, "#interrupt-cells");
+++		if (cellprop) {
+++			parent_cellsize = propval_cell(cellprop);
+++		} else {
+++			FAIL(c, dti, node, "Missing property '#interrupt-cells' in node %s or bad phandle (referred from interrupt-map[%zu])",
+++			     provider_node->fullpath, cell);
+++			break;
+++		}
 ++
-++	mtk_flow_entry_update(ppe, entry);
-++	timestamp = entry->data.ib1 & MTK_FOE_IB1_BIND_TIMESTAMP;
+++		cellprop = get_property(provider_node, "#address-cells");
+++		if (cellprop)
+++			parent_cellsize += propval_cell(cellprop);
 ++
-++	if (timestamp > now)
-++		return MTK_FOE_IB1_BIND_TIMESTAMP + 1 - timestamp + now;
-++	else
-++		return now - timestamp;
+++		cell += 1 + parent_cellsize;
+++	}
 + }
++-WARNING(interrupt_provider, check_interrupt_provider, NULL);
+++WARNING(interrupt_map, check_interrupt_map, NULL, &phandle_references, &addr_size_cells, &interrupt_provider);
++ 
++ static void check_interrupts_property(struct check *c,
++ 				      struct dt_info *dti,
++@@ -1584,13 +1681,13 @@ static void check_interrupts_property(st
++ 	struct node *root = dti->dt;
++ 	struct node *irq_node = NULL, *parent = node;
++ 	struct property *irq_prop, *prop = NULL;
++-	int irq_cells, phandle;
+++	cell_t irq_cells, phandle;
++ 
++ 	irq_prop = get_property(node, "interrupts");
++ 	if (!irq_prop)
++ 		return;
 + 
-+-struct mtk_ppe *mtk_ppe_init(struct device *dev, void __iomem *base,
-++struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base,
-+ 		 int version)
++-	if (irq_prop->val.len % sizeof(cell_t))
+++	if (!is_multiple_of(irq_prop->val.len, sizeof(cell_t)))
++ 		FAIL_PROP(c, dti, node, irq_prop, "size (%d) is invalid, expected multiple of %zu",
++ 		     irq_prop->val.len, sizeof(cell_t));
++ 
++@@ -1603,7 +1700,7 @@ static void check_interrupts_property(st
++ 		prop = get_property(parent, "interrupt-parent");
++ 		if (prop) {
++ 			phandle = propval_cell(prop);
++-			if ((phandle == 0) || (phandle == -1)) {
+++			if (!phandle_is_valid(phandle)) {
++ 				/* Give up if this is an overlay with
++ 				 * external references */
++ 				if (dti->dtsflags & DTSF_PLUGIN)
++@@ -1639,7 +1736,7 @@ static void check_interrupts_property(st
++ 	}
++ 
++ 	irq_cells = propval_cell(prop);
++-	if (irq_prop->val.len % (irq_cells * sizeof(cell_t))) {
+++	if (!is_multiple_of(irq_prop->val.len, irq_cells * sizeof(cell_t))) {
++ 		FAIL_PROP(c, dti, node, prop,
++ 			  "size is (%d), expected multiple of %d",
++ 			  irq_prop->val.len, (int)(irq_cells * sizeof(cell_t)));
++@@ -1750,7 +1847,7 @@ WARNING(graph_port, check_graph_port, NU
++ static struct node *get_remote_endpoint(struct check *c, struct dt_info *dti,
++ 					struct node *endpoint)
 + {
-++	struct device *dev = eth->dev;
-+ 	struct mtk_foe_entry *foe;
-+ 	struct mtk_ppe *ppe;
++-	int phandle;
+++	cell_t phandle;
++ 	struct node *node;
++ 	struct property *prop;
 + 
-+@@ -398,6 +503,7 @@ struct mtk_ppe *mtk_ppe_init(struct devi
-+ 	 * not coherent.
-+ 	 */
-+ 	ppe->base = base;
-++	ppe->eth = eth;
-+ 	ppe->dev = dev;
-+ 	ppe->version = version;
++@@ -1760,7 +1857,7 @@ static struct node *get_remote_endpoint(
 + 
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
-+@@ -235,7 +235,17 @@ enum {
-+ 	MTK_PPE_CPU_REASON_INVALID			= 0x1f,
-+ };
++ 	phandle = propval_cell(prop);
++ 	/* Give up if this is an overlay with external references */
++-	if (phandle == 0 || phandle == -1)
+++	if (!phandle_is_valid(phandle))
++ 		return NULL;
 + 
-++struct mtk_flow_entry {
-++	struct rhash_head node;
-++	struct hlist_node list;
-++	unsigned long cookie;
-++	struct mtk_foe_entry data;
-++	u16 hash;
-++	s8 wed_index;
-++};
-++
-+ struct mtk_ppe {
-++	struct mtk_eth *eth;
-+ 	struct device *dev;
-+ 	void __iomem *base;
-+ 	int version;
-+@@ -243,18 +253,33 @@ struct mtk_ppe {
-+ 	struct mtk_foe_entry *foe_table;
-+ 	dma_addr_t foe_phys;
++ 	node = get_node_by_phandle(dti->dt, phandle);
++@@ -1796,7 +1893,7 @@ WARNING(graph_endpoint, check_graph_endp
++ static struct check *check_table[] = {
++ 	&duplicate_node_names, &duplicate_property_names,
++ 	&node_name_chars, &node_name_format, &property_name_chars,
++-	&name_is_string, &name_properties,
+++	&name_is_string, &name_properties, &node_name_vs_property_name,
++ 
++ 	&duplicate_label,
++ 
++@@ -1804,7 +1901,7 @@ static struct check *check_table[] = {
++ 	&phandle_references, &path_references,
++ 	&omit_unused_nodes,
++ 
++-	&address_cells_is_cell, &size_cells_is_cell, &interrupt_cells_is_cell,
+++	&address_cells_is_cell, &size_cells_is_cell,
++ 	&device_type_is_string, &model_is_string, &status_is_string,
++ 	&label_is_string,
++ 
++@@ -1839,26 +1936,43 @@ static struct check *check_table[] = {
++ 	&chosen_node_is_root, &chosen_node_bootargs, &chosen_node_stdout_path,
++ 
++ 	&clocks_property,
+++	&clocks_is_cell,
++ 	&cooling_device_property,
+++	&cooling_device_is_cell,
++ 	&dmas_property,
+++	&dmas_is_cell,
++ 	&hwlocks_property,
+++	&hwlocks_is_cell,
++ 	&interrupts_extended_property,
+++	&interrupts_extended_is_cell,
++ 	&io_channels_property,
+++	&io_channels_is_cell,
++ 	&iommus_property,
+++	&iommus_is_cell,
++ 	&mboxes_property,
+++	&mboxes_is_cell,
++ 	&msi_parent_property,
+++	&msi_parent_is_cell,
++ 	&mux_controls_property,
+++	&mux_controls_is_cell,
++ 	&phys_property,
+++	&phys_is_cell,
++ 	&power_domains_property,
+++	&power_domains_is_cell,
++ 	&pwms_property,
+++	&pwms_is_cell,
++ 	&resets_property,
+++	&resets_is_cell,
++ 	&sound_dai_property,
+++	&sound_dai_is_cell,
++ 	&thermal_sensors_property,
+++	&thermal_sensors_is_cell,
++ 
++ 	&deprecated_gpio_property,
++ 	&gpios_property,
++ 	&interrupts_property,
++ 	&interrupt_provider,
+++	&interrupt_map,
++ 
++ 	&alias_paths,
++ 
++@@ -1882,7 +1996,7 @@ static void enable_warning_error(struct
++ 
++ static void disable_warning_error(struct check *c, bool warn, bool error)
++ {
++-	int i;
+++	unsigned int i;
 + 
-++	u16 foe_check_time[MTK_PPE_ENTRIES];
-++	struct hlist_head foe_flow[MTK_PPE_ENTRIES / 2];
-++
-+ 	void *acct_table;
-+ };
++ 	/* Lowering level, also lower it for things this is the prereq
++ 	 * for */
++@@ -1903,7 +2017,7 @@ static void disable_warning_error(struct
 + 
-+-struct mtk_ppe *mtk_ppe_init(struct device *dev, void __iomem *base, int version);
-++struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base, int version);
-+ int mtk_ppe_start(struct mtk_ppe *ppe);
-+ int mtk_ppe_stop(struct mtk_ppe *ppe);
++ void parse_checks_option(bool warn, bool error, const char *arg)
++ {
++-	int i;
+++	unsigned int i;
++ 	const char *name = arg;
++ 	bool enable = true;
 + 
-++void __mtk_ppe_check_skb(struct mtk_ppe *ppe, struct sk_buff *skb, u16 hash);
++@@ -1930,7 +2044,7 @@ void parse_checks_option(bool warn, bool
++ 
++ void process_checks(bool force, struct dt_info *dti)
++ {
++-	int i;
+++	unsigned int i;
++ 	int error = 0;
++ 
++ 	for (i = 0; i < ARRAY_SIZE(check_table); i++) {
++--- a/scripts/dtc/dtc-lexer.l
+++++ b/scripts/dtc/dtc-lexer.l
++@@ -57,7 +57,7 @@ static void PRINTF(1, 2) lexical_error(c
++ 			push_input_file(name);
++ 		}
++ 
++-<*>^"#"(line)?[ \t]+[0-9]+[ \t]+{STRING}([ \t]+[0-9]+)? {
+++<*>^"#"(line)?[ \t]+[0-9]+[ \t]+{STRING}([ \t]+[0-9]+)* {
++ 			char *line, *fnstart, *fnend;
++ 			struct data fn;
++ 			/* skip text before line # */
++--- a/scripts/dtc/dtc.c
+++++ b/scripts/dtc/dtc.c
++@@ -12,7 +12,7 @@
++  * Command line options
++  */
++ int quiet;		/* Level of quietness */
++-int reservenum;		/* Number of memory reservation slots */
+++unsigned int reservenum;/* Number of memory reservation slots */
++ int minsize;		/* Minimum blob size */
++ int padsize;		/* Additional padding to blob */
++ int alignsize;		/* Additional padding to blob accroding to the alignsize */
++@@ -197,7 +197,7 @@ int main(int argc, char *argv[])
++ 			depname = optarg;
++ 			break;
++ 		case 'R':
++-			reservenum = strtol(optarg, NULL, 0);
+++			reservenum = strtoul(optarg, NULL, 0);
++ 			break;
++ 		case 'S':
++ 			minsize = strtol(optarg, NULL, 0);
++@@ -359,8 +359,6 @@ int main(int argc, char *argv[])
++ #endif
++ 	} else if (streq(outform, "dtb")) {
++ 		dt_to_blob(outf, dti, outversion);
++-	} else if (streq(outform, "dtbo")) {
++-		dt_to_blob(outf, dti, outversion);
++ 	} else if (streq(outform, "asm")) {
++ 		dt_to_asm(outf, dti, outversion);
++ 	} else if (streq(outform, "null")) {
++--- a/scripts/dtc/dtc.h
+++++ b/scripts/dtc/dtc.h
++@@ -35,7 +35,7 @@
++  * Command line options
++  */
++ extern int quiet;		/* Level of quietness */
++-extern int reservenum;		/* Number of memory reservation slots */
+++extern unsigned int reservenum;	/* Number of memory reservation slots */
++ extern int minsize;		/* Minimum blob size */
++ extern int padsize;		/* Additional padding to blob */
++ extern int alignsize;		/* Additional padding to blob accroding to the alignsize */
++@@ -51,6 +51,11 @@ extern int annotate;		/* annotate .dts w
++ 
++ typedef uint32_t cell_t;
++ 
+++static inline bool phandle_is_valid(cell_t phandle)
+++{
+++	return phandle != 0 && phandle != ~0U;
+++}
 ++
-+ static inline void
-+-mtk_foe_entry_clear(struct mtk_ppe *ppe, u16 hash)
-++mtk_ppe_check_skb(struct mtk_ppe *ppe, struct sk_buff *skb, u16 hash)
++ static inline uint16_t dtb_ld16(const void *p)
 + {
-+-	ppe->foe_table[hash].ib1 = 0;
-+-	dma_wmb();
-++	u16 now, diff;
++ 	const uint8_t *bp = (const uint8_t *)p;
++@@ -86,6 +91,16 @@ static inline uint64_t dtb_ld64(const vo
++ #define streq(a, b)	(strcmp((a), (b)) == 0)
++ #define strstarts(s, prefix)	(strncmp((s), (prefix), strlen(prefix)) == 0)
++ #define strprefixeq(a, n, b)	(strlen(b) == (n) && (memcmp(a, b, n) == 0))
+++static inline bool strends(const char *str, const char *suffix)
+++{
+++	unsigned int len, suffix_len;
 ++
-++	if (!ppe)
-++		return;
+++	len = strlen(str);
+++	suffix_len = strlen(suffix);
+++	if (len < suffix_len)
+++		return false;
+++	return streq(str + len - suffix_len, suffix);
+++}
++ 
++ #define ALIGN(x, a)	(((x) + (a) - 1) & ~((a) - 1))
++ 
++@@ -101,6 +116,12 @@ enum markertype {
++ 	TYPE_UINT64,
++ 	TYPE_STRING,
++ };
 ++
-++	now = (u16)jiffies;
-++	diff = now - ppe->foe_check_time[hash];
-++	if (diff < HZ / 10)
-++		return;
+++static inline bool is_type_marker(enum markertype type)
+++{
+++	return type >= TYPE_UINT8;
+++}
 ++
-++	ppe->foe_check_time[hash] = now;
-++	__mtk_ppe_check_skb(ppe, skb, hash);
-+ }
++ extern const char *markername(enum markertype markertype);
 + 
-+ static inline int
-+@@ -282,8 +307,9 @@ int mtk_foe_entry_set_vlan(struct mtk_fo
-+ int mtk_foe_entry_set_pppoe(struct mtk_foe_entry *entry, int sid);
-+ int mtk_foe_entry_set_wdma(struct mtk_foe_entry *entry, int wdma_idx, int txq,
-+ 			   int bss, int wcid);
-+-int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_foe_entry *entry,
-+-			 u16 timestamp);
-++int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
-++void mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
-++int mtk_foe_entry_idle_time(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
-+ int mtk_ppe_debugfs_init(struct mtk_ppe *ppe);
++ struct  marker {
++@@ -125,7 +146,22 @@ struct data {
++ 	for_each_marker(m) \
++ 		if ((m)->type == (t))
 + 
-+ #endif
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
-+@@ -42,13 +42,6 @@ struct mtk_flow_data {
-+ 	} pppoe;
-+ };
++-size_t type_marker_length(struct marker *m);
+++static inline struct marker *next_type_marker(struct marker *m)
+++{
+++	for_each_marker(m)
+++		if (is_type_marker(m->type))
+++			break;
+++	return m;
+++}
+++
+++static inline size_t type_marker_length(struct marker *m)
+++{
+++	struct marker *next = next_type_marker(m->next);
+++
+++	if (next)
+++		return next->offset - m->offset;
+++	return 0;
+++}
 + 
-+-struct mtk_flow_entry {
-+-	struct rhash_head node;
-+-	unsigned long cookie;
-+-	u16 hash;
-+-	s8 wed_index;
-+-};
-+-
-+ static const struct rhashtable_params mtk_flow_ht_params = {
-+ 	.head_offset = offsetof(struct mtk_flow_entry, node),
-+ 	.key_offset = offsetof(struct mtk_flow_entry, cookie),
-+@@ -56,12 +49,6 @@ static const struct rhashtable_params mt
-+ 	.automatic_shrinking = true,
-+ };
++ void data_free(struct data d);
 + 
-+-static u32
-+-mtk_eth_timestamp(struct mtk_eth *eth)
-+-{
-+-	return mtk_r32(eth, 0x0010) & MTK_FOE_IB1_BIND_TIMESTAMP;
-+-}
-+-
-+ static int
-+ mtk_flow_set_ipv4_addr(struct mtk_foe_entry *foe, struct mtk_flow_data *data,
-+ 		       bool egress)
-+@@ -237,10 +224,8 @@ mtk_flow_offload_replace(struct mtk_eth
-+ 	int offload_type = 0;
-+ 	int wed_index = -1;
-+ 	u16 addr_type = 0;
-+-	u32 timestamp;
-+ 	u8 l4proto = 0;
-+ 	int err = 0;
-+-	int hash;
-+ 	int i;
++--- a/scripts/dtc/flattree.c
+++++ b/scripts/dtc/flattree.c
++@@ -124,7 +124,8 @@ static void asm_emit_cell(void *e, cell_
++ {
++ 	FILE *f = e;
 + 
-+ 	if (rhashtable_lookup(&eth->flow_table, &f->cookie, mtk_flow_ht_params))
-+@@ -410,23 +395,21 @@ mtk_flow_offload_replace(struct mtk_eth
-+ 		return -ENOMEM;
++-	fprintf(f, "\t.byte 0x%02x; .byte 0x%02x; .byte 0x%02x; .byte 0x%02x\n",
+++	fprintf(f, "\t.byte\t0x%02x\n" "\t.byte\t0x%02x\n"
+++		"\t.byte\t0x%02x\n" "\t.byte\t0x%02x\n",
++ 		(val >> 24) & 0xff, (val >> 16) & 0xff,
++ 		(val >> 8) & 0xff, val & 0xff);
++ }
++@@ -134,9 +135,9 @@ static void asm_emit_string(void *e, con
++ 	FILE *f = e;
 + 
-+ 	entry->cookie = f->cookie;
-+-	timestamp = mtk_eth_timestamp(eth);
-+-	hash = mtk_foe_entry_commit(eth->ppe, &foe, timestamp);
-+-	if (hash < 0) {
-+-		err = hash;
-++	memcpy(&entry->data, &foe, sizeof(entry->data));
-++	entry->wed_index = wed_index;
-++
-++	if (mtk_foe_entry_commit(eth->ppe, entry) < 0)
-+ 		goto free;
-+-	}
++ 	if (len != 0)
++-		fprintf(f, "\t.string\t\"%.*s\"\n", len, str);
+++		fprintf(f, "\t.asciz\t\"%.*s\"\n", len, str);
++ 	else
++-		fprintf(f, "\t.string\t\"%s\"\n", str);
+++		fprintf(f, "\t.asciz\t\"%s\"\n", str);
++ }
 + 
-+-	entry->hash = hash;
-+-	entry->wed_index = wed_index;
-+ 	err = rhashtable_insert_fast(&eth->flow_table, &entry->node,
-+ 				     mtk_flow_ht_params);
-+ 	if (err < 0)
-+-		goto clear_flow;
-++		goto clear;
++ static void asm_emit_align(void *e, int a)
++@@ -295,7 +296,7 @@ static struct data flatten_reserve_list(
++ {
++ 	struct reserve_info *re;
++ 	struct data d = empty_data;
++-	int    j;
+++	unsigned int j;
++ 
++ 	for (re = reservelist; re; re = re->next) {
++ 		d = data_append_re(d, re->address, re->size);
++@@ -438,7 +439,7 @@ static void dump_stringtable_asm(FILE *f
++ 
++ 	while (p < (strbuf.val + strbuf.len)) {
++ 		len = strlen(p);
++-		fprintf(f, "\t.string \"%s\"\n", p);
+++		fprintf(f, "\t.asciz \"%s\"\n", p);
++ 		p += len+1;
++ 	}
++ }
++--- a/scripts/dtc/libfdt/fdt.c
+++++ b/scripts/dtc/libfdt/fdt.c
++@@ -90,6 +90,10 @@ int fdt_check_header(const void *fdt)
++ {
++ 	size_t hdrsize;
++ 
+++	/* The device tree must be at an 8-byte aligned address */
+++	if ((uintptr_t)fdt & 7)
+++		return -FDT_ERR_ALIGNMENT;
+++
++ 	if (fdt_magic(fdt) != FDT_MAGIC)
++ 		return -FDT_ERR_BADMAGIC;
++ 	if (!can_assume(LATEST)) {
++--- a/scripts/dtc/libfdt/fdt_rw.c
+++++ b/scripts/dtc/libfdt/fdt_rw.c
++@@ -349,7 +349,10 @@ int fdt_add_subnode_namelen(void *fdt, i
++ 		return offset;
++ 
++ 	/* Try to place the new node after the parent's properties */
++-	fdt_next_tag(fdt, parentoffset, &nextoffset); /* skip the BEGIN_NODE */
+++	tag = fdt_next_tag(fdt, parentoffset, &nextoffset);
+++	/* the fdt_subnode_offset_namelen() should ensure this never hits */
+++	if (!can_assume(LIBFDT_FLAWLESS) && (tag != FDT_BEGIN_NODE))
+++		return -FDT_ERR_INTERNAL;
++ 	do {
++ 		offset = nextoffset;
++ 		tag = fdt_next_tag(fdt, offset, &nextoffset);
++@@ -391,7 +394,9 @@ int fdt_del_node(void *fdt, int nodeoffs
++ }
++ 
++ static void fdt_packblocks_(const char *old, char *new,
++-			    int mem_rsv_size, int struct_size)
+++			    int mem_rsv_size,
+++			    int struct_size,
+++			    int strings_size)
++ {
++ 	int mem_rsv_off, struct_off, strings_off;
++ 
++@@ -406,8 +411,7 @@ static void fdt_packblocks_(const char *
++ 	fdt_set_off_dt_struct(new, struct_off);
++ 	fdt_set_size_dt_struct(new, struct_size);
++ 
++-	memmove(new + strings_off, old + fdt_off_dt_strings(old),
++-		fdt_size_dt_strings(old));
+++	memmove(new + strings_off, old + fdt_off_dt_strings(old), strings_size);
++ 	fdt_set_off_dt_strings(new, strings_off);
++ 	fdt_set_size_dt_strings(new, fdt_size_dt_strings(old));
++ }
++@@ -467,7 +471,8 @@ int fdt_open_into(const void *fdt, void
++ 			return -FDT_ERR_NOSPACE;
++ 	}
++ 
++-	fdt_packblocks_(fdt, tmp, mem_rsv_size, struct_size);
+++	fdt_packblocks_(fdt, tmp, mem_rsv_size, struct_size,
+++			fdt_size_dt_strings(fdt));
++ 	memmove(buf, tmp, newsize);
++ 
++ 	fdt_set_magic(buf, FDT_MAGIC);
++@@ -487,7 +492,8 @@ int fdt_pack(void *fdt)
++ 
++ 	mem_rsv_size = (fdt_num_mem_rsv(fdt)+1)
++ 		* sizeof(struct fdt_reserve_entry);
++-	fdt_packblocks_(fdt, fdt, mem_rsv_size, fdt_size_dt_struct(fdt));
+++	fdt_packblocks_(fdt, fdt, mem_rsv_size, fdt_size_dt_struct(fdt),
+++			fdt_size_dt_strings(fdt));
++ 	fdt_set_totalsize(fdt, fdt_data_size_(fdt));
 + 
 + 	return 0;
-+-clear_flow:
-+-	mtk_foe_entry_clear(eth->ppe, hash);
-++
-++clear:
-++	mtk_foe_entry_clear(eth->ppe, entry);
-+ free:
-+ 	kfree(entry);
-+ 	if (wed_index >= 0)
-+@@ -444,7 +427,7 @@ mtk_flow_offload_destroy(struct mtk_eth
-+ 	if (!entry)
-+ 		return -ENOENT;
++--- a/scripts/dtc/libfdt/fdt_strerror.c
+++++ b/scripts/dtc/libfdt/fdt_strerror.c
++@@ -39,6 +39,7 @@ static struct fdt_errtabent fdt_errtable
++ 	FDT_ERRTABENT(FDT_ERR_BADOVERLAY),
++ 	FDT_ERRTABENT(FDT_ERR_NOPHANDLES),
++ 	FDT_ERRTABENT(FDT_ERR_BADFLAGS),
+++	FDT_ERRTABENT(FDT_ERR_ALIGNMENT),
++ };
++ #define FDT_ERRTABSIZE	((int)(sizeof(fdt_errtable) / sizeof(fdt_errtable[0])))
 + 
-+-	mtk_foe_entry_clear(eth->ppe, entry->hash);
-++	mtk_foe_entry_clear(eth->ppe, entry);
-+ 	rhashtable_remove_fast(&eth->flow_table, &entry->node,
-+ 			       mtk_flow_ht_params);
-+ 	if (entry->wed_index >= 0)
-+@@ -458,7 +441,6 @@ static int
-+ mtk_flow_offload_stats(struct mtk_eth *eth, struct flow_cls_offload *f)
++--- a/scripts/dtc/libfdt/libfdt.h
+++++ b/scripts/dtc/libfdt/libfdt.h
++@@ -131,6 +131,13 @@ uint32_t fdt_next_tag(const void *fdt, i
++  * to work even with unaligned pointers on platforms (such as ARMv5) that don't
++  * like unaligned loads and stores.
++  */
+++static inline uint16_t fdt16_ld(const fdt16_t *p)
+++{
+++	const uint8_t *bp = (const uint8_t *)p;
+++
+++	return ((uint16_t)bp[0] << 8) | bp[1];
+++}
+++
++ static inline uint32_t fdt32_ld(const fdt32_t *p)
 + {
-+ 	struct mtk_flow_entry *entry;
-+-	int timestamp;
-+ 	u32 idle;
++ 	const uint8_t *bp = (const uint8_t *)p;
++--- a/scripts/dtc/livetree.c
+++++ b/scripts/dtc/livetree.c
++@@ -526,7 +526,7 @@ struct node *get_node_by_path(struct nod
++ 	p = strchr(path, '/');
++ 
++ 	for_each_child(tree, child) {
++-		if (p && strprefixeq(path, p - path, child->name))
+++		if (p && strprefixeq(path, (size_t)(p - path), child->name))
++ 			return get_node_by_path(child, p+1);
++ 		else if (!p && streq(path, child->name))
++ 			return child;
++@@ -559,7 +559,7 @@ struct node *get_node_by_phandle(struct
++ {
++ 	struct node *child, *node;
 + 
-+ 	entry = rhashtable_lookup(&eth->flow_table, &f->cookie,
-+@@ -466,11 +448,7 @@ mtk_flow_offload_stats(struct mtk_eth *e
-+ 	if (!entry)
-+ 		return -ENOENT;
++-	if ((phandle == 0) || (phandle == -1)) {
+++	if (!phandle_is_valid(phandle)) {
++ 		assert(generate_fixups);
++ 		return NULL;
++ 	}
++@@ -594,7 +594,7 @@ cell_t get_node_phandle(struct node *roo
++ 	static cell_t phandle = 1; /* FIXME: ick, static local */
++ 	struct data d = empty_data;
++ 
++-	if ((node->phandle != 0) && (node->phandle != -1))
+++	if (phandle_is_valid(node->phandle))
++ 		return node->phandle;
++ 
++ 	while (get_node_by_phandle(root, phandle))
++--- a/scripts/dtc/treesource.c
+++++ b/scripts/dtc/treesource.c
++@@ -124,27 +124,6 @@ static void write_propval_int(FILE *f, c
++ 	}
++ }
 + 
-+-	timestamp = mtk_foe_entry_timestamp(eth->ppe, entry->hash);
-+-	if (timestamp < 0)
-+-		return -ETIMEDOUT;
++-static bool has_data_type_information(struct marker *m)
++-{
++-	return m->type >= TYPE_UINT8;
++-}
 +-
-+-	idle = mtk_eth_timestamp(eth) - timestamp;
-++	idle = mtk_foe_entry_idle_time(eth->ppe, entry);
-+ 	f->stats.lastused = jiffies - idle * HZ;
++-static struct marker *next_type_marker(struct marker *m)
++-{
++-	while (m && !has_data_type_information(m))
++-		m = m->next;
++-	return m;
++-}
++-
++-size_t type_marker_length(struct marker *m)
++-{
++-	struct marker *next = next_type_marker(m->next);
++-
++-	if (next)
++-		return next->offset - m->offset;
++-	return 0;
++-}
++-
++ static const char *delim_start[] = {
++ 	[TYPE_UINT8] = "[",
++ 	[TYPE_UINT16] = "/bits/ 16 <",
++@@ -229,26 +208,39 @@ static void write_propval(FILE *f, struc
++ 		size_t chunk_len = (m->next ? m->next->offset : len) - m->offset;
++ 		size_t data_len = type_marker_length(m) ? : len - m->offset;
++ 		const char *p = &prop->val.val[m->offset];
+++		struct marker *m_phandle;
++ 
++-		if (has_data_type_information(m)) {
+++		if (is_type_marker(m->type)) {
++ 			emit_type = m->type;
++ 			fprintf(f, " %s", delim_start[emit_type]);
++ 		} else if (m->type == LABEL)
++ 			fprintf(f, " %s:", m->ref);
++-		else if (m->offset)
++-			fputc(' ', f);
++ 
++-		if (emit_type == TYPE_NONE) {
++-			assert(chunk_len == 0);
+++		if (emit_type == TYPE_NONE || chunk_len == 0)
++ 			continue;
++-		}
 + 
-+ 	return 0;
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-09-net-ethernet-mtk_eth_soc-remove-bridge-flow-offload-.patch b/target/linux/generic/backport-5.15/702-v5.19-09-net-ethernet-mtk_eth_soc-remove-bridge-flow-offload-.patch
++ 		switch(emit_type) {
++ 		case TYPE_UINT16:
++ 			write_propval_int(f, p, chunk_len, 2);
++ 			break;
++ 		case TYPE_UINT32:
++-			write_propval_int(f, p, chunk_len, 4);
+++			m_phandle = prop->val.markers;
+++			for_each_marker_of_type(m_phandle, REF_PHANDLE)
+++				if (m->offset == m_phandle->offset)
+++					break;
+++
+++			if (m_phandle) {
+++				if (m_phandle->ref[0] == '/')
+++					fprintf(f, "&{%s}", m_phandle->ref);
+++				else
+++					fprintf(f, "&%s", m_phandle->ref);
+++				if (chunk_len > 4) {
+++					fputc(' ', f);
+++					write_propval_int(f, p + 4, chunk_len - 4, 4);
+++				}
+++			} else {
+++				write_propval_int(f, p, chunk_len, 4);
+++			}
++ 			break;
++ 		case TYPE_UINT64:
++ 			write_propval_int(f, p, chunk_len, 8);
++--- a/scripts/dtc/util.h
+++++ b/scripts/dtc/util.h
++@@ -13,10 +13,10 @@
++  */
++ 
++ #ifdef __GNUC__
++-#ifdef __clang__
++-#define PRINTF(i, j)	__attribute__((format (printf, i, j)))
++-#else
+++#if __GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)
++ #define PRINTF(i, j)	__attribute__((format (gnu_printf, i, j)))
+++#else
+++#define PRINTF(i, j)	__attribute__((format (printf, i, j)))
++ #endif
++ #define NORETURN	__attribute__((noreturn))
++ #else
++--- a/scripts/dtc/version_gen.h
+++++ b/scripts/dtc/version_gen.h
++@@ -1 +1 @@
++-#define DTC_VERSION "DTC 1.6.0-g183df9e9"
+++#define DTC_VERSION "DTC 1.6.1-g0a3a9d34"
++--- a/scripts/dtc/yamltree.c
+++++ b/scripts/dtc/yamltree.c
++@@ -29,11 +29,12 @@ char *yaml_error_name[] = {
++ 		    (emitter)->problem, __func__, __LINE__);		\
++ })
++ 
++-static void yaml_propval_int(yaml_emitter_t *emitter, struct marker *markers, char *data, unsigned int len, int width)
+++static void yaml_propval_int(yaml_emitter_t *emitter, struct marker *markers,
+++	char *data, unsigned int seq_offset, unsigned int len, int width)
++ {
++ 	yaml_event_t event;
++ 	void *tag;
++-	unsigned int off, start_offset = markers->offset;
+++	unsigned int off;
++ 
++ 	switch(width) {
++ 		case 1: tag = "!u8"; break;
++@@ -66,7 +67,7 @@ static void yaml_propval_int(yaml_emitte
++ 			m = markers;
++ 			is_phandle = false;
++ 			for_each_marker_of_type(m, REF_PHANDLE) {
++-				if (m->offset == (start_offset + off)) {
+++				if (m->offset == (seq_offset + off)) {
++ 					is_phandle = true;
++ 					break;
++ 				}
++@@ -114,6 +115,7 @@ static void yaml_propval(yaml_emitter_t
++ 	yaml_event_t event;
++ 	unsigned int len = prop->val.len;
++ 	struct marker *m = prop->val.markers;
+++	struct marker *markers = prop->val.markers;
++ 
++ 	/* Emit the property name */
++ 	yaml_scalar_event_initialize(&event, NULL,
++@@ -151,19 +153,19 @@ static void yaml_propval(yaml_emitter_t
++ 
++ 		switch(m->type) {
++ 		case TYPE_UINT16:
++-			yaml_propval_int(emitter, m, data, chunk_len, 2);
+++			yaml_propval_int(emitter, markers, data, m->offset, chunk_len, 2);
++ 			break;
++ 		case TYPE_UINT32:
++-			yaml_propval_int(emitter, m, data, chunk_len, 4);
+++			yaml_propval_int(emitter, markers, data, m->offset, chunk_len, 4);
++ 			break;
++ 		case TYPE_UINT64:
++-			yaml_propval_int(emitter, m, data, chunk_len, 8);
+++			yaml_propval_int(emitter, markers, data, m->offset, chunk_len, 8);
++ 			break;
++ 		case TYPE_STRING:
++ 			yaml_propval_string(emitter, data, chunk_len);
++ 			break;
++ 		default:
++-			yaml_propval_int(emitter, m, data, chunk_len, 1);
+++			yaml_propval_int(emitter, markers, data, m->offset, chunk_len, 1);
++ 			break;
++ 		}
++ 	}
+diff --git a/target/linux/generic/backport-5.15/300-v5.18-pinctrl-qcom-Return--EINVAL-for-setting-affinity-if-no-IRQ-parent.patch b/target/linux/generic/backport-5.15/300-v5.18-pinctrl-qcom-Return--EINVAL-for-setting-affinity-if-no-IRQ-parent.patch
 new file mode 100644
-index 0000000000..2ff0b341f9
+index 0000000000..18a8752a18
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-09-net-ethernet-mtk_eth_soc-remove-bridge-flow-offload-.patch
-@@ -0,0 +1,44 @@
-+From: Felix Fietkau <nbd@nbd.name>
-+Date: Mon, 21 Feb 2022 15:55:19 +0100
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: remove bridge flow offload
-+ type entry support
++++ b/target/linux/generic/backport-5.15/300-v5.18-pinctrl-qcom-Return--EINVAL-for-setting-affinity-if-no-IRQ-parent.patch
+@@ -0,0 +1,48 @@
++From: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
++To: linus.walleij@linaro.org
++Cc: bjorn.andersson@linaro.org, dianders@chromium.org,
++        linux-arm-msm@vger.kernel.org, linux-gpio@vger.kernel.org,
++        linux-kernel@vger.kernel.org,
++        Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
++Subject: [PATCH] pinctrl: qcom: Return -EINVAL for setting affinity if no IRQ
++ parent
++Date: Thu, 13 Jan 2022 21:56:17 +0530
++Message-Id: <20220113162617.131697-1-manivannan.sadhasivam@linaro.org>
 +
-+According to MediaTek, this feature is not supported in current hardware
++The MSM GPIO IRQ controller relies on the parent IRQ controller to set the
++CPU affinity for the IRQ. And this is only valid if there is any wakeup
++parent available and defined in DT.
 +
-+Signed-off-by: Felix Fietkau <nbd@nbd.name>
++For the case of no parent IRQ controller defined in DT,
++msm_gpio_irq_set_affinity() and msm_gpio_irq_set_vcpu_affinity() should
++return -EINVAL instead of 0 as the affinity can't be set.
++
++Otherwise, below warning will be printed by genirq:
++
++genirq: irq_chip msmgpio did not update eff. affinity mask of irq 70
++
++Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
 +---
++ drivers/pinctrl/qcom/pinctrl-msm.c | 4 ++--
++ 1 file changed, 2 insertions(+), 2 deletions(-)
 +
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
-+@@ -84,13 +84,6 @@ static u32 mtk_ppe_hash_entry(struct mtk
-+ 	u32 hash;
++--- a/drivers/pinctrl/qcom/pinctrl-msm.c
+++++ b/drivers/pinctrl/qcom/pinctrl-msm.c
++@@ -1157,7 +1157,7 @@ static int msm_gpio_irq_set_affinity(str
++ 	if (d->parent_data && test_bit(d->hwirq, pctrl->skip_wake_irqs))
++ 		return irq_chip_set_affinity_parent(d, dest, force);
 + 
-+ 	switch (FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, e->ib1)) {
-+-		case MTK_PPE_PKT_TYPE_BRIDGE:
-+-			hv1 = e->bridge.src_mac_lo;
-+-			hv1 ^= ((e->bridge.src_mac_hi & 0xffff) << 16);
-+-			hv2 = e->bridge.src_mac_hi >> 16;
-+-			hv2 ^= e->bridge.dest_mac_lo;
-+-			hv3 = e->bridge.dest_mac_hi;
-+-			break;
-+ 		case MTK_PPE_PKT_TYPE_IPV4_ROUTE:
-+ 		case MTK_PPE_PKT_TYPE_IPV4_HNAPT:
-+ 			hv1 = e->ipv4.orig.ports;
-+@@ -572,7 +565,6 @@ int mtk_ppe_start(struct mtk_ppe *ppe)
-+ 	      MTK_PPE_FLOW_CFG_IP4_NAT |
-+ 	      MTK_PPE_FLOW_CFG_IP4_NAPT |
-+ 	      MTK_PPE_FLOW_CFG_IP4_DSLITE |
-+-	      MTK_PPE_FLOW_CFG_L2_BRIDGE |
-+ 	      MTK_PPE_FLOW_CFG_IP4_NAT_FRAG;
-+ 	ppe_w32(ppe, MTK_PPE_FLOW_CFG, val);
++-	return 0;
+++	return -EINVAL;
++ }
 + 
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c
-+@@ -32,7 +32,6 @@ static const char *mtk_foe_pkt_type_str(
-+ 	static const char * const type_str[] = {
-+ 		[MTK_PPE_PKT_TYPE_IPV4_HNAPT] = "IPv4 5T",
-+ 		[MTK_PPE_PKT_TYPE_IPV4_ROUTE] = "IPv4 3T",
-+-		[MTK_PPE_PKT_TYPE_BRIDGE] = "L2",
-+ 		[MTK_PPE_PKT_TYPE_IPV4_DSLITE] = "DS-LITE",
-+ 		[MTK_PPE_PKT_TYPE_IPV6_ROUTE_3T] = "IPv6 3T",
-+ 		[MTK_PPE_PKT_TYPE_IPV6_ROUTE_5T] = "IPv6 5T",
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-10-net-ethernet-mtk_eth_soc-support-creating-mac-addres.patch b/target/linux/generic/backport-5.15/702-v5.19-10-net-ethernet-mtk_eth_soc-support-creating-mac-addres.patch
++ static int msm_gpio_irq_set_vcpu_affinity(struct irq_data *d, void *vcpu_info)
++@@ -1168,7 +1168,7 @@ static int msm_gpio_irq_set_vcpu_affinit
++ 	if (d->parent_data && test_bit(d->hwirq, pctrl->skip_wake_irqs))
++ 		return irq_chip_set_vcpu_affinity_parent(d, vcpu_info);
++ 
++-	return 0;
+++	return -EINVAL;
++ }
++ 
++ static void msm_gpio_irq_handler(struct irq_desc *desc)
+diff --git a/target/linux/generic/backport-5.15/301-v5.16-soc-qcom-smem-Support-reserved-memory-description.patch b/target/linux/generic/backport-5.15/301-v5.16-soc-qcom-smem-Support-reserved-memory-description.patch
 new file mode 100644
-index 0000000000..209c65e66a
+index 0000000000..ee0bf9309f
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-10-net-ethernet-mtk_eth_soc-support-creating-mac-addres.patch
-@@ -0,0 +1,553 @@
-+From: Felix Fietkau <nbd@nbd.name>
-+Date: Wed, 23 Feb 2022 10:56:34 +0100
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: support creating mac
-+ address based offload entries
-+
-+This will be used to implement a limited form of bridge offloading.
-+Since the hardware does not support flow table entries with just source
-+and destination MAC address, the driver has to emulate it.
-+
-+The hardware automatically creates entries entries for incoming flows, even
-+when they are bridged instead of routed, and reports when packets for these
-+flows have reached the minimum PPS rate for offloading.
-+
-+After this happens, we look up the L2 flow offload entry based on the MAC
-+header and fill in the output routing information in the flow table.
-+The dynamically created per-flow entries are automatically removed when
-+either the hardware flowtable entry expires, is replaced, or if the offload
-+rule they belong to is removed
-+
-+Signed-off-by: Felix Fietkau <nbd@nbd.name>
++++ b/target/linux/generic/backport-5.15/301-v5.16-soc-qcom-smem-Support-reserved-memory-description.patch
+@@ -0,0 +1,166 @@
++From b5af64fceb04dc298c5e69c517b4d83893ff060b Mon Sep 17 00:00:00 2001
++From: Bjorn Andersson <bjorn.andersson@linaro.org>
++Date: Thu, 30 Sep 2021 11:21:10 -0700
++Subject: [PATCH 1/1] soc: qcom: smem: Support reserved-memory description
++
++Practically all modern Qualcomm platforms has a single reserved-memory
++region for SMEM. So rather than having to describe SMEM in the form of a
++node with a reference to a reserved-memory node, allow the SMEM device
++to be instantiated directly from the reserved-memory node.
++
++The current means of falling back to dereferencing the "memory-region"
++is kept as a fallback, if it's determined that the SMEM node is a
++reserved-memory node.
++
++The "qcom,smem" compatible is added to the reserved_mem_matches list, to
++allow the reserved-memory device to be probed.
++
++In order to retain the readability of the code, the resolution of
++resources is split from the actual ioremapping.
++
++Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
++Acked-by: Rob Herring <robh@kernel.org>
++Reviewed-by: Vladimir Zapolskiy <vladimir.zapolskiy@linaro.org>
++Link: https://lore.kernel.org/r/20210930182111.57353-4-bjorn.andersson@linaro.org
 +---
-+
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
-+@@ -6,12 +6,22 @@
-+ #include <linux/iopoll.h>
-+ #include <linux/etherdevice.h>
++ drivers/of/platform.c   |  1 +
++ drivers/soc/qcom/smem.c | 57 ++++++++++++++++++++++++++++-------------
++ 2 files changed, 40 insertions(+), 18 deletions(-)
++
++--- a/drivers/of/platform.c
+++++ b/drivers/of/platform.c
++@@ -509,6 +509,7 @@ EXPORT_SYMBOL_GPL(of_platform_default_po
++ static const struct of_device_id reserved_mem_matches[] = {
++ 	{ .compatible = "qcom,rmtfs-mem" },
++ 	{ .compatible = "qcom,cmd-db" },
+++	{ .compatible = "qcom,smem" },
++ 	{ .compatible = "ramoops" },
++ 	{ .compatible = "nvmem-rmem" },
++ 	{}
++--- a/drivers/soc/qcom/smem.c
+++++ b/drivers/soc/qcom/smem.c
++@@ -9,6 +9,7 @@
++ #include <linux/module.h>
++ #include <linux/of.h>
++ #include <linux/of_address.h>
+++#include <linux/of_reserved_mem.h>
 + #include <linux/platform_device.h>
-++#include <linux/if_ether.h>
-++#include <linux/if_vlan.h>
-++#include <net/dsa.h>
-+ #include "mtk_eth_soc.h"
-+ #include "mtk_ppe.h"
-+ #include "mtk_ppe_regs.h"
++ #include <linux/sizes.h>
++ #include <linux/slab.h>
++@@ -240,7 +241,7 @@ static const u8 SMEM_INFO_MAGIC[] = { 0x
++  * @size:	size of the memory region
++  */
++ struct smem_region {
++-	u32 aux_base;
+++	phys_addr_t aux_base;
++ 	void __iomem *virt_base;
++ 	size_t size;
++ };
++@@ -499,7 +500,7 @@ static void *qcom_smem_get_global(struct
++ 	for (i = 0; i < smem->num_regions; i++) {
++ 		region = &smem->regions[i];
++ 
++-		if (region->aux_base == aux_base || !aux_base) {
+++		if ((u32)region->aux_base == aux_base || !aux_base) {
++ 			if (size != NULL)
++ 				*size = le32_to_cpu(entry->size);
++ 			return region->virt_base + le32_to_cpu(entry->offset);
++@@ -664,7 +665,7 @@ phys_addr_t qcom_smem_virt_to_phys(void
++ 		if (p < region->virt_base + region->size) {
++ 			u64 offset = p - region->virt_base;
++ 
++-			return (phys_addr_t)region->aux_base + offset;
+++			return region->aux_base + offset;
++ 		}
++ 	}
 + 
-+ static DEFINE_SPINLOCK(ppe_lock);
++@@ -863,12 +864,12 @@ qcom_smem_enumerate_partitions(struct qc
++ 	return 0;
++ }
 + 
-++static const struct rhashtable_params mtk_flow_l2_ht_params = {
-++	.head_offset = offsetof(struct mtk_flow_entry, l2_node),
-++	.key_offset = offsetof(struct mtk_flow_entry, data.bridge),
-++	.key_len = offsetof(struct mtk_foe_bridge, key_end),
-++	.automatic_shrinking = true,
-++};
-++
-+ static void ppe_w32(struct mtk_ppe *ppe, u32 reg, u32 val)
-+ {
-+ 	writel(val, ppe->base + reg);
-+@@ -123,6 +133,9 @@ mtk_foe_entry_l2(struct mtk_foe_entry *e
++-static int qcom_smem_map_memory(struct qcom_smem *smem, struct device *dev,
++-				const char *name, int i)
+++static int qcom_smem_resolve_mem(struct qcom_smem *smem, const char *name,
+++				 struct smem_region *region)
 + {
-+ 	int type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->ib1);
+++	struct device *dev = smem->dev;
++ 	struct device_node *np;
++ 	struct resource r;
++-	resource_size_t size;
++ 	int ret;
 + 
-++	if (type == MTK_PPE_PKT_TYPE_BRIDGE)
-++		return &entry->bridge.l2;
-++
-+ 	if (type >= MTK_PPE_PKT_TYPE_IPV4_DSLITE)
-+ 		return &entry->ipv6.l2;
++ 	np = of_parse_phandle(dev->of_node, name, 0);
++@@ -881,13 +882,9 @@ static int qcom_smem_map_memory(struct q
++ 	of_node_put(np);
++ 	if (ret)
++ 		return ret;
++-	size = resource_size(&r);
 + 
-+@@ -134,6 +147,9 @@ mtk_foe_entry_ib2(struct mtk_foe_entry *
++-	smem->regions[i].virt_base = devm_ioremap_wc(dev, r.start, size);
++-	if (!smem->regions[i].virt_base)
++-		return -ENOMEM;
++-	smem->regions[i].aux_base = (u32)r.start;
++-	smem->regions[i].size = size;
+++	region->aux_base = r.start;
+++	region->size = resource_size(&r);
++ 
++ 	return 0;
++ }
++@@ -895,12 +892,14 @@ static int qcom_smem_map_memory(struct q
++ static int qcom_smem_probe(struct platform_device *pdev)
 + {
-+ 	int type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->ib1);
++ 	struct smem_header *header;
+++	struct reserved_mem *rmem;
++ 	struct qcom_smem *smem;
++ 	size_t array_size;
++ 	int num_regions;
++ 	int hwlock_id;
++ 	u32 version;
++ 	int ret;
+++	int i;
 + 
-++	if (type == MTK_PPE_PKT_TYPE_BRIDGE)
-++		return &entry->bridge.ib2;
-++
-+ 	if (type >= MTK_PPE_PKT_TYPE_IPV4_DSLITE)
-+ 		return &entry->ipv6.ib2;
++ 	num_regions = 1;
++ 	if (of_find_property(pdev->dev.of_node, "qcom,rpm-msg-ram", NULL))
++@@ -914,13 +913,35 @@ static int qcom_smem_probe(struct platfo
++ 	smem->dev = &pdev->dev;
++ 	smem->num_regions = num_regions;
 + 
-+@@ -168,7 +184,12 @@ int mtk_foe_entry_prepare(struct mtk_foe
-+ 	if (type == MTK_PPE_PKT_TYPE_IPV6_ROUTE_3T)
-+ 		entry->ipv6.ports = ports_pad;
++-	ret = qcom_smem_map_memory(smem, &pdev->dev, "memory-region", 0);
++-	if (ret)
++-		return ret;
++-
++-	if (num_regions > 1 && (ret = qcom_smem_map_memory(smem, &pdev->dev,
++-					"qcom,rpm-msg-ram", 1)))
++-		return ret;
+++	rmem = of_reserved_mem_lookup(pdev->dev.of_node);
+++	if (rmem) {
+++		smem->regions[0].aux_base = rmem->base;
+++		smem->regions[0].size = rmem->size;
+++	} else {
+++		/*
+++		 * Fall back to the memory-region reference, if we're not a
+++		 * reserved-memory node.
+++		 */
+++		ret = qcom_smem_resolve_mem(smem, "memory-region", &smem->regions[0]);
+++		if (ret)
+++			return ret;
+++	}
+++
+++	if (num_regions > 1) {
+++		ret = qcom_smem_resolve_mem(smem, "qcom,rpm-msg-ram", &smem->regions[1]);
+++		if (ret)
+++			return ret;
+++	}
+++
+++	for (i = 0; i < num_regions; i++) {
+++		smem->regions[i].virt_base = devm_ioremap_wc(&pdev->dev,
+++							     smem->regions[i].aux_base,
+++							     smem->regions[i].size);
+++		if (!smem->regions[i].virt_base) {
+++			dev_err(&pdev->dev, "failed to remap %pa\n", &smem->regions[i].aux_base);
+++			return -ENOMEM;
+++		}
+++	}
 + 
-+-	if (type >= MTK_PPE_PKT_TYPE_IPV4_DSLITE) {
-++	if (type == MTK_PPE_PKT_TYPE_BRIDGE) {
-++		ether_addr_copy(entry->bridge.src_mac, src_mac);
-++		ether_addr_copy(entry->bridge.dest_mac, dest_mac);
-++		entry->bridge.ib2 = val;
-++		l2 = &entry->bridge.l2;
-++	} else if (type >= MTK_PPE_PKT_TYPE_IPV4_DSLITE) {
-+ 		entry->ipv6.ib2 = val;
-+ 		l2 = &entry->ipv6.l2;
-+ 	} else {
-+@@ -372,12 +393,96 @@ mtk_flow_entry_match(struct mtk_flow_ent
-+ }
++ 	header = smem->regions[0].virt_base;
++ 	if (le32_to_cpu(header->initialized) != 1 ||
+diff --git a/target/linux/generic/backport-5.15/330-v5.16-01-MIPS-kernel-proc-add-CPU-option-reporting.patch b/target/linux/generic/backport-5.15/330-v5.16-01-MIPS-kernel-proc-add-CPU-option-reporting.patch
+new file mode 100644
+index 0000000000..c66a3f11b4
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/330-v5.16-01-MIPS-kernel-proc-add-CPU-option-reporting.patch
+@@ -0,0 +1,162 @@
++From 626bfa03729959ea9917181fb3d8ffaa1594d02a Mon Sep 17 00:00:00 2001
++From: Hauke Mehrtens <hauke@hauke-m.de>
++Date: Wed, 13 Oct 2021 22:40:18 -0700
++Subject: [PATCH 1/1] MIPS: kernel: proc: add CPU option reporting
++
++Many MIPS CPUs have optional CPU features which are not activated for
++all CPU cores. Print the CPU options, which are implemented in the core,
++in /proc/cpuinfo. This makes it possible to see which features are
++supported and which are not supported. This should cover all standard
++MIPS extensions. Before, it only printed information about the main MIPS
++ASEs.
++
++Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
++
++Changes from original patch[0]:
++- Remove cpu_has_6k_cache and cpu_has_8k_cache due to commit 6ce91ba8589a
++  ("MIPS: Remove cpu_has_6k_cache and cpu_has_8k_cache in cpu_cache_init()")
++- Add new options: mac2008_only, ftlbparex, gsexcex, mmid, mm_sysad,
++  mm_full
++- Use seq_puts instead of seq_printf as suggested by checkpatch
++- Minor commit message reword
++
++[0]: https://lore.kernel.org/linux-mips/20181223225224.23042-1-hauke@hauke-m.de/
++
++Signed-off-by: Ilya Lipnitskiy <ilya.lipnitskiy@gmail.com>
++Acked-by: Hauke Mehrtens <hauke@hauke-m.de>
++Signed-off-by: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
++---
++ arch/mips/kernel/proc.c | 122 ++++++++++++++++++++++++++++++++++++++++
++ 1 file changed, 122 insertions(+)
++
++--- a/arch/mips/kernel/proc.c
+++++ b/arch/mips/kernel/proc.c
++@@ -138,6 +138,128 @@ static int show_cpuinfo(struct seq_file
++ 		seq_printf(m, "micromips kernel\t: %s\n",
++ 		      (read_c0_config3() & MIPS_CONF3_ISA_OE) ?  "yes" : "no");
++ 	}
+++
+++	seq_puts(m, "Options implemented\t:");
+++	if (cpu_has_tlb)
+++		seq_puts(m, " tlb");
+++	if (cpu_has_ftlb)
+++		seq_puts(m, " ftlb");
+++	if (cpu_has_tlbinv)
+++		seq_puts(m, " tlbinv");
+++	if (cpu_has_segments)
+++		seq_puts(m, " segments");
+++	if (cpu_has_rixiex)
+++		seq_puts(m, " rixiex");
+++	if (cpu_has_ldpte)
+++		seq_puts(m, " ldpte");
+++	if (cpu_has_maar)
+++		seq_puts(m, " maar");
+++	if (cpu_has_rw_llb)
+++		seq_puts(m, " rw_llb");
+++	if (cpu_has_4kex)
+++		seq_puts(m, " 4kex");
+++	if (cpu_has_3k_cache)
+++		seq_puts(m, " 3k_cache");
+++	if (cpu_has_4k_cache)
+++		seq_puts(m, " 4k_cache");
+++	if (cpu_has_tx39_cache)
+++		seq_puts(m, " tx39_cache");
+++	if (cpu_has_octeon_cache)
+++		seq_puts(m, " octeon_cache");
+++	if (cpu_has_fpu)
+++		seq_puts(m, " fpu");
+++	if (cpu_has_32fpr)
+++		seq_puts(m, " 32fpr");
+++	if (cpu_has_cache_cdex_p)
+++		seq_puts(m, " cache_cdex_p");
+++	if (cpu_has_cache_cdex_s)
+++		seq_puts(m, " cache_cdex_s");
+++	if (cpu_has_prefetch)
+++		seq_puts(m, " prefetch");
+++	if (cpu_has_mcheck)
+++		seq_puts(m, " mcheck");
+++	if (cpu_has_ejtag)
+++		seq_puts(m, " ejtag");
+++	if (cpu_has_llsc)
+++		seq_puts(m, " llsc");
+++	if (cpu_has_guestctl0ext)
+++		seq_puts(m, " guestctl0ext");
+++	if (cpu_has_guestctl1)
+++		seq_puts(m, " guestctl1");
+++	if (cpu_has_guestctl2)
+++		seq_puts(m, " guestctl2");
+++	if (cpu_has_guestid)
+++		seq_puts(m, " guestid");
+++	if (cpu_has_drg)
+++		seq_puts(m, " drg");
+++	if (cpu_has_rixi)
+++		seq_puts(m, " rixi");
+++	if (cpu_has_lpa)
+++		seq_puts(m, " lpa");
+++	if (cpu_has_mvh)
+++		seq_puts(m, " mvh");
+++	if (cpu_has_vtag_icache)
+++		seq_puts(m, " vtag_icache");
+++	if (cpu_has_dc_aliases)
+++		seq_puts(m, " dc_aliases");
+++	if (cpu_has_ic_fills_f_dc)
+++		seq_puts(m, " ic_fills_f_dc");
+++	if (cpu_has_pindexed_dcache)
+++		seq_puts(m, " pindexed_dcache");
+++	if (cpu_has_userlocal)
+++		seq_puts(m, " userlocal");
+++	if (cpu_has_nofpuex)
+++		seq_puts(m, " nofpuex");
+++	if (cpu_has_vint)
+++		seq_puts(m, " vint");
+++	if (cpu_has_veic)
+++		seq_puts(m, " veic");
+++	if (cpu_has_inclusive_pcaches)
+++		seq_puts(m, " inclusive_pcaches");
+++	if (cpu_has_perf_cntr_intr_bit)
+++		seq_puts(m, " perf_cntr_intr_bit");
+++	if (cpu_has_ufr)
+++		seq_puts(m, " ufr");
+++	if (cpu_has_fre)
+++		seq_puts(m, " fre");
+++	if (cpu_has_cdmm)
+++		seq_puts(m, " cdmm");
+++	if (cpu_has_small_pages)
+++		seq_puts(m, " small_pages");
+++	if (cpu_has_nan_legacy)
+++		seq_puts(m, " nan_legacy");
+++	if (cpu_has_nan_2008)
+++		seq_puts(m, " nan_2008");
+++	if (cpu_has_ebase_wg)
+++		seq_puts(m, " ebase_wg");
+++	if (cpu_has_badinstr)
+++		seq_puts(m, " badinstr");
+++	if (cpu_has_badinstrp)
+++		seq_puts(m, " badinstrp");
+++	if (cpu_has_contextconfig)
+++		seq_puts(m, " contextconfig");
+++	if (cpu_has_perf)
+++		seq_puts(m, " perf");
+++	if (cpu_has_mac2008_only)
+++		seq_puts(m, " mac2008_only");
+++	if (cpu_has_ftlbparex)
+++		seq_puts(m, " ftlbparex");
+++	if (cpu_has_gsexcex)
+++		seq_puts(m, " gsexcex");
+++	if (cpu_has_shared_ftlb_ram)
+++		seq_puts(m, " shared_ftlb_ram");
+++	if (cpu_has_shared_ftlb_entries)
+++		seq_puts(m, " shared_ftlb_entries");
+++	if (cpu_has_mipsmt_pertccounters)
+++		seq_puts(m, " mipsmt_pertccounters");
+++	if (cpu_has_mmid)
+++		seq_puts(m, " mmid");
+++	if (cpu_has_mm_sysad)
+++		seq_puts(m, " mm_sysad");
+++	if (cpu_has_mm_full)
+++		seq_puts(m, " mm_full");
+++	seq_puts(m, "\n");
+++
++ 	seq_printf(m, "shadow register sets\t: %d\n",
++ 		      cpu_data[n].srsets);
++ 	seq_printf(m, "kscratch registers\t: %d\n",
+diff --git a/target/linux/generic/backport-5.15/330-v5.16-02-MIPS-Fix-using-smp_processor_id-in-preemptible-in-sh.patch b/target/linux/generic/backport-5.15/330-v5.16-02-MIPS-Fix-using-smp_processor_id-in-preemptible-in-sh.patch
+new file mode 100644
+index 0000000000..6caf7d06d4
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/330-v5.16-02-MIPS-Fix-using-smp_processor_id-in-preemptible-in-sh.patch
+@@ -0,0 +1,62 @@
++From 1cab5bd69eb1f995ced2d7576cb15f8a8941fd85 Mon Sep 17 00:00:00 2001
++From: Tiezhu Yang <yangtiezhu@loongson.cn>
++Date: Thu, 25 Nov 2021 19:39:32 +0800
++Subject: [PATCH 1/1] MIPS: Fix using smp_processor_id() in preemptible in
++ show_cpuinfo()
++
++There exists the following issue under DEBUG_PREEMPT:
++
++ BUG: using smp_processor_id() in preemptible [00000000] code: systemd/1
++ caller is show_cpuinfo+0x460/0xea0
++ ...
++ Call Trace:
++ [<ffffffff8020f0dc>] show_stack+0x94/0x128
++ [<ffffffff80e6cab4>] dump_stack_lvl+0x94/0xd8
++ [<ffffffff80e74c5c>] check_preemption_disabled+0x104/0x110
++ [<ffffffff802209c8>] show_cpuinfo+0x460/0xea0
++ [<ffffffff80539d54>] seq_read_iter+0xfc/0x4f8
++ [<ffffffff804fcc10>] new_sync_read+0x110/0x1b8
++ [<ffffffff804ff57c>] vfs_read+0x1b4/0x1d0
++ [<ffffffff804ffb18>] ksys_read+0xd0/0x110
++ [<ffffffff8021c090>] syscall_common+0x34/0x58
++
++We can see the following call trace:
++ show_cpuinfo()
++   cpu_has_fpu
++     current_cpu_data
++       smp_processor_id()
++
++ $ addr2line -f -e vmlinux 0xffffffff802209c8
++ show_cpuinfo
++ arch/mips/kernel/proc.c:188
++
++ $ head -188 arch/mips/kernel/proc.c | tail -1
++	 if (cpu_has_fpu)
++
++ arch/mips/include/asm/cpu-features.h
++ #  define cpu_has_fpu		(current_cpu_data.options & MIPS_CPU_FPU)
++
++ arch/mips/include/asm/cpu-info.h
++ #define current_cpu_data cpu_data[smp_processor_id()]
++
++Based on the above analysis, fix the issue by using raw_cpu_has_fpu
++which calls raw_smp_processor_id() in show_cpuinfo().
++
++Fixes: 626bfa037299 ("MIPS: kernel: proc: add CPU option reporting")
++Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
++Signed-off-by: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
++---
++ arch/mips/kernel/proc.c | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
++
++--- a/arch/mips/kernel/proc.c
+++++ b/arch/mips/kernel/proc.c
++@@ -166,7 +166,7 @@ static int show_cpuinfo(struct seq_file
++ 		seq_puts(m, " tx39_cache");
++ 	if (cpu_has_octeon_cache)
++ 		seq_puts(m, " octeon_cache");
++-	if (cpu_has_fpu)
+++	if (raw_cpu_has_fpu)
++ 		seq_puts(m, " fpu");
++ 	if (cpu_has_32fpr)
++ 		seq_puts(m, " 32fpr");
+diff --git a/target/linux/generic/backport-5.15/331-v5.19-mtd-spinand-Add-support-for-XTX-XT26G0xA.patch b/target/linux/generic/backport-5.15/331-v5.19-mtd-spinand-Add-support-for-XTX-XT26G0xA.patch
+new file mode 100644
+index 0000000000..541d247542
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/331-v5.19-mtd-spinand-Add-support-for-XTX-XT26G0xA.patch
+@@ -0,0 +1,186 @@
++From f4c5c7f9d2e5ab005d57826b740b694b042a737c Mon Sep 17 00:00:00 2001
++From: Felix Matouschek <felix@matouschek.org>
++Date: Mon, 18 Apr 2022 15:28:03 +0200
++Subject: [PATCH 1/1] mtd: spinand: Add support for XTX XT26G0xA
++
++Add support for XTX Technology XT26G01AXXXXX, XTX26G02AXXXXX and
++XTX26G04AXXXXX SPI NAND.
++
++These are 3V, 1G/2G/4Gbit serial SLC NAND flash devices with on-die ECC
++(8bit strength per 512bytes).
++
++Tested on Teltonika RUTX10 flashed with OpenWrt.
++
++Links:
++  - http://www.xtxtech.com/download/?AId=225
++  - https://datasheet.lcsc.com/szlcsc/2005251034_XTX-XT26G01AWSEGA_C558841.pdf
++Signed-off-by: Felix Matouschek <felix@matouschek.org>
++Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Link: https://lore.kernel.org/linux-mtd/20220418132803.664103-1-felix@matouschek.org
++---
++ drivers/mtd/nand/spi/Makefile |   2 +-
++ drivers/mtd/nand/spi/core.c   |   1 +
++ drivers/mtd/nand/spi/xtx.c    | 129 ++++++++++++++++++++++++++++++++++
++ include/linux/mtd/spinand.h   |   1 +
++ 4 files changed, 132 insertions(+), 1 deletion(-)
++ create mode 100644 drivers/mtd/nand/spi/xtx.c
++
++--- a/drivers/mtd/nand/spi/Makefile
+++++ b/drivers/mtd/nand/spi/Makefile
++@@ -1,3 +1,3 @@
++ # SPDX-License-Identifier: GPL-2.0
++-spinand-objs := core.o gigadevice.o macronix.o micron.o paragon.o toshiba.o winbond.o
+++spinand-objs := core.o gigadevice.o macronix.o micron.o paragon.o toshiba.o winbond.o xtx.o
++ obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
++--- a/drivers/mtd/nand/spi/core.c
+++++ b/drivers/mtd/nand/spi/core.c
++@@ -902,6 +902,7 @@ static const struct spinand_manufacturer
++ 	&paragon_spinand_manufacturer,
++ 	&toshiba_spinand_manufacturer,
++ 	&winbond_spinand_manufacturer,
+++	&xtx_spinand_manufacturer,
++ };
 + 
-+ static void
-++__mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
-++{
-++	struct hlist_head *head;
-++	struct hlist_node *tmp;
++ static int spinand_manufacturer_match(struct spinand_device *spinand,
++--- /dev/null
+++++ b/drivers/mtd/nand/spi/xtx.c
++@@ -0,0 +1,129 @@
+++// SPDX-License-Identifier: GPL-2.0
+++/*
+++ * Author:
+++ * Felix Matouschek <felix@matouschek.org>
+++ */
 ++
-++	if (entry->type == MTK_FLOW_TYPE_L2) {
-++		rhashtable_remove_fast(&ppe->l2_flows, &entry->l2_node,
-++				       mtk_flow_l2_ht_params);
+++#include <linux/device.h>
+++#include <linux/kernel.h>
+++#include <linux/mtd/spinand.h>
 ++
-++		head = &entry->l2_flows;
-++		hlist_for_each_entry_safe(entry, tmp, head, l2_data.list)
-++			__mtk_foe_entry_clear(ppe, entry);
-++		return;
-++	}
+++#define SPINAND_MFR_XTX	0x0B
 ++
-++	hlist_del_init(&entry->list);
-++	if (entry->hash != 0xffff) {
-++		ppe->foe_table[entry->hash].ib1 &= ~MTK_FOE_IB1_STATE;
-++		ppe->foe_table[entry->hash].ib1 |= FIELD_PREP(MTK_FOE_IB1_STATE,
-++							      MTK_FOE_STATE_BIND);
-++		dma_wmb();
-++	}
-++	entry->hash = 0xffff;
+++#define XT26G0XA_STATUS_ECC_MASK	GENMASK(5, 2)
+++#define XT26G0XA_STATUS_ECC_NO_DETECTED	(0 << 2)
+++#define XT26G0XA_STATUS_ECC_8_CORRECTED	(3 << 4)
+++#define XT26G0XA_STATUS_ECC_UNCOR_ERROR	(2 << 4)
 ++
-++	if (entry->type != MTK_FLOW_TYPE_L2_SUBFLOW)
-++		return;
+++static SPINAND_OP_VARIANTS(read_cache_variants,
+++		SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0),
+++		SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
+++		SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
+++		SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
+++		SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
+++		SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
 ++
-++	hlist_del_init(&entry->l2_data.list);
-++	kfree(entry);
-++}
+++static SPINAND_OP_VARIANTS(write_cache_variants,
+++		SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
+++		SPINAND_PROG_LOAD(true, 0, NULL, 0));
 ++
-++static int __mtk_foe_entry_idle_time(struct mtk_ppe *ppe, u32 ib1)
+++static SPINAND_OP_VARIANTS(update_cache_variants,
+++		SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
+++		SPINAND_PROG_LOAD(false, 0, NULL, 0));
+++
+++static int xt26g0xa_ooblayout_ecc(struct mtd_info *mtd, int section,
+++				   struct mtd_oob_region *region)
 ++{
-++	u16 timestamp;
-++	u16 now;
+++	if (section)
+++		return -ERANGE;
 ++
-++	now = mtk_eth_timestamp(ppe->eth) & MTK_FOE_IB1_BIND_TIMESTAMP;
-++	timestamp = ib1 & MTK_FOE_IB1_BIND_TIMESTAMP;
+++	region->offset = 48;
+++	region->length = 16;
 ++
-++	if (timestamp > now)
-++		return MTK_FOE_IB1_BIND_TIMESTAMP + 1 - timestamp + now;
-++	else
-++		return now - timestamp;
+++	return 0;
 ++}
 ++
-++static void
-++mtk_flow_entry_update_l2(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
+++static int xt26g0xa_ooblayout_free(struct mtd_info *mtd, int section,
+++				   struct mtd_oob_region *region)
 ++{
-++	struct mtk_flow_entry *cur;
-++	struct mtk_foe_entry *hwe;
-++	struct hlist_node *tmp;
-++	int idle;
+++	if (section)
+++		return -ERANGE;
 ++
-++	idle = __mtk_foe_entry_idle_time(ppe, entry->data.ib1);
-++	hlist_for_each_entry_safe(cur, tmp, &entry->l2_flows, l2_data.list) {
-++		int cur_idle;
-++		u32 ib1;
+++	region->offset = 1;
+++	region->length = 47;
 ++
-++		hwe = &ppe->foe_table[cur->hash];
-++		ib1 = READ_ONCE(hwe->ib1);
+++	return 0;
+++}
 ++
-++		if (FIELD_GET(MTK_FOE_IB1_STATE, ib1) != MTK_FOE_STATE_BIND) {
-++			cur->hash = 0xffff;
-++			__mtk_foe_entry_clear(ppe, cur);
-++			continue;
-++		}
+++static const struct mtd_ooblayout_ops xt26g0xa_ooblayout = {
+++	.ecc = xt26g0xa_ooblayout_ecc,
+++	.free = xt26g0xa_ooblayout_free,
+++};
 ++
-++		cur_idle = __mtk_foe_entry_idle_time(ppe, ib1);
-++		if (cur_idle >= idle)
-++			continue;
+++static int xt26g0xa_ecc_get_status(struct spinand_device *spinand,
+++					 u8 status)
+++{
+++	status = status & XT26G0XA_STATUS_ECC_MASK;
 ++
-++		idle = cur_idle;
-++		entry->data.ib1 &= ~MTK_FOE_IB1_BIND_TIMESTAMP;
-++		entry->data.ib1 |= hwe->ib1 & MTK_FOE_IB1_BIND_TIMESTAMP;
+++	switch (status) {
+++	case XT26G0XA_STATUS_ECC_NO_DETECTED:
+++		return 0;
+++	case XT26G0XA_STATUS_ECC_8_CORRECTED:
+++		return 8;
+++	case XT26G0XA_STATUS_ECC_UNCOR_ERROR:
+++		return -EBADMSG;
+++	default:
+++		break;
 ++	}
+++
+++	/* At this point values greater than (2 << 4) are invalid  */
+++	if (status > XT26G0XA_STATUS_ECC_UNCOR_ERROR)
+++		return -EINVAL;
+++
+++	/* (1 << 2) through (7 << 2) are 1-7 corrected errors */
+++	return status >> 2;
 ++}
 ++
-++static void
-+ mtk_flow_entry_update(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
-+ {
-+ 	struct mtk_foe_entry *hwe;
-+ 	struct mtk_foe_entry foe;
-+ 
-+ 	spin_lock_bh(&ppe_lock);
+++static const struct spinand_info xtx_spinand_table[] = {
+++	SPINAND_INFO("XT26G01A",
+++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE1),
+++		     NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
+++		     NAND_ECCREQ(8, 512),
+++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+++					      &write_cache_variants,
+++					      &update_cache_variants),
+++		     SPINAND_HAS_QE_BIT,
+++		     SPINAND_ECCINFO(&xt26g0xa_ooblayout,
+++				     xt26g0xa_ecc_get_status)),
+++	SPINAND_INFO("XT26G02A",
+++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE2),
+++		     NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1),
+++		     NAND_ECCREQ(8, 512),
+++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+++					      &write_cache_variants,
+++					      &update_cache_variants),
+++		     SPINAND_HAS_QE_BIT,
+++		     SPINAND_ECCINFO(&xt26g0xa_ooblayout,
+++				     xt26g0xa_ecc_get_status)),
+++	SPINAND_INFO("XT26G04A",
+++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE3),
+++		     NAND_MEMORG(1, 2048, 64, 128, 2048, 40, 1, 1, 1),
+++		     NAND_ECCREQ(8, 512),
+++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+++					      &write_cache_variants,
+++					      &update_cache_variants),
+++		     SPINAND_HAS_QE_BIT,
+++		     SPINAND_ECCINFO(&xt26g0xa_ooblayout,
+++				     xt26g0xa_ecc_get_status)),
+++};
 ++
-++	if (entry->type == MTK_FLOW_TYPE_L2) {
-++		mtk_flow_entry_update_l2(ppe, entry);
-++		goto out;
-++	}
+++static const struct spinand_manufacturer_ops xtx_spinand_manuf_ops = {
+++};
 ++
-+ 	if (entry->hash == 0xffff)
-+ 		goto out;
+++const struct spinand_manufacturer xtx_spinand_manufacturer = {
+++	.id = SPINAND_MFR_XTX,
+++	.name = "XTX",
+++	.chips = xtx_spinand_table,
+++	.nchips = ARRAY_SIZE(xtx_spinand_table),
+++	.ops = &xtx_spinand_manuf_ops,
+++};
++--- a/include/linux/mtd/spinand.h
+++++ b/include/linux/mtd/spinand.h
++@@ -266,6 +266,7 @@ extern const struct spinand_manufacturer
++ extern const struct spinand_manufacturer paragon_spinand_manufacturer;
++ extern const struct spinand_manufacturer toshiba_spinand_manufacturer;
++ extern const struct spinand_manufacturer winbond_spinand_manufacturer;
+++extern const struct spinand_manufacturer xtx_spinand_manufacturer;
 + 
-+@@ -419,21 +524,28 @@ __mtk_foe_entry_commit(struct mtk_ppe *p
-+ void mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
-+ {
-+ 	spin_lock_bh(&ppe_lock);
-+-	hlist_del_init(&entry->list);
-+-	if (entry->hash != 0xffff) {
-+-		ppe->foe_table[entry->hash].ib1 &= ~MTK_FOE_IB1_STATE;
-+-		ppe->foe_table[entry->hash].ib1 |= FIELD_PREP(MTK_FOE_IB1_STATE,
-+-							      MTK_FOE_STATE_BIND);
-+-		dma_wmb();
-+-	}
-+-	entry->hash = 0xffff;
-++	__mtk_foe_entry_clear(ppe, entry);
-+ 	spin_unlock_bh(&ppe_lock);
++ /**
++  * struct spinand_op_variants - SPI NAND operation variants
+diff --git a/target/linux/generic/backport-5.15/344-v5.18-01-phy-marvell-phy-mvebu-a3700-comphy-Remove-port-from-.patch b/target/linux/generic/backport-5.15/344-v5.18-01-phy-marvell-phy-mvebu-a3700-comphy-Remove-port-from-.patch
+new file mode 100644
+index 0000000000..d6c4b4fd1e
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/344-v5.18-01-phy-marvell-phy-mvebu-a3700-comphy-Remove-port-from-.patch
+@@ -0,0 +1,219 @@
++From 4bf18d5a2dd02db8c5b16a2cfae513510506df5b Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
++Date: Thu, 3 Feb 2022 22:44:40 +0100
++Subject: [PATCH 1/2] phy: marvell: phy-mvebu-a3700-comphy: Remove port from
++ driver configuration
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++Port number is encoded into argument for SMC call. It is zero for SATA,
++PCIe and also both USB 3.0 PHYs. It is non-zero only for Ethernet PHY
++(incorrectly called SGMII) on lane 0. Ethernet PHY on lane 1 also uses zero
++port number.
++
++So construct "port" bits for SMC call argument can be constructed directly
++from PHY type and lane number.
++
++Change driver code to always pass zero port number for non-ethernet PHYs
++and for ethernet PHYs determinate port number from lane number. This
++simplifies the driver.
++
++As port number from DT PHY configuration is not used anymore, remove whole
++driver code which parses it. This also simplifies the driver.
++
++Signed-off-by: Pali Rohár <pali@kernel.org>
++Signed-off-by: Marek Behún <kabel@kernel.org>
++Reviewed-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Link: https://lore.kernel.org/r/20220203214444.1508-2-kabel@kernel.org
++Signed-off-by: Vinod Koul <vkoul@kernel.org>
++---
++ drivers/phy/marvell/phy-mvebu-a3700-comphy.c | 62 +++++++++-----------
++ 1 file changed, 29 insertions(+), 33 deletions(-)
++
++--- a/drivers/phy/marvell/phy-mvebu-a3700-comphy.c
+++++ b/drivers/phy/marvell/phy-mvebu-a3700-comphy.c
++@@ -20,7 +20,6 @@
++ #include <linux/platform_device.h>
++ 
++ #define MVEBU_A3700_COMPHY_LANES		3
++-#define MVEBU_A3700_COMPHY_PORTS		2
++ 
++ /* COMPHY Fast SMC function identifiers */
++ #define COMPHY_SIP_POWER_ON			0x82000001
++@@ -45,51 +44,47 @@
++ #define COMPHY_FW_NET(mode, idx, speed)		(COMPHY_FW_MODE(mode) | \
++ 						 ((idx) << 8) |	\
++ 						 ((speed) << 2))
++-#define COMPHY_FW_PCIE(mode, idx, speed, width)	(COMPHY_FW_NET(mode, idx, speed) | \
+++#define COMPHY_FW_PCIE(mode, speed, width)	(COMPHY_FW_NET(mode, 0, speed) | \
++ 						 ((width) << 18))
++ 
++ struct mvebu_a3700_comphy_conf {
++ 	unsigned int lane;
++ 	enum phy_mode mode;
++ 	int submode;
++-	unsigned int port;
++ 	u32 fw_mode;
++ };
++ 
++-#define MVEBU_A3700_COMPHY_CONF(_lane, _mode, _smode, _port, _fw)	\
+++#define MVEBU_A3700_COMPHY_CONF(_lane, _mode, _smode, _fw)		\
++ 	{								\
++ 		.lane = _lane,						\
++ 		.mode = _mode,						\
++ 		.submode = _smode,					\
++-		.port = _port,						\
++ 		.fw_mode = _fw,						\
++ 	}
++ 
++-#define MVEBU_A3700_COMPHY_CONF_GEN(_lane, _mode, _port, _fw) \
++-	MVEBU_A3700_COMPHY_CONF(_lane, _mode, PHY_INTERFACE_MODE_NA, _port, _fw)
+++#define MVEBU_A3700_COMPHY_CONF_GEN(_lane, _mode, _fw) \
+++	MVEBU_A3700_COMPHY_CONF(_lane, _mode, PHY_INTERFACE_MODE_NA, _fw)
++ 
++-#define MVEBU_A3700_COMPHY_CONF_ETH(_lane, _smode, _port, _fw) \
++-	MVEBU_A3700_COMPHY_CONF(_lane, PHY_MODE_ETHERNET, _smode, _port, _fw)
+++#define MVEBU_A3700_COMPHY_CONF_ETH(_lane, _smode, _fw) \
+++	MVEBU_A3700_COMPHY_CONF(_lane, PHY_MODE_ETHERNET, _smode, _fw)
++ 
++ static const struct mvebu_a3700_comphy_conf mvebu_a3700_comphy_modes[] = {
++ 	/* lane 0 */
++-	MVEBU_A3700_COMPHY_CONF_GEN(0, PHY_MODE_USB_HOST_SS, 0,
+++	MVEBU_A3700_COMPHY_CONF_GEN(0, PHY_MODE_USB_HOST_SS,
++ 				    COMPHY_FW_MODE_USB3H),
++-	MVEBU_A3700_COMPHY_CONF_ETH(0, PHY_INTERFACE_MODE_SGMII, 1,
+++	MVEBU_A3700_COMPHY_CONF_ETH(0, PHY_INTERFACE_MODE_SGMII,
++ 				    COMPHY_FW_MODE_SGMII),
++-	MVEBU_A3700_COMPHY_CONF_ETH(0, PHY_INTERFACE_MODE_2500BASEX, 1,
+++	MVEBU_A3700_COMPHY_CONF_ETH(0, PHY_INTERFACE_MODE_2500BASEX,
++ 				    COMPHY_FW_MODE_2500BASEX),
++ 	/* lane 1 */
++-	MVEBU_A3700_COMPHY_CONF_GEN(1, PHY_MODE_PCIE, 0,
++-				    COMPHY_FW_MODE_PCIE),
++-	MVEBU_A3700_COMPHY_CONF_ETH(1, PHY_INTERFACE_MODE_SGMII, 0,
+++	MVEBU_A3700_COMPHY_CONF_GEN(1, PHY_MODE_PCIE, COMPHY_FW_MODE_PCIE),
+++	MVEBU_A3700_COMPHY_CONF_ETH(1, PHY_INTERFACE_MODE_SGMII,
++ 				    COMPHY_FW_MODE_SGMII),
++-	MVEBU_A3700_COMPHY_CONF_ETH(1, PHY_INTERFACE_MODE_2500BASEX, 0,
+++	MVEBU_A3700_COMPHY_CONF_ETH(1, PHY_INTERFACE_MODE_2500BASEX,
++ 				    COMPHY_FW_MODE_2500BASEX),
++ 	/* lane 2 */
++-	MVEBU_A3700_COMPHY_CONF_GEN(2, PHY_MODE_SATA, 0,
++-				    COMPHY_FW_MODE_SATA),
++-	MVEBU_A3700_COMPHY_CONF_GEN(2, PHY_MODE_USB_HOST_SS, 0,
+++	MVEBU_A3700_COMPHY_CONF_GEN(2, PHY_MODE_SATA, COMPHY_FW_MODE_SATA),
+++	MVEBU_A3700_COMPHY_CONF_GEN(2, PHY_MODE_USB_HOST_SS,
++ 				    COMPHY_FW_MODE_USB3H),
++ };
++ 
++@@ -98,7 +93,6 @@ struct mvebu_a3700_comphy_lane {
++ 	unsigned int id;
++ 	enum phy_mode mode;
++ 	int submode;
++-	int port;
++ };
++ 
++ static int mvebu_a3700_comphy_smc(unsigned long function, unsigned long lane,
++@@ -120,7 +114,7 @@ static int mvebu_a3700_comphy_smc(unsign
++ 	}
 + }
 + 
-++static int
-++mtk_foe_entry_commit_l2(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
-++{
-++	entry->type = MTK_FLOW_TYPE_L2;
-++
-++	return rhashtable_insert_fast(&ppe->l2_flows, &entry->l2_node,
-++				      mtk_flow_l2_ht_params);
-++}
-++
-+ int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
++-static int mvebu_a3700_comphy_get_fw_mode(int lane, int port,
+++static int mvebu_a3700_comphy_get_fw_mode(int lane,
++ 					  enum phy_mode mode,
++ 					  int submode)
 + {
-+-	u32 hash = mtk_ppe_hash_entry(&entry->data);
-++	int type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->data.ib1);
-++	u32 hash;
-++
-++	if (type == MTK_PPE_PKT_TYPE_BRIDGE)
-++		return mtk_foe_entry_commit_l2(ppe, entry);
++@@ -132,7 +126,6 @@ static int mvebu_a3700_comphy_get_fw_mod
 + 
-++	hash = mtk_ppe_hash_entry(&entry->data);
-+ 	entry->hash = 0xffff;
-+ 	spin_lock_bh(&ppe_lock);
-+ 	hlist_add_head(&entry->list, &ppe->foe_flow[hash / 2]);
-+@@ -442,18 +554,72 @@ int mtk_foe_entry_commit(struct mtk_ppe
-+ 	return 0;
-+ }
++ 	for (i = 0; i < n; i++) {
++ 		if (mvebu_a3700_comphy_modes[i].lane == lane &&
++-		    mvebu_a3700_comphy_modes[i].port == port &&
++ 		    mvebu_a3700_comphy_modes[i].mode == mode &&
++ 		    mvebu_a3700_comphy_modes[i].submode == submode)
++ 			break;
++@@ -153,7 +146,7 @@ static int mvebu_a3700_comphy_set_mode(s
++ 	if (submode == PHY_INTERFACE_MODE_1000BASEX)
++ 		submode = PHY_INTERFACE_MODE_SGMII;
++ 
++-	fw_mode = mvebu_a3700_comphy_get_fw_mode(lane->id, lane->port, mode,
+++	fw_mode = mvebu_a3700_comphy_get_fw_mode(lane->id, mode,
++ 						 submode);
++ 	if (fw_mode < 0) {
++ 		dev_err(lane->dev, "invalid COMPHY mode\n");
++@@ -172,9 +165,10 @@ static int mvebu_a3700_comphy_power_on(s
++ 	struct mvebu_a3700_comphy_lane *lane = phy_get_drvdata(phy);
++ 	u32 fw_param;
++ 	int fw_mode;
+++	int fw_port;
++ 	int ret;
 + 
-++static void
-++mtk_foe_entry_commit_subflow(struct mtk_ppe *ppe, struct mtk_flow_entry *entry,
-++			     u16 hash)
-++{
-++	struct mtk_flow_entry *flow_info;
-++	struct mtk_foe_entry foe, *hwe;
-++	struct mtk_foe_mac_info *l2;
-++	u32 ib1_mask = MTK_FOE_IB1_PACKET_TYPE | MTK_FOE_IB1_UDP;
-++	int type;
++-	fw_mode = mvebu_a3700_comphy_get_fw_mode(lane->id, lane->port,
+++	fw_mode = mvebu_a3700_comphy_get_fw_mode(lane->id,
++ 						 lane->mode, lane->submode);
++ 	if (fw_mode < 0) {
++ 		dev_err(lane->dev, "invalid COMPHY mode\n");
++@@ -191,17 +185,18 @@ static int mvebu_a3700_comphy_power_on(s
++ 		fw_param = COMPHY_FW_MODE(fw_mode);
++ 		break;
++ 	case PHY_MODE_ETHERNET:
+++		fw_port = (lane->id == 0) ? 1 : 0;
++ 		switch (lane->submode) {
++ 		case PHY_INTERFACE_MODE_SGMII:
++ 			dev_dbg(lane->dev, "set lane %d to SGMII mode\n",
++ 				lane->id);
++-			fw_param = COMPHY_FW_NET(fw_mode, lane->port,
+++			fw_param = COMPHY_FW_NET(fw_mode, fw_port,
++ 						 COMPHY_FW_SPEED_1_25G);
++ 			break;
++ 		case PHY_INTERFACE_MODE_2500BASEX:
++ 			dev_dbg(lane->dev, "set lane %d to 2500BASEX mode\n",
++ 				lane->id);
++-			fw_param = COMPHY_FW_NET(fw_mode, lane->port,
+++			fw_param = COMPHY_FW_NET(fw_mode, fw_port,
++ 						 COMPHY_FW_SPEED_3_125G);
++ 			break;
++ 		default:
++@@ -212,8 +207,7 @@ static int mvebu_a3700_comphy_power_on(s
++ 		break;
++ 	case PHY_MODE_PCIE:
++ 		dev_dbg(lane->dev, "set lane %d to PCIe mode\n", lane->id);
++-		fw_param = COMPHY_FW_PCIE(fw_mode, lane->port,
++-					  COMPHY_FW_SPEED_5G,
+++		fw_param = COMPHY_FW_PCIE(fw_mode, COMPHY_FW_SPEED_5G,
++ 					  phy->attrs.bus_width);
++ 		break;
++ 	default:
++@@ -247,17 +241,20 @@ static struct phy *mvebu_a3700_comphy_xl
++ 					    struct of_phandle_args *args)
++ {
++ 	struct mvebu_a3700_comphy_lane *lane;
+++	unsigned int port;
++ 	struct phy *phy;
++ 
++-	if (WARN_ON(args->args[0] >= MVEBU_A3700_COMPHY_PORTS))
++-		return ERR_PTR(-EINVAL);
++-
++ 	phy = of_phy_simple_xlate(dev, args);
++ 	if (IS_ERR(phy))
++ 		return phy;
++ 
++ 	lane = phy_get_drvdata(phy);
++-	lane->port = args->args[0];
 ++
-++	flow_info = kzalloc(offsetof(struct mtk_flow_entry, l2_data.end),
-++			    GFP_ATOMIC);
-++	if (!flow_info)
-++		return;
+++	port = args->args[0];
+++	if (port != 0 && (port != 1 || lane->id != 0)) {
+++		dev_err(lane->dev, "invalid port number %u\n", port);
+++		return ERR_PTR(-EINVAL);
+++	}
++ 
++ 	return phy;
++ }
++@@ -302,7 +299,6 @@ static int mvebu_a3700_comphy_probe(stru
++ 		lane->mode = PHY_MODE_INVALID;
++ 		lane->submode = PHY_INTERFACE_MODE_NA;
++ 		lane->id = lane_id;
++-		lane->port = -1;
++ 		phy_set_drvdata(phy, lane);
++ 	}
++ 
+diff --git a/target/linux/generic/backport-5.15/344-v5.18-02-phy-marvell-phy-mvebu-a3700-comphy-Add-native-kernel.patch b/target/linux/generic/backport-5.15/344-v5.18-02-phy-marvell-phy-mvebu-a3700-comphy-Add-native-kernel.patch
+new file mode 100644
+index 0000000000..4593d14bfe
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/344-v5.18-02-phy-marvell-phy-mvebu-a3700-comphy-Add-native-kernel.patch
+@@ -0,0 +1,1552 @@
++From 934337080c6c59b75db76b180b509f218640ad48 Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
++Date: Thu, 3 Feb 2022 22:44:41 +0100
++Subject: [PATCH 2/2] phy: marvell: phy-mvebu-a3700-comphy: Add native kernel
++ implementation
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++Remove old RPC implementation and add a new native kernel implementation.
++
++The old implementation uses ARM SMC API to issue RPC calls to ARM Trusted
++Firmware which provides real implementation of PHY configuration.
++
++But older versions of ARM Trusted Firmware do not provide this PHY
++configuration functionality, simply returning: operation not supported; or
++worse, some versions provide the configuration functionality incorrectly.
++
++For example the firmware shipped in ESPRESSObin board has this older
++version of ARM Trusted Firmware and therefore SATA, USB 3.0 and PCIe
++functionality do not work with newer versions of Linux kernel.
++
++Due to the above reasons, the following commits were introduced into Linux,
++to workaround these issues by ignoring -EOPNOTSUPP error code from
++phy-mvebu-a3700-comphy driver function phy_power_on():
++
++commit 45aefe3d2251 ("ata: ahci: mvebu: Make SATA PHY optional for Armada
++3720")
++commit 3241929b67d2 ("usb: host: xhci: mvebu: make USB 3.0 PHY optional for
++Armada 3720")
++commit b0c6ae0f8948 ("PCI: aardvark: Fix initialization with old Marvell's
++Arm Trusted Firmware")
++
++Replace this RPC implementation with proper native kernel implementation,
++which is independent on the firmware. Never return -EOPNOTSUPP for proper
++arguments.
++
++This should solve multiple issues with real-world boards, where it is not
++possible or really inconvenient to change the firmware. Let's eliminate
++these issues.
++
++This implementation is ported directly from Armada 3720 comphy driver found
++in newest version of ARM Trusted Firmware source code, but with various
++fixes of register names, some added comments, some refactoring due to the
++original code not conforming to kernel standards. Also PCIe mode poweroff
++support was added here, and PHY reset support. These changes are also going
++to be sent to ARM Trusted Firmware.
++
++[ Pali did the porting from ATF.
++  I (Marek) then fixed some register names, some various other things,
++  added some comments and refactored the code to kernel standards. Also
++  fixed PHY poweroff and added PHY reset. ]
++
++Signed-off-by: Pali Rohár <pali@kernel.org>
++Acked-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Signed-off-by: Marek Behún <kabel@kernel.org>
++Link: https://lore.kernel.org/r/20220203214444.1508-3-kabel@kernel.org
++Signed-off-by: Vinod Koul <vkoul@kernel.org>
++---
++ drivers/phy/marvell/phy-mvebu-a3700-comphy.c | 1332 ++++++++++++++++--
++ 1 file changed, 1215 insertions(+), 117 deletions(-)
++
++--- a/drivers/phy/marvell/phy-mvebu-a3700-comphy.c
+++++ b/drivers/phy/marvell/phy-mvebu-a3700-comphy.c
++@@ -5,12 +5,16 @@
++  * Authors:
++  *   Evan Wang <xswang@marvell.com>
++  *   Miquèl Raynal <miquel.raynal@bootlin.com>
+++ *   Pali Rohár <pali@kernel.org>
+++ *   Marek Behún <kabel@kernel.org>
++  *
++  * Structure inspired from phy-mvebu-cp110-comphy.c written by Antoine Tenart.
++- * SMC call initial support done by Grzegorz Jaszczyk.
+++ * Comphy code from ARM Trusted Firmware ported by Pali Rohár <pali@kernel.org>
+++ * and Marek Behún <kabel@kernel.org>.
++  */
++ 
++-#include <linux/arm-smccc.h>
+++#include <linux/bitfield.h>
+++#include <linux/clk.h>
++ #include <linux/io.h>
++ #include <linux/iopoll.h>
++ #include <linux/mfd/syscon.h>
++@@ -18,103 +22,1118 @@
++ #include <linux/phy.h>
++ #include <linux/phy/phy.h>
++ #include <linux/platform_device.h>
+++#include <linux/spinlock.h>
++ 
++-#define MVEBU_A3700_COMPHY_LANES		3
+++#define PLL_SET_DELAY_US		600
+++#define COMPHY_PLL_SLEEP		1000
+++#define COMPHY_PLL_TIMEOUT		150000
 ++
-++	flow_info->l2_data.base_flow = entry;
-++	flow_info->type = MTK_FLOW_TYPE_L2_SUBFLOW;
-++	flow_info->hash = hash;
-++	hlist_add_head(&flow_info->list, &ppe->foe_flow[hash / 2]);
-++	hlist_add_head(&flow_info->l2_data.list, &entry->l2_flows);
+++/* Comphy lane2 indirect access register offset */
+++#define COMPHY_LANE2_INDIR_ADDR		0x0
+++#define COMPHY_LANE2_INDIR_DATA		0x4
 ++
-++	hwe = &ppe->foe_table[hash];
-++	memcpy(&foe, hwe, sizeof(foe));
-++	foe.ib1 &= ib1_mask;
-++	foe.ib1 |= entry->data.ib1 & ~ib1_mask;
+++/* SATA and USB3 PHY offset compared to SATA PHY */
+++#define COMPHY_LANE2_REGS_BASE		0x200
 ++
-++	l2 = mtk_foe_entry_l2(&foe);
-++	memcpy(l2, &entry->data.bridge.l2, sizeof(*l2));
+++/*
+++ * When accessing common PHY lane registers directly, we need to shift by 1,
+++ * since the registers are 16-bit.
+++ */
+++#define COMPHY_LANE_REG_DIRECT(reg)	(((reg) & 0x7FF) << 1)
+++
+++/* COMPHY registers */
+++#define COMPHY_POWER_PLL_CTRL		0x01
+++#define PU_IVREF_BIT			BIT(15)
+++#define PU_PLL_BIT			BIT(14)
+++#define PU_RX_BIT			BIT(13)
+++#define PU_TX_BIT			BIT(12)
+++#define PU_TX_INTP_BIT			BIT(11)
+++#define PU_DFE_BIT			BIT(10)
+++#define RESET_DTL_RX_BIT		BIT(9)
+++#define PLL_LOCK_BIT			BIT(8)
+++#define REF_FREF_SEL_MASK		GENMASK(4, 0)
+++#define REF_FREF_SEL_SERDES_25MHZ	FIELD_PREP(REF_FREF_SEL_MASK, 0x1)
+++#define REF_FREF_SEL_SERDES_40MHZ	FIELD_PREP(REF_FREF_SEL_MASK, 0x3)
+++#define REF_FREF_SEL_SERDES_50MHZ	FIELD_PREP(REF_FREF_SEL_MASK, 0x4)
+++#define REF_FREF_SEL_PCIE_USB3_25MHZ	FIELD_PREP(REF_FREF_SEL_MASK, 0x2)
+++#define REF_FREF_SEL_PCIE_USB3_40MHZ	FIELD_PREP(REF_FREF_SEL_MASK, 0x3)
+++#define COMPHY_MODE_MASK		GENMASK(7, 5)
+++#define COMPHY_MODE_SATA		FIELD_PREP(COMPHY_MODE_MASK, 0x0)
+++#define COMPHY_MODE_PCIE		FIELD_PREP(COMPHY_MODE_MASK, 0x3)
+++#define COMPHY_MODE_SERDES		FIELD_PREP(COMPHY_MODE_MASK, 0x4)
+++#define COMPHY_MODE_USB3		FIELD_PREP(COMPHY_MODE_MASK, 0x5)
+++
+++#define COMPHY_KVCO_CAL_CTRL		0x02
+++#define USE_MAX_PLL_RATE_BIT		BIT(12)
+++#define SPEED_PLL_MASK			GENMASK(7, 2)
+++#define SPEED_PLL_VALUE_16		FIELD_PREP(SPEED_PLL_MASK, 0x10)
+++
+++#define COMPHY_DIG_LOOPBACK_EN		0x23
+++#define SEL_DATA_WIDTH_MASK		GENMASK(11, 10)
+++#define DATA_WIDTH_10BIT		FIELD_PREP(SEL_DATA_WIDTH_MASK, 0x0)
+++#define DATA_WIDTH_20BIT		FIELD_PREP(SEL_DATA_WIDTH_MASK, 0x1)
+++#define DATA_WIDTH_40BIT		FIELD_PREP(SEL_DATA_WIDTH_MASK, 0x2)
+++#define PLL_READY_TX_BIT		BIT(4)
+++
+++#define COMPHY_SYNC_PATTERN		0x24
+++#define TXD_INVERT_BIT			BIT(10)
+++#define RXD_INVERT_BIT			BIT(11)
+++
+++#define COMPHY_SYNC_MASK_GEN		0x25
+++#define PHY_GEN_MAX_MASK		GENMASK(11, 10)
+++#define PHY_GEN_MAX_USB3_5G		FIELD_PREP(PHY_GEN_MAX_MASK, 0x1)
+++
+++#define COMPHY_ISOLATION_CTRL		0x26
+++#define PHY_ISOLATE_MODE		BIT(15)
+++
+++#define COMPHY_GEN2_SET2		0x3e
+++#define GS2_TX_SSC_AMP_MASK		GENMASK(15, 9)
+++#define GS2_TX_SSC_AMP_4128		FIELD_PREP(GS2_TX_SSC_AMP_MASK, 0x20)
+++#define GS2_VREG_RXTX_MAS_ISET_MASK	GENMASK(8, 7)
+++#define GS2_VREG_RXTX_MAS_ISET_60U	FIELD_PREP(GS2_VREG_RXTX_MAS_ISET_MASK,\
+++						   0x0)
+++#define GS2_VREG_RXTX_MAS_ISET_80U	FIELD_PREP(GS2_VREG_RXTX_MAS_ISET_MASK,\
+++						   0x1)
+++#define GS2_VREG_RXTX_MAS_ISET_100U	FIELD_PREP(GS2_VREG_RXTX_MAS_ISET_MASK,\
+++						   0x2)
+++#define GS2_VREG_RXTX_MAS_ISET_120U	FIELD_PREP(GS2_VREG_RXTX_MAS_ISET_MASK,\
+++						   0x3)
+++#define GS2_RSVD_6_0_MASK		GENMASK(6, 0)
+++
+++#define COMPHY_GEN3_SET2		0x3f
+++
+++#define COMPHY_IDLE_SYNC_EN		0x48
+++#define IDLE_SYNC_EN			BIT(12)
+++
+++#define COMPHY_MISC_CTRL0		0x4F
+++#define CLK100M_125M_EN			BIT(4)
+++#define TXDCLK_2X_SEL			BIT(6)
+++#define CLK500M_EN			BIT(7)
+++#define PHY_REF_CLK_SEL			BIT(10)
+++
+++#define COMPHY_SFT_RESET		0x52
+++#define SFT_RST				BIT(9)
+++#define SFT_RST_NO_REG			BIT(10)
+++
+++#define COMPHY_MISC_CTRL1		0x73
+++#define SEL_BITS_PCIE_FORCE		BIT(15)
+++
+++#define COMPHY_GEN2_SET3		0x112
+++#define GS3_FFE_CAP_SEL_MASK		GENMASK(3, 0)
+++#define GS3_FFE_CAP_SEL_VALUE		FIELD_PREP(GS3_FFE_CAP_SEL_MASK, 0xF)
+++
+++/* PIPE registers */
+++#define COMPHY_PIPE_LANE_CFG0		0x180
+++#define PRD_TXDEEMPH0_MASK		BIT(0)
+++#define PRD_TXMARGIN_MASK		GENMASK(3, 1)
+++#define PRD_TXSWING_MASK		BIT(4)
+++#define CFG_TX_ALIGN_POS_MASK		GENMASK(8, 5)
+++
+++#define COMPHY_PIPE_LANE_CFG1		0x181
+++#define PRD_TXDEEMPH1_MASK		BIT(15)
+++#define USE_MAX_PLL_RATE_EN		BIT(9)
+++#define TX_DET_RX_MODE			BIT(6)
+++#define GEN2_TX_DATA_DLY_MASK		GENMASK(4, 3)
+++#define GEN2_TX_DATA_DLY_DEFT		FIELD_PREP(GEN2_TX_DATA_DLY_MASK, 2)
+++#define TX_ELEC_IDLE_MODE_EN		BIT(0)
+++
+++#define COMPHY_PIPE_LANE_STAT1		0x183
+++#define TXDCLK_PCLK_EN			BIT(0)
+++
+++#define COMPHY_PIPE_LANE_CFG4		0x188
+++#define SPREAD_SPECTRUM_CLK_EN		BIT(7)
+++
+++#define COMPHY_PIPE_RST_CLK_CTRL	0x1C1
+++#define PIPE_SOFT_RESET			BIT(0)
+++#define PIPE_REG_RESET			BIT(1)
+++#define MODE_CORE_CLK_FREQ_SEL		BIT(9)
+++#define MODE_PIPE_WIDTH_32		BIT(3)
+++#define MODE_REFDIV_MASK		GENMASK(5, 4)
+++#define MODE_REFDIV_BY_4		FIELD_PREP(MODE_REFDIV_MASK, 0x2)
+++
+++#define COMPHY_PIPE_TEST_MODE_CTRL	0x1C2
+++#define MODE_MARGIN_OVERRIDE		BIT(2)
+++
+++#define COMPHY_PIPE_CLK_SRC_LO		0x1C3
+++#define MODE_CLK_SRC			BIT(0)
+++#define BUNDLE_PERIOD_SEL		BIT(1)
+++#define BUNDLE_PERIOD_SCALE_MASK	GENMASK(3, 2)
+++#define BUNDLE_SAMPLE_CTRL		BIT(4)
+++#define PLL_READY_DLY_MASK		GENMASK(7, 5)
+++#define CFG_SEL_20B			BIT(15)
+++
+++#define COMPHY_PIPE_PWR_MGM_TIM1	0x1D0
+++#define CFG_PM_OSCCLK_WAIT_MASK		GENMASK(15, 12)
+++#define CFG_PM_RXDEN_WAIT_MASK		GENMASK(11, 8)
+++#define CFG_PM_RXDEN_WAIT_1_UNIT	FIELD_PREP(CFG_PM_RXDEN_WAIT_MASK, 0x1)
+++#define CFG_PM_RXDLOZ_WAIT_MASK		GENMASK(7, 0)
+++#define CFG_PM_RXDLOZ_WAIT_7_UNIT	FIELD_PREP(CFG_PM_RXDLOZ_WAIT_MASK, 0x7)
+++#define CFG_PM_RXDLOZ_WAIT_12_UNIT	FIELD_PREP(CFG_PM_RXDLOZ_WAIT_MASK, 0xC)
 ++
-++	type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, foe.ib1);
-++	if (type == MTK_PPE_PKT_TYPE_IPV4_HNAPT)
-++		memcpy(&foe.ipv4.new, &foe.ipv4.orig, sizeof(foe.ipv4.new));
-++	else if (type >= MTK_PPE_PKT_TYPE_IPV6_ROUTE_3T && l2->etype == ETH_P_IP)
-++		l2->etype = ETH_P_IPV6;
+++/*
+++ * This register is not from PHY lane register space. It only exists in the
+++ * indirect register space, before the actual PHY lane 2 registers. So the
+++ * offset is absolute, not relative to COMPHY_LANE2_REGS_BASE.
+++ * It is used only for SATA PHY initialization.
+++ */
+++#define COMPHY_RESERVED_REG		0x0E
+++#define PHYCTRL_FRM_PIN_BIT		BIT(13)
++ 
++-/* COMPHY Fast SMC function identifiers */
++-#define COMPHY_SIP_POWER_ON			0x82000001
++-#define COMPHY_SIP_POWER_OFF			0x82000002
++-#define COMPHY_SIP_PLL_LOCK			0x82000003
++-
++-#define COMPHY_FW_MODE_SATA			0x1
++-#define COMPHY_FW_MODE_SGMII			0x2
++-#define COMPHY_FW_MODE_2500BASEX		0x3
++-#define COMPHY_FW_MODE_USB3H			0x4
++-#define COMPHY_FW_MODE_USB3D			0x5
++-#define COMPHY_FW_MODE_PCIE			0x6
++-#define COMPHY_FW_MODE_USB3			0xa
++-
++-#define COMPHY_FW_SPEED_1_25G			0 /* SGMII 1G */
++-#define COMPHY_FW_SPEED_2_5G			1
++-#define COMPHY_FW_SPEED_3_125G			2 /* 2500BASE-X */
++-#define COMPHY_FW_SPEED_5G			3
++-#define COMPHY_FW_SPEED_MAX			0x3F
++-
++-#define COMPHY_FW_MODE(mode)			((mode) << 12)
++-#define COMPHY_FW_NET(mode, idx, speed)		(COMPHY_FW_MODE(mode) | \
++-						 ((idx) << 8) |	\
++-						 ((speed) << 2))
++-#define COMPHY_FW_PCIE(mode, speed, width)	(COMPHY_FW_NET(mode, 0, speed) | \
++-						 ((width) << 18))
+++/* South Bridge PHY Configuration Registers */
+++#define COMPHY_PHY_REG(lane, reg)	(((1 - (lane)) * 0x28) + ((reg) & 0x3f))
 ++
-++	*mtk_foe_entry_ib2(&foe) = entry->data.bridge.ib2;
+++/*
+++ * lane0: USB3/GbE1 PHY Configuration 1
+++ * lane1: PCIe/GbE0 PHY Configuration 1
+++ * (used only by SGMII code)
+++ */
+++#define COMPHY_PHY_CFG1			0x0
+++#define PIN_PU_IVREF_BIT		BIT(1)
+++#define PIN_RESET_CORE_BIT		BIT(11)
+++#define PIN_RESET_COMPHY_BIT		BIT(12)
+++#define PIN_PU_PLL_BIT			BIT(16)
+++#define PIN_PU_RX_BIT			BIT(17)
+++#define PIN_PU_TX_BIT			BIT(18)
+++#define PIN_TX_IDLE_BIT			BIT(19)
+++#define GEN_RX_SEL_MASK			GENMASK(25, 22)
+++#define GEN_RX_SEL_VALUE(val)		FIELD_PREP(GEN_RX_SEL_MASK, (val))
+++#define GEN_TX_SEL_MASK			GENMASK(29, 26)
+++#define GEN_TX_SEL_VALUE(val)		FIELD_PREP(GEN_TX_SEL_MASK, (val))
+++#define SERDES_SPEED_1_25_G		0x6
+++#define SERDES_SPEED_3_125_G		0x8
+++#define PHY_RX_INIT_BIT			BIT(30)
 ++
-++	__mtk_foe_entry_commit(ppe, &foe, hash);
-++}
+++/*
+++ * lane0: USB3/GbE1 PHY Status 1
+++ * lane1: PCIe/GbE0 PHY Status 1
+++ * (used only by SGMII code)
+++ */
+++#define COMPHY_PHY_STAT1		0x18
+++#define PHY_RX_INIT_DONE_BIT		BIT(0)
+++#define PHY_PLL_READY_RX_BIT		BIT(2)
+++#define PHY_PLL_READY_TX_BIT		BIT(3)
+++
+++/* PHY Selector */
+++#define COMPHY_SELECTOR_PHY_REG			0xFC
+++/* bit0: 0: Lane1 is GbE0; 1: Lane1 is PCIe */
+++#define COMPHY_SELECTOR_PCIE_GBE0_SEL_BIT	BIT(0)
+++/* bit4: 0: Lane0 is GbE1; 1: Lane0 is USB3 */
+++#define COMPHY_SELECTOR_USB3_GBE1_SEL_BIT	BIT(4)
+++/* bit8: 0: Lane0 is USB3 instead of GbE1, Lane2 is SATA; 1: Lane2 is USB3 */
+++#define COMPHY_SELECTOR_USB3_PHY_SEL_BIT	BIT(8)
++ 
++ struct mvebu_a3700_comphy_conf {
++ 	unsigned int lane;
++ 	enum phy_mode mode;
++ 	int submode;
++-	u32 fw_mode;
++ };
++ 
++-#define MVEBU_A3700_COMPHY_CONF(_lane, _mode, _smode, _fw)		\
+++#define MVEBU_A3700_COMPHY_CONF(_lane, _mode, _smode)			\
++ 	{								\
++ 		.lane = _lane,						\
++ 		.mode = _mode,						\
++ 		.submode = _smode,					\
++-		.fw_mode = _fw,						\
++ 	}
++ 
++-#define MVEBU_A3700_COMPHY_CONF_GEN(_lane, _mode, _fw) \
++-	MVEBU_A3700_COMPHY_CONF(_lane, _mode, PHY_INTERFACE_MODE_NA, _fw)
+++#define MVEBU_A3700_COMPHY_CONF_GEN(_lane, _mode) \
+++	MVEBU_A3700_COMPHY_CONF(_lane, _mode, PHY_INTERFACE_MODE_NA)
++ 
++-#define MVEBU_A3700_COMPHY_CONF_ETH(_lane, _smode, _fw) \
++-	MVEBU_A3700_COMPHY_CONF(_lane, PHY_MODE_ETHERNET, _smode, _fw)
+++#define MVEBU_A3700_COMPHY_CONF_ETH(_lane, _smode) \
+++	MVEBU_A3700_COMPHY_CONF(_lane, PHY_MODE_ETHERNET, _smode)
++ 
++ static const struct mvebu_a3700_comphy_conf mvebu_a3700_comphy_modes[] = {
++ 	/* lane 0 */
++-	MVEBU_A3700_COMPHY_CONF_GEN(0, PHY_MODE_USB_HOST_SS,
++-				    COMPHY_FW_MODE_USB3H),
++-	MVEBU_A3700_COMPHY_CONF_ETH(0, PHY_INTERFACE_MODE_SGMII,
++-				    COMPHY_FW_MODE_SGMII),
++-	MVEBU_A3700_COMPHY_CONF_ETH(0, PHY_INTERFACE_MODE_2500BASEX,
++-				    COMPHY_FW_MODE_2500BASEX),
+++	MVEBU_A3700_COMPHY_CONF_GEN(0, PHY_MODE_USB_HOST_SS),
+++	MVEBU_A3700_COMPHY_CONF_ETH(0, PHY_INTERFACE_MODE_SGMII),
+++	MVEBU_A3700_COMPHY_CONF_ETH(0, PHY_INTERFACE_MODE_1000BASEX),
+++	MVEBU_A3700_COMPHY_CONF_ETH(0, PHY_INTERFACE_MODE_2500BASEX),
++ 	/* lane 1 */
++-	MVEBU_A3700_COMPHY_CONF_GEN(1, PHY_MODE_PCIE, COMPHY_FW_MODE_PCIE),
++-	MVEBU_A3700_COMPHY_CONF_ETH(1, PHY_INTERFACE_MODE_SGMII,
++-				    COMPHY_FW_MODE_SGMII),
++-	MVEBU_A3700_COMPHY_CONF_ETH(1, PHY_INTERFACE_MODE_2500BASEX,
++-				    COMPHY_FW_MODE_2500BASEX),
+++	MVEBU_A3700_COMPHY_CONF_GEN(1, PHY_MODE_PCIE),
+++	MVEBU_A3700_COMPHY_CONF_ETH(1, PHY_INTERFACE_MODE_SGMII),
+++	MVEBU_A3700_COMPHY_CONF_ETH(1, PHY_INTERFACE_MODE_1000BASEX),
+++	MVEBU_A3700_COMPHY_CONF_ETH(1, PHY_INTERFACE_MODE_2500BASEX),
++ 	/* lane 2 */
++-	MVEBU_A3700_COMPHY_CONF_GEN(2, PHY_MODE_SATA, COMPHY_FW_MODE_SATA),
++-	MVEBU_A3700_COMPHY_CONF_GEN(2, PHY_MODE_USB_HOST_SS,
++-				    COMPHY_FW_MODE_USB3H),
+++	MVEBU_A3700_COMPHY_CONF_GEN(2, PHY_MODE_SATA),
+++	MVEBU_A3700_COMPHY_CONF_GEN(2, PHY_MODE_USB_HOST_SS),
+++};
 ++
-+ void __mtk_ppe_check_skb(struct mtk_ppe *ppe, struct sk_buff *skb, u16 hash)
+++struct mvebu_a3700_comphy_priv {
+++	void __iomem *comphy_regs;
+++	void __iomem *lane0_phy_regs; /* USB3 and GbE1 */
+++	void __iomem *lane1_phy_regs; /* PCIe and GbE0 */
+++	void __iomem *lane2_phy_indirect; /* SATA and USB3 */
+++	spinlock_t lock; /* for PHY selector access */
+++	bool xtal_is_40m;
++ };
++ 
++ struct mvebu_a3700_comphy_lane {
+++	struct mvebu_a3700_comphy_priv *priv;
++ 	struct device *dev;
++ 	unsigned int id;
++ 	enum phy_mode mode;
++ 	int submode;
+++	bool invert_tx;
+++	bool invert_rx;
+++	bool needs_reset;
+++};
+++
+++struct gbe_phy_init_data_fix {
+++	u16 addr;
+++	u16 value;
+++};
+++
+++/* Changes to 40M1G25 mode data required for running 40M3G125 init mode */
+++static struct gbe_phy_init_data_fix gbe_phy_init_fix[] = {
+++	{ 0x005, 0x07CC }, { 0x015, 0x0000 }, { 0x01B, 0x0000 },
+++	{ 0x01D, 0x0000 }, { 0x01E, 0x0000 }, { 0x01F, 0x0000 },
+++	{ 0x020, 0x0000 }, { 0x021, 0x0030 }, { 0x026, 0x0888 },
+++	{ 0x04D, 0x0152 }, { 0x04F, 0xA020 }, { 0x050, 0x07CC },
+++	{ 0x053, 0xE9CA }, { 0x055, 0xBD97 }, { 0x071, 0x3015 },
+++	{ 0x076, 0x03AA }, { 0x07C, 0x0FDF }, { 0x0C2, 0x3030 },
+++	{ 0x0C3, 0x8000 }, { 0x0E2, 0x5550 }, { 0x0E3, 0x12A4 },
+++	{ 0x0E4, 0x7D00 }, { 0x0E6, 0x0C83 }, { 0x101, 0xFCC0 },
+++	{ 0x104, 0x0C10 }
++ };
++ 
++-static int mvebu_a3700_comphy_smc(unsigned long function, unsigned long lane,
++-				  unsigned long mode)
+++/* 40M1G25 mode init data */
+++static u16 gbe_phy_init[512] = {
+++	/* 0       1       2       3       4       5       6       7 */
+++	/*-----------------------------------------------------------*/
+++	/* 8       9       A       B       C       D       E       F */
+++	0x3110, 0xFD83, 0x6430, 0x412F, 0x82C0, 0x06FA, 0x4500, 0x6D26,	/* 00 */
+++	0xAFC0, 0x8000, 0xC000, 0x0000, 0x2000, 0x49CC, 0x0BC9, 0x2A52,	/* 08 */
+++	0x0BD2, 0x0CDE, 0x13D2, 0x0CE8, 0x1149, 0x10E0, 0x0000, 0x0000,	/* 10 */
+++	0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x4134, 0x0D2D, 0xFFFF,	/* 18 */
+++	0xFFE0, 0x4030, 0x1016, 0x0030, 0x0000, 0x0800, 0x0866, 0x0000,	/* 20 */
+++	0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,	/* 28 */
+++	0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/* 30 */
+++	0x0000, 0x0000, 0x000F, 0x6A62, 0x1988, 0x3100, 0x3100, 0x3100,	/* 38 */
+++	0x3100, 0xA708, 0x2430, 0x0830, 0x1030, 0x4610, 0xFF00, 0xFF00,	/* 40 */
+++	0x0060, 0x1000, 0x0400, 0x0040, 0x00F0, 0x0155, 0x1100, 0xA02A,	/* 48 */
+++	0x06FA, 0x0080, 0xB008, 0xE3ED, 0x5002, 0xB592, 0x7A80, 0x0001,	/* 50 */
+++	0x020A, 0x8820, 0x6014, 0x8054, 0xACAA, 0xFC88, 0x2A02, 0x45CF,	/* 58 */
+++	0x000F, 0x1817, 0x2860, 0x064F, 0x0000, 0x0204, 0x1800, 0x6000,	/* 60 */
+++	0x810F, 0x4F23, 0x4000, 0x4498, 0x0850, 0x0000, 0x000E, 0x1002,	/* 68 */
+++	0x9D3A, 0x3009, 0xD066, 0x0491, 0x0001, 0x6AB0, 0x0399, 0x3780,	/* 70 */
+++	0x0040, 0x5AC0, 0x4A80, 0x0000, 0x01DF, 0x0000, 0x0007, 0x0000,	/* 78 */
+++	0x2D54, 0x00A1, 0x4000, 0x0100, 0xA20A, 0x0000, 0x0000, 0x0000,	/* 80 */
+++	0x0000, 0x0000, 0x0000, 0x7400, 0x0E81, 0x1000, 0x1242, 0x0210,	/* 88 */
+++	0x80DF, 0x0F1F, 0x2F3F, 0x4F5F, 0x6F7F, 0x0F1F, 0x2F3F, 0x4F5F,	/* 90 */
+++	0x6F7F, 0x4BAD, 0x0000, 0x0000, 0x0800, 0x0000, 0x2400, 0xB651,	/* 98 */
+++	0xC9E0, 0x4247, 0x0A24, 0x0000, 0xAF19, 0x1004, 0x0000, 0x0000,	/* A0 */
+++	0x0000, 0x0013, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/* A8 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/* B0 */
+++	0x0000, 0x0000, 0x0000, 0x0060, 0x0000, 0x0000, 0x0000, 0x0000,	/* B8 */
+++	0x0000, 0x0000, 0x3010, 0xFA00, 0x0000, 0x0000, 0x0000, 0x0003,	/* C0 */
+++	0x1618, 0x8200, 0x8000, 0x0400, 0x050F, 0x0000, 0x0000, 0x0000,	/* C8 */
+++	0x4C93, 0x0000, 0x1000, 0x1120, 0x0010, 0x1242, 0x1242, 0x1E00,	/* D0 */
+++	0x0000, 0x0000, 0x0000, 0x00F8, 0x0000, 0x0041, 0x0800, 0x0000,	/* D8 */
+++	0x82A0, 0x572E, 0x2490, 0x14A9, 0x4E00, 0x0000, 0x0803, 0x0541,	/* E0 */
+++	0x0C15, 0x0000, 0x0000, 0x0400, 0x2626, 0x0000, 0x0000, 0x4200,	/* E8 */
+++	0x0000, 0xAA55, 0x1020, 0x0000, 0x0000, 0x5010, 0x0000, 0x0000,	/* F0 */
+++	0x0000, 0x0000, 0x5000, 0x0000, 0x0000, 0x0000, 0x02F2, 0x0000,	/* F8 */
+++	0x101F, 0xFDC0, 0x4000, 0x8010, 0x0110, 0x0006, 0x0000, 0x0000,	/*100 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*108 */
+++	0x04CF, 0x0000, 0x04CF, 0x0000, 0x04CF, 0x0000, 0x04C6, 0x0000,	/*110 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*118 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*120 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*128 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*130 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*138 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*140 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*148 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*150 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*158 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*160 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*168 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*170 */
+++	0x0000, 0x0000, 0x0000, 0x00F0, 0x08A2, 0x3112, 0x0A14, 0x0000,	/*178 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*180 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*188 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*190 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*198 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*1A0 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*1A8 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*1B0 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*1B8 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*1C0 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*1C8 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*1D0 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*1D8 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*1E0 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*1E8 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,	/*1F0 */
+++	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000	/*1F8 */
+++};
+++
+++static inline void comphy_reg_set(void __iomem *addr, u32 data, u32 mask)
 + {
-+ 	struct hlist_head *head = &ppe->foe_flow[hash / 2];
-+-	struct mtk_flow_entry *entry;
-+ 	struct mtk_foe_entry *hwe = &ppe->foe_table[hash];
-++	struct mtk_flow_entry *entry;
-++	struct mtk_foe_bridge key = {};
-++	struct ethhdr *eh;
-+ 	bool found = false;
-+-
-+-	if (hlist_empty(head))
-+-		return;
-++	u8 *tag;
++-	struct arm_smccc_res res;
++-	s32 ret;
+++	u32 val;
+++
+++	val = readl(addr);
+++	val = (val & ~mask) | (data & mask);
+++	writel(val, addr);
+++}
 + 
-+ 	spin_lock_bh(&ppe_lock);
++-	arm_smccc_smc(function, lane, mode, 0, 0, 0, 0, 0, &res);
++-	ret = res.a0;
+++static inline void comphy_reg_set16(void __iomem *addr, u16 data, u16 mask)
+++{
+++	u16 val;
++ 
++-	switch (ret) {
++-	case SMCCC_RET_SUCCESS:
++-		return 0;
++-	case SMCCC_RET_NOT_SUPPORTED:
++-		return -EOPNOTSUPP;
+++	val = readw(addr);
+++	val = (val & ~mask) | (data & mask);
+++	writew(val, addr);
+++}
 ++
-++	if (FIELD_GET(MTK_FOE_IB1_STATE, hwe->ib1) == MTK_FOE_STATE_BIND)
-++		goto out;
+++/* Used for accessing lane 2 registers (SATA/USB3 PHY) */
+++static void comphy_set_indirect(struct mvebu_a3700_comphy_priv *priv,
+++				u32 offset, u16 data, u16 mask)
+++{
+++	writel(offset,
+++	       priv->lane2_phy_indirect + COMPHY_LANE2_INDIR_ADDR);
+++	comphy_reg_set(priv->lane2_phy_indirect + COMPHY_LANE2_INDIR_DATA,
+++		       data, mask);
+++}
 ++
-+ 	hlist_for_each_entry(entry, head, list) {
-++		if (entry->type == MTK_FLOW_TYPE_L2_SUBFLOW) {
-++			if (unlikely(FIELD_GET(MTK_FOE_IB1_STATE, hwe->ib1) ==
-++				     MTK_FOE_STATE_BIND))
-++				continue;
+++static void comphy_lane_reg_set(struct mvebu_a3700_comphy_lane *lane,
+++				u16 reg, u16 data, u16 mask)
+++{
+++	if (lane->id == 2) {
+++		/* lane 2 PHY registers are accessed indirectly */
+++		comphy_set_indirect(lane->priv,
+++				    reg + COMPHY_LANE2_REGS_BASE,
+++				    data, mask);
+++	} else {
+++		void __iomem *base = lane->id == 1 ?
+++				     lane->priv->lane1_phy_regs :
+++				     lane->priv->lane0_phy_regs;
 ++
-++			entry->hash = 0xffff;
-++			__mtk_foe_entry_clear(ppe, entry);
-++			continue;
-++		}
+++		comphy_reg_set16(base + COMPHY_LANE_REG_DIRECT(reg),
+++				 data, mask);
+++	}
+++}
 ++
-+ 		if (found || !mtk_flow_entry_match(entry, hwe)) {
-+ 			if (entry->hash != 0xffff)
-+ 				entry->hash = 0xffff;
-+@@ -464,21 +630,50 @@ void __mtk_ppe_check_skb(struct mtk_ppe
-+ 		__mtk_foe_entry_commit(ppe, &entry->data, hash);
-+ 		found = true;
-+ 	}
+++static int comphy_lane_reg_poll(struct mvebu_a3700_comphy_lane *lane,
+++				u16 reg, u16 bits,
+++				ulong sleep_us, ulong timeout_us)
+++{
+++	int ret;
 ++
-++	if (found)
-++		goto out;
+++	if (lane->id == 2) {
+++		u32 data;
 ++
-++	eh = eth_hdr(skb);
-++	ether_addr_copy(key.dest_mac, eh->h_dest);
-++	ether_addr_copy(key.src_mac, eh->h_source);
-++	tag = skb->data - 2;
-++	key.vlan = 0;
-++	switch (skb->protocol) {
-++#if IS_ENABLED(CONFIG_NET_DSA)
-++	case htons(ETH_P_XDSA):
-++		if (!netdev_uses_dsa(skb->dev) ||
-++		    skb->dev->dsa_ptr->tag_ops->proto != DSA_TAG_PROTO_MTK)
-++			goto out;
+++		/* lane 2 PHY registers are accessed indirectly */
+++		writel(reg + COMPHY_LANE2_REGS_BASE,
+++		       lane->priv->lane2_phy_indirect +
+++		       COMPHY_LANE2_INDIR_ADDR);
 ++
-++		tag += 4;
-++		if (get_unaligned_be16(tag) != ETH_P_8021Q)
-++			break;
+++		ret = readl_poll_timeout(lane->priv->lane2_phy_indirect +
+++					 COMPHY_LANE2_INDIR_DATA,
+++					 data, (data & bits) == bits,
+++					 sleep_us, timeout_us);
+++	} else {
+++		void __iomem *base = lane->id == 1 ?
+++				     lane->priv->lane1_phy_regs :
+++				     lane->priv->lane0_phy_regs;
+++		u16 data;
+++
+++		ret = readw_poll_timeout(base + COMPHY_LANE_REG_DIRECT(reg),
+++					 data, (data & bits) == bits,
+++					 sleep_us, timeout_us);
+++	}
 ++
-++		fallthrough;
-++#endif
-++	case htons(ETH_P_8021Q):
-++		key.vlan = get_unaligned_be16(tag + 2) & VLAN_VID_MASK;
+++	return ret;
+++}
+++
+++static void comphy_periph_reg_set(struct mvebu_a3700_comphy_lane *lane,
+++				  u8 reg, u32 data, u32 mask)
+++{
+++	comphy_reg_set(lane->priv->comphy_regs + COMPHY_PHY_REG(lane->id, reg),
+++		       data, mask);
+++}
+++
+++static int comphy_periph_reg_poll(struct mvebu_a3700_comphy_lane *lane,
+++				  u8 reg, u32 bits,
+++				  ulong sleep_us, ulong timeout_us)
+++{
+++	u32 data;
+++
+++	return readl_poll_timeout(lane->priv->comphy_regs +
+++				  COMPHY_PHY_REG(lane->id, reg),
+++				  data, (data & bits) == bits,
+++				  sleep_us, timeout_us);
+++}
+++
+++/* PHY selector configures with corresponding modes */
+++static int
+++mvebu_a3700_comphy_set_phy_selector(struct mvebu_a3700_comphy_lane *lane)
+++{
+++	u32 old, new, clr = 0, set = 0;
+++	unsigned long flags;
+++
+++	switch (lane->mode) {
+++	case PHY_MODE_SATA:
+++		/* SATA must be in Lane2 */
+++		if (lane->id == 2)
+++			clr = COMPHY_SELECTOR_USB3_PHY_SEL_BIT;
+++		else
+++			goto error;
 ++		break;
-++	default:
+++
+++	case PHY_MODE_ETHERNET:
+++		if (lane->id == 0)
+++			clr = COMPHY_SELECTOR_USB3_GBE1_SEL_BIT;
+++		else if (lane->id == 1)
+++			clr = COMPHY_SELECTOR_PCIE_GBE0_SEL_BIT;
+++		else
+++			goto error;
 ++		break;
+++
+++	case PHY_MODE_USB_HOST_SS:
+++		if (lane->id == 2)
+++			set = COMPHY_SELECTOR_USB3_PHY_SEL_BIT;
+++		else if (lane->id == 0)
+++			set = COMPHY_SELECTOR_USB3_GBE1_SEL_BIT;
+++		else
+++			goto error;
+++		break;
+++
+++	case PHY_MODE_PCIE:
+++		/* PCIE must be in Lane1 */
+++		if (lane->id == 1)
+++			set = COMPHY_SELECTOR_PCIE_GBE0_SEL_BIT;
+++		else
+++			goto error;
+++		break;
+++
+++	default:
+++		goto error;
 ++	}
 ++
-++	entry = rhashtable_lookup_fast(&ppe->l2_flows, &key, mtk_flow_l2_ht_params);
-++	if (!entry)
-++		goto out;
+++	spin_lock_irqsave(&lane->priv->lock, flags);
 ++
-++	mtk_foe_entry_commit_subflow(ppe, entry, hash);
+++	old = readl(lane->priv->comphy_regs + COMPHY_SELECTOR_PHY_REG);
+++	new = (old & ~clr) | set;
+++	writel(new, lane->priv->comphy_regs + COMPHY_SELECTOR_PHY_REG);
 ++
-++out:
-+ 	spin_unlock_bh(&ppe_lock);
-+ }
-+ 
-+ int mtk_foe_entry_idle_time(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
-+ {
-+-	u16 now = mtk_eth_timestamp(ppe->eth) & MTK_FOE_IB1_BIND_TIMESTAMP;
-+-	u16 timestamp;
-+-
-+ 	mtk_flow_entry_update(ppe, entry);
-+-	timestamp = entry->data.ib1 & MTK_FOE_IB1_BIND_TIMESTAMP;
-+ 
-+-	if (timestamp > now)
-+-		return MTK_FOE_IB1_BIND_TIMESTAMP + 1 - timestamp + now;
-+-	else
-+-		return now - timestamp;
-++	return __mtk_foe_entry_idle_time(ppe, entry->data.ib1);
-+ }
-+ 
-+ struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base,
-+@@ -492,6 +687,8 @@ struct mtk_ppe *mtk_ppe_init(struct mtk_
-+ 	if (!ppe)
-+ 		return NULL;
-+ 
-++	rhashtable_init(&ppe->l2_flows, &mtk_flow_l2_ht_params);
+++	spin_unlock_irqrestore(&lane->priv->lock, flags);
 ++
-+ 	/* need to allocate a separate device, since it PPE DMA access is
-+ 	 * not coherent.
-+ 	 */
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
-+@@ -6,6 +6,7 @@
-+ 
-+ #include <linux/kernel.h>
-+ #include <linux/bitfield.h>
-++#include <linux/rhashtable.h>
-+ 
-+ #define MTK_ETH_PPE_BASE		0xc00
-+ 
-+@@ -84,19 +85,16 @@ struct mtk_foe_mac_info {
-+ 	u16 src_mac_lo;
-+ };
-+ 
-++/* software-only entry type */
-+ struct mtk_foe_bridge {
-+-	u32 dest_mac_hi;
-+-
-+-	u16 src_mac_lo;
-+-	u16 dest_mac_lo;
-++	u8 dest_mac[ETH_ALEN];
-++	u8 src_mac[ETH_ALEN];
-++	u16 vlan;
-+ 
-+-	u32 src_mac_hi;
-++	struct {} key_end;
-+ 
-+ 	u32 ib2;
-+ 
-+-	u32 _rsv[5];
-+-
-+-	u32 udf_tsid;
-+ 	struct mtk_foe_mac_info l2;
-+ };
-+ 
-+@@ -235,13 +233,33 @@ enum {
-+ 	MTK_PPE_CPU_REASON_INVALID			= 0x1f,
-+ };
-+ 
-++enum {
-++	MTK_FLOW_TYPE_L4,
-++	MTK_FLOW_TYPE_L2,
-++	MTK_FLOW_TYPE_L2_SUBFLOW,
-++};
+++	dev_dbg(lane->dev,
+++		"COMPHY[%d] mode[%d] changed PHY selector 0x%08x -> 0x%08x\n",
+++		lane->id, lane->mode, old, new);
 ++
-+ struct mtk_flow_entry {
-++	union {
-++		struct hlist_node list;
-++		struct {
-++			struct rhash_head l2_node;
-++			struct hlist_head l2_flows;
-++		};
-++	};
-++	u8 type;
-++	s8 wed_index;
-++	u16 hash;
-++	union {
-++		struct mtk_foe_entry data;
-++		struct {
-++			struct mtk_flow_entry *base_flow;
-++			struct hlist_node list;
-++			struct {} end;
-++		} l2_data;
-++	};
-+ 	struct rhash_head node;
-+-	struct hlist_node list;
-+ 	unsigned long cookie;
-+-	struct mtk_foe_entry data;
-+-	u16 hash;
-+-	s8 wed_index;
-+ };
-+ 
-+ struct mtk_ppe {
-+@@ -256,6 +274,8 @@ struct mtk_ppe {
-+ 	u16 foe_check_time[MTK_PPE_ENTRIES];
-+ 	struct hlist_head foe_flow[MTK_PPE_ENTRIES / 2];
-+ 
-++	struct rhashtable l2_flows;
+++	return 0;
+++error:
+++	dev_err(lane->dev, "COMPHY[%d] mode[%d] is invalid\n", lane->id,
+++		lane->mode);
+++	return -EINVAL;
+++}
 ++
-+ 	void *acct_table;
-+ };
-+ 
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
-+@@ -31,6 +31,8 @@ struct mtk_flow_data {
-+ 	__be16 src_port;
-+ 	__be16 dst_port;
-+ 
-++	u16 vlan_in;
+++static int
+++mvebu_a3700_comphy_sata_power_on(struct mvebu_a3700_comphy_lane *lane)
+++{
+++	u32 mask, data, ref_clk;
+++	int ret;
 ++
-+ 	struct {
-+ 		u16 id;
-+ 		__be16 proto;
-+@@ -257,9 +259,45 @@ mtk_flow_offload_replace(struct mtk_eth
-+ 		return -EOPNOTSUPP;
-+ 	}
-+ 
-++	switch (addr_type) {
-++	case 0:
-++		offload_type = MTK_PPE_PKT_TYPE_BRIDGE;
-++		if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
-++			struct flow_match_eth_addrs match;
+++	/* Configure phy selector for SATA */
+++	ret = mvebu_a3700_comphy_set_phy_selector(lane);
+++	if (ret)
+++		return ret;
 ++
-++			flow_rule_match_eth_addrs(rule, &match);
-++			memcpy(data.eth.h_dest, match.key->dst, ETH_ALEN);
-++			memcpy(data.eth.h_source, match.key->src, ETH_ALEN);
+++	/* Clear phy isolation mode to make it work in normal mode */
+++	comphy_lane_reg_set(lane, COMPHY_ISOLATION_CTRL,
+++			    0x0, PHY_ISOLATE_MODE);
+++
+++	/* 0. Check the Polarity invert bits */
+++	data = 0x0;
+++	if (lane->invert_tx)
+++		data |= TXD_INVERT_BIT;
+++	if (lane->invert_rx)
+++		data |= RXD_INVERT_BIT;
+++	mask = TXD_INVERT_BIT | RXD_INVERT_BIT;
+++	comphy_lane_reg_set(lane, COMPHY_SYNC_PATTERN, data, mask);
+++
+++	/* 1. Select 40-bit data width */
+++	comphy_lane_reg_set(lane, COMPHY_DIG_LOOPBACK_EN,
+++			    DATA_WIDTH_40BIT, SEL_DATA_WIDTH_MASK);
+++
+++	/* 2. Select reference clock(25M) and PHY mode (SATA) */
+++	if (lane->priv->xtal_is_40m)
+++		ref_clk = REF_FREF_SEL_SERDES_40MHZ;
+++	else
+++		ref_clk = REF_FREF_SEL_SERDES_25MHZ;
+++
+++	data = ref_clk | COMPHY_MODE_SATA;
+++	mask = REF_FREF_SEL_MASK | COMPHY_MODE_MASK;
+++	comphy_lane_reg_set(lane, COMPHY_POWER_PLL_CTRL, data, mask);
+++
+++	/* 3. Use maximum PLL rate (no power save) */
+++	comphy_lane_reg_set(lane, COMPHY_KVCO_CAL_CTRL,
+++			    USE_MAX_PLL_RATE_BIT, USE_MAX_PLL_RATE_BIT);
+++
+++	/* 4. Reset reserved bit */
+++	comphy_set_indirect(lane->priv, COMPHY_RESERVED_REG,
+++			    0x0, PHYCTRL_FRM_PIN_BIT);
+++
+++	/* 5. Set vendor-specific configuration (It is done in sata driver) */
+++	/* XXX: in U-Boot below sequence was executed in this place, in Linux
+++	 * not.  Now it is done only in U-Boot before this comphy
+++	 * initialization - tests shows that it works ok, but in case of any
+++	 * future problem it is left for reference.
+++	 *   reg_set(MVEBU_REGS_BASE + 0xe00a0, 0, 0xffffffff);
+++	 *   reg_set(MVEBU_REGS_BASE + 0xe00a4, BIT(6), BIT(6));
+++	 */
+++
+++	/* Wait for > 55 us to allow PLL be enabled */
+++	udelay(PLL_SET_DELAY_US);
+++
+++	/* Polling status */
+++	ret = comphy_lane_reg_poll(lane, COMPHY_DIG_LOOPBACK_EN,
+++				   PLL_READY_TX_BIT, COMPHY_PLL_SLEEP,
+++				   COMPHY_PLL_TIMEOUT);
+++	if (ret)
+++		dev_err(lane->dev, "Failed to lock SATA PLL\n");
+++
+++	return ret;
+++}
+++
+++static void comphy_gbe_phy_init(struct mvebu_a3700_comphy_lane *lane,
+++				bool is_1gbps)
+++{
+++	int addr, fix_idx;
+++	u16 val;
+++
+++	fix_idx = 0;
+++	for (addr = 0; addr < 512; addr++) {
+++		/*
+++		 * All PHY register values are defined in full for 3.125Gbps
+++		 * SERDES speed. The values required for 1.25 Gbps are almost
+++		 * the same and only few registers should be "fixed" in
+++		 * comparison to 3.125 Gbps values. These register values are
+++		 * stored in "gbe_phy_init_fix" array.
+++		 */
+++		if (!is_1gbps && gbe_phy_init_fix[fix_idx].addr == addr) {
+++			/* Use new value */
+++			val = gbe_phy_init_fix[fix_idx].value;
+++			if (fix_idx < ARRAY_SIZE(gbe_phy_init_fix))
+++				fix_idx++;
 ++		} else {
-++			return -EOPNOTSUPP;
+++			val = gbe_phy_init[addr];
 ++		}
 ++
-++		if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_VLAN)) {
-++			struct flow_match_vlan match;
+++		comphy_lane_reg_set(lane, addr, val, 0xFFFF);
+++	}
+++}
 ++
-++			flow_rule_match_vlan(rule, &match);
+++static int
+++mvebu_a3700_comphy_ethernet_power_on(struct mvebu_a3700_comphy_lane *lane)
+++{
+++	u32 mask, data, speed_sel;
+++	int ret;
 ++
-++			if (match.key->vlan_tpid != cpu_to_be16(ETH_P_8021Q))
-++				return -EOPNOTSUPP;
+++	/* Set selector */
+++	ret = mvebu_a3700_comphy_set_phy_selector(lane);
+++	if (ret)
+++		return ret;
 ++
-++			data.vlan_in = match.key->vlan_id;
-++		}
-++		break;
-++	case FLOW_DISSECTOR_KEY_IPV4_ADDRS:
-++		offload_type = MTK_PPE_PKT_TYPE_IPV4_HNAPT;
+++	/*
+++	 * 1. Reset PHY by setting PHY input port PIN_RESET=1.
+++	 * 2. Set PHY input port PIN_TX_IDLE=1, PIN_PU_IVREF=1 to keep
+++	 *    PHY TXP/TXN output to idle state during PHY initialization
+++	 * 3. Set PHY input port PIN_PU_PLL=0, PIN_PU_RX=0, PIN_PU_TX=0.
+++	 */
+++	data = PIN_PU_IVREF_BIT | PIN_TX_IDLE_BIT | PIN_RESET_COMPHY_BIT;
+++	mask = data | PIN_RESET_CORE_BIT | PIN_PU_PLL_BIT | PIN_PU_RX_BIT |
+++	       PIN_PU_TX_BIT | PHY_RX_INIT_BIT;
+++	comphy_periph_reg_set(lane, COMPHY_PHY_CFG1, data, mask);
+++
+++	/* 4. Release reset to the PHY by setting PIN_RESET=0. */
+++	data = 0x0;
+++	mask = PIN_RESET_COMPHY_BIT;
+++	comphy_periph_reg_set(lane, COMPHY_PHY_CFG1, data, mask);
+++
+++	/*
+++	 * 5. Set PIN_PHY_GEN_TX[3:0] and PIN_PHY_GEN_RX[3:0] to decide COMPHY
+++	 * bit rate
+++	 */
+++	switch (lane->submode) {
+++	case PHY_INTERFACE_MODE_SGMII:
+++	case PHY_INTERFACE_MODE_1000BASEX:
+++		/* SGMII 1G, SerDes speed 1.25G */
+++		speed_sel = SERDES_SPEED_1_25_G;
 ++		break;
-++	case FLOW_DISSECTOR_KEY_IPV6_ADDRS:
-++		offload_type = MTK_PPE_PKT_TYPE_IPV6_ROUTE_5T;
+++	case PHY_INTERFACE_MODE_2500BASEX:
+++		/* 2500Base-X, SerDes speed 3.125G */
+++		speed_sel = SERDES_SPEED_3_125_G;
 ++		break;
-++	default:
-++		return -EOPNOTSUPP;
++ 	default:
+++		/* Other rates are not supported */
+++		dev_err(lane->dev,
+++			"unsupported phy speed %d on comphy lane%d\n",
+++			lane->submode, lane->id);
++ 		return -EINVAL;
++ 	}
+++	data = GEN_RX_SEL_VALUE(speed_sel) | GEN_TX_SEL_VALUE(speed_sel);
+++	mask = GEN_RX_SEL_MASK | GEN_TX_SEL_MASK;
+++	comphy_periph_reg_set(lane, COMPHY_PHY_CFG1, data, mask);
+++
+++	/*
+++	 * 6. Wait 10mS for bandgap and reference clocks to stabilize; then
+++	 * start SW programming.
+++	 */
+++	mdelay(10);
+++
+++	/* 7. Program COMPHY register PHY_MODE */
+++	data = COMPHY_MODE_SERDES;
+++	mask = COMPHY_MODE_MASK;
+++	comphy_lane_reg_set(lane, COMPHY_POWER_PLL_CTRL, data, mask);
+++
+++	/*
+++	 * 8. Set COMPHY register REFCLK_SEL to select the correct REFCLK
+++	 * source
+++	 */
+++	data = 0x0;
+++	mask = PHY_REF_CLK_SEL;
+++	comphy_lane_reg_set(lane, COMPHY_MISC_CTRL0, data, mask);
+++
+++	/*
+++	 * 9. Set correct reference clock frequency in COMPHY register
+++	 * REF_FREF_SEL.
+++	 */
+++	if (lane->priv->xtal_is_40m)
+++		data = REF_FREF_SEL_SERDES_50MHZ;
+++	else
+++		data = REF_FREF_SEL_SERDES_25MHZ;
+++
+++	mask = REF_FREF_SEL_MASK;
+++	comphy_lane_reg_set(lane, COMPHY_POWER_PLL_CTRL, data, mask);
+++
+++	/*
+++	 * 10. Program COMPHY register PHY_GEN_MAX[1:0]
+++	 * This step is mentioned in the flow received from verification team.
+++	 * However the PHY_GEN_MAX value is only meaningful for other interfaces
+++	 * (not SERDES). For instance, it selects SATA speed 1.5/3/6 Gbps or
+++	 * PCIe speed 2.5/5 Gbps
+++	 */
+++
+++	/*
+++	 * 11. Program COMPHY register SEL_BITS to set correct parallel data
+++	 * bus width
+++	 */
+++	data = DATA_WIDTH_10BIT;
+++	mask = SEL_DATA_WIDTH_MASK;
+++	comphy_lane_reg_set(lane, COMPHY_DIG_LOOPBACK_EN, data, mask);
+++
+++	/*
+++	 * 12. As long as DFE function needs to be enabled in any mode,
+++	 * COMPHY register DFE_UPDATE_EN[5:0] shall be programmed to 0x3F
+++	 * for real chip during COMPHY power on.
+++	 * The value of the DFE_UPDATE_EN already is 0x3F, because it is the
+++	 * default value after reset of the PHY.
+++	 */
+++
+++	/*
+++	 * 13. Program COMPHY GEN registers.
+++	 * These registers should be programmed based on the lab testing result
+++	 * to achieve optimal performance. Please contact the CEA group to get
+++	 * the related GEN table during real chip bring-up. We only required to
+++	 * run though the entire registers programming flow defined by
+++	 * "comphy_gbe_phy_init" when the REF clock is 40 MHz. For REF clock
+++	 * 25 MHz the default values stored in PHY registers are OK.
+++	 */
+++	dev_dbg(lane->dev, "Running C-DPI phy init %s mode\n",
+++		lane->submode == PHY_INTERFACE_MODE_2500BASEX ? "2G5" : "1G");
+++	if (lane->priv->xtal_is_40m)
+++		comphy_gbe_phy_init(lane,
+++				    lane->submode != PHY_INTERFACE_MODE_2500BASEX);
+++
+++	/*
+++	 * 14. Check the PHY Polarity invert bit
+++	 */
+++	data = 0x0;
+++	if (lane->invert_tx)
+++		data |= TXD_INVERT_BIT;
+++	if (lane->invert_rx)
+++		data |= RXD_INVERT_BIT;
+++	mask = TXD_INVERT_BIT | RXD_INVERT_BIT;
+++	comphy_lane_reg_set(lane, COMPHY_SYNC_PATTERN, data, mask);
+++
+++	/*
+++	 * 15. Set PHY input ports PIN_PU_PLL, PIN_PU_TX and PIN_PU_RX to 1 to
+++	 * start PHY power up sequence. All the PHY register programming should
+++	 * be done before PIN_PU_PLL=1. There should be no register programming
+++	 * for normal PHY operation from this point.
+++	 */
+++	data = PIN_PU_PLL_BIT | PIN_PU_RX_BIT | PIN_PU_TX_BIT;
+++	mask = data;
+++	comphy_periph_reg_set(lane, COMPHY_PHY_CFG1, data, mask);
+++
+++	/*
+++	 * 16. Wait for PHY power up sequence to finish by checking output ports
+++	 * PIN_PLL_READY_TX=1 and PIN_PLL_READY_RX=1.
+++	 */
+++	ret = comphy_periph_reg_poll(lane, COMPHY_PHY_STAT1,
+++				     PHY_PLL_READY_TX_BIT |
+++				     PHY_PLL_READY_RX_BIT,
+++				     COMPHY_PLL_SLEEP, COMPHY_PLL_TIMEOUT);
+++	if (ret) {
+++		dev_err(lane->dev, "Failed to lock PLL for SERDES PHY %d\n",
+++			lane->id);
+++		return ret;
 ++	}
 ++
-+ 	flow_action_for_each(i, act, &rule->action) {
-+ 		switch (act->id) {
-+ 		case FLOW_ACTION_MANGLE:
-++			if (offload_type == MTK_PPE_PKT_TYPE_BRIDGE)
-++				return -EOPNOTSUPP;
-+ 			if (act->mangle.htype == FLOW_ACT_MANGLE_HDR_TYPE_ETH)
-+ 				mtk_flow_offload_mangle_eth(act, &data.eth);
-+ 			break;
-+@@ -291,17 +329,6 @@ mtk_flow_offload_replace(struct mtk_eth
-+ 		}
-+ 	}
-+ 
-+-	switch (addr_type) {
-+-	case FLOW_DISSECTOR_KEY_IPV4_ADDRS:
-+-		offload_type = MTK_PPE_PKT_TYPE_IPV4_HNAPT;
-+-		break;
-+-	case FLOW_DISSECTOR_KEY_IPV6_ADDRS:
-+-		offload_type = MTK_PPE_PKT_TYPE_IPV6_ROUTE_5T;
-+-		break;
-+-	default:
-+-		return -EOPNOTSUPP;
-+-	}
-+-
-+ 	if (!is_valid_ether_addr(data.eth.h_source) ||
-+ 	    !is_valid_ether_addr(data.eth.h_dest))
-+ 		return -EINVAL;
-+@@ -315,10 +342,13 @@ mtk_flow_offload_replace(struct mtk_eth
-+ 	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) {
-+ 		struct flow_match_ports ports;
-+ 
-++		if (offload_type == MTK_PPE_PKT_TYPE_BRIDGE)
-++			return -EOPNOTSUPP;
+++	/*
+++	 * 17. Set COMPHY input port PIN_TX_IDLE=0
+++	 */
+++	comphy_periph_reg_set(lane, COMPHY_PHY_CFG1, 0x0, PIN_TX_IDLE_BIT);
 ++
-+ 		flow_rule_match_ports(rule, &ports);
-+ 		data.src_port = ports.key->src;
-+ 		data.dst_port = ports.key->dst;
-+-	} else {
-++	} else if (offload_type != MTK_PPE_PKT_TYPE_BRIDGE) {
-+ 		return -EOPNOTSUPP;
-+ 	}
-+ 
-+@@ -348,6 +378,9 @@ mtk_flow_offload_replace(struct mtk_eth
-+ 		if (act->id != FLOW_ACTION_MANGLE)
-+ 			continue;
-+ 
-++		if (offload_type == MTK_PPE_PKT_TYPE_BRIDGE)
-++			return -EOPNOTSUPP;
+++	/*
+++	 * 18. After valid data appear on PIN_RXDATA bus, set PIN_RX_INIT=1. To
+++	 * start RX initialization. PIN_RX_INIT_DONE will be cleared to 0 by the
+++	 * PHY After RX initialization is done, PIN_RX_INIT_DONE will be set to
+++	 * 1 by COMPHY Set PIN_RX_INIT=0 after PIN_RX_INIT_DONE= 1. Please
+++	 * refer to RX initialization part for details.
+++	 */
+++	comphy_periph_reg_set(lane, COMPHY_PHY_CFG1,
+++			      PHY_RX_INIT_BIT, PHY_RX_INIT_BIT);
 ++
-+ 		switch (act->mangle.htype) {
-+ 		case FLOW_ACT_MANGLE_HDR_TYPE_TCP:
-+ 		case FLOW_ACT_MANGLE_HDR_TYPE_UDP:
-+@@ -373,6 +406,9 @@ mtk_flow_offload_replace(struct mtk_eth
-+ 			return err;
-+ 	}
-+ 
-++	if (offload_type == MTK_PPE_PKT_TYPE_BRIDGE)
-++		foe.bridge.vlan = data.vlan_in;
+++	ret = comphy_periph_reg_poll(lane, COMPHY_PHY_STAT1,
+++				     PHY_PLL_READY_TX_BIT |
+++				     PHY_PLL_READY_RX_BIT,
+++				     COMPHY_PLL_SLEEP, COMPHY_PLL_TIMEOUT);
+++	if (ret) {
+++		dev_err(lane->dev, "Failed to lock PLL for SERDES PHY %d\n",
+++			lane->id);
+++		return ret;
+++	}
 ++
-+ 	if (data.vlan.num == 1) {
-+ 		if (data.vlan.proto != htons(ETH_P_8021Q))
-+ 			return -EOPNOTSUPP;
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-11-net-ethernet-mtk_eth_soc-wed-fix-sparse-endian-warni.patch b/target/linux/generic/backport-5.15/702-v5.19-11-net-ethernet-mtk_eth_soc-wed-fix-sparse-endian-warni.patch
-new file mode 100644
-index 0000000000..8f3dfe8239
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-11-net-ethernet-mtk_eth_soc-wed-fix-sparse-endian-warni.patch
-@@ -0,0 +1,56 @@
-+From: Felix Fietkau <nbd@nbd.name>
-+Date: Fri, 8 Apr 2022 10:59:45 +0200
-+Subject: [PATCH] net: ethernet: mtk_eth_soc/wed: fix sparse endian warnings
-+
-+Descriptor fields are little-endian
-+
-+Fixes: 804775dfc288 ("net: ethernet: mtk_eth_soc: add support for Wireless Ethernet Dispatch (WED)")
-+Reported-by: kernel test robot <lkp@intel.com>
-+Signed-off-by: Felix Fietkau <nbd@nbd.name>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+
-+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
-++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
-+@@ -144,16 +144,17 @@ mtk_wed_buffer_alloc(struct mtk_wed_devi
-+ 
-+ 		for (s = 0; s < MTK_WED_BUF_PER_PAGE; s++) {
-+ 			u32 txd_size;
-++			u32 ctrl;
-+ 
-+ 			txd_size = dev->wlan.init_buf(buf, buf_phys, token++);
+++	ret = comphy_periph_reg_poll(lane, COMPHY_PHY_STAT1,
+++				     PHY_RX_INIT_DONE_BIT,
+++				     COMPHY_PLL_SLEEP, COMPHY_PLL_TIMEOUT);
+++	if (ret)
+++		dev_err(lane->dev, "Failed to init RX of SERDES PHY %d\n",
+++			lane->id);
+++
+++	return ret;
++ }
 + 
-+-			desc->buf0 = buf_phys;
-+-			desc->buf1 = buf_phys + txd_size;
-+-			desc->ctrl = FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN0,
-+-						txd_size) |
-+-				     FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN1,
-+-						MTK_WED_BUF_SIZE - txd_size) |
-+-				     MTK_WDMA_DESC_CTRL_LAST_SEG1;
-++			desc->buf0 = cpu_to_le32(buf_phys);
-++			desc->buf1 = cpu_to_le32(buf_phys + txd_size);
-++			ctrl = FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN0, txd_size) |
-++			       FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN1,
-++					  MTK_WED_BUF_SIZE - txd_size) |
-++			       MTK_WDMA_DESC_CTRL_LAST_SEG1;
-++			desc->ctrl = cpu_to_le32(ctrl);
-+ 			desc->info = 0;
-+ 			desc++;
++-static int mvebu_a3700_comphy_get_fw_mode(int lane,
+++static int
+++mvebu_a3700_comphy_usb3_power_on(struct mvebu_a3700_comphy_lane *lane)
+++{
+++	u32 mask, data, cfg, ref_clk;
+++	int ret;
+++
+++	/* Set phy seclector */
+++	ret = mvebu_a3700_comphy_set_phy_selector(lane);
+++	if (ret)
+++		return ret;
+++
+++	/*
+++	 * 0. Set PHY OTG Control(0x5d034), bit 4, Power up OTG module The
+++	 * register belong to UTMI module, so it is set in UTMI phy driver.
+++	 */
+++
+++	/*
+++	 * 1. Set PRD_TXDEEMPH (3.5db de-emph)
+++	 */
+++	data = PRD_TXDEEMPH0_MASK;
+++	mask = PRD_TXDEEMPH0_MASK | PRD_TXMARGIN_MASK | PRD_TXSWING_MASK |
+++	       CFG_TX_ALIGN_POS_MASK;
+++	comphy_lane_reg_set(lane, COMPHY_PIPE_LANE_CFG0, data, mask);
+++
+++	/*
+++	 * 2. Set BIT0: enable transmitter in high impedance mode
+++	 *    Set BIT[3:4]: delay 2 clock cycles for HiZ off latency
+++	 *    Set BIT6: Tx detect Rx at HiZ mode
+++	 *    Unset BIT15: set to 0 to set USB3 De-emphasize level to -3.5db
+++	 *            together with bit 0 of COMPHY_PIPE_LANE_CFG0 register
+++	 */
+++	data = TX_DET_RX_MODE | GEN2_TX_DATA_DLY_DEFT | TX_ELEC_IDLE_MODE_EN;
+++	mask = PRD_TXDEEMPH1_MASK | TX_DET_RX_MODE | GEN2_TX_DATA_DLY_MASK |
+++	       TX_ELEC_IDLE_MODE_EN;
+++	comphy_lane_reg_set(lane, COMPHY_PIPE_LANE_CFG1, data, mask);
+++
+++	/*
+++	 * 3. Set Spread Spectrum Clock Enabled
+++	 */
+++	comphy_lane_reg_set(lane, COMPHY_PIPE_LANE_CFG4,
+++			    SPREAD_SPECTRUM_CLK_EN, SPREAD_SPECTRUM_CLK_EN);
+++
+++	/*
+++	 * 4. Set Override Margining Controls From the MAC:
+++	 *    Use margining signals from lane configuration
+++	 */
+++	comphy_lane_reg_set(lane, COMPHY_PIPE_TEST_MODE_CTRL,
+++			    MODE_MARGIN_OVERRIDE, 0xFFFF);
+++
+++	/*
+++	 * 5. Set Lane-to-Lane Bundle Clock Sampling Period = per PCLK cycles
+++	 *    set Mode Clock Source = PCLK is generated from REFCLK
+++	 */
+++	data = 0x0;
+++	mask = MODE_CLK_SRC | BUNDLE_PERIOD_SEL | BUNDLE_PERIOD_SCALE_MASK |
+++	       BUNDLE_SAMPLE_CTRL | PLL_READY_DLY_MASK;
+++	comphy_lane_reg_set(lane, COMPHY_PIPE_CLK_SRC_LO, data, mask);
+++
+++	/*
+++	 * 6. Set G2 Spread Spectrum Clock Amplitude at 4K
+++	 */
+++	comphy_lane_reg_set(lane, COMPHY_GEN2_SET2,
+++			    GS2_TX_SSC_AMP_4128, GS2_TX_SSC_AMP_MASK);
+++
+++	/*
+++	 * 7. Unset G3 Spread Spectrum Clock Amplitude
+++	 *    set G3 TX and RX Register Master Current Select
+++	 */
+++	data = GS2_VREG_RXTX_MAS_ISET_60U;
+++	mask = GS2_TX_SSC_AMP_MASK | GS2_VREG_RXTX_MAS_ISET_MASK |
+++	       GS2_RSVD_6_0_MASK;
+++	comphy_lane_reg_set(lane, COMPHY_GEN3_SET2, data, mask);
+++
+++	/*
+++	 * 8. Check crystal jumper setting and program the Power and PLL Control
+++	 * accordingly Change RX wait
+++	 */
+++	if (lane->priv->xtal_is_40m) {
+++		ref_clk = REF_FREF_SEL_PCIE_USB3_40MHZ;
+++		cfg = CFG_PM_RXDLOZ_WAIT_12_UNIT;
+++	} else {
+++		ref_clk = REF_FREF_SEL_PCIE_USB3_25MHZ;
+++		cfg = CFG_PM_RXDLOZ_WAIT_7_UNIT;
+++	}
+++
+++	data = PU_IVREF_BIT | PU_PLL_BIT | PU_RX_BIT | PU_TX_BIT |
+++	       PU_TX_INTP_BIT | PU_DFE_BIT | COMPHY_MODE_USB3 | ref_clk;
+++	mask = PU_IVREF_BIT | PU_PLL_BIT | PU_RX_BIT | PU_TX_BIT |
+++	       PU_TX_INTP_BIT | PU_DFE_BIT | PLL_LOCK_BIT | COMPHY_MODE_MASK |
+++	       REF_FREF_SEL_MASK;
+++	comphy_lane_reg_set(lane, COMPHY_POWER_PLL_CTRL, data, mask);
+++
+++	data = CFG_PM_RXDEN_WAIT_1_UNIT | cfg;
+++	mask = CFG_PM_OSCCLK_WAIT_MASK | CFG_PM_RXDEN_WAIT_MASK |
+++	       CFG_PM_RXDLOZ_WAIT_MASK;
+++	comphy_lane_reg_set(lane, COMPHY_PIPE_PWR_MGM_TIM1, data, mask);
+++
+++	/*
+++	 * 9. Enable idle sync
+++	 */
+++	comphy_lane_reg_set(lane, COMPHY_IDLE_SYNC_EN,
+++			    IDLE_SYNC_EN, IDLE_SYNC_EN);
+++
+++	/*
+++	 * 10. Enable the output of 500M clock
+++	 */
+++	comphy_lane_reg_set(lane, COMPHY_MISC_CTRL0, CLK500M_EN, CLK500M_EN);
+++
+++	/*
+++	 * 11. Set 20-bit data width
+++	 */
+++	comphy_lane_reg_set(lane, COMPHY_DIG_LOOPBACK_EN,
+++			    DATA_WIDTH_20BIT, 0xFFFF);
+++
+++	/*
+++	 * 12. Override Speed_PLL value and use MAC PLL
+++	 */
+++	data = SPEED_PLL_VALUE_16 | USE_MAX_PLL_RATE_BIT;
+++	mask = 0xFFFF;
+++	comphy_lane_reg_set(lane, COMPHY_KVCO_CAL_CTRL, data, mask);
+++
+++	/*
+++	 * 13. Check the Polarity invert bit
+++	 */
+++	data = 0x0;
+++	if (lane->invert_tx)
+++		data |= TXD_INVERT_BIT;
+++	if (lane->invert_rx)
+++		data |= RXD_INVERT_BIT;
+++	mask = TXD_INVERT_BIT | RXD_INVERT_BIT;
+++	comphy_lane_reg_set(lane, COMPHY_SYNC_PATTERN, data, mask);
+++
+++	/*
+++	 * 14. Set max speed generation to USB3.0 5Gbps
+++	 */
+++	comphy_lane_reg_set(lane, COMPHY_SYNC_MASK_GEN,
+++			    PHY_GEN_MAX_USB3_5G, PHY_GEN_MAX_MASK);
+++
+++	/*
+++	 * 15. Set capacitor value for FFE gain peaking to 0xF
+++	 */
+++	comphy_lane_reg_set(lane, COMPHY_GEN2_SET3,
+++			    GS3_FFE_CAP_SEL_VALUE, GS3_FFE_CAP_SEL_MASK);
+++
+++	/*
+++	 * 16. Release SW reset
+++	 */
+++	data = MODE_CORE_CLK_FREQ_SEL | MODE_PIPE_WIDTH_32 | MODE_REFDIV_BY_4;
+++	mask = 0xFFFF;
+++	comphy_lane_reg_set(lane, COMPHY_PIPE_RST_CLK_CTRL, data, mask);
+++
+++	/* Wait for > 55 us to allow PCLK be enabled */
+++	udelay(PLL_SET_DELAY_US);
+++
+++	ret = comphy_lane_reg_poll(lane, COMPHY_PIPE_LANE_STAT1, TXDCLK_PCLK_EN,
+++				   COMPHY_PLL_SLEEP, COMPHY_PLL_TIMEOUT);
+++	if (ret)
+++		dev_err(lane->dev, "Failed to lock USB3 PLL\n");
+++
+++	return ret;
+++}
+++
+++static int
+++mvebu_a3700_comphy_pcie_power_on(struct mvebu_a3700_comphy_lane *lane)
+++{
+++	u32 mask, data, ref_clk;
+++	int ret;
+++
+++	/* Configure phy selector for PCIe */
+++	ret = mvebu_a3700_comphy_set_phy_selector(lane);
+++	if (ret)
+++		return ret;
+++
+++	/* 1. Enable max PLL. */
+++	comphy_lane_reg_set(lane, COMPHY_PIPE_LANE_CFG1,
+++			    USE_MAX_PLL_RATE_EN, USE_MAX_PLL_RATE_EN);
+++
+++	/* 2. Select 20 bit SERDES interface. */
+++	comphy_lane_reg_set(lane, COMPHY_PIPE_CLK_SRC_LO,
+++			    CFG_SEL_20B, CFG_SEL_20B);
+++
+++	/* 3. Force to use reg setting for PCIe mode */
+++	comphy_lane_reg_set(lane, COMPHY_MISC_CTRL1,
+++			    SEL_BITS_PCIE_FORCE, SEL_BITS_PCIE_FORCE);
+++
+++	/* 4. Change RX wait */
+++	data = CFG_PM_RXDEN_WAIT_1_UNIT | CFG_PM_RXDLOZ_WAIT_12_UNIT;
+++	mask = CFG_PM_OSCCLK_WAIT_MASK | CFG_PM_RXDEN_WAIT_MASK |
+++	       CFG_PM_RXDLOZ_WAIT_MASK;
+++	comphy_lane_reg_set(lane, COMPHY_PIPE_PWR_MGM_TIM1, data, mask);
+++
+++	/* 5. Enable idle sync */
+++	comphy_lane_reg_set(lane, COMPHY_IDLE_SYNC_EN,
+++			    IDLE_SYNC_EN, IDLE_SYNC_EN);
+++
+++	/* 6. Enable the output of 100M/125M/500M clock */
+++	data = CLK500M_EN | TXDCLK_2X_SEL | CLK100M_125M_EN;
+++	mask = data;
+++	comphy_lane_reg_set(lane, COMPHY_MISC_CTRL0, data, mask);
+++
+++	/*
+++	 * 7. Enable TX, PCIE global register, 0xd0074814, it is done in
+++	 * PCI-E driver
+++	 */
+++
+++	/*
+++	 * 8. Check crystal jumper setting and program the Power and PLL
+++	 * Control accordingly
+++	 */
+++
+++	if (lane->priv->xtal_is_40m)
+++		ref_clk = REF_FREF_SEL_PCIE_USB3_40MHZ;
+++	else
+++		ref_clk = REF_FREF_SEL_PCIE_USB3_25MHZ;
+++
+++	data = PU_IVREF_BIT | PU_PLL_BIT | PU_RX_BIT | PU_TX_BIT |
+++	       PU_TX_INTP_BIT | PU_DFE_BIT | COMPHY_MODE_PCIE | ref_clk;
+++	mask = 0xFFFF;
+++	comphy_lane_reg_set(lane, COMPHY_POWER_PLL_CTRL, data, mask);
+++
+++	/* 9. Override Speed_PLL value and use MAC PLL */
+++	comphy_lane_reg_set(lane, COMPHY_KVCO_CAL_CTRL,
+++			    SPEED_PLL_VALUE_16 | USE_MAX_PLL_RATE_BIT,
+++			    0xFFFF);
+++
+++	/* 10. Check the Polarity invert bit */
+++	data = 0x0;
+++	if (lane->invert_tx)
+++		data |= TXD_INVERT_BIT;
+++	if (lane->invert_rx)
+++		data |= RXD_INVERT_BIT;
+++	mask = TXD_INVERT_BIT | RXD_INVERT_BIT;
+++	comphy_lane_reg_set(lane, COMPHY_SYNC_PATTERN, data, mask);
+++
+++	/* 11. Release SW reset */
+++	data = MODE_CORE_CLK_FREQ_SEL | MODE_PIPE_WIDTH_32;
+++	mask = data | PIPE_SOFT_RESET | MODE_REFDIV_MASK;
+++	comphy_lane_reg_set(lane, COMPHY_PIPE_RST_CLK_CTRL, data, mask);
+++
+++	/* Wait for > 55 us to allow PCLK be enabled */
+++	udelay(PLL_SET_DELAY_US);
+++
+++	ret = comphy_lane_reg_poll(lane, COMPHY_PIPE_LANE_STAT1, TXDCLK_PCLK_EN,
+++				   COMPHY_PLL_SLEEP, COMPHY_PLL_TIMEOUT);
+++	if (ret)
+++		dev_err(lane->dev, "Failed to lock PCIE PLL\n");
+++
+++	return ret;
+++}
+++
+++static void
+++mvebu_a3700_comphy_sata_power_off(struct mvebu_a3700_comphy_lane *lane)
+++{
+++	/* Set phy isolation mode */
+++	comphy_lane_reg_set(lane, COMPHY_ISOLATION_CTRL,
+++			    PHY_ISOLATE_MODE, PHY_ISOLATE_MODE);
+++
+++	/* Power off PLL, Tx, Rx */
+++	comphy_lane_reg_set(lane, COMPHY_POWER_PLL_CTRL,
+++			    0x0, PU_PLL_BIT | PU_RX_BIT | PU_TX_BIT);
+++}
+++
+++static void
+++mvebu_a3700_comphy_ethernet_power_off(struct mvebu_a3700_comphy_lane *lane)
+++{
+++	u32 mask, data;
+++
+++	data = PIN_RESET_CORE_BIT | PIN_RESET_COMPHY_BIT | PIN_PU_IVREF_BIT |
+++	       PHY_RX_INIT_BIT;
+++	mask = data;
+++	comphy_periph_reg_set(lane, COMPHY_PHY_CFG1, data, mask);
+++}
+++
+++static void
+++mvebu_a3700_comphy_pcie_power_off(struct mvebu_a3700_comphy_lane *lane)
+++{
+++	/* Power off PLL, Tx, Rx */
+++	comphy_lane_reg_set(lane, COMPHY_POWER_PLL_CTRL,
+++			    0x0, PU_PLL_BIT | PU_RX_BIT | PU_TX_BIT);
+++}
+++
+++static int mvebu_a3700_comphy_reset(struct phy *phy)
+++{
+++	struct mvebu_a3700_comphy_lane *lane = phy_get_drvdata(phy);
+++	u16 mask, data;
+++
+++	dev_dbg(lane->dev, "resetting lane %d\n", lane->id);
+++
+++	/* COMPHY reset for internal logic */
+++	comphy_lane_reg_set(lane, COMPHY_SFT_RESET,
+++			    SFT_RST_NO_REG, SFT_RST_NO_REG);
+++
+++	/* COMPHY register reset (cleared automatically) */
+++	comphy_lane_reg_set(lane, COMPHY_SFT_RESET, SFT_RST, SFT_RST);
+++
+++	/* PIPE soft and register reset */
+++	data = PIPE_SOFT_RESET | PIPE_REG_RESET;
+++	mask = data;
+++	comphy_lane_reg_set(lane, COMPHY_PIPE_RST_CLK_CTRL, data, mask);
+++
+++	/* Release PIPE register reset */
+++	comphy_lane_reg_set(lane, COMPHY_PIPE_RST_CLK_CTRL,
+++			    0x0, PIPE_REG_RESET);
+++
+++	/* Reset SB configuration register (only for lanes 0 and 1) */
+++	if (lane->id == 0 || lane->id == 1) {
+++		u32 mask, data;
+++
+++		data = PIN_RESET_CORE_BIT | PIN_RESET_COMPHY_BIT |
+++		       PIN_PU_PLL_BIT | PIN_PU_RX_BIT | PIN_PU_TX_BIT;
+++		mask = data | PIN_PU_IVREF_BIT | PIN_TX_IDLE_BIT;
+++		comphy_periph_reg_set(lane, COMPHY_PHY_CFG1, data, mask);
+++	}
+++
+++	return 0;
+++}
+++
+++static bool mvebu_a3700_comphy_check_mode(int lane,
++ 					  enum phy_mode mode,
++ 					  int submode)
++ {
++@@ -122,7 +1141,7 @@ static int mvebu_a3700_comphy_get_fw_mod
 + 
-+@@ -184,12 +185,14 @@ mtk_wed_free_buffer(struct mtk_wed_devic
++ 	/* Unused PHY mux value is 0x0 */
++ 	if (mode == PHY_MODE_INVALID)
++-		return -EINVAL;
+++		return false;
 + 
-+ 	for (i = 0, page_idx = 0; i < dev->buf_ring.size; i += MTK_WED_BUF_PER_PAGE) {
-+ 		void *page = page_list[page_idx++];
-++		dma_addr_t buf_addr;
++ 	for (i = 0; i < n; i++) {
++ 		if (mvebu_a3700_comphy_modes[i].lane == lane &&
++@@ -132,27 +1151,30 @@ static int mvebu_a3700_comphy_get_fw_mod
++ 	}
 + 
-+ 		if (!page)
-+ 			break;
++ 	if (i == n)
++-		return -EINVAL;
+++		return false;
 + 
-+-		dma_unmap_page(dev->hw->dev, desc[i].buf0,
-+-			       PAGE_SIZE, DMA_BIDIRECTIONAL);
-++		buf_addr = le32_to_cpu(desc[i].buf0);
-++		dma_unmap_page(dev->hw->dev, buf_addr, PAGE_SIZE,
-++			       DMA_BIDIRECTIONAL);
-+ 		__free_page(page);
++-	return mvebu_a3700_comphy_modes[i].fw_mode;
+++	return true;
++ }
++ 
++ static int mvebu_a3700_comphy_set_mode(struct phy *phy, enum phy_mode mode,
++ 				       int submode)
++ {
++ 	struct mvebu_a3700_comphy_lane *lane = phy_get_drvdata(phy);
++-	int fw_mode;
++ 
++-	if (submode == PHY_INTERFACE_MODE_1000BASEX)
++-		submode = PHY_INTERFACE_MODE_SGMII;
++-
++-	fw_mode = mvebu_a3700_comphy_get_fw_mode(lane->id, mode,
++-						 submode);
++-	if (fw_mode < 0) {
+++	if (!mvebu_a3700_comphy_check_mode(lane->id, mode, submode)) {
++ 		dev_err(lane->dev, "invalid COMPHY mode\n");
++-		return fw_mode;
+++		return -EINVAL;
 + 	}
 + 
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-12-net-ethernet-mtk_eth_soc-fix-return-value-check-in-m.patch b/target/linux/generic/backport-5.15/702-v5.19-12-net-ethernet-mtk_eth_soc-fix-return-value-check-in-m.patch
-new file mode 100644
-index 0000000000..4ec8fe74bc
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-12-net-ethernet-mtk_eth_soc-fix-return-value-check-in-m.patch
-@@ -0,0 +1,25 @@
-+From: Yang Yingliang <yangyingliang@huawei.com>
-+Date: Fri, 8 Apr 2022 11:22:46 +0800
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: fix return value check in
-+ mtk_wed_add_hw()
-+
-+If syscon_regmap_lookup_by_phandle() fails, it never return NULL pointer,
-+change the check to IS_ERR().
-+
-+Fixes: 804775dfc288 ("net: ethernet: mtk_eth_soc: add support for Wireless Ethernet Dispatch (WED)")
-+Reported-by: Hulk Robot <hulkci@huawei.com>
-+Signed-off-by: Yang Yingliang <yangyingliang@huawei.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+
-+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
-++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
-+@@ -816,7 +816,7 @@ void mtk_wed_add_hw(struct device_node *
-+ 		return;
+++	/* Mode cannot be changed while the PHY is powered on */
+++	if (phy->power_count &&
+++	    (lane->mode != mode || lane->submode != submode))
+++		return -EBUSY;
+++
+++	/* If changing mode, ensure reset is called */
+++	if (lane->mode != PHY_MODE_INVALID && lane->mode != mode)
+++		lane->needs_reset = true;
+++
++ 	/* Just remember the mode, ->power_on() will do the real setup */
++ 	lane->mode = mode;
++ 	lane->submode = submode;
++@@ -163,76 +1185,77 @@ static int mvebu_a3700_comphy_set_mode(s
++ static int mvebu_a3700_comphy_power_on(struct phy *phy)
++ {
++ 	struct mvebu_a3700_comphy_lane *lane = phy_get_drvdata(phy);
++-	u32 fw_param;
++-	int fw_mode;
++-	int fw_port;
++ 	int ret;
 + 
-+ 	regs = syscon_regmap_lookup_by_phandle(np, NULL);
-+-	if (!regs)
-++	if (IS_ERR(regs))
-+ 		return;
++-	fw_mode = mvebu_a3700_comphy_get_fw_mode(lane->id,
++-						 lane->mode, lane->submode);
++-	if (fw_mode < 0) {
+++	if (!mvebu_a3700_comphy_check_mode(lane->id, lane->mode,
+++					   lane->submode)) {
++ 		dev_err(lane->dev, "invalid COMPHY mode\n");
++-		return fw_mode;
+++		return -EINVAL;
+++	}
+++
+++	if (lane->needs_reset) {
+++		ret = mvebu_a3700_comphy_reset(phy);
+++		if (ret)
+++			return ret;
+++
+++		lane->needs_reset = false;
++ 	}
 + 
-+ 	rcu_assign_pointer(mtk_soc_wed_ops, &wed_ops);
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-13-net-ethernet-mtk_eth_soc-use-standard-property-for-c.patch b/target/linux/generic/backport-5.15/702-v5.19-13-net-ethernet-mtk_eth_soc-use-standard-property-for-c.patch
++ 	switch (lane->mode) {
++ 	case PHY_MODE_USB_HOST_SS:
++ 		dev_dbg(lane->dev, "set lane %d to USB3 host mode\n", lane->id);
++-		fw_param = COMPHY_FW_MODE(fw_mode);
++-		break;
+++		return mvebu_a3700_comphy_usb3_power_on(lane);
++ 	case PHY_MODE_SATA:
++ 		dev_dbg(lane->dev, "set lane %d to SATA mode\n", lane->id);
++-		fw_param = COMPHY_FW_MODE(fw_mode);
++-		break;
+++		return mvebu_a3700_comphy_sata_power_on(lane);
++ 	case PHY_MODE_ETHERNET:
++-		fw_port = (lane->id == 0) ? 1 : 0;
++-		switch (lane->submode) {
++-		case PHY_INTERFACE_MODE_SGMII:
++-			dev_dbg(lane->dev, "set lane %d to SGMII mode\n",
++-				lane->id);
++-			fw_param = COMPHY_FW_NET(fw_mode, fw_port,
++-						 COMPHY_FW_SPEED_1_25G);
++-			break;
++-		case PHY_INTERFACE_MODE_2500BASEX:
++-			dev_dbg(lane->dev, "set lane %d to 2500BASEX mode\n",
++-				lane->id);
++-			fw_param = COMPHY_FW_NET(fw_mode, fw_port,
++-						 COMPHY_FW_SPEED_3_125G);
++-			break;
++-		default:
++-			dev_err(lane->dev, "unsupported PHY submode (%d)\n",
++-				lane->submode);
++-			return -ENOTSUPP;
++-		}
++-		break;
+++		dev_dbg(lane->dev, "set lane %d to Ethernet mode\n", lane->id);
+++		return mvebu_a3700_comphy_ethernet_power_on(lane);
++ 	case PHY_MODE_PCIE:
++ 		dev_dbg(lane->dev, "set lane %d to PCIe mode\n", lane->id);
++-		fw_param = COMPHY_FW_PCIE(fw_mode, COMPHY_FW_SPEED_5G,
++-					  phy->attrs.bus_width);
++-		break;
+++		return mvebu_a3700_comphy_pcie_power_on(lane);
++ 	default:
++ 		dev_err(lane->dev, "unsupported PHY mode (%d)\n", lane->mode);
++-		return -ENOTSUPP;
+++		return -EOPNOTSUPP;
++ 	}
++-
++-	ret = mvebu_a3700_comphy_smc(COMPHY_SIP_POWER_ON, lane->id, fw_param);
++-	if (ret == -EOPNOTSUPP)
++-		dev_err(lane->dev,
++-			"unsupported SMC call, try updating your firmware\n");
++-
++-	return ret;
++ }
++ 
++ static int mvebu_a3700_comphy_power_off(struct phy *phy)
++ {
++ 	struct mvebu_a3700_comphy_lane *lane = phy_get_drvdata(phy);
++ 
++-	return mvebu_a3700_comphy_smc(COMPHY_SIP_POWER_OFF, lane->id, 0);
+++	switch (lane->mode) {
+++	case PHY_MODE_USB_HOST_SS:
+++		/*
+++		 * The USB3 MAC sets the USB3 PHY to low state, so we do not
+++		 * need to power off USB3 PHY again.
+++		 */
+++		break;
+++
+++	case PHY_MODE_SATA:
+++		mvebu_a3700_comphy_sata_power_off(lane);
+++		break;
+++
+++	case PHY_MODE_ETHERNET:
+++		mvebu_a3700_comphy_ethernet_power_off(lane);
+++		break;
+++
+++	case PHY_MODE_PCIE:
+++		mvebu_a3700_comphy_pcie_power_off(lane);
+++		break;
+++
+++	default:
+++		dev_err(lane->dev, "invalid COMPHY mode\n");
+++		return -EINVAL;
+++	}
+++
+++	return 0;
++ }
++ 
++ static const struct phy_ops mvebu_a3700_comphy_ops = {
++ 	.power_on	= mvebu_a3700_comphy_power_on,
++ 	.power_off	= mvebu_a3700_comphy_power_off,
+++	.reset		= mvebu_a3700_comphy_reset,
++ 	.set_mode	= mvebu_a3700_comphy_set_mode,
++ 	.owner		= THIS_MODULE,
++ };
++@@ -256,13 +1279,75 @@ static struct phy *mvebu_a3700_comphy_xl
++ 		return ERR_PTR(-EINVAL);
++ 	}
++ 
+++	lane->invert_tx = args->args[1] & BIT(0);
+++	lane->invert_rx = args->args[1] & BIT(1);
+++
++ 	return phy;
++ }
++ 
++ static int mvebu_a3700_comphy_probe(struct platform_device *pdev)
++ {
+++	struct mvebu_a3700_comphy_priv *priv;
++ 	struct phy_provider *provider;
++ 	struct device_node *child;
+++	struct resource *res;
+++	struct clk *clk;
+++	int ret;
+++
+++	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+++	if (!priv)
+++		return -ENOMEM;
+++
+++	spin_lock_init(&priv->lock);
+++
+++	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "comphy");
+++	priv->comphy_regs = devm_ioremap_resource(&pdev->dev, res);
+++	if (IS_ERR(priv->comphy_regs))
+++		return PTR_ERR(priv->comphy_regs);
+++
+++	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+++					   "lane1_pcie_gbe");
+++	priv->lane1_phy_regs = devm_ioremap_resource(&pdev->dev, res);
+++	if (IS_ERR(priv->lane1_phy_regs))
+++		return PTR_ERR(priv->lane1_phy_regs);
+++
+++	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+++					   "lane0_usb3_gbe");
+++	priv->lane0_phy_regs = devm_ioremap_resource(&pdev->dev, res);
+++	if (IS_ERR(priv->lane0_phy_regs))
+++		return PTR_ERR(priv->lane0_phy_regs);
+++
+++	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+++					   "lane2_sata_usb3");
+++	priv->lane2_phy_indirect = devm_ioremap_resource(&pdev->dev, res);
+++	if (IS_ERR(priv->lane2_phy_indirect))
+++		return PTR_ERR(priv->lane2_phy_indirect);
+++
+++	/*
+++	 * Driver needs to know if reference xtal clock is 40MHz or 25MHz.
+++	 * Old DT bindings do not have xtal clk present. So do not fail here
+++	 * and expects that default 25MHz reference clock is used.
+++	 */
+++	clk = clk_get(&pdev->dev, "xtal");
+++	if (IS_ERR(clk)) {
+++		if (PTR_ERR(clk) == -EPROBE_DEFER)
+++			return -EPROBE_DEFER;
+++		dev_warn(&pdev->dev, "missing 'xtal' clk (%ld)\n",
+++			 PTR_ERR(clk));
+++	} else {
+++		ret = clk_prepare_enable(clk);
+++		if (ret) {
+++			dev_warn(&pdev->dev, "enabling xtal clk failed (%d)\n",
+++				 ret);
+++		} else {
+++			if (clk_get_rate(clk) == 40000000)
+++				priv->xtal_is_40m = true;
+++			clk_disable_unprepare(clk);
+++		}
+++		clk_put(clk);
+++	}
+++
+++	dev_set_drvdata(&pdev->dev, priv);
++ 
++ 	for_each_available_child_of_node(pdev->dev.of_node, child) {
++ 		struct mvebu_a3700_comphy_lane *lane;
++@@ -277,7 +1362,7 @@ static int mvebu_a3700_comphy_probe(stru
++ 			continue;
++ 		}
++ 
++-		if (lane_id >= MVEBU_A3700_COMPHY_LANES) {
+++		if (lane_id >= 3) {
++ 			dev_err(&pdev->dev, "invalid 'reg' property\n");
++ 			continue;
++ 		}
++@@ -295,15 +1380,26 @@ static int mvebu_a3700_comphy_probe(stru
++ 			return PTR_ERR(phy);
++ 		}
++ 
+++		lane->priv = priv;
++ 		lane->dev = &pdev->dev;
++ 		lane->mode = PHY_MODE_INVALID;
++ 		lane->submode = PHY_INTERFACE_MODE_NA;
++ 		lane->id = lane_id;
+++		lane->invert_tx = false;
+++		lane->invert_rx = false;
++ 		phy_set_drvdata(phy, lane);
+++
+++		/*
+++		 * To avoid relying on the bootloader/firmware configuration,
+++		 * power off all comphys.
+++		 */
+++		mvebu_a3700_comphy_reset(phy);
+++		lane->needs_reset = false;
++ 	}
++ 
++ 	provider = devm_of_phy_provider_register(&pdev->dev,
++ 						 mvebu_a3700_comphy_xlate);
+++
++ 	return PTR_ERR_OR_ZERO(provider);
++ }
++ 
++@@ -323,5 +1419,7 @@ static struct platform_driver mvebu_a370
++ module_platform_driver(mvebu_a3700_comphy_driver);
++ 
++ MODULE_AUTHOR("Miquèl Raynal <miquel.raynal@bootlin.com>");
+++MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
+++MODULE_AUTHOR("Marek Behún <kabel@kernel.org>");
++ MODULE_DESCRIPTION("Common PHY driver for A3700");
++ MODULE_LICENSE("GPL v2");
+diff --git a/target/linux/generic/backport-5.15/345-v5.17-arm64-dts-marvell-armada-37xx-Add-xtal-clock-to-comp.patch b/target/linux/generic/backport-5.15/345-v5.17-arm64-dts-marvell-armada-37xx-Add-xtal-clock-to-comp.patch
 new file mode 100644
-index 0000000000..d87022fce4
+index 0000000000..03b6a5754d
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-13-net-ethernet-mtk_eth_soc-use-standard-property-for-c.patch
-@@ -0,0 +1,35 @@
-+From: Lorenzo Bianconi <lorenzo@kernel.org>
-+Date: Mon, 11 Apr 2022 12:13:25 +0200
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: use standard property for
-+ cci-control-port
++++ b/target/linux/generic/backport-5.15/345-v5.17-arm64-dts-marvell-armada-37xx-Add-xtal-clock-to-comp.patch
+@@ -0,0 +1,32 @@
++From 73a78b6130d9e13daca22b86ad52f063b9403e03 Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
++Date: Wed, 8 Dec 2021 03:40:35 +0100
++Subject: [PATCH 1/1] arm64: dts: marvell: armada-37xx: Add xtal clock to
++ comphy node
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
 +
-+Rely on standard cci-control-port property to identify CCI port
-+reference.
-+Update mt7622 dts binding.
++Kernel driver phy-mvebu-a3700-comphy.c needs to know the rate of the
++reference xtal clock. So add missing xtal clock source into comphy device
++tree node. If the property is not present, the driver defaults to 25 MHz
++xtal rate (which, as far as we know, is used by all the existing boards).
 +
-+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Pali Rohár <pali@kernel.org>
++Signed-off-by: Marek Behún <kabel@kernel.org>
++Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com>
 +---
++ arch/arm64/boot/dts/marvell/armada-37xx.dtsi | 2 ++
++ 1 file changed, 2 insertions(+)
 +
-+--- a/arch/arm64/boot/dts/mediatek/mt7622.dtsi
-++++ b/arch/arm64/boot/dts/mediatek/mt7622.dtsi
-+@@ -962,7 +962,7 @@
-+ 		power-domains = <&scpsys MT7622_POWER_DOMAIN_ETHSYS>;
-+ 		mediatek,ethsys = <&ethsys>;
-+ 		mediatek,sgmiisys = <&sgmiisys>;
-+-		mediatek,cci-control = <&cci_control2>;
-++		cci-control-port = <&cci_control2>;
-+ 		mediatek,wed = <&wed0>, <&wed1>;
-+ 		mediatek,pcie-mirror = <&pcie_mirror>;
-+ 		mediatek,hifsys = <&hifsys>;
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -3185,7 +3185,7 @@ static int mtk_probe(struct platform_dev
-+ 		struct regmap *cci;
-+ 
-+ 		cci = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
-+-						      "mediatek,cci-control");
-++						      "cci-control-port");
-+ 		/* enable CPU/bus coherency */
-+ 		if (!IS_ERR(cci))
-+ 			regmap_write(cci, 0, 3);
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-14-net-ethernet-mtk_eth_soc-use-after-free-in-__mtk_ppe.patch b/target/linux/generic/backport-5.15/702-v5.19-14-net-ethernet-mtk_eth_soc-use-after-free-in-__mtk_ppe.patch
++--- a/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
+++++ b/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
++@@ -265,6 +265,8 @@
++ 					    "lane2_sata_usb3";
++ 				#address-cells = <1>;
++ 				#size-cells = <0>;
+++				clocks = <&xtalclk>;
+++				clock-names = "xtal";
++ 
++ 				comphy0: phy@0 {
++ 					reg = <0>;
+diff --git a/target/linux/generic/backport-5.15/346-v5.18-01-Revert-ata-ahci-mvebu-Make-SATA-PHY-optional-for-Arm.patch b/target/linux/generic/backport-5.15/346-v5.18-01-Revert-ata-ahci-mvebu-Make-SATA-PHY-optional-for-Arm.patch
 new file mode 100644
-index 0000000000..656b3a159e
+index 0000000000..b254e7c0b3
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-14-net-ethernet-mtk_eth_soc-use-after-free-in-__mtk_ppe.patch
-@@ -0,0 +1,33 @@
-+From: Dan Carpenter <dan.carpenter@oracle.com>
-+Date: Tue, 12 Apr 2022 12:24:19 +0300
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: use after free in
-+ __mtk_ppe_check_skb()
++++ b/target/linux/generic/backport-5.15/346-v5.18-01-Revert-ata-ahci-mvebu-Make-SATA-PHY-optional-for-Arm.patch
+@@ -0,0 +1,64 @@
++From ee995101fde67f85a3cd4c74f4f92fc4592e726b Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
++Date: Thu, 3 Feb 2022 22:44:42 +0100
++Subject: [PATCH 1/3] Revert "ata: ahci: mvebu: Make SATA PHY optional for
++ Armada 3720"
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
 +
-+The __mtk_foe_entry_clear() function frees "entry" so we have to use
-+the _safe() version of hlist_for_each_entry() to prevent a use after
-+free.
++This reverts commit 45aefe3d2251e4e229d7662052739f96ad1d08d9.
 +
-+Fixes: 33fc42de3327 ("net: ethernet: mtk_eth_soc: support creating mac address based offload entries")
-+Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
++Armada 3720 PHY driver (phy-mvebu-a3700-comphy.c) does not return
++-EOPNOTSUPP from phy_power_on() callback anymore.
 +
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
-+@@ -600,6 +600,7 @@ void __mtk_ppe_check_skb(struct mtk_ppe
-+ 	struct mtk_foe_entry *hwe = &ppe->foe_table[hash];
-+ 	struct mtk_flow_entry *entry;
-+ 	struct mtk_foe_bridge key = {};
-++	struct hlist_node *n;
-+ 	struct ethhdr *eh;
-+ 	bool found = false;
-+ 	u8 *tag;
-+@@ -609,7 +610,7 @@ void __mtk_ppe_check_skb(struct mtk_ppe
-+ 	if (FIELD_GET(MTK_FOE_IB1_STATE, hwe->ib1) == MTK_FOE_STATE_BIND)
-+ 		goto out;
-+ 
-+-	hlist_for_each_entry(entry, head, list) {
-++	hlist_for_each_entry_safe(entry, n, head, list) {
-+ 		if (entry->type == MTK_FLOW_TYPE_L2_SUBFLOW) {
-+ 			if (unlikely(FIELD_GET(MTK_FOE_IB1_STATE, hwe->ib1) ==
-+ 				     MTK_FOE_STATE_BIND))
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-15-net-ethernet-mtk_eth_soc-add-check-for-allocation-fa.patch b/target/linux/generic/backport-5.15/702-v5.19-15-net-ethernet-mtk_eth_soc-add-check-for-allocation-fa.patch
-new file mode 100644
-index 0000000000..714163c86b
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-15-net-ethernet-mtk_eth_soc-add-check-for-allocation-fa.patch
-@@ -0,0 +1,22 @@
-+From: Dan Carpenter <dan.carpenter@oracle.com>
-+Date: Thu, 21 Apr 2022 18:49:02 +0300
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: add check for allocation failure
++So remove AHCI_HFLAG_IGN_NOTSUPP_POWER_ON flag from Armada 3720 plat data.
 +
-+Check if the kzalloc() failed.
++AHCI_HFLAG_IGN_NOTSUPP_POWER_ON is not used by any other ahci driver, so
++remove this flag completely.
 +
-+Fixes: 804775dfc288 ("net: ethernet: mtk_eth_soc: add support for Wireless Ethernet Dispatch (WED)")
-+Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Pali Rohár <pali@kernel.org>
++Signed-off-by: Marek Behún <kabel@kernel.org>
++Acked-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Acked-by: Damien Le Moal <damien.lemoal@opensource.wdc.com>
++Link: https://lore.kernel.org/r/20220203214444.1508-4-kabel@kernel.org
++Signed-off-by: Vinod Koul <vkoul@kernel.org>
 +---
-+
-+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
-++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
-+@@ -827,6 +827,8 @@ void mtk_wed_add_hw(struct device_node *
-+ 		goto unlock;
++ drivers/ata/ahci.h             | 2 --
++ drivers/ata/ahci_mvebu.c       | 2 +-
++ drivers/ata/libahci_platform.c | 2 +-
++ 3 files changed, 2 insertions(+), 4 deletions(-)
++
++--- a/drivers/ata/ahci.h
+++++ b/drivers/ata/ahci.h
++@@ -240,8 +240,6 @@ enum {
++ 							as default lpm_policy */
++ 	AHCI_HFLAG_SUSPEND_PHYS		= (1 << 26), /* handle PHYs during
++ 							suspend/resume */
++-	AHCI_HFLAG_IGN_NOTSUPP_POWER_ON	= (1 << 27), /* ignore -EOPNOTSUPP
++-							from phy_power_on() */
++ 	AHCI_HFLAG_NO_SXS		= (1 << 28), /* SXS not supported */
++ 
++ 	/* ap->flags bits */
++--- a/drivers/ata/ahci_mvebu.c
+++++ b/drivers/ata/ahci_mvebu.c
++@@ -227,7 +227,7 @@ static const struct ahci_mvebu_plat_data
++ 
++ static const struct ahci_mvebu_plat_data ahci_mvebu_armada_3700_plat_data = {
++ 	.plat_config = ahci_mvebu_armada_3700_config,
++-	.flags = AHCI_HFLAG_SUSPEND_PHYS | AHCI_HFLAG_IGN_NOTSUPP_POWER_ON,
+++	.flags = AHCI_HFLAG_SUSPEND_PHYS,
++ };
 + 
-+ 	hw = kzalloc(sizeof(*hw), GFP_KERNEL);
-++	if (!hw)
-++		goto unlock;
-+ 	hw->node = np;
-+ 	hw->regs = regs;
-+ 	hw->eth = eth;
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-16-eth-mtk_eth_soc-silence-the-GCC-12-array-bounds-warn.patch b/target/linux/generic/backport-5.15/702-v5.19-16-eth-mtk_eth_soc-silence-the-GCC-12-array-bounds-warn.patch
++ static const struct of_device_id ahci_mvebu_of_match[] = {
++--- a/drivers/ata/libahci_platform.c
+++++ b/drivers/ata/libahci_platform.c
++@@ -59,7 +59,7 @@ int ahci_platform_enable_phys(struct ahc
++ 		}
++ 
++ 		rc = phy_power_on(hpriv->phys[i]);
++-		if (rc && !(rc == -EOPNOTSUPP && (hpriv->flags & AHCI_HFLAG_IGN_NOTSUPP_POWER_ON))) {
+++		if (rc) {
++ 			phy_exit(hpriv->phys[i]);
++ 			goto disable_phys;
++ 		}
+diff --git a/target/linux/generic/backport-5.15/346-v5.18-02-Revert-usb-host-xhci-mvebu-make-USB-3.0-PHY-optional.patch b/target/linux/generic/backport-5.15/346-v5.18-02-Revert-usb-host-xhci-mvebu-make-USB-3.0-PHY-optional.patch
 new file mode 100644
-index 0000000000..aa98745ac6
+index 0000000000..1e8afb7bbf
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-16-eth-mtk_eth_soc-silence-the-GCC-12-array-bounds-warn.patch
-@@ -0,0 +1,26 @@
-+From: Jakub Kicinski <kuba@kernel.org>
-+Date: Fri, 20 May 2022 12:56:03 -0700
-+Subject: [PATCH] eth: mtk_eth_soc: silence the GCC 12 array-bounds warning
-+
-+GCC 12 gets upset because in mtk_foe_entry_commit_subflow()
-+this driver allocates a partial structure. The writes are
-+within bounds.
++++ b/target/linux/generic/backport-5.15/346-v5.18-02-Revert-usb-host-xhci-mvebu-make-USB-3.0-PHY-optional.patch
+@@ -0,0 +1,166 @@
++From 8e10548f7f4814e530857d2049d6af6bc78add53 Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
++Date: Thu, 3 Feb 2022 22:44:43 +0100
++Subject: [PATCH 2/3] Revert "usb: host: xhci: mvebu: make USB 3.0 PHY optional
++ for Armada 3720"
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
 +
-+Silence these warnings for now, our build bot runs GCC 12
-+so we won't allow any new instances.
++This reverts commit 3241929b67d28c83945d3191c6816a3271fd6b85.
 +
-+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
++Armada 3720 phy driver (phy-mvebu-a3700-comphy.c) does not return
++-EOPNOTSUPP from phy_power_on() callback anymore.
 +
-+--- a/drivers/net/ethernet/mediatek/Makefile
-++++ b/drivers/net/ethernet/mediatek/Makefile
-+@@ -11,3 +11,8 @@ mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) +
-+ endif
-+ obj-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed_ops.o
-+ obj-$(CONFIG_NET_MEDIATEK_STAR_EMAC) += mtk_star_emac.o
-++
-++# FIXME: temporarily silence -Warray-bounds on non W=1+ builds
-++ifndef KBUILD_EXTRA_WARN
-++CFLAGS_mtk_ppe.o += -Wno-array-bounds
-++endif
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-17-net-ethernet-mtk_eth_soc-rely-on-GFP_KERNEL-for-dma_.patch b/target/linux/generic/backport-5.15/702-v5.19-17-net-ethernet-mtk_eth_soc-rely-on-GFP_KERNEL-for-dma_.patch
-new file mode 100644
-index 0000000000..97677670cc
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-17-net-ethernet-mtk_eth_soc-rely-on-GFP_KERNEL-for-dma_.patch
-@@ -0,0 +1,52 @@
-+From: Lorenzo Bianconi <lorenzo@kernel.org>
-+Date: Fri, 20 May 2022 20:11:26 +0200
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: rely on GFP_KERNEL for
-+ dma_alloc_coherent whenever possible
++So remove XHCI_SKIP_PHY_INIT flag from xhci_mvebu_a3700_plat_setup() and
++then also whole xhci_mvebu_a3700_plat_setup() function which is there just
++to handle -EOPNOTSUPP for XHCI_SKIP_PHY_INIT.
 +
-+Rely on GFP_KERNEL for dma descriptors mappings in mtk_tx_alloc(),
-+mtk_rx_alloc() and mtk_init_fq_dma() since they are run in non-irq
-+context.
++xhci plat_setup callback is not used by any other xhci plat driver, so
++remove this callback completely.
 +
-+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Pali Rohár <pali@kernel.org>
++Signed-off-by: Marek Behún <kabel@kernel.org>
++Acked-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
++Link: https://lore.kernel.org/r/20220203214444.1508-5-kabel@kernel.org
++Signed-off-by: Vinod Koul <vkoul@kernel.org>
 +---
-+
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -845,7 +845,7 @@ static int mtk_init_fq_dma(struct mtk_et
-+ 	eth->scratch_ring = dma_alloc_coherent(eth->dma_dev,
-+ 					       cnt * sizeof(struct mtk_tx_dma),
-+ 					       &eth->phy_scratch_ring,
-+-					       GFP_ATOMIC);
-++					       GFP_KERNEL);
-+ 	if (unlikely(!eth->scratch_ring))
-+ 		return -ENOMEM;
++ drivers/usb/host/xhci-mvebu.c | 42 -----------------------------------
++ drivers/usb/host/xhci-mvebu.h |  6 -----
++ drivers/usb/host/xhci-plat.c  | 20 +----------------
++ drivers/usb/host/xhci-plat.h  |  1 -
++ 4 files changed, 1 insertion(+), 68 deletions(-)
++
++--- a/drivers/usb/host/xhci-mvebu.c
+++++ b/drivers/usb/host/xhci-mvebu.c
++@@ -8,7 +8,6 @@
++ #include <linux/mbus.h>
++ #include <linux/of.h>
++ #include <linux/platform_device.h>
++-#include <linux/phy/phy.h>
 + 
-+@@ -1623,7 +1623,7 @@ static int mtk_tx_alloc(struct mtk_eth *
-+ 		goto no_tx_mem;
++ #include <linux/usb.h>
++ #include <linux/usb/hcd.h>
++@@ -74,47 +73,6 @@ int xhci_mvebu_mbus_init_quirk(struct us
 + 
-+ 	ring->dma = dma_alloc_coherent(eth->dma_dev, MTK_DMA_SIZE * sz,
-+-				       &ring->phys, GFP_ATOMIC);
-++				       &ring->phys, GFP_KERNEL);
-+ 	if (!ring->dma)
-+ 		goto no_tx_mem;
++ 	return 0;
++ }
++-
++-int xhci_mvebu_a3700_plat_setup(struct usb_hcd *hcd)
++-{
++-	struct xhci_hcd *xhci = hcd_to_xhci(hcd);
++-	struct device *dev = hcd->self.controller;
++-	struct phy *phy;
++-	int ret;
++-
++-	/* Old bindings miss the PHY handle */
++-	phy = of_phy_get(dev->of_node, "usb3-phy");
++-	if (IS_ERR(phy) && PTR_ERR(phy) == -EPROBE_DEFER)
++-		return -EPROBE_DEFER;
++-	else if (IS_ERR(phy))
++-		goto phy_out;
++-
++-	ret = phy_init(phy);
++-	if (ret)
++-		goto phy_put;
++-
++-	ret = phy_set_mode(phy, PHY_MODE_USB_HOST_SS);
++-	if (ret)
++-		goto phy_exit;
++-
++-	ret = phy_power_on(phy);
++-	if (ret == -EOPNOTSUPP) {
++-		/* Skip initializatin of XHCI PHY when it is unsupported by firmware */
++-		dev_warn(dev, "PHY unsupported by firmware\n");
++-		xhci->quirks |= XHCI_SKIP_PHY_INIT;
++-	}
++-	if (ret)
++-		goto phy_exit;
++-
++-	phy_power_off(phy);
++-phy_exit:
++-	phy_exit(phy);
++-phy_put:
++-	of_phy_put(phy);
++-phy_out:
++-
++-	return 0;
++-}
 + 
-+@@ -1641,8 +1641,7 @@ static int mtk_tx_alloc(struct mtk_eth *
-+ 	 */
-+ 	if (!MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
-+ 		ring->dma_pdma = dma_alloc_coherent(eth->dma_dev, MTK_DMA_SIZE * sz,
-+-						    &ring->phys_pdma,
-+-						    GFP_ATOMIC);
-++						    &ring->phys_pdma, GFP_KERNEL);
-+ 		if (!ring->dma_pdma)
-+ 			goto no_tx_mem;
++ int xhci_mvebu_a3700_init_quirk(struct usb_hcd *hcd)
++ {
++--- a/drivers/usb/host/xhci-mvebu.h
+++++ b/drivers/usb/host/xhci-mvebu.h
++@@ -12,18 +12,12 @@ struct usb_hcd;
++ 
++ #if IS_ENABLED(CONFIG_USB_XHCI_MVEBU)
++ int xhci_mvebu_mbus_init_quirk(struct usb_hcd *hcd);
++-int xhci_mvebu_a3700_plat_setup(struct usb_hcd *hcd);
++ int xhci_mvebu_a3700_init_quirk(struct usb_hcd *hcd);
++ #else
++ static inline int xhci_mvebu_mbus_init_quirk(struct usb_hcd *hcd)
++ {
++ 	return 0;
++ }
++-
++-static inline int xhci_mvebu_a3700_plat_setup(struct usb_hcd *hcd)
++-{
++-	return 0;
++-}
 + 
-+@@ -1757,7 +1756,7 @@ static int mtk_rx_alloc(struct mtk_eth *
++ static inline int xhci_mvebu_a3700_init_quirk(struct usb_hcd *hcd)
++ {
++--- a/drivers/usb/host/xhci-plat.c
+++++ b/drivers/usb/host/xhci-plat.c
++@@ -44,16 +44,6 @@ static void xhci_priv_plat_start(struct
++ 		priv->plat_start(hcd);
++ }
 + 
-+ 	ring->dma = dma_alloc_coherent(eth->dma_dev,
-+ 				       rx_dma_size * sizeof(*ring->dma),
-+-				       &ring->phys, GFP_ATOMIC);
-++				       &ring->phys, GFP_KERNEL);
-+ 	if (!ring->dma)
-+ 		return -ENOMEM;
++-static int xhci_priv_plat_setup(struct usb_hcd *hcd)
++-{
++-	struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd);
++-
++-	if (!priv->plat_setup)
++-		return 0;
++-
++-	return priv->plat_setup(hcd);
++-}
++-
++ static int xhci_priv_init_quirk(struct usb_hcd *hcd)
++ {
++ 	struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd);
++@@ -121,7 +111,6 @@ static const struct xhci_plat_priv xhci_
++ };
 + 
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-18-net-ethernet-mtk_eth_soc-move-tx-dma-desc-configurat.patch b/target/linux/generic/backport-5.15/702-v5.19-18-net-ethernet-mtk_eth_soc-move-tx-dma-desc-configurat.patch
++ static const struct xhci_plat_priv xhci_plat_marvell_armada3700 = {
++-	.plat_setup = xhci_mvebu_a3700_plat_setup,
++ 	.init_quirk = xhci_mvebu_a3700_init_quirk,
++ };
++ 
++@@ -341,14 +330,7 @@ static int xhci_plat_probe(struct platfo
++ 
++ 	hcd->tpl_support = of_usb_host_tpl_support(sysdev->of_node);
++ 	xhci->shared_hcd->tpl_support = hcd->tpl_support;
++-
++-	if (priv) {
++-		ret = xhci_priv_plat_setup(hcd);
++-		if (ret)
++-			goto disable_usb_phy;
++-	}
++-
++-	if ((xhci->quirks & XHCI_SKIP_PHY_INIT) || (priv && (priv->quirks & XHCI_SKIP_PHY_INIT)))
+++	if (priv && (priv->quirks & XHCI_SKIP_PHY_INIT))
++ 		hcd->skip_phy_initialization = 1;
++ 
++ 	if (priv && (priv->quirks & XHCI_SG_TRB_CACHE_SIZE_QUIRK))
++--- a/drivers/usb/host/xhci-plat.h
+++++ b/drivers/usb/host/xhci-plat.h
++@@ -13,7 +13,6 @@
++ struct xhci_plat_priv {
++ 	const char *firmware_name;
++ 	unsigned long long quirks;
++-	int (*plat_setup)(struct usb_hcd *);
++ 	void (*plat_start)(struct usb_hcd *);
++ 	int (*init_quirk)(struct usb_hcd *);
++ 	int (*suspend_quirk)(struct usb_hcd *);
+diff --git a/target/linux/generic/backport-5.15/346-v5.18-03-Revert-PCI-aardvark-Fix-initialization-with-old-Marv.patch b/target/linux/generic/backport-5.15/346-v5.18-03-Revert-PCI-aardvark-Fix-initialization-with-old-Marv.patch
 new file mode 100644
-index 0000000000..95f122f730
+index 0000000000..fcfb02d35a
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-18-net-ethernet-mtk_eth_soc-move-tx-dma-desc-configurat.patch
-@@ -0,0 +1,206 @@
-+From: Lorenzo Bianconi <lorenzo@kernel.org>
-+Date: Fri, 20 May 2022 20:11:27 +0200
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: move tx dma desc configuration in
-+ mtk_tx_set_dma_desc
++++ b/target/linux/generic/backport-5.15/346-v5.18-03-Revert-PCI-aardvark-Fix-initialization-with-old-Marv.patch
+@@ -0,0 +1,39 @@
++From 9a4556dad7bd0a6b8339cb72e169f5c76f2af6f1 Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
++Date: Thu, 3 Feb 2022 22:44:44 +0100
++Subject: [PATCH 3/3] Revert "PCI: aardvark: Fix initialization with old
++ Marvell's Arm Trusted Firmware"
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
 +
-+Move tx dma descriptor configuration in mtk_tx_set_dma_desc routine.
-+This is a preliminary patch to introduce mt7986 ethernet support since
-+it relies on a different tx dma descriptor layout.
++This reverts commit b0c6ae0f8948a2be6bf4e8b4bbab9ca1343289b6.
 +
-+Tested-by: Sam Shih <sam.shih@mediatek.com>
-+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Armada 3720 phy driver (phy-mvebu-a3700-comphy.c) does not return
++-EOPNOTSUPP from phy_power_on() callback anymore.
++
++So remove dead code which handles -EOPNOTSUPP return value.
++
++Signed-off-by: Pali Rohár <pali@kernel.org>
++Signed-off-by: Marek Behún <kabel@kernel.org>
++Acked-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Acked-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
++Link: https://lore.kernel.org/r/20220203214444.1508-6-kabel@kernel.org
++Signed-off-by: Vinod Koul <vkoul@kernel.org>
 +---
++ drivers/pci/controller/pci-aardvark.c | 4 +---
++ 1 file changed, 1 insertion(+), 3 deletions(-)
 +
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -971,18 +971,51 @@ static void setup_tx_buf(struct mtk_eth
++--- a/drivers/pci/controller/pci-aardvark.c
+++++ b/drivers/pci/controller/pci-aardvark.c
++@@ -1642,9 +1642,7 @@ static int advk_pcie_enable_phy(struct a
 + 	}
-+ }
 + 
-++static void mtk_tx_set_dma_desc(struct net_device *dev, struct mtk_tx_dma *desc,
-++				struct mtk_tx_dma_desc_info *info)
-++{
-++	struct mtk_mac *mac = netdev_priv(dev);
-++	u32 data;
-++
-++	WRITE_ONCE(desc->txd1, info->addr);
-++
-++	data = TX_DMA_SWC | TX_DMA_PLEN0(info->size);
-++	if (info->last)
-++		data |= TX_DMA_LS0;
-++	WRITE_ONCE(desc->txd3, data);
-++
-++	data = (mac->id + 1) << TX_DMA_FPORT_SHIFT; /* forward port */
-++	if (info->first) {
-++		if (info->gso)
-++			data |= TX_DMA_TSO;
-++		/* tx checksum offload */
-++		if (info->csum)
-++			data |= TX_DMA_CHKSUM;
-++		/* vlan header offload */
-++		if (info->vlan)
-++			data |= TX_DMA_INS_VLAN | info->vlan_tci;
-++	}
-++	WRITE_ONCE(desc->txd4, data);
-++}
-++
-+ static int mtk_tx_map(struct sk_buff *skb, struct net_device *dev,
-+ 		      int tx_num, struct mtk_tx_ring *ring, bool gso)
-+ {
-++	struct mtk_tx_dma_desc_info txd_info = {
-++		.size = skb_headlen(skb),
-++		.gso = gso,
-++		.csum = skb->ip_summed == CHECKSUM_PARTIAL,
-++		.vlan = skb_vlan_tag_present(skb),
-++		.vlan_tci = skb_vlan_tag_get(skb),
-++		.first = true,
-++		.last = !skb_is_nonlinear(skb),
-++	};
-+ 	struct mtk_mac *mac = netdev_priv(dev);
-+ 	struct mtk_eth *eth = mac->hw;
-+ 	struct mtk_tx_dma *itxd, *txd;
-+ 	struct mtk_tx_dma *itxd_pdma, *txd_pdma;
-+ 	struct mtk_tx_buf *itx_buf, *tx_buf;
-+-	dma_addr_t mapped_addr;
-+-	unsigned int nr_frags;
-+ 	int i, n_desc = 1;
-+-	u32 txd4 = 0, fport;
-+ 	int k = 0;
++ 	ret = phy_power_on(pcie->phy);
++-	if (ret == -EOPNOTSUPP) {
++-		dev_warn(&pcie->pdev->dev, "PHY unsupported by firmware\n");
++-	} else if (ret) {
+++	if (ret) {
++ 		phy_exit(pcie->phy);
++ 		return ret;
++ 	}
+diff --git a/target/linux/generic/backport-5.15/347-v6.0-phy-marvell-phy-mvebu-a3700-comphy-Remove-broken-res.patch b/target/linux/generic/backport-5.15/347-v6.0-phy-marvell-phy-mvebu-a3700-comphy-Remove-broken-res.patch
+new file mode 100644
+index 0000000000..a2c897b7a9
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/347-v6.0-phy-marvell-phy-mvebu-a3700-comphy-Remove-broken-res.patch
+@@ -0,0 +1,194 @@
++From 0a6fc70d76bddf98278af2ac000379c82aec8f11 Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
++Date: Mon, 29 Aug 2022 10:30:46 +0200
++Subject: [PATCH] phy: marvell: phy-mvebu-a3700-comphy: Remove broken reset
++ support
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++Reset support for SATA PHY is somehow broken and after calling it, kernel
++is not able to detect and initialize SATA disk Samsung SSD 850 EMT0 [1].
++
++Reset support was introduced in commit 934337080c6c ("phy: marvell:
++phy-mvebu-a3700-comphy: Add native kernel implementation") as part of
++complete rewrite of this driver. v1 patch series of that commit [2] did
++not contain reset support and was tested that is working fine with
++Ethernet, SATA and USB PHYs without issues too.
++
++So for now remove broken reset support and change implementation of
++power_off callback to power off all functions on specified lane (and not
++only selected function) because during startup kernel does not know which
++function was selected and configured by bootloader. Same logic was used
++also in v1 patch series of that commit.
++
++This change fixes issues with initialization of SATA disk Samsung SSD 850
++and disk is working again, like before mentioned commit.
++
++Once problem with PHY reset callback is solved its functionality could be
++re-introduced. But for now it is unknown why it does not work.
++
++[1] - https://lore.kernel.org/r/20220531124159.3e4lgn2v462irbtz@shindev/
++[2] - https://lore.kernel.org/r/20211028184242.22105-1-kabel@kernel.org/
++
++Reported-by: Shinichiro Kawasaki <shinichiro.kawasaki@wdc.com>
++Fixes: 934337080c6c ("phy: marvell: phy-mvebu-a3700-comphy: Add native kernel implementation")
++Cc: stable@vger.kernel.org # v5.18+
++Signed-off-by: Pali Rohár <pali@kernel.org>
++Tested-by: Shinichiro Kawasaki <shinichiro.kawasaki@wdc.com>
++Link: https://lore.kernel.org/r/20220829083046.15082-1-pali@kernel.org
++Signed-off-by: Vinod Koul <vkoul@kernel.org>
++---
++ drivers/phy/marvell/phy-mvebu-a3700-comphy.c | 87 ++++----------------
++ 1 file changed, 17 insertions(+), 70 deletions(-)
++
++--- a/drivers/phy/marvell/phy-mvebu-a3700-comphy.c
+++++ b/drivers/phy/marvell/phy-mvebu-a3700-comphy.c
++@@ -274,7 +274,6 @@ struct mvebu_a3700_comphy_lane {
++ 	int submode;
++ 	bool invert_tx;
++ 	bool invert_rx;
++-	bool needs_reset;
++ };
 + 
-+ 	itxd = ring->next_free;
-+@@ -990,49 +1023,32 @@ static int mtk_tx_map(struct sk_buff *sk
-+ 	if (itxd == ring->last_free)
-+ 		return -ENOMEM;
++ struct gbe_phy_init_data_fix {
++@@ -1097,40 +1096,12 @@ mvebu_a3700_comphy_pcie_power_off(struct
++ 			    0x0, PU_PLL_BIT | PU_RX_BIT | PU_TX_BIT);
++ }
 + 
-+-	/* set the forward port */
-+-	fport = (mac->id + 1) << TX_DMA_FPORT_SHIFT;
-+-	txd4 |= fport;
++-static int mvebu_a3700_comphy_reset(struct phy *phy)
+++static void mvebu_a3700_comphy_usb3_power_off(struct mvebu_a3700_comphy_lane *lane)
++ {
++-	struct mvebu_a3700_comphy_lane *lane = phy_get_drvdata(phy);
++-	u16 mask, data;
 +-
-+ 	itx_buf = mtk_desc_to_tx_buf(ring, itxd);
-+ 	memset(itx_buf, 0, sizeof(*itx_buf));
-+ 
-+-	if (gso)
-+-		txd4 |= TX_DMA_TSO;
++-	dev_dbg(lane->dev, "resetting lane %d\n", lane->id);
 +-
-+-	/* TX Checksum offload */
-+-	if (skb->ip_summed == CHECKSUM_PARTIAL)
-+-		txd4 |= TX_DMA_CHKSUM;
++-	/* COMPHY reset for internal logic */
++-	comphy_lane_reg_set(lane, COMPHY_SFT_RESET,
++-			    SFT_RST_NO_REG, SFT_RST_NO_REG);
 +-
-+-	/* VLAN header offload */
-+-	if (skb_vlan_tag_present(skb))
-+-		txd4 |= TX_DMA_INS_VLAN | skb_vlan_tag_get(skb);
++-	/* COMPHY register reset (cleared automatically) */
++-	comphy_lane_reg_set(lane, COMPHY_SFT_RESET, SFT_RST, SFT_RST);
 +-
-+-	mapped_addr = dma_map_single(eth->dma_dev, skb->data,
-+-				     skb_headlen(skb), DMA_TO_DEVICE);
-+-	if (unlikely(dma_mapping_error(eth->dma_dev, mapped_addr)))
-++	txd_info.addr = dma_map_single(eth->dma_dev, skb->data, txd_info.size,
-++				       DMA_TO_DEVICE);
-++	if (unlikely(dma_mapping_error(eth->dma_dev, txd_info.addr)))
-+ 		return -ENOMEM;
-+ 
-+-	WRITE_ONCE(itxd->txd1, mapped_addr);
-++	mtk_tx_set_dma_desc(dev, itxd, &txd_info);
-++
-+ 	itx_buf->flags |= MTK_TX_FLAGS_SINGLE0;
-+ 	itx_buf->flags |= (!mac->id) ? MTK_TX_FLAGS_FPORT0 :
-+ 			  MTK_TX_FLAGS_FPORT1;
-+-	setup_tx_buf(eth, itx_buf, itxd_pdma, mapped_addr, skb_headlen(skb),
-++	setup_tx_buf(eth, itx_buf, itxd_pdma, txd_info.addr, txd_info.size,
-+ 		     k++);
-+ 
-+ 	/* TX SG offload */
-+ 	txd = itxd;
-+ 	txd_pdma = qdma_to_pdma(ring, txd);
-+-	nr_frags = skb_shinfo(skb)->nr_frags;
++-	/* PIPE soft and register reset */
++-	data = PIPE_SOFT_RESET | PIPE_REG_RESET;
++-	mask = data;
++-	comphy_lane_reg_set(lane, COMPHY_PIPE_RST_CLK_CTRL, data, mask);
++-
++-	/* Release PIPE register reset */
++-	comphy_lane_reg_set(lane, COMPHY_PIPE_RST_CLK_CTRL,
++-			    0x0, PIPE_REG_RESET);
++-
++-	/* Reset SB configuration register (only for lanes 0 and 1) */
++-	if (lane->id == 0 || lane->id == 1) {
++-		u32 mask, data;
++-
++-		data = PIN_RESET_CORE_BIT | PIN_RESET_COMPHY_BIT |
++-		       PIN_PU_PLL_BIT | PIN_PU_RX_BIT | PIN_PU_TX_BIT;
++-		mask = data | PIN_PU_IVREF_BIT | PIN_TX_IDLE_BIT;
++-		comphy_periph_reg_set(lane, COMPHY_PHY_CFG1, data, mask);
++-	}
++-
++-	return 0;
+++	/*
+++	 * The USB3 MAC sets the USB3 PHY to low state, so we do not
+++	 * need to power off USB3 PHY again.
+++	 */
++ }
 + 
-+-	for (i = 0; i < nr_frags; i++) {
-++	for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
-+ 		skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
-+ 		unsigned int offset = 0;
-+ 		int frag_size = skb_frag_size(frag);
++ static bool mvebu_a3700_comphy_check_mode(int lane,
++@@ -1171,10 +1142,6 @@ static int mvebu_a3700_comphy_set_mode(s
++ 	    (lane->mode != mode || lane->submode != submode))
++ 		return -EBUSY;
 + 
-+ 		while (frag_size) {
-+-			bool last_frag = false;
-+-			unsigned int frag_map_size;
-+ 			bool new_desc = true;
++-	/* If changing mode, ensure reset is called */
++-	if (lane->mode != PHY_MODE_INVALID && lane->mode != mode)
++-		lane->needs_reset = true;
++-
++ 	/* Just remember the mode, ->power_on() will do the real setup */
++ 	lane->mode = mode;
++ 	lane->submode = submode;
++@@ -1185,7 +1152,6 @@ static int mvebu_a3700_comphy_set_mode(s
++ static int mvebu_a3700_comphy_power_on(struct phy *phy)
++ {
++ 	struct mvebu_a3700_comphy_lane *lane = phy_get_drvdata(phy);
++-	int ret;
 + 
-+ 			if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA) ||
-+@@ -1047,23 +1063,17 @@ static int mtk_tx_map(struct sk_buff *sk
-+ 				new_desc = false;
-+ 			}
++ 	if (!mvebu_a3700_comphy_check_mode(lane->id, lane->mode,
++ 					   lane->submode)) {
++@@ -1193,14 +1159,6 @@ static int mvebu_a3700_comphy_power_on(s
++ 		return -EINVAL;
++ 	}
 + 
++-	if (lane->needs_reset) {
++-		ret = mvebu_a3700_comphy_reset(phy);
++-		if (ret)
++-			return ret;
 +-
-+-			frag_map_size = min(frag_size, MTK_TX_DMA_BUF_LEN);
-+-			mapped_addr = skb_frag_dma_map(eth->dma_dev, frag, offset,
-+-						       frag_map_size,
-+-						       DMA_TO_DEVICE);
-+-			if (unlikely(dma_mapping_error(eth->dma_dev, mapped_addr)))
-++			memset(&txd_info, 0, sizeof(struct mtk_tx_dma_desc_info));
-++			txd_info.size = min(frag_size, MTK_TX_DMA_BUF_LEN);
-++			txd_info.last = i == skb_shinfo(skb)->nr_frags - 1 &&
-++					!(frag_size - txd_info.size);
-++			txd_info.addr = skb_frag_dma_map(eth->dma_dev, frag,
-++							 offset, txd_info.size,
-++							 DMA_TO_DEVICE);
-++			if (unlikely(dma_mapping_error(eth->dma_dev, txd_info.addr)))
-+ 				goto err_dma;
-+ 
-+-			if (i == nr_frags - 1 &&
-+-			    (frag_size - frag_map_size) == 0)
-+-				last_frag = true;
++-		lane->needs_reset = false;
++-	}
 +-
-+-			WRITE_ONCE(txd->txd1, mapped_addr);
-+-			WRITE_ONCE(txd->txd3, (TX_DMA_SWC |
-+-					       TX_DMA_PLEN0(frag_map_size) |
-+-					       last_frag * TX_DMA_LS0));
-+-			WRITE_ONCE(txd->txd4, fport);
-++			mtk_tx_set_dma_desc(dev, txd, &txd_info);
-+ 
-+ 			tx_buf = mtk_desc_to_tx_buf(ring, txd);
-+ 			if (new_desc)
-+@@ -1073,20 +1083,17 @@ static int mtk_tx_map(struct sk_buff *sk
-+ 			tx_buf->flags |= (!mac->id) ? MTK_TX_FLAGS_FPORT0 :
-+ 					 MTK_TX_FLAGS_FPORT1;
-+ 
-+-			setup_tx_buf(eth, tx_buf, txd_pdma, mapped_addr,
-+-				     frag_map_size, k++);
-++			setup_tx_buf(eth, tx_buf, txd_pdma, txd_info.addr,
-++				     txd_info.size, k++);
++ 	switch (lane->mode) {
++ 	case PHY_MODE_USB_HOST_SS:
++ 		dev_dbg(lane->dev, "set lane %d to USB3 host mode\n", lane->id);
++@@ -1224,38 +1182,28 @@ static int mvebu_a3700_comphy_power_off(
++ {
++ 	struct mvebu_a3700_comphy_lane *lane = phy_get_drvdata(phy);
 + 
-+-			frag_size -= frag_map_size;
-+-			offset += frag_map_size;
-++			frag_size -= txd_info.size;
-++			offset += txd_info.size;
-+ 		}
++-	switch (lane->mode) {
++-	case PHY_MODE_USB_HOST_SS:
++-		/*
++-		 * The USB3 MAC sets the USB3 PHY to low state, so we do not
++-		 * need to power off USB3 PHY again.
++-		 */
++-		break;
++-
++-	case PHY_MODE_SATA:
++-		mvebu_a3700_comphy_sata_power_off(lane);
++-		break;
++-
++-	case PHY_MODE_ETHERNET:
+++	switch (lane->id) {
+++	case 0:
+++		mvebu_a3700_comphy_usb3_power_off(lane);
++ 		mvebu_a3700_comphy_ethernet_power_off(lane);
++-		break;
++-
++-	case PHY_MODE_PCIE:
+++		return 0;
+++	case 1:
++ 		mvebu_a3700_comphy_pcie_power_off(lane);
++-		break;
++-
+++		mvebu_a3700_comphy_ethernet_power_off(lane);
+++		return 0;
+++	case 2:
+++		mvebu_a3700_comphy_usb3_power_off(lane);
+++		mvebu_a3700_comphy_sata_power_off(lane);
+++		return 0;
++ 	default:
++ 		dev_err(lane->dev, "invalid COMPHY mode\n");
++ 		return -EINVAL;
 + 	}
++-
++-	return 0;
++ }
 + 
-+ 	/* store skb to cleanup */
-+ 	itx_buf->skb = skb;
-+ 
-+-	WRITE_ONCE(itxd->txd4, txd4);
-+-	WRITE_ONCE(itxd->txd3, (TX_DMA_SWC | TX_DMA_PLEN0(skb_headlen(skb)) |
-+-				(!nr_frags * TX_DMA_LS0)));
-+ 	if (!MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
-+ 		if (k & 0x1)
-+ 			txd_pdma->txd2 |= TX_DMA_LS0;
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-+@@ -842,6 +842,17 @@ enum mkt_eth_capabilities {
-+ 		      MTK_MUX_U3_GMAC2_TO_QPHY | \
-+ 		      MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA)
++ static const struct phy_ops mvebu_a3700_comphy_ops = {
++ 	.power_on	= mvebu_a3700_comphy_power_on,
++ 	.power_off	= mvebu_a3700_comphy_power_off,
++-	.reset		= mvebu_a3700_comphy_reset,
++ 	.set_mode	= mvebu_a3700_comphy_set_mode,
++ 	.owner		= THIS_MODULE,
++ };
++@@ -1393,8 +1341,7 @@ static int mvebu_a3700_comphy_probe(stru
++ 		 * To avoid relying on the bootloader/firmware configuration,
++ 		 * power off all comphys.
++ 		 */
++-		mvebu_a3700_comphy_reset(phy);
++-		lane->needs_reset = false;
+++		mvebu_a3700_comphy_power_off(phy);
++ 	}
 + 
-++struct mtk_tx_dma_desc_info {
-++	dma_addr_t	addr;
-++	u32		size;
-++	u16		vlan_tci;
-++	u8		gso:1;
-++	u8		csum:1;
-++	u8		vlan:1;
-++	u8		first:1;
-++	u8		last:1;
-++};
-++
-+ /* struct mtk_eth_data -	This is the structure holding all differences
-+  *				among various plaforms
-+  * @ana_rgc3:                   The offset for register ANA_RGC3 related to
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-19-net-ethernet-mtk_eth_soc-add-txd_size-to-mtk_soc_dat.patch b/target/linux/generic/backport-5.15/702-v5.19-19-net-ethernet-mtk_eth_soc-add-txd_size-to-mtk_soc_dat.patch
++ 	provider = devm_of_phy_provider_register(&pdev->dev,
+diff --git a/target/linux/generic/backport-5.15/400-v5.19-mtd-call-of_platform_populate-for-MTD-partitions.patch b/target/linux/generic/backport-5.15/400-v5.19-mtd-call-of_platform_populate-for-MTD-partitions.patch
 new file mode 100644
-index 0000000000..7aeeda0ba0
+index 0000000000..1f3aae13b4
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-19-net-ethernet-mtk_eth_soc-add-txd_size-to-mtk_soc_dat.patch
-@@ -0,0 +1,167 @@
-+From: Lorenzo Bianconi <lorenzo@kernel.org>
-+Date: Fri, 20 May 2022 20:11:28 +0200
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: add txd_size to mtk_soc_data
++++ b/target/linux/generic/backport-5.15/400-v5.19-mtd-call-of_platform_populate-for-MTD-partitions.patch
+@@ -0,0 +1,72 @@
++From bcdf0315a61a29eb753a607d3a85a4032de72d94 Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Tue, 10 May 2022 15:12:59 +0200
++Subject: [PATCH] mtd: call of_platform_populate() for MTD partitions
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
 +
-+In order to remove mtk_tx_dma size dependency, introduce txd_size in
-+mtk_soc_data data structure. Rely on txd_size in mtk_init_fq_dma() and
-+mtk_dma_free() routines.
-+This is a preliminary patch to add mt7986 ethernet support.
++Until this change MTD subsystem supported handling partitions only with
++MTD partitions parsers. That's a specific / limited API designed around
++partitions.
 +
-+Tested-by: Sam Shih <sam.shih@mediatek.com>
-+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Some MTD partitions may however require different handling. They may
++contain specific data that needs to be parsed and somehow extracted. For
++that purpose MTD subsystem should allow binding of standard platform
++drivers.
++
++An example can be U-Boot (sub)partition with environment variables.
++There exist a "u-boot,env" DT binding for MTD (sub)partition that
++requires an NVMEM driver.
++
++Ref: 5db1c2dbc04c ("dt-bindings: nvmem: add U-Boot environment variables binding")
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Link: https://lore.kernel.org/linux-mtd/20220510131259.555-1-zajec5@gmail.com
 +---
++ drivers/mtd/mtdpart.c | 9 +++++++++
++ 1 file changed, 9 insertions(+)
 +
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -837,20 +837,20 @@ static void *mtk_max_lro_buf_alloc(gfp_t
-+ /* the qdma core needs scratch memory to be setup */
-+ static int mtk_init_fq_dma(struct mtk_eth *eth)
-+ {
-++	const struct mtk_soc_data *soc = eth->soc;
-+ 	dma_addr_t phy_ring_tail;
-+ 	int cnt = MTK_DMA_SIZE;
-+ 	dma_addr_t dma_addr;
-+ 	int i;
-+ 
-+ 	eth->scratch_ring = dma_alloc_coherent(eth->dma_dev,
-+-					       cnt * sizeof(struct mtk_tx_dma),
-++					       cnt * soc->txrx.txd_size,
-+ 					       &eth->phy_scratch_ring,
-+ 					       GFP_KERNEL);
-+ 	if (unlikely(!eth->scratch_ring))
-+ 		return -ENOMEM;
-+ 
-+-	eth->scratch_head = kcalloc(cnt, MTK_QDMA_PAGE_SIZE,
-+-				    GFP_KERNEL);
-++	eth->scratch_head = kcalloc(cnt, MTK_QDMA_PAGE_SIZE, GFP_KERNEL);
-+ 	if (unlikely(!eth->scratch_head))
-+ 		return -ENOMEM;
++--- a/drivers/mtd/mtdpart.c
+++++ b/drivers/mtd/mtdpart.c
++@@ -17,6 +17,7 @@
++ #include <linux/mtd/partitions.h>
++ #include <linux/err.h>
++ #include <linux/of.h>
+++#include <linux/of_platform.h>
 + 
-+@@ -860,16 +860,19 @@ static int mtk_init_fq_dma(struct mtk_et
-+ 	if (unlikely(dma_mapping_error(eth->dma_dev, dma_addr)))
-+ 		return -ENOMEM;
++ #include "mtdcore.h"
 + 
-+-	phy_ring_tail = eth->phy_scratch_ring +
-+-			(sizeof(struct mtk_tx_dma) * (cnt - 1));
-++	phy_ring_tail = eth->phy_scratch_ring + soc->txrx.txd_size * (cnt - 1);
++@@ -577,10 +578,16 @@ static int mtd_part_of_parse(struct mtd_
++ 	struct mtd_part_parser *parser;
++ 	struct device_node *np;
++ 	struct property *prop;
+++	struct device *dev;
++ 	const char *compat;
++ 	const char *fixed = "fixed-partitions";
++ 	int ret, err = 0;
 + 
-+ 	for (i = 0; i < cnt; i++) {
-+-		eth->scratch_ring[i].txd1 =
-+-					(dma_addr + (i * MTK_QDMA_PAGE_SIZE));
-++		struct mtk_tx_dma *txd;
-++
-++		txd = (void *)eth->scratch_ring + i * soc->txrx.txd_size;
-++		txd->txd1 = dma_addr + i * MTK_QDMA_PAGE_SIZE;
-+ 		if (i < cnt - 1)
-+-			eth->scratch_ring[i].txd2 = (eth->phy_scratch_ring +
-+-				((i + 1) * sizeof(struct mtk_tx_dma)));
-+-		eth->scratch_ring[i].txd3 = TX_DMA_SDL(MTK_QDMA_PAGE_SIZE);
-++			txd->txd2 = eth->phy_scratch_ring +
-++				    (i + 1) * soc->txrx.txd_size;
+++	dev = &master->dev;
+++	/* Use parent device (controller) if the top level MTD is not registered */
+++	if (!IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER) && !mtd_is_partition(master))
+++		dev = master->dev.parent;
 ++
-++		txd->txd3 = TX_DMA_PLEN0(MTK_QDMA_PAGE_SIZE);
-++		txd->txd4 = 0;
-+ 	}
-+ 
-+ 	mtk_w32(eth, eth->phy_scratch_ring, MTK_QDMA_FQ_HEAD);
-+@@ -2169,6 +2172,7 @@ static int mtk_dma_init(struct mtk_eth *
-+ 
-+ static void mtk_dma_free(struct mtk_eth *eth)
-+ {
-++	const struct mtk_soc_data *soc = eth->soc;
-+ 	int i;
-+ 
-+ 	for (i = 0; i < MTK_MAC_COUNT; i++)
-+@@ -2176,9 +2180,8 @@ static void mtk_dma_free(struct mtk_eth
-+ 			netdev_reset_queue(eth->netdev[i]);
-+ 	if (eth->scratch_ring) {
-+ 		dma_free_coherent(eth->dma_dev,
-+-				  MTK_DMA_SIZE * sizeof(struct mtk_tx_dma),
-+-				  eth->scratch_ring,
-+-				  eth->phy_scratch_ring);
-++				  MTK_DMA_SIZE * soc->txrx.txd_size,
-++				  eth->scratch_ring, eth->phy_scratch_ring);
-+ 		eth->scratch_ring = NULL;
-+ 		eth->phy_scratch_ring = 0;
++ 	np = mtd_get_of_node(master);
++ 	if (mtd_is_partition(master))
++ 		of_node_get(np);
++@@ -593,6 +600,7 @@ static int mtd_part_of_parse(struct mtd_
++ 			continue;
++ 		ret = mtd_part_do_parse(parser, master, pparts, NULL);
++ 		if (ret > 0) {
+++			of_platform_populate(np, NULL, NULL, dev);
++ 			of_node_put(np);
++ 			return ret;
++ 		}
++@@ -600,6 +608,7 @@ static int mtd_part_of_parse(struct mtd_
++ 		if (ret < 0 && !err)
++ 			err = ret;
 + 	}
-+@@ -3388,6 +3391,9 @@ static const struct mtk_soc_data mt2701_
-+ 	.hw_features = MTK_HW_FEATURES,
-+ 	.required_clks = MT7623_CLKS_BITMAP,
-+ 	.required_pctl = true,
-++	.txrx = {
-++		.txd_size = sizeof(struct mtk_tx_dma),
-++	},
-+ };
-+ 
-+ static const struct mtk_soc_data mt7621_data = {
-+@@ -3396,6 +3402,9 @@ static const struct mtk_soc_data mt7621_
-+ 	.required_clks = MT7621_CLKS_BITMAP,
-+ 	.required_pctl = false,
-+ 	.offload_version = 2,
-++	.txrx = {
-++		.txd_size = sizeof(struct mtk_tx_dma),
-++	},
-+ };
-+ 
-+ static const struct mtk_soc_data mt7622_data = {
-+@@ -3405,6 +3414,9 @@ static const struct mtk_soc_data mt7622_
-+ 	.required_clks = MT7622_CLKS_BITMAP,
-+ 	.required_pctl = false,
-+ 	.offload_version = 2,
-++	.txrx = {
-++		.txd_size = sizeof(struct mtk_tx_dma),
-++	},
-+ };
-+ 
-+ static const struct mtk_soc_data mt7623_data = {
-+@@ -3413,6 +3425,9 @@ static const struct mtk_soc_data mt7623_
-+ 	.required_clks = MT7623_CLKS_BITMAP,
-+ 	.required_pctl = true,
-+ 	.offload_version = 2,
-++	.txrx = {
-++		.txd_size = sizeof(struct mtk_tx_dma),
-++	},
-+ };
-+ 
-+ static const struct mtk_soc_data mt7629_data = {
-+@@ -3421,6 +3436,9 @@ static const struct mtk_soc_data mt7629_
-+ 	.hw_features = MTK_HW_FEATURES,
-+ 	.required_clks = MT7629_CLKS_BITMAP,
-+ 	.required_pctl = false,
-++	.txrx = {
-++		.txd_size = sizeof(struct mtk_tx_dma),
-++	},
-+ };
-+ 
-+ static const struct mtk_soc_data rt5350_data = {
-+@@ -3428,6 +3446,9 @@ static const struct mtk_soc_data rt5350_
-+ 	.hw_features = MTK_HW_FEATURES_MT7628,
-+ 	.required_clks = MT7628_CLKS_BITMAP,
-+ 	.required_pctl = false,
-++	.txrx = {
-++		.txd_size = sizeof(struct mtk_tx_dma),
-++	},
-+ };
-+ 
-+ const struct of_device_id of_mtk_match[] = {
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-+@@ -863,6 +863,7 @@ struct mtk_tx_dma_desc_info {
-+  *				the target SoC
-+  * @required_pctl		A bool value to show whether the SoC requires
-+  *				the extra setup for those pins used by GMAC.
-++ * @txd_size			Tx DMA descriptor size.
-+  */
-+ struct mtk_soc_data {
-+ 	u32             ana_rgc3;
-+@@ -871,6 +872,9 @@ struct mtk_soc_data {
-+ 	bool		required_pctl;
-+ 	u8		offload_version;
-+ 	netdev_features_t hw_features;
-++	struct {
-++		u32	txd_size;
-++	} txrx;
-+ };
+++	of_platform_populate(np, NULL, NULL, dev);
++ 	of_node_put(np);
 + 
-+ /* currently no SoC has more than 2 macs */
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-20-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-mtk_tx_.patch b/target/linux/generic/backport-5.15/702-v5.19-20-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-mtk_tx_.patch
++ 	/*
+diff --git a/target/linux/generic/backport-5.15/401-v6.0-mtd-parsers-add-support-for-Sercomm-partitions.patch b/target/linux/generic/backport-5.15/401-v6.0-mtd-parsers-add-support-for-Sercomm-partitions.patch
 new file mode 100644
-index 0000000000..01dbca0753
+index 0000000000..113a96ad42
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-20-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-mtk_tx_.patch
-@@ -0,0 +1,78 @@
-+From: Lorenzo Bianconi <lorenzo@kernel.org>
-+Date: Fri, 20 May 2022 20:11:29 +0200
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: rely on txd_size in
-+ mtk_tx_alloc/mtk_tx_clean
++++ b/target/linux/generic/backport-5.15/401-v6.0-mtd-parsers-add-support-for-Sercomm-partitions.patch
+@@ -0,0 +1,302 @@
++From 9b78ef0c7997052e9eaa0f7a4513d546fa17358c Mon Sep 17 00:00:00 2001
++From: Mikhail Zhilkin <csharper2005@gmail.com>
++Date: Sun, 29 May 2022 11:07:14 +0000
++Subject: [PATCH] mtd: parsers: add support for Sercomm partitions
 +
-+This is a preliminary patch to add mt7986 ethernet support.
++This adds an MTD partition parser for the Sercomm partition table that
++is used in some Beeline, Netgear and Sercomm routers.
 +
-+Tested-by: Sam Shih <sam.shih@mediatek.com>
-+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++The Sercomm partition map table contains real partition offsets, which
++may differ from device to device depending on the number and location of
++bad blocks on NAND.
++
++Original patch (proposed by NOGUCHI Hiroshi):
++Link: https://github.com/openwrt/openwrt/pull/1318#issuecomment-420607394
++
++Signed-off-by: NOGUCHI Hiroshi <drvlabo@gmail.com>
++Signed-off-by: Mikhail Zhilkin <csharper2005@gmail.com>
++Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Link: https://lore.kernel.org/linux-mtd/20220529110714.189732-1-csharper2005@gmail.com
 +---
++ drivers/mtd/parsers/Kconfig  |   9 ++
++ drivers/mtd/parsers/Makefile |   1 +
++ drivers/mtd/parsers/scpart.c | 248 +++++++++++++++++++++++++++++++++++
++ 3 files changed, 258 insertions(+)
++ create mode 100644 drivers/mtd/parsers/scpart.c
 +
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -1624,8 +1624,10 @@ static int mtk_napi_rx(struct napi_struc
-+ 
-+ static int mtk_tx_alloc(struct mtk_eth *eth)
-+ {
-++	const struct mtk_soc_data *soc = eth->soc;
-+ 	struct mtk_tx_ring *ring = &eth->tx_ring;
-+-	int i, sz = sizeof(*ring->dma);
-++	int i, sz = soc->txrx.txd_size;
-++	struct mtk_tx_dma *txd;
-+ 
-+ 	ring->buf = kcalloc(MTK_DMA_SIZE, sizeof(*ring->buf),
-+ 			       GFP_KERNEL);
-+@@ -1641,8 +1643,10 @@ static int mtk_tx_alloc(struct mtk_eth *
-+ 		int next = (i + 1) % MTK_DMA_SIZE;
-+ 		u32 next_ptr = ring->phys + next * sz;
-+ 
-+-		ring->dma[i].txd2 = next_ptr;
-+-		ring->dma[i].txd3 = TX_DMA_LS0 | TX_DMA_OWNER_CPU;
-++		txd = (void *)ring->dma + i * sz;
-++		txd->txd2 = next_ptr;
-++		txd->txd3 = TX_DMA_LS0 | TX_DMA_OWNER_CPU;
-++		txd->txd4 = 0;
-+ 	}
-+ 
-+ 	/* On MT7688 (PDMA only) this driver uses the ring->dma structs
-+@@ -1664,7 +1668,7 @@ static int mtk_tx_alloc(struct mtk_eth *
-+ 	ring->dma_size = MTK_DMA_SIZE;
-+ 	atomic_set(&ring->free_count, MTK_DMA_SIZE - 2);
-+ 	ring->next_free = &ring->dma[0];
-+-	ring->last_free = &ring->dma[MTK_DMA_SIZE - 1];
-++	ring->last_free = (void *)txd;
-+ 	ring->last_free_ptr = (u32)(ring->phys + ((MTK_DMA_SIZE - 1) * sz));
-+ 	ring->thresh = MAX_SKB_FRAGS;
-+ 
-+@@ -1697,6 +1701,7 @@ no_tx_mem:
-+ 
-+ static void mtk_tx_clean(struct mtk_eth *eth)
-+ {
-++	const struct mtk_soc_data *soc = eth->soc;
-+ 	struct mtk_tx_ring *ring = &eth->tx_ring;
-+ 	int i;
-+ 
-+@@ -1709,17 +1714,15 @@ static void mtk_tx_clean(struct mtk_eth
-+ 
-+ 	if (ring->dma) {
-+ 		dma_free_coherent(eth->dma_dev,
-+-				  MTK_DMA_SIZE * sizeof(*ring->dma),
-+-				  ring->dma,
-+-				  ring->phys);
-++				  MTK_DMA_SIZE * soc->txrx.txd_size,
-++				  ring->dma, ring->phys);
-+ 		ring->dma = NULL;
-+ 	}
-+ 
-+ 	if (ring->dma_pdma) {
-+ 		dma_free_coherent(eth->dma_dev,
-+-				  MTK_DMA_SIZE * sizeof(*ring->dma_pdma),
-+-				  ring->dma_pdma,
-+-				  ring->phys_pdma);
-++				  MTK_DMA_SIZE * soc->txrx.txd_size,
-++				  ring->dma_pdma, ring->phys_pdma);
-+ 		ring->dma_pdma = NULL;
-+ 	}
-+ }
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-21-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-mtk_des.patch b/target/linux/generic/backport-5.15/702-v5.19-21-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-mtk_des.patch
++--- a/drivers/mtd/parsers/Kconfig
+++++ b/drivers/mtd/parsers/Kconfig
++@@ -186,3 +186,12 @@ config MTD_QCOMSMEM_PARTS
++ 	help
++ 	  This provides support for parsing partitions from Shared Memory (SMEM)
++ 	  for NAND and SPI flash on Qualcomm platforms.
+++
+++config MTD_SERCOMM_PARTS
+++	tristate "Sercomm partition table parser"
+++	depends on MTD && RALINK
+++	help
+++	  This provides partitions table parser for devices with Sercomm
+++	  partition map. This partition table contains real partition
+++	  offsets, which may differ from device to device depending on the
+++	  number and location of bad blocks on NAND.
++--- a/drivers/mtd/parsers/Makefile
+++++ b/drivers/mtd/parsers/Makefile
++@@ -10,6 +10,7 @@ ofpart-$(CONFIG_MTD_OF_PARTS_LINKSYS_NS)
++ obj-$(CONFIG_MTD_PARSER_IMAGETAG)	+= parser_imagetag.o
++ obj-$(CONFIG_MTD_AFS_PARTS)		+= afs.o
++ obj-$(CONFIG_MTD_PARSER_TRX)		+= parser_trx.o
+++obj-$(CONFIG_MTD_SERCOMM_PARTS)		+= scpart.o
++ obj-$(CONFIG_MTD_SHARPSL_PARTS)		+= sharpslpart.o
++ obj-$(CONFIG_MTD_REDBOOT_PARTS)		+= redboot.o
++ obj-$(CONFIG_MTD_QCOMSMEM_PARTS)	+= qcomsmempart.o
++--- /dev/null
+++++ b/drivers/mtd/parsers/scpart.c
++@@ -0,0 +1,248 @@
+++// SPDX-License-Identifier: GPL-2.0-or-later
+++/*
+++ *    drivers/mtd/scpart.c: Sercomm Partition Parser
+++ *
+++ *    Copyright (C) 2018 NOGUCHI Hiroshi
+++ *    Copyright (C) 2022 Mikhail Zhilkin
+++ */
+++
+++#include <linux/kernel.h>
+++#include <linux/slab.h>
+++#include <linux/mtd/mtd.h>
+++#include <linux/mtd/partitions.h>
+++#include <linux/module.h>
+++
+++#define	MOD_NAME	"scpart"
+++
+++#ifdef pr_fmt
+++#undef pr_fmt
+++#endif
+++
+++#define pr_fmt(fmt) MOD_NAME ": " fmt
+++
+++#define	ID_ALREADY_FOUND	0xffffffffUL
+++
+++#define	MAP_OFFS_IN_BLK		0x800
+++#define	MAP_MIRROR_NUM		2
+++
+++static const char sc_part_magic[] = {
+++	'S', 'C', 'F', 'L', 'M', 'A', 'P', 'O', 'K', '\0',
+++};
+++#define	PART_MAGIC_LEN		sizeof(sc_part_magic)
+++
+++/* assumes that all fields are set by CPU native endian */
+++struct sc_part_desc {
+++	uint32_t	part_id;
+++	uint32_t	part_offs;
+++	uint32_t	part_bytes;
+++};
+++
+++static uint32_t scpart_desc_is_valid(struct sc_part_desc *pdesc)
+++{
+++	return ((pdesc->part_id != 0xffffffffUL) &&
+++		(pdesc->part_offs != 0xffffffffUL) &&
+++		(pdesc->part_bytes != 0xffffffffUL));
+++}
+++
+++static int scpart_scan_partmap(struct mtd_info *master, loff_t partmap_offs,
+++			       struct sc_part_desc **ppdesc)
+++{
+++	int cnt = 0;
+++	int res = 0;
+++	int res2;
+++	loff_t offs;
+++	size_t retlen;
+++	struct sc_part_desc *pdesc = NULL;
+++	struct sc_part_desc *tmpdesc;
+++	uint8_t *buf;
+++
+++	buf = kzalloc(master->erasesize, GFP_KERNEL);
+++	if (!buf) {
+++		res = -ENOMEM;
+++		goto out;
+++	}
+++
+++	res2 = mtd_read(master, partmap_offs, master->erasesize, &retlen, buf);
+++	if (res2 || retlen != master->erasesize) {
+++		res = -EIO;
+++		goto free;
+++	}
+++
+++	for (offs = MAP_OFFS_IN_BLK;
+++	     offs < master->erasesize - sizeof(*tmpdesc);
+++	     offs += sizeof(*tmpdesc)) {
+++		tmpdesc = (struct sc_part_desc *)&buf[offs];
+++		if (!scpart_desc_is_valid(tmpdesc))
+++			break;
+++		cnt++;
+++	}
+++
+++	if (cnt > 0) {
+++		int bytes = cnt * sizeof(*pdesc);
+++
+++		pdesc = kcalloc(cnt, sizeof(*pdesc), GFP_KERNEL);
+++		if (!pdesc) {
+++			res = -ENOMEM;
+++			goto free;
+++		}
+++		memcpy(pdesc, &(buf[MAP_OFFS_IN_BLK]), bytes);
+++
+++		*ppdesc = pdesc;
+++		res = cnt;
+++	}
+++
+++free:
+++	kfree(buf);
+++
+++out:
+++	return res;
+++}
+++
+++static int scpart_find_partmap(struct mtd_info *master,
+++			       struct sc_part_desc **ppdesc)
+++{
+++	int magic_found = 0;
+++	int res = 0;
+++	int res2;
+++	loff_t offs = 0;
+++	size_t retlen;
+++	uint8_t rdbuf[PART_MAGIC_LEN];
+++
+++	while ((magic_found < MAP_MIRROR_NUM) &&
+++			(offs < master->size) &&
+++			 !mtd_block_isbad(master, offs)) {
+++		res2 = mtd_read(master, offs, PART_MAGIC_LEN, &retlen, rdbuf);
+++		if (res2 || retlen != PART_MAGIC_LEN) {
+++			res = -EIO;
+++			goto out;
+++		}
+++		if (!memcmp(rdbuf, sc_part_magic, PART_MAGIC_LEN)) {
+++			pr_debug("Signature found at 0x%llx\n", offs);
+++			magic_found++;
+++			res = scpart_scan_partmap(master, offs, ppdesc);
+++			if (res > 0)
+++				goto out;
+++		}
+++		offs += master->erasesize;
+++	}
+++
+++out:
+++	if (res > 0)
+++		pr_info("Valid 'SC PART MAP' (%d partitions) found at 0x%llx\n", res, offs);
+++	else
+++		pr_info("No valid 'SC PART MAP' was found\n");
+++
+++	return res;
+++}
+++
+++static int scpart_parse(struct mtd_info *master,
+++			const struct mtd_partition **pparts,
+++			struct mtd_part_parser_data *data)
+++{
+++	const char *partname;
+++	int n;
+++	int nr_scparts;
+++	int nr_parts = 0;
+++	int res = 0;
+++	struct sc_part_desc *scpart_map = NULL;
+++	struct mtd_partition *parts = NULL;
+++	struct device_node *mtd_node;
+++	struct device_node *ofpart_node;
+++	struct device_node *pp;
+++
+++	mtd_node = mtd_get_of_node(master);
+++	if (!mtd_node) {
+++		res = -ENOENT;
+++		goto out;
+++	}
+++
+++	ofpart_node = of_get_child_by_name(mtd_node, "partitions");
+++	if (!ofpart_node) {
+++		pr_info("%s: 'partitions' subnode not found on %pOF.\n",
+++				master->name, mtd_node);
+++		res = -ENOENT;
+++		goto out;
+++	}
+++
+++	nr_scparts = scpart_find_partmap(master, &scpart_map);
+++	if (nr_scparts <= 0) {
+++		pr_info("No any partitions was found in 'SC PART MAP'.\n");
+++		res = -ENOENT;
+++		goto free;
+++	}
+++
+++	parts = kcalloc(of_get_child_count(ofpart_node), sizeof(*parts),
+++		GFP_KERNEL);
+++	if (!parts) {
+++		res = -ENOMEM;
+++		goto free;
+++	}
+++
+++	for_each_child_of_node(ofpart_node, pp) {
+++		u32 scpart_id;
+++
+++		if (of_property_read_u32(pp, "sercomm,scpart-id", &scpart_id))
+++			continue;
+++
+++		for (n = 0 ; n < nr_scparts ; n++)
+++			if ((scpart_map[n].part_id != ID_ALREADY_FOUND) &&
+++					(scpart_id == scpart_map[n].part_id))
+++				break;
+++		if (n >= nr_scparts)
+++			/* not match */
+++			continue;
+++
+++		/* add the partition found in OF into MTD partition array */
+++		parts[nr_parts].offset = scpart_map[n].part_offs;
+++		parts[nr_parts].size = scpart_map[n].part_bytes;
+++		parts[nr_parts].of_node = pp;
+++
+++		if (!of_property_read_string(pp, "label", &partname))
+++			parts[nr_parts].name = partname;
+++		if (of_property_read_bool(pp, "read-only"))
+++			parts[nr_parts].mask_flags |= MTD_WRITEABLE;
+++		if (of_property_read_bool(pp, "lock"))
+++			parts[nr_parts].mask_flags |= MTD_POWERUP_LOCK;
+++
+++		/* mark as 'done' */
+++		scpart_map[n].part_id = ID_ALREADY_FOUND;
+++
+++		nr_parts++;
+++	}
+++
+++	if (nr_parts > 0) {
+++		*pparts = parts;
+++		res = nr_parts;
+++	} else
+++		pr_info("No partition in OF matches partition ID with 'SC PART MAP'.\n");
+++
+++	of_node_put(pp);
+++
+++free:
+++	kfree(scpart_map);
+++	if (res <= 0)
+++		kfree(parts);
+++
+++out:
+++	return res;
+++}
+++
+++static const struct of_device_id scpart_parser_of_match_table[] = {
+++	{ .compatible = "sercomm,sc-partitions" },
+++	{},
+++};
+++MODULE_DEVICE_TABLE(of, scpart_parser_of_match_table);
+++
+++static struct mtd_part_parser scpart_parser = {
+++	.parse_fn = scpart_parse,
+++	.name = "scpart",
+++	.of_match_table = scpart_parser_of_match_table,
+++};
+++module_mtd_part_parser(scpart_parser);
+++
+++/* mtd parsers will request the module by parser name */
+++MODULE_ALIAS("scpart");
+++MODULE_LICENSE("GPL");
+++MODULE_AUTHOR("NOGUCHI Hiroshi <drvlabo@gmail.com>");
+++MODULE_AUTHOR("Mikhail Zhilkin <csharper2005@gmail.com>");
+++MODULE_DESCRIPTION("Sercomm partition parser");
+diff --git a/target/linux/generic/backport-5.15/402-v6.0-mtd-next-mtd-core-introduce-of-support-for-dynamic-partitions.patch b/target/linux/generic/backport-5.15/402-v6.0-mtd-next-mtd-core-introduce-of-support-for-dynamic-partitions.patch
 new file mode 100644
-index 0000000000..1d23a178b4
+index 0000000000..8b8e478b00
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-21-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-mtk_des.patch
-@@ -0,0 +1,109 @@
-+From: Lorenzo Bianconi <lorenzo@kernel.org>
-+Date: Fri, 20 May 2022 20:11:30 +0200
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: rely on txd_size in
-+ mtk_desc_to_tx_buf
++++ b/target/linux/generic/backport-5.15/402-v6.0-mtd-next-mtd-core-introduce-of-support-for-dynamic-partitions.patch
+@@ -0,0 +1,106 @@
++From ad9b10d1eaada169bd764abcab58f08538877e26 Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Wed, 22 Jun 2022 03:06:28 +0200
++Subject: mtd: core: introduce of support for dynamic partitions
 +
-+This is a preliminary patch to add mt7986 ethernet support.
++We have many parser that register mtd partitions at runtime. One example
++is the cmdlinepart or the smem-part parser where the compatible is defined
++in the dts and the partitions gets detected and registered by the
++parser. This is problematic for the NVMEM subsystem that requires an OF
++node to detect NVMEM cells.
 +
-+Tested-by: Sam Shih <sam.shih@mediatek.com>
-+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
++To fix this problem, introduce an additional logic that will try to
++assign an OF node to the MTD if declared.
 +
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -890,10 +890,11 @@ static inline void *mtk_qdma_phys_to_vir
-+ 	return ret + (desc - ring->phys);
-+ }
-+ 
-+-static inline struct mtk_tx_buf *mtk_desc_to_tx_buf(struct mtk_tx_ring *ring,
-+-						    struct mtk_tx_dma *txd)
-++static struct mtk_tx_buf *mtk_desc_to_tx_buf(struct mtk_tx_ring *ring,
-++					     struct mtk_tx_dma *txd,
-++					     u32 txd_size)
-+ {
-+-	int idx = txd - ring->dma;
-++	int idx = ((void *)txd - (void *)ring->dma) / txd_size;
-+ 
-+ 	return &ring->buf[idx];
-+ }
-+@@ -1015,6 +1016,7 @@ static int mtk_tx_map(struct sk_buff *sk
-+ 	};
-+ 	struct mtk_mac *mac = netdev_priv(dev);
-+ 	struct mtk_eth *eth = mac->hw;
-++	const struct mtk_soc_data *soc = eth->soc;
-+ 	struct mtk_tx_dma *itxd, *txd;
-+ 	struct mtk_tx_dma *itxd_pdma, *txd_pdma;
-+ 	struct mtk_tx_buf *itx_buf, *tx_buf;
-+@@ -1026,7 +1028,7 @@ static int mtk_tx_map(struct sk_buff *sk
-+ 	if (itxd == ring->last_free)
-+ 		return -ENOMEM;
-+ 
-+-	itx_buf = mtk_desc_to_tx_buf(ring, itxd);
-++	itx_buf = mtk_desc_to_tx_buf(ring, itxd, soc->txrx.txd_size);
-+ 	memset(itx_buf, 0, sizeof(*itx_buf));
-+ 
-+ 	txd_info.addr = dma_map_single(eth->dma_dev, skb->data, txd_info.size,
-+@@ -1054,7 +1056,7 @@ static int mtk_tx_map(struct sk_buff *sk
-+ 		while (frag_size) {
-+ 			bool new_desc = true;
-+ 
-+-			if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA) ||
-++			if (MTK_HAS_CAPS(soc->caps, MTK_QDMA) ||
-+ 			    (i & 0x1)) {
-+ 				txd = mtk_qdma_phys_to_virt(ring, txd->txd2);
-+ 				txd_pdma = qdma_to_pdma(ring, txd);
-+@@ -1078,7 +1080,8 @@ static int mtk_tx_map(struct sk_buff *sk
-+ 
-+ 			mtk_tx_set_dma_desc(dev, txd, &txd_info);
-+ 
-+-			tx_buf = mtk_desc_to_tx_buf(ring, txd);
-++			tx_buf = mtk_desc_to_tx_buf(ring, txd,
-++						    soc->txrx.txd_size);
-+ 			if (new_desc)
-+ 				memset(tx_buf, 0, sizeof(*tx_buf));
-+ 			tx_buf->skb = (struct sk_buff *)MTK_DMA_DUMMY_DESC;
-+@@ -1097,7 +1100,7 @@ static int mtk_tx_map(struct sk_buff *sk
-+ 	/* store skb to cleanup */
-+ 	itx_buf->skb = skb;
-+ 
-+-	if (!MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
-++	if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
-+ 		if (k & 0x1)
-+ 			txd_pdma->txd2 |= TX_DMA_LS0;
-+ 		else
-+@@ -1115,7 +1118,7 @@ static int mtk_tx_map(struct sk_buff *sk
-+ 	 */
-+ 	wmb();
-+ 
-+-	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
-++	if (MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
-+ 		if (netif_xmit_stopped(netdev_get_tx_queue(dev, 0)) ||
-+ 		    !netdev_xmit_more())
-+ 			mtk_w32(eth, txd->txd2, MTK_QTX_CTX_PTR);
-+@@ -1129,13 +1132,13 @@ static int mtk_tx_map(struct sk_buff *sk
-+ 
-+ err_dma:
-+ 	do {
-+-		tx_buf = mtk_desc_to_tx_buf(ring, itxd);
-++		tx_buf = mtk_desc_to_tx_buf(ring, itxd, soc->txrx.txd_size);
-+ 
-+ 		/* unmap dma */
-+ 		mtk_tx_unmap(eth, tx_buf, false);
-+ 
-+ 		itxd->txd3 = TX_DMA_LS0 | TX_DMA_OWNER_CPU;
-+-		if (!MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA))
-++		if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA))
-+ 			itxd_pdma->txd2 = TX_DMA_DESP2_DEF;
-+ 
-+ 		itxd = mtk_qdma_phys_to_virt(ring, itxd->txd2);
-+@@ -1449,7 +1452,8 @@ static int mtk_poll_tx_qdma(struct mtk_e
-+ 		if ((desc->txd3 & TX_DMA_OWNER_CPU) == 0)
-+ 			break;
-+ 
-+-		tx_buf = mtk_desc_to_tx_buf(ring, desc);
-++		tx_buf = mtk_desc_to_tx_buf(ring, desc,
-++					    eth->soc->txrx.txd_size);
-+ 		if (tx_buf->flags & MTK_TX_FLAGS_FPORT1)
-+ 			mac = 1;
-+ 
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-22-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-txd_to_.patch b/target/linux/generic/backport-5.15/702-v5.19-22-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-txd_to_.patch
-new file mode 100644
-index 0000000000..2989d190d8
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-22-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-txd_to_.patch
-@@ -0,0 +1,39 @@
-+From: Lorenzo Bianconi <lorenzo@kernel.org>
-+Date: Fri, 20 May 2022 20:11:31 +0200
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: rely on txd_size in txd_to_idx
++On MTD addition, it will be checked if the MTD has an OF node and if
++not declared will check if a partition with the same label / node name is
++declared in DTS. If an exact match is found, the partition dynamically
++allocated by the parser will have a connected OF node.
 +
-+This is a preliminary patch to add mt7986 ethernet support.
++The NVMEM subsystem will detect the OF node and register any NVMEM cells
++declared statically in the DTS.
 +
-+Tested-by: Sam Shih <sam.shih@mediatek.com>
-+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
++Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Link: https://lore.kernel.org/linux-mtd/20220622010628.30414-4-ansuelsmth@gmail.com
 +---
++ drivers/mtd/mtdcore.c | 61 +++++++++++++++++++++++++++++++++++++++++++
++ 1 file changed, 61 insertions(+)
 +
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -905,9 +905,10 @@ static struct mtk_tx_dma *qdma_to_pdma(s
-+ 	return ring->dma_pdma - ring->dma + dma;
-+ }
-+ 
-+-static int txd_to_idx(struct mtk_tx_ring *ring, struct mtk_tx_dma *dma)
-++static int txd_to_idx(struct mtk_tx_ring *ring, struct mtk_tx_dma *dma,
-++		      u32 txd_size)
-+ {
-+-	return ((void *)dma - (void *)ring->dma) / sizeof(*dma);
-++	return ((void *)dma - (void *)ring->dma) / txd_size;
++--- a/drivers/mtd/mtdcore.c
+++++ b/drivers/mtd/mtdcore.c
++@@ -564,6 +564,66 @@ static int mtd_nvmem_add(struct mtd_info
++ 	return 0;
 + }
 + 
-+ static void mtk_tx_unmap(struct mtk_eth *eth, struct mtk_tx_buf *tx_buf,
-+@@ -1123,8 +1124,10 @@ static int mtk_tx_map(struct sk_buff *sk
-+ 		    !netdev_xmit_more())
-+ 			mtk_w32(eth, txd->txd2, MTK_QTX_CTX_PTR);
-+ 	} else {
-+-		int next_idx = NEXT_DESP_IDX(txd_to_idx(ring, txd),
-+-					     ring->dma_size);
-++		int next_idx;
+++static void mtd_check_of_node(struct mtd_info *mtd)
+++{
+++	struct device_node *partitions, *parent_dn, *mtd_dn = NULL;
+++	const char *pname, *prefix = "partition-";
+++	int plen, mtd_name_len, offset, prefix_len;
+++	struct mtd_info *parent;
+++	bool found = false;
 ++
-++		next_idx = NEXT_DESP_IDX(txd_to_idx(ring, txd, soc->txrx.txd_size),
-++					 ring->dma_size);
-+ 		mtk_w32(eth, next_idx, MT7628_TX_CTX_IDX0);
-+ 	}
-+ 
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-23-net-ethernet-mtk_eth_soc-add-rxd_size-to-mtk_soc_dat.patch b/target/linux/generic/backport-5.15/702-v5.19-23-net-ethernet-mtk_eth_soc-add-rxd_size-to-mtk_soc_dat.patch
-new file mode 100644
-index 0000000000..be258da75a
+++	/* Check if MTD already has a device node */
+++	if (dev_of_node(&mtd->dev))
+++		return;
+++
+++	/* Check if a partitions node exist */
+++	parent = mtd->parent;
+++	parent_dn = dev_of_node(&parent->dev);
+++	if (!parent_dn)
+++		return;
+++
+++	partitions = of_get_child_by_name(parent_dn, "partitions");
+++	if (!partitions)
+++		goto exit_parent;
+++
+++	prefix_len = strlen(prefix);
+++	mtd_name_len = strlen(mtd->name);
+++
+++	/* Search if a partition is defined with the same name */
+++	for_each_child_of_node(partitions, mtd_dn) {
+++		offset = 0;
+++
+++		/* Skip partition with no/wrong prefix */
+++		if (!of_node_name_prefix(mtd_dn, "partition-"))
+++			continue;
+++
+++		/* Label have priority. Check that first */
+++		if (of_property_read_string(mtd_dn, "label", &pname)) {
+++			of_property_read_string(mtd_dn, "name", &pname);
+++			offset = prefix_len;
+++		}
+++
+++		plen = strlen(pname) - offset;
+++		if (plen == mtd_name_len &&
+++		    !strncmp(mtd->name, pname + offset, plen)) {
+++			found = true;
+++			break;
+++		}
+++	}
+++
+++	if (!found)
+++		goto exit_partitions;
+++
+++	/* Set of_node only for nvmem */
+++	if (of_device_is_compatible(mtd_dn, "nvmem-cells"))
+++		mtd_set_of_node(mtd, mtd_dn);
+++
+++exit_partitions:
+++	of_node_put(partitions);
+++exit_parent:
+++	of_node_put(parent_dn);
+++}
+++
++ /**
++  *	add_mtd_device - register an MTD device
++  *	@mtd: pointer to new MTD device info structure
++@@ -669,6 +729,7 @@ int add_mtd_device(struct mtd_info *mtd)
++ 	mtd->dev.devt = MTD_DEVT(i);
++ 	dev_set_name(&mtd->dev, "mtd%d", i);
++ 	dev_set_drvdata(&mtd->dev, mtd);
+++	mtd_check_of_node(mtd);
++ 	of_node_get(mtd_get_of_node(mtd));
++ 	error = device_register(&mtd->dev);
++ 	if (error)
+diff --git a/target/linux/generic/backport-5.15/403-v6.1-mtd-allow-getting-MTD-device-associated-with-a-speci.patch b/target/linux/generic/backport-5.15/403-v6.1-mtd-allow-getting-MTD-device-associated-with-a-speci.patch
+new file mode 100644
+index 0000000000..be1aa9cfb8
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-23-net-ethernet-mtk_eth_soc-add-rxd_size-to-mtk_soc_dat.patch
-@@ -0,0 +1,102 @@
-+From: Lorenzo Bianconi <lorenzo@kernel.org>
-+Date: Fri, 20 May 2022 20:11:32 +0200
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: add rxd_size to mtk_soc_data
++++ b/target/linux/generic/backport-5.15/403-v6.1-mtd-allow-getting-MTD-device-associated-with-a-speci.patch
+@@ -0,0 +1,72 @@
++From b0321721be50b80c03a51866a94fde4f94690e18 Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Wed, 15 Jun 2022 21:42:59 +0200
++Subject: [PATCH] mtd: allow getting MTD device associated with a specific DT
++ node
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
 +
-+Similar to tx counterpart, introduce rxd_size in mtk_soc_data data
-+structure.
-+This is a preliminary patch to add mt7986 ethernet support.
++MTD subsystem API allows interacting with MTD devices (e.g. reading,
++writing, handling bad blocks). So far a random driver could get MTD
++device only by its name (get_mtd_device_nm()). This change allows
++getting them also by a DT node.
 +
-+Tested-by: Sam Shih <sam.shih@mediatek.com>
-+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++This API is required for drivers handling DT defined MTD partitions in a
++specific way (e.g. U-Boot (sub)partition with environment variables).
++
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Acked-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
 +---
++ drivers/mtd/mtdcore.c   | 28 ++++++++++++++++++++++++++++
++ include/linux/mtd/mtd.h |  1 +
++ 2 files changed, 29 insertions(+)
 +
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -1775,7 +1775,7 @@ static int mtk_rx_alloc(struct mtk_eth *
-+ 	}
++--- a/drivers/mtd/mtdcore.c
+++++ b/drivers/mtd/mtdcore.c
++@@ -1227,6 +1227,34 @@ int __get_mtd_device(struct mtd_info *mt
++ EXPORT_SYMBOL_GPL(__get_mtd_device);
 + 
-+ 	ring->dma = dma_alloc_coherent(eth->dma_dev,
-+-				       rx_dma_size * sizeof(*ring->dma),
-++				       rx_dma_size * eth->soc->txrx.rxd_size,
-+ 				       &ring->phys, GFP_KERNEL);
-+ 	if (!ring->dma)
-+ 		return -ENOMEM;
-+@@ -1833,9 +1833,8 @@ static void mtk_rx_clean(struct mtk_eth
++ /**
+++ * of_get_mtd_device_by_node - obtain an MTD device associated with a given node
+++ *
+++ * @np: device tree node
+++ */
+++struct mtd_info *of_get_mtd_device_by_node(struct device_node *np)
+++{
+++	struct mtd_info *mtd = NULL;
+++	struct mtd_info *tmp;
+++	int err;
+++
+++	mutex_lock(&mtd_table_mutex);
+++
+++	err = -EPROBE_DEFER;
+++	mtd_for_each_device(tmp) {
+++		if (mtd_get_of_node(tmp) == np) {
+++			mtd = tmp;
+++			err = __get_mtd_device(mtd);
+++			break;
+++		}
+++	}
+++
+++	mutex_unlock(&mtd_table_mutex);
+++
+++	return err ? ERR_PTR(err) : mtd;
+++}
+++EXPORT_SYMBOL_GPL(of_get_mtd_device_by_node);
+++
+++/**
++  *	get_mtd_device_nm - obtain a validated handle for an MTD device by
++  *	device name
++  *	@name: MTD device name to open
++--- a/include/linux/mtd/mtd.h
+++++ b/include/linux/mtd/mtd.h
++@@ -682,6 +682,7 @@ extern int mtd_device_unregister(struct
++ extern struct mtd_info *get_mtd_device(struct mtd_info *mtd, int num);
++ extern int __get_mtd_device(struct mtd_info *mtd);
++ extern void __put_mtd_device(struct mtd_info *mtd);
+++extern struct mtd_info *of_get_mtd_device_by_node(struct device_node *np);
++ extern struct mtd_info *get_mtd_device_nm(const char *name);
++ extern void put_mtd_device(struct mtd_info *mtd);
++ 
+diff --git a/target/linux/generic/backport-5.15/404-v6.0-mtd-core-check-partition-before-dereference.patch b/target/linux/generic/backport-5.15/404-v6.0-mtd-core-check-partition-before-dereference.patch
+new file mode 100644
+index 0000000000..e45e2ab48e
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/404-v6.0-mtd-core-check-partition-before-dereference.patch
+@@ -0,0 +1,30 @@
++From 7ec4cdb321738d44ae5d405e7b6ac73dfbf99caa Mon Sep 17 00:00:00 2001
++From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
++Date: Mon, 25 Jul 2022 22:49:25 +0900
++Subject: [PATCH] mtd: core: check partition before dereference
++
++syzbot is reporting NULL pointer dereference at mtd_check_of_node() [1],
++for mtdram test device (CONFIG_MTD_MTDRAM) is not partition.
++
++Link: https://syzkaller.appspot.com/bug?extid=fe013f55a2814a9e8cfd [1]
++Reported-by: syzbot <syzbot+fe013f55a2814a9e8cfd@syzkaller.appspotmail.com>
++Reported-by: kernel test robot <oliver.sang@intel.com>
++Fixes: ad9b10d1eaada169 ("mtd: core: introduce of support for dynamic partitions")
++Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
++CC: stable@vger.kernel.org
++Signed-off-by: Richard Weinberger <richard@nod.at>
++---
++ drivers/mtd/mtdcore.c | 2 ++
++ 1 file changed, 2 insertions(+)
++
++--- a/drivers/mtd/mtdcore.c
+++++ b/drivers/mtd/mtdcore.c
++@@ -577,6 +577,8 @@ static void mtd_check_of_node(struct mtd
++ 		return;
 + 
-+ 	if (ring->dma) {
-+ 		dma_free_coherent(eth->dma_dev,
-+-				  ring->dma_size * sizeof(*ring->dma),
-+-				  ring->dma,
-+-				  ring->phys);
-++				  ring->dma_size * eth->soc->txrx.rxd_size,
-++				  ring->dma, ring->phys);
-+ 		ring->dma = NULL;
-+ 	}
-+ }
-+@@ -3403,6 +3402,7 @@ static const struct mtk_soc_data mt2701_
-+ 	.required_pctl = true,
-+ 	.txrx = {
-+ 		.txd_size = sizeof(struct mtk_tx_dma),
-++		.rxd_size = sizeof(struct mtk_rx_dma),
-+ 	},
-+ };
++ 	/* Check if a partitions node exist */
+++	if (!mtd_is_partition(mtd))
+++		return;
++ 	parent = mtd->parent;
++ 	parent_dn = dev_of_node(&parent->dev);
++ 	if (!parent_dn)
+diff --git a/target/linux/generic/backport-5.15/405-v6.1-mtd-core-add-missing-of_node_get-in-dynamic-partitio.patch b/target/linux/generic/backport-5.15/405-v6.1-mtd-core-add-missing-of_node_get-in-dynamic-partitio.patch
+new file mode 100644
+index 0000000000..9399a00aa1
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/405-v6.1-mtd-core-add-missing-of_node_get-in-dynamic-partitio.patch
+@@ -0,0 +1,101 @@
++From 12b58961de0bd88b3c7dfa5d21f6d67f4678b780 Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Tue, 18 Oct 2022 07:18:22 +0200
++Subject: [PATCH] mtd: core: add missing of_node_get() in dynamic partitions
++ code
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++This fixes unbalanced of_node_put():
++[    1.078910] 6 cmdlinepart partitions found on MTD device gpmi-nand
++[    1.085116] Creating 6 MTD partitions on "gpmi-nand":
++[    1.090181] 0x000000000000-0x000008000000 : "nandboot"
++[    1.096952] 0x000008000000-0x000009000000 : "nandfit"
++[    1.103547] 0x000009000000-0x00000b000000 : "nandkernel"
++[    1.110317] 0x00000b000000-0x00000c000000 : "nanddtb"
++[    1.115525] ------------[ cut here ]------------
++[    1.120141] refcount_t: addition on 0; use-after-free.
++[    1.125328] WARNING: CPU: 0 PID: 1 at lib/refcount.c:25 refcount_warn_saturate+0xdc/0x148
++[    1.133528] Modules linked in:
++[    1.136589] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 6.0.0-rc7-next-20220930-04543-g8cf3f7
++[    1.146342] Hardware name: Freescale i.MX8DXL DDR3L EVK (DT)
++[    1.151999] pstate: 600000c5 (nZCv daIF -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
++[    1.158965] pc : refcount_warn_saturate+0xdc/0x148
++[    1.163760] lr : refcount_warn_saturate+0xdc/0x148
++[    1.168556] sp : ffff800009ddb080
++[    1.171866] x29: ffff800009ddb080 x28: ffff800009ddb35a x27: 0000000000000002
++[    1.179015] x26: ffff8000098b06ad x25: ffffffffffffffff x24: ffff0a00ffffff05
++[    1.186165] x23: ffff00001fdf6470 x22: ffff800009ddb367 x21: 0000000000000000
++[    1.193314] x20: ffff00001fdfebe8 x19: ffff00001fdfec50 x18: ffffffffffffffff
++[    1.200464] x17: 0000000000000000 x16: 0000000000000118 x15: 0000000000000004
++[    1.207614] x14: 0000000000000fff x13: ffff800009bca248 x12: 0000000000000003
++[    1.214764] x11: 00000000ffffefff x10: c0000000ffffefff x9 : 4762cb2ccb52de00
++[    1.221914] x8 : 4762cb2ccb52de00 x7 : 205d313431303231 x6 : 312e31202020205b
++[    1.229063] x5 : ffff800009d55c1f x4 : 0000000000000001 x3 : 0000000000000000
++[    1.236213] x2 : 0000000000000000 x1 : ffff800009954be6 x0 : 000000000000002a
++[    1.243365] Call trace:
++[    1.245806]  refcount_warn_saturate+0xdc/0x148
++[    1.250253]  kobject_get+0x98/0x9c
++[    1.253658]  of_node_get+0x20/0x34
++[    1.257072]  of_fwnode_get+0x3c/0x54
++[    1.260652]  fwnode_get_nth_parent+0xd8/0xf4
++[    1.264926]  fwnode_full_name_string+0x3c/0xb4
++[    1.269373]  device_node_string+0x498/0x5b4
++[    1.273561]  pointer+0x41c/0x5d0
++[    1.276793]  vsnprintf+0x4d8/0x694
++[    1.280198]  vprintk_store+0x164/0x528
++[    1.283951]  vprintk_emit+0x98/0x164
++[    1.287530]  vprintk_default+0x44/0x6c
++[    1.291284]  vprintk+0xf0/0x134
++[    1.294428]  _printk+0x54/0x7c
++[    1.297486]  of_node_release+0xe8/0x128
++[    1.301326]  kobject_put+0x98/0xfc
++[    1.304732]  of_node_put+0x1c/0x28
++[    1.308137]  add_mtd_device+0x484/0x6d4
++[    1.311977]  add_mtd_partitions+0xf0/0x1d0
++[    1.316078]  parse_mtd_partitions+0x45c/0x518
++[    1.320439]  mtd_device_parse_register+0xb0/0x274
++[    1.325147]  gpmi_nand_probe+0x51c/0x650
++[    1.329074]  platform_probe+0xa8/0xd0
++[    1.332740]  really_probe+0x130/0x334
++[    1.336406]  __driver_probe_device+0xb4/0xe0
++[    1.340681]  driver_probe_device+0x3c/0x1f8
++[    1.344869]  __driver_attach+0xdc/0x1a4
++[    1.348708]  bus_for_each_dev+0x80/0xcc
++[    1.352548]  driver_attach+0x24/0x30
++[    1.356127]  bus_add_driver+0x108/0x1f4
++[    1.359967]  driver_register+0x78/0x114
++[    1.363807]  __platform_driver_register+0x24/0x30
++[    1.368515]  gpmi_nand_driver_init+0x1c/0x28
++[    1.372798]  do_one_initcall+0xbc/0x238
++[    1.376638]  do_initcall_level+0x94/0xb4
++[    1.380565]  do_initcalls+0x54/0x94
++[    1.384058]  do_basic_setup+0x1c/0x28
++[    1.387724]  kernel_init_freeable+0x110/0x188
++[    1.392084]  kernel_init+0x20/0x1a0
++[    1.395578]  ret_from_fork+0x10/0x20
++[    1.399157] ---[ end trace 0000000000000000 ]---
++[    1.403782] ------------[ cut here ]------------
++
++Reported-by: Han Xu <han.xu@nxp.com>
++Fixes: ad9b10d1eaada169 ("mtd: core: introduce of support for dynamic partitions")
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Tested-by: Han Xu <han.xu@nxp.com>
++Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Link: https://lore.kernel.org/linux-mtd/20221018051822.28685-1-zajec5@gmail.com
++---
++ drivers/mtd/mtdcore.c | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
++
++--- a/drivers/mtd/mtdcore.c
+++++ b/drivers/mtd/mtdcore.c
++@@ -580,7 +580,7 @@ static void mtd_check_of_node(struct mtd
++ 	if (!mtd_is_partition(mtd))
++ 		return;
++ 	parent = mtd->parent;
++-	parent_dn = dev_of_node(&parent->dev);
+++	parent_dn = of_node_get(dev_of_node(&parent->dev));
++ 	if (!parent_dn)
++ 		return;
 + 
-+@@ -3414,6 +3414,7 @@ static const struct mtk_soc_data mt7621_
-+ 	.offload_version = 2,
-+ 	.txrx = {
-+ 		.txd_size = sizeof(struct mtk_tx_dma),
-++		.rxd_size = sizeof(struct mtk_rx_dma),
-+ 	},
-+ };
+diff --git a/target/linux/generic/backport-5.15/406-v6.2-0001-mtd-core-simplify-a-bit-code-find-partition-matching.patch b/target/linux/generic/backport-5.15/406-v6.2-0001-mtd-core-simplify-a-bit-code-find-partition-matching.patch
+new file mode 100644
+index 0000000000..7e9645ea98
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/406-v6.2-0001-mtd-core-simplify-a-bit-code-find-partition-matching.patch
+@@ -0,0 +1,65 @@
++From 63db0cb35e1cb3b3c134906d1062f65513fdda2d Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Tue, 4 Oct 2022 10:37:09 +0200
++Subject: [PATCH] mtd: core: simplify (a bit) code find partition-matching
++ dynamic OF node
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++1. Don't hardcode "partition-" string twice
++2. Use simpler logic & use ->name to avoid of_property_read_string()
++3. Use mtd_get_of_node() helper
++
++Cc: Christian Marangi <ansuelsmth@gmail.com>
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Link: https://lore.kernel.org/linux-mtd/20221004083710.27704-1-zajec5@gmail.com
++---
++ drivers/mtd/mtdcore.c | 16 +++++++---------
++ 1 file changed, 7 insertions(+), 9 deletions(-)
++
++--- a/drivers/mtd/mtdcore.c
+++++ b/drivers/mtd/mtdcore.c
++@@ -569,18 +569,16 @@ static void mtd_check_of_node(struct mtd
++ 	struct device_node *partitions, *parent_dn, *mtd_dn = NULL;
++ 	const char *pname, *prefix = "partition-";
++ 	int plen, mtd_name_len, offset, prefix_len;
++-	struct mtd_info *parent;
++ 	bool found = false;
 + 
-+@@ -3426,6 +3427,7 @@ static const struct mtk_soc_data mt7622_
-+ 	.offload_version = 2,
-+ 	.txrx = {
-+ 		.txd_size = sizeof(struct mtk_tx_dma),
-++		.rxd_size = sizeof(struct mtk_rx_dma),
-+ 	},
-+ };
++ 	/* Check if MTD already has a device node */
++-	if (dev_of_node(&mtd->dev))
+++	if (mtd_get_of_node(mtd))
++ 		return;
 + 
-+@@ -3437,6 +3439,7 @@ static const struct mtk_soc_data mt7623_
-+ 	.offload_version = 2,
-+ 	.txrx = {
-+ 		.txd_size = sizeof(struct mtk_tx_dma),
-++		.rxd_size = sizeof(struct mtk_rx_dma),
-+ 	},
-+ };
++ 	/* Check if a partitions node exist */
++ 	if (!mtd_is_partition(mtd))
++ 		return;
++-	parent = mtd->parent;
++-	parent_dn = of_node_get(dev_of_node(&parent->dev));
+++	parent_dn = of_node_get(mtd_get_of_node(mtd->parent));
++ 	if (!parent_dn)
++ 		return;
 + 
-+@@ -3448,6 +3451,7 @@ static const struct mtk_soc_data mt7629_
-+ 	.required_pctl = false,
-+ 	.txrx = {
-+ 		.txd_size = sizeof(struct mtk_tx_dma),
-++		.rxd_size = sizeof(struct mtk_rx_dma),
-+ 	},
-+ };
++@@ -593,15 +591,15 @@ static void mtd_check_of_node(struct mtd
 + 
-+@@ -3458,6 +3462,7 @@ static const struct mtk_soc_data rt5350_
-+ 	.required_pctl = false,
-+ 	.txrx = {
-+ 		.txd_size = sizeof(struct mtk_tx_dma),
-++		.rxd_size = sizeof(struct mtk_rx_dma),
-+ 	},
-+ };
++ 	/* Search if a partition is defined with the same name */
++ 	for_each_child_of_node(partitions, mtd_dn) {
++-		offset = 0;
++-
++ 		/* Skip partition with no/wrong prefix */
++-		if (!of_node_name_prefix(mtd_dn, "partition-"))
+++		if (!of_node_name_prefix(mtd_dn, prefix))
++ 			continue;
 + 
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-+@@ -864,6 +864,7 @@ struct mtk_tx_dma_desc_info {
-+  * @required_pctl		A bool value to show whether the SoC requires
-+  *				the extra setup for those pins used by GMAC.
-+  * @txd_size			Tx DMA descriptor size.
-++ * @rxd_size			Rx DMA descriptor size.
-+  */
-+ struct mtk_soc_data {
-+ 	u32             ana_rgc3;
-+@@ -874,6 +875,7 @@ struct mtk_soc_data {
-+ 	netdev_features_t hw_features;
-+ 	struct {
-+ 		u32	txd_size;
-++		u32	rxd_size;
-+ 	} txrx;
-+ };
++ 		/* Label have priority. Check that first */
++-		if (of_property_read_string(mtd_dn, "label", &pname)) {
++-			of_property_read_string(mtd_dn, "name", &pname);
+++		if (!of_property_read_string(mtd_dn, "label", &pname)) {
+++			offset = 0;
+++		} else {
+++			pname = mtd_dn->name;
++ 			offset = prefix_len;
++ 		}
 + 
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-24-net-ethernet-mtk_eth_soc-rely-on-txd_size-field-in-m.patch b/target/linux/generic/backport-5.15/702-v5.19-24-net-ethernet-mtk_eth_soc-rely-on-txd_size-field-in-m.patch
+diff --git a/target/linux/generic/backport-5.15/406-v6.2-0002-mtd-core-try-to-find-OF-node-for-every-MTD-partition.patch b/target/linux/generic/backport-5.15/406-v6.2-0002-mtd-core-try-to-find-OF-node-for-every-MTD-partition.patch
 new file mode 100644
-index 0000000000..53af586b6c
+index 0000000000..48a7c13cd0
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-24-net-ethernet-mtk_eth_soc-rely-on-txd_size-field-in-m.patch
-@@ -0,0 +1,46 @@
-+From: Lorenzo Bianconi <lorenzo@kernel.org>
-+Date: Fri, 20 May 2022 20:11:33 +0200
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: rely on txd_size field in
-+ mtk_poll_tx/mtk_poll_rx
++++ b/target/linux/generic/backport-5.15/406-v6.2-0002-mtd-core-try-to-find-OF-node-for-every-MTD-partition.patch
+@@ -0,0 +1,84 @@
++From ddb8cefb7af288950447ca6eeeafb09977dab56f Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Tue, 4 Oct 2022 10:37:10 +0200
++Subject: [PATCH] mtd: core: try to find OF node for every MTD partition
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
 +
-+This is a preliminary to ad mt7986 ethernet support.
++So far this feature was limited to the top-level "nvmem-cells" node.
++There are multiple parsers creating partitions and subpartitions
++dynamically. Extend that code to handle them too.
 +
-+Tested-by: Sam Shih <sam.shih@mediatek.com>
-+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++This allows finding partition-* node for every MTD (sub)partition.
++
++Random example:
++
++partitions {
++	compatible = "brcm,bcm947xx-cfe-partitions";
++
++	partition-firmware {
++		compatible = "brcm,trx";
++
++		partition-loader {
++		};
++	};
++};
++
++Cc: Christian Marangi <ansuelsmth@gmail.com>
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Link: https://lore.kernel.org/linux-mtd/20221004083710.27704-2-zajec5@gmail.com
 +---
++ drivers/mtd/mtdcore.c | 18 ++++++------------
++ 1 file changed, 6 insertions(+), 12 deletions(-)
 +
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -1264,9 +1264,12 @@ static struct mtk_rx_ring *mtk_get_rx_ri
-+ 		return &eth->rx_ring[0];
++--- a/drivers/mtd/mtdcore.c
+++++ b/drivers/mtd/mtdcore.c
++@@ -569,20 +569,22 @@ static void mtd_check_of_node(struct mtd
++ 	struct device_node *partitions, *parent_dn, *mtd_dn = NULL;
++ 	const char *pname, *prefix = "partition-";
++ 	int plen, mtd_name_len, offset, prefix_len;
++-	bool found = false;
++ 
++ 	/* Check if MTD already has a device node */
++ 	if (mtd_get_of_node(mtd))
++ 		return;
 + 
-+ 	for (i = 0; i < MTK_MAX_RX_RING_NUM; i++) {
-++		struct mtk_rx_dma *rxd;
++-	/* Check if a partitions node exist */
++ 	if (!mtd_is_partition(mtd))
++ 		return;
 ++
-+ 		ring = &eth->rx_ring[i];
-+ 		idx = NEXT_DESP_IDX(ring->calc_idx, ring->dma_size);
-+-		if (ring->dma[idx].rxd2 & RX_DMA_DONE) {
-++		rxd = (void *)ring->dma + idx * eth->soc->txrx.rxd_size;
-++		if (rxd->rxd2 & RX_DMA_DONE) {
-+ 			ring->calc_idx_update = true;
-+ 			return ring;
-+ 		}
-+@@ -1317,7 +1320,7 @@ static int mtk_poll_rx(struct napi_struc
-+ 			goto rx_done;
-+ 
-+ 		idx = NEXT_DESP_IDX(ring->calc_idx, ring->dma_size);
-+-		rxd = &ring->dma[idx];
-++		rxd = (void *)ring->dma + idx * eth->soc->txrx.rxd_size;
-+ 		data = ring->data[idx];
-+ 
-+ 		if (!mtk_rx_get_desc(&trxd, rxd))
-+@@ -1509,7 +1512,7 @@ static int mtk_poll_tx_pdma(struct mtk_e
-+ 
-+ 		mtk_tx_unmap(eth, tx_buf, true);
++ 	parent_dn = of_node_get(mtd_get_of_node(mtd->parent));
++ 	if (!parent_dn)
++ 		return;
 + 
-+-		desc = &ring->dma[cpu];
-++		desc = (void *)ring->dma + cpu * eth->soc->txrx.txd_size;
-+ 		ring->last_free = desc;
-+ 		atomic_inc(&ring->free_count);
++-	partitions = of_get_child_by_name(parent_dn, "partitions");
+++	if (mtd_is_partition(mtd->parent))
+++		partitions = of_node_get(parent_dn);
+++	else
+++		partitions = of_get_child_by_name(parent_dn, "partitions");
++ 	if (!partitions)
++ 		goto exit_parent;
++ 
++@@ -606,19 +608,11 @@ static void mtd_check_of_node(struct mtd
++ 		plen = strlen(pname) - offset;
++ 		if (plen == mtd_name_len &&
++ 		    !strncmp(mtd->name, pname + offset, plen)) {
++-			found = true;
+++			mtd_set_of_node(mtd, mtd_dn);
++ 			break;
++ 		}
++ 	}
 + 
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-25-net-ethernet-mtk_eth_soc-rely-on-rxd_size-field-in-m.patch b/target/linux/generic/backport-5.15/702-v5.19-25-net-ethernet-mtk_eth_soc-rely-on-rxd_size-field-in-m.patch
++-	if (!found)
++-		goto exit_partitions;
++-
++-	/* Set of_node only for nvmem */
++-	if (of_device_is_compatible(mtd_dn, "nvmem-cells"))
++-		mtd_set_of_node(mtd, mtd_dn);
++-
++-exit_partitions:
++ 	of_node_put(partitions);
++ exit_parent:
++ 	of_node_put(parent_dn);
+diff --git a/target/linux/generic/backport-5.15/407-v5.17-mtd-parsers-qcom-Don-t-print-error-message-on-EPROBE.patch b/target/linux/generic/backport-5.15/407-v5.17-mtd-parsers-qcom-Don-t-print-error-message-on-EPROBE.patch
 new file mode 100644
-index 0000000000..1f4fa1dfb5
+index 0000000000..0efad99157
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-25-net-ethernet-mtk_eth_soc-rely-on-rxd_size-field-in-m.patch
-@@ -0,0 +1,68 @@
-+From: Lorenzo Bianconi <lorenzo@kernel.org>
-+Date: Fri, 20 May 2022 20:11:34 +0200
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: rely on rxd_size field in
-+ mtk_rx_alloc/mtk_rx_clean
++++ b/target/linux/generic/backport-5.15/407-v5.17-mtd-parsers-qcom-Don-t-print-error-message-on-EPROBE.patch
+@@ -0,0 +1,32 @@
++From 26bccc9671ba5e01f7153addbe94e7dc3f677375 Mon Sep 17 00:00:00 2001
++From: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
++Date: Mon, 3 Jan 2022 03:03:16 +0000
++Subject: [PATCH 13/14] mtd: parsers: qcom: Don't print error message on
++ -EPROBE_DEFER
 +
-+Remove mtk_rx_dma structure layout dependency in mtk_rx_alloc/mtk_rx_clean.
-+Initialize to 0 rxd3 and rxd4 in mtk_rx_alloc.
-+This is a preliminary patch to add mt7986 ethernet support.
++Its possible for the main smem driver to not be loaded by the time we come
++along to parse the smem partition description but, this is a perfectly
++normal thing.
 +
-+Tested-by: Sam Shih <sam.shih@mediatek.com>
-+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++No need to print out an error message in this case.
++
++Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
++Reviewed-by: Manivannan Sadhasivam <mani@kernel.org>
++Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Link: https://lore.kernel.org/linux-mtd/20220103030316.58301-3-bryan.odonoghue@linaro.org
 +---
++ drivers/mtd/parsers/qcomsmempart.c | 3 ++-
++ 1 file changed, 2 insertions(+), 1 deletion(-)
 +
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -1784,18 +1784,25 @@ static int mtk_rx_alloc(struct mtk_eth *
-+ 		return -ENOMEM;
-+ 
-+ 	for (i = 0; i < rx_dma_size; i++) {
-++		struct mtk_rx_dma *rxd;
-++
-+ 		dma_addr_t dma_addr = dma_map_single(eth->dma_dev,
-+ 				ring->data[i] + NET_SKB_PAD + eth->ip_align,
-+ 				ring->buf_size,
-+ 				DMA_FROM_DEVICE);
-+ 		if (unlikely(dma_mapping_error(eth->dma_dev, dma_addr)))
-+ 			return -ENOMEM;
-+-		ring->dma[i].rxd1 = (unsigned int)dma_addr;
-++
-++		rxd = (void *)ring->dma + i * eth->soc->txrx.rxd_size;
-++		rxd->rxd1 = (unsigned int)dma_addr;
-+ 
-+ 		if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628))
-+-			ring->dma[i].rxd2 = RX_DMA_LSO;
-++			rxd->rxd2 = RX_DMA_LSO;
-+ 		else
-+-			ring->dma[i].rxd2 = RX_DMA_PLEN0(ring->buf_size);
-++			rxd->rxd2 = RX_DMA_PLEN0(ring->buf_size);
-++
-++		rxd->rxd3 = 0;
-++		rxd->rxd4 = 0;
++--- a/drivers/mtd/parsers/qcomsmempart.c
+++++ b/drivers/mtd/parsers/qcomsmempart.c
++@@ -75,7 +75,8 @@ static int parse_qcomsmem_part(struct mt
++ 	pr_debug("Parsing partition table info from SMEM\n");
++ 	ptable = qcom_smem_get(SMEM_APPS, SMEM_AARM_PARTITION_TABLE, &len);
++ 	if (IS_ERR(ptable)) {
++-		pr_err("Error reading partition table header\n");
+++		if (PTR_ERR(ptable) != -EPROBE_DEFER)
+++			pr_err("Error reading partition table header\n");
++ 		return PTR_ERR(ptable);
 + 	}
-+ 	ring->dma_size = rx_dma_size;
-+ 	ring->calc_idx_update = false;
-+@@ -1820,14 +1827,17 @@ static void mtk_rx_clean(struct mtk_eth
 + 
-+ 	if (ring->data && ring->dma) {
-+ 		for (i = 0; i < ring->dma_size; i++) {
-++			struct mtk_rx_dma *rxd;
-++
-+ 			if (!ring->data[i])
-+ 				continue;
-+-			if (!ring->dma[i].rxd1)
-++
-++			rxd = (void *)ring->dma + i * eth->soc->txrx.rxd_size;
-++			if (!rxd->rxd1)
-+ 				continue;
-+-			dma_unmap_single(eth->dma_dev,
-+-					 ring->dma[i].rxd1,
-+-					 ring->buf_size,
-+-					 DMA_FROM_DEVICE);
-++
-++			dma_unmap_single(eth->dma_dev, rxd->rxd1,
-++					 ring->buf_size, DMA_FROM_DEVICE);
-+ 			skb_free_frag(ring->data[i]);
-+ 		}
-+ 		kfree(ring->data);
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-26-net-ethernet-mtk_eth_soc-introduce-device-register-m.patch b/target/linux/generic/backport-5.15/702-v5.19-26-net-ethernet-mtk_eth_soc-introduce-device-register-m.patch
+diff --git a/target/linux/generic/backport-5.15/408-v6.2-mtd-core-set-ROOT_DEV-for-partitions-marked-as-rootf.patch b/target/linux/generic/backport-5.15/408-v6.2-mtd-core-set-ROOT_DEV-for-partitions-marked-as-rootf.patch
 new file mode 100644
-index 0000000000..99f482c418
+index 0000000000..60b1764252
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-26-net-ethernet-mtk_eth_soc-introduce-device-register-m.patch
-@@ -0,0 +1,814 @@
-+From: Lorenzo Bianconi <lorenzo@kernel.org>
-+Date: Fri, 20 May 2022 20:11:35 +0200
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: introduce device register map
++++ b/target/linux/generic/backport-5.15/408-v6.2-mtd-core-set-ROOT_DEV-for-partitions-marked-as-rootf.patch
+@@ -0,0 +1,47 @@
++From 26422ac78e9d8767bd4aabfbae616b15edbf6a1b Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Sat, 22 Oct 2022 23:13:18 +0200
++Subject: [PATCH] mtd: core: set ROOT_DEV for partitions marked as rootfs in DT
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
 +
-+Introduce reg_map structure to add the capability to support different
-+register definitions. Move register definitions in mtk_regmap structure.
-+This is a preliminary patch to introduce mt7986 ethernet support.
++This adds support for "linux,rootfs" binding that is used to mark flash
++partition containing rootfs. It's useful for devices using device tree
++that don't have bootloader passing root info in cmdline.
 +
-+Tested-by: Sam Shih <sam.shih@mediatek.com>
-+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Link: https://lore.kernel.org/linux-mtd/20221022211318.32009-2-zajec5@gmail.com
 +---
++ drivers/mtd/mtdcore.c | 12 ++++++++++++
++ 1 file changed, 12 insertions(+)
 +
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -34,6 +34,59 @@ MODULE_PARM_DESC(msg_level, "Message lev
-+ #define MTK_ETHTOOL_STAT(x) { #x, \
-+ 			      offsetof(struct mtk_hw_stats, x) / sizeof(u64) }
++--- a/drivers/mtd/mtdcore.c
+++++ b/drivers/mtd/mtdcore.c
++@@ -28,6 +28,7 @@
++ #include <linux/leds.h>
++ #include <linux/debugfs.h>
++ #include <linux/nvmem-provider.h>
+++#include <linux/root_dev.h>
 + 
-++static const struct mtk_reg_map mtk_reg_map = {
-++	.tx_irq_mask		= 0x1a1c,
-++	.tx_irq_status		= 0x1a18,
-++	.pdma = {
-++		.rx_ptr		= 0x0900,
-++		.rx_cnt_cfg	= 0x0904,
-++		.pcrx_ptr	= 0x0908,
-++		.glo_cfg	= 0x0a04,
-++		.rst_idx	= 0x0a08,
-++		.delay_irq	= 0x0a0c,
-++		.irq_status	= 0x0a20,
-++		.irq_mask	= 0x0a28,
-++		.int_grp	= 0x0a50,
-++	},
-++	.qdma = {
-++		.qtx_cfg	= 0x1800,
-++		.rx_ptr		= 0x1900,
-++		.rx_cnt_cfg	= 0x1904,
-++		.qcrx_ptr	= 0x1908,
-++		.glo_cfg	= 0x1a04,
-++		.rst_idx	= 0x1a08,
-++		.delay_irq	= 0x1a0c,
-++		.fc_th		= 0x1a10,
-++		.int_grp	= 0x1a20,
-++		.hred		= 0x1a44,
-++		.ctx_ptr	= 0x1b00,
-++		.dtx_ptr	= 0x1b04,
-++		.crx_ptr	= 0x1b10,
-++		.drx_ptr	= 0x1b14,
-++		.fq_head	= 0x1b20,
-++		.fq_tail	= 0x1b24,
-++		.fq_count	= 0x1b28,
-++		.fq_blen	= 0x1b2c,
-++	},
-++	.gdm1_cnt		= 0x2400,
-++};
++ #include <linux/mtd/mtd.h>
++ #include <linux/mtd/partitions.h>
++@@ -746,6 +747,17 @@ int add_mtd_device(struct mtd_info *mtd)
++ 		not->add(mtd);
++ 
++ 	mutex_unlock(&mtd_table_mutex);
 ++
-++static const struct mtk_reg_map mt7628_reg_map = {
-++	.tx_irq_mask		= 0x0a28,
-++	.tx_irq_status		= 0x0a20,
-++	.pdma = {
-++		.rx_ptr		= 0x0900,
-++		.rx_cnt_cfg	= 0x0904,
-++		.pcrx_ptr	= 0x0908,
-++		.glo_cfg	= 0x0a04,
-++		.rst_idx	= 0x0a08,
-++		.delay_irq	= 0x0a0c,
-++		.irq_status	= 0x0a20,
-++		.irq_mask	= 0x0a28,
-++		.int_grp	= 0x0a50,
-++	},
-++};
+++	if (of_find_property(mtd_get_of_node(mtd), "linux,rootfs", NULL)) {
+++		if (IS_BUILTIN(CONFIG_MTD)) {
+++			pr_info("mtd: setting mtd%d (%s) as root device\n", mtd->index, mtd->name);
+++			ROOT_DEV = MKDEV(MTD_BLOCK_MAJOR, mtd->index);
+++		} else {
+++			pr_warn("mtd: can't set mtd%d (%s) as root device - mtd must be builtin\n",
+++				mtd->index, mtd->name);
+++		}
+++	}
 ++
-+ /* strings used by ethtool */
-+ static const struct mtk_ethtool_stats {
-+ 	char str[ETH_GSTRING_LEN];
-+@@ -618,8 +671,8 @@ static inline void mtk_tx_irq_disable(st
-+ 	u32 val;
-+ 
-+ 	spin_lock_irqsave(&eth->tx_irq_lock, flags);
-+-	val = mtk_r32(eth, eth->tx_int_mask_reg);
-+-	mtk_w32(eth, val & ~mask, eth->tx_int_mask_reg);
-++	val = mtk_r32(eth, eth->soc->reg_map->tx_irq_mask);
-++	mtk_w32(eth, val & ~mask, eth->soc->reg_map->tx_irq_mask);
-+ 	spin_unlock_irqrestore(&eth->tx_irq_lock, flags);
-+ }
-+ 
-+@@ -629,8 +682,8 @@ static inline void mtk_tx_irq_enable(str
-+ 	u32 val;
-+ 
-+ 	spin_lock_irqsave(&eth->tx_irq_lock, flags);
-+-	val = mtk_r32(eth, eth->tx_int_mask_reg);
-+-	mtk_w32(eth, val | mask, eth->tx_int_mask_reg);
-++	val = mtk_r32(eth, eth->soc->reg_map->tx_irq_mask);
-++	mtk_w32(eth, val | mask, eth->soc->reg_map->tx_irq_mask);
-+ 	spin_unlock_irqrestore(&eth->tx_irq_lock, flags);
-+ }
-+ 
-+@@ -640,8 +693,8 @@ static inline void mtk_rx_irq_disable(st
-+ 	u32 val;
-+ 
-+ 	spin_lock_irqsave(&eth->rx_irq_lock, flags);
-+-	val = mtk_r32(eth, MTK_PDMA_INT_MASK);
-+-	mtk_w32(eth, val & ~mask, MTK_PDMA_INT_MASK);
-++	val = mtk_r32(eth, eth->soc->reg_map->pdma.irq_mask);
-++	mtk_w32(eth, val & ~mask, eth->soc->reg_map->pdma.irq_mask);
-+ 	spin_unlock_irqrestore(&eth->rx_irq_lock, flags);
-+ }
++ 	/* We _know_ we aren't being removed, because
++ 	   our caller is still holding us here. So none
++ 	   of this try_ nonsense, and no bitching about it
+diff --git a/target/linux/generic/backport-5.15/410-v5.18-mtd-parsers-trx-allow-to-use-on-MediaTek-MIPS-SoCs.patch b/target/linux/generic/backport-5.15/410-v5.18-mtd-parsers-trx-allow-to-use-on-MediaTek-MIPS-SoCs.patch
+new file mode 100644
+index 0000000000..5c49841760
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/410-v5.18-mtd-parsers-trx-allow-to-use-on-MediaTek-MIPS-SoCs.patch
+@@ -0,0 +1,33 @@
++From 2365f91c861cbfeef7141c69842848c7b2d3c2db Mon Sep 17 00:00:00 2001
++From: INAGAKI Hiroshi <musashino.open@gmail.com>
++Date: Sun, 13 Feb 2022 15:40:44 +0900
++Subject: [PATCH] mtd: parsers: trx: allow to use on MediaTek MIPS SoCs
++
++Buffalo sells some router devices which have trx-formatted firmware,
++based on MediaTek MIPS SoCs. To use parser_trx on those devices, add
++"RALINK" to dependency and allow to compile for MediaTek MIPS SoCs.
++
++examples:
++
++- WCR-1166DS  (MT7628)
++- WSR-1166DHP (MT7621)
++- WSR-2533DHP (MT7621)
++
++Signed-off-by: INAGAKI Hiroshi <musashino.open@gmail.com>
++Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Link: https://lore.kernel.org/linux-mtd/20220213064045.1781-1-musashino.open@gmail.com
++---
++ drivers/mtd/parsers/Kconfig | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
++
++--- a/drivers/mtd/parsers/Kconfig
+++++ b/drivers/mtd/parsers/Kconfig
++@@ -115,7 +115,7 @@ config MTD_AFS_PARTS
 + 
-+@@ -651,8 +704,8 @@ static inline void mtk_rx_irq_enable(str
-+ 	u32 val;
++ config MTD_PARSER_TRX
++ 	tristate "Parser for TRX format partitions"
++-	depends on MTD && (BCM47XX || ARCH_BCM_5301X || ARCH_MEDIATEK || COMPILE_TEST)
+++	depends on MTD && (BCM47XX || ARCH_BCM_5301X || ARCH_MEDIATEK || RALINK || COMPILE_TEST)
++ 	help
++ 	  TRX is a firmware format used by Broadcom on their devices. It
++ 	  may contain up to 3/4 partitions (depending on the version).
+diff --git a/target/linux/generic/backport-5.15/420-v5.19-02-mtd-spinand-gigadevice-add-support-for-GD5FxGQ4xExxG.patch b/target/linux/generic/backport-5.15/420-v5.19-02-mtd-spinand-gigadevice-add-support-for-GD5FxGQ4xExxG.patch
+new file mode 100644
+index 0000000000..181c912fbf
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/420-v5.19-02-mtd-spinand-gigadevice-add-support-for-GD5FxGQ4xExxG.patch
+@@ -0,0 +1,58 @@
++From 573eec222bc82fb5e724586267fbbb1aed9ffd03 Mon Sep 17 00:00:00 2001
++From: Chuanhong Guo <gch981213@gmail.com>
++Date: Sun, 20 Mar 2022 17:59:58 +0800
++Subject: [PATCH 2/5] mtd: spinand: gigadevice: add support for GD5FxGQ4xExxG
++
++Add support for:
++ GD5F1GQ4RExxG
++ GD5F2GQ4{U,R}ExxG
++
++These chips differ from GD5F1GQ4UExxG only in chip ID, voltage
++and capacity.
++
++Signed-off-by: Chuanhong Guo <gch981213@gmail.com>
++Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Link: https://lore.kernel.org/linux-mtd/20220320100001.247905-3-gch981213@gmail.com
++---
++ drivers/mtd/nand/spi/gigadevice.c | 30 ++++++++++++++++++++++++++++++
++ 1 file changed, 30 insertions(+)
++
++--- a/drivers/mtd/nand/spi/gigadevice.c
+++++ b/drivers/mtd/nand/spi/gigadevice.c
++@@ -333,6 +333,36 @@ static const struct spinand_info gigadev
++ 		     SPINAND_HAS_QE_BIT,
++ 		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
++ 				     gd5fxgq4uexxg_ecc_get_status)),
+++	SPINAND_INFO("GD5F1GQ4RExxG",
+++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xc1),
+++		     NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
+++		     NAND_ECCREQ(8, 512),
+++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+++					      &write_cache_variants,
+++					      &update_cache_variants),
+++		     SPINAND_HAS_QE_BIT,
+++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
+++				     gd5fxgq4uexxg_ecc_get_status)),
+++	SPINAND_INFO("GD5F2GQ4UExxG",
+++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xd2),
+++		     NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
+++		     NAND_ECCREQ(8, 512),
+++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+++					      &write_cache_variants,
+++					      &update_cache_variants),
+++		     SPINAND_HAS_QE_BIT,
+++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
+++				     gd5fxgq4uexxg_ecc_get_status)),
+++	SPINAND_INFO("GD5F2GQ4RExxG",
+++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xc2),
+++		     NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
+++		     NAND_ECCREQ(8, 512),
+++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+++					      &write_cache_variants,
+++					      &update_cache_variants),
+++		     SPINAND_HAS_QE_BIT,
+++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
+++				     gd5fxgq4uexxg_ecc_get_status)),
++ 	SPINAND_INFO("GD5F1GQ4UFxxG",
++ 		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE, 0xb1, 0x48),
++ 		     NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
+diff --git a/target/linux/generic/backport-5.15/420-v5.19-03-mtd-spinand-gigadevice-add-support-for-GD5F1GQ5RExxG.patch b/target/linux/generic/backport-5.15/420-v5.19-03-mtd-spinand-gigadevice-add-support-for-GD5F1GQ5RExxG.patch
+new file mode 100644
+index 0000000000..3a1cc9efcf
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/420-v5.19-03-mtd-spinand-gigadevice-add-support-for-GD5F1GQ5RExxG.patch
+@@ -0,0 +1,33 @@
++From 620a988813403318023296b61228ee8f3fcdb8e0 Mon Sep 17 00:00:00 2001
++From: Chuanhong Guo <gch981213@gmail.com>
++Date: Sun, 20 Mar 2022 17:59:59 +0800
++Subject: [PATCH 3/5] mtd: spinand: gigadevice: add support for GD5F1GQ5RExxG
++
++This chip is the 1.8v version of GD5F1GQ5UExxG.
++
++Signed-off-by: Chuanhong Guo <gch981213@gmail.com>
++Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Link: https://lore.kernel.org/linux-mtd/20220320100001.247905-4-gch981213@gmail.com
++---
++ drivers/mtd/nand/spi/gigadevice.c | 10 ++++++++++
++ 1 file changed, 10 insertions(+)
++
++--- a/drivers/mtd/nand/spi/gigadevice.c
+++++ b/drivers/mtd/nand/spi/gigadevice.c
++@@ -383,6 +383,16 @@ static const struct spinand_info gigadev
++ 		     SPINAND_HAS_QE_BIT,
++ 		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
++ 				     gd5fxgq5xexxg_ecc_get_status)),
+++	SPINAND_INFO("GD5F1GQ5RExxG",
+++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x41),
+++		     NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
+++		     NAND_ECCREQ(4, 512),
+++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
+++					      &write_cache_variants,
+++					      &update_cache_variants),
+++		     SPINAND_HAS_QE_BIT,
+++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
+++				     gd5fxgq5xexxg_ecc_get_status)),
++ };
 + 
-+ 	spin_lock_irqsave(&eth->rx_irq_lock, flags);
-+-	val = mtk_r32(eth, MTK_PDMA_INT_MASK);
-+-	mtk_w32(eth, val | mask, MTK_PDMA_INT_MASK);
-++	val = mtk_r32(eth, eth->soc->reg_map->pdma.irq_mask);
-++	mtk_w32(eth, val | mask, eth->soc->reg_map->pdma.irq_mask);
-+ 	spin_unlock_irqrestore(&eth->rx_irq_lock, flags);
-+ }
++ static const struct spinand_manufacturer_ops gigadevice_spinand_manuf_ops = {
+diff --git a/target/linux/generic/backport-5.15/420-v5.19-04-mtd-spinand-gigadevice-add-support-for-GD5F-2-4-GQ5x.patch b/target/linux/generic/backport-5.15/420-v5.19-04-mtd-spinand-gigadevice-add-support-for-GD5F-2-4-GQ5x.patch
+new file mode 100644
+index 0000000000..cee9d9db3e
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/420-v5.19-04-mtd-spinand-gigadevice-add-support-for-GD5F-2-4-GQ5x.patch
+@@ -0,0 +1,84 @@
++From 194ec04b3a9e7fa97d1fbef296410631bc3cf1c8 Mon Sep 17 00:00:00 2001
++From: Chuanhong Guo <gch981213@gmail.com>
++Date: Sun, 20 Mar 2022 18:00:00 +0800
++Subject: [PATCH 4/5] mtd: spinand: gigadevice: add support for GD5F{2,
++ 4}GQ5xExxG
++
++Add support for:
++ GD5F2GQ5{U,R}ExxG
++ GD5F4GQ6{U,R}ExxG
++
++These chips uses 4 dummy bytes for quad io and 2 dummy bytes for dual io.
++Besides that and memory layout, they are identical to their 1G variant.
++
++Signed-off-by: Chuanhong Guo <gch981213@gmail.com>
++Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Link: https://lore.kernel.org/linux-mtd/20220320100001.247905-5-gch981213@gmail.com
++---
++ drivers/mtd/nand/spi/gigadevice.c | 48 +++++++++++++++++++++++++++++++
++ 1 file changed, 48 insertions(+)
++
++--- a/drivers/mtd/nand/spi/gigadevice.c
+++++ b/drivers/mtd/nand/spi/gigadevice.c
++@@ -47,6 +47,14 @@ static SPINAND_OP_VARIANTS(read_cache_va
++ 		SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
++ 		SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
 + 
-+@@ -703,39 +756,39 @@ void mtk_stats_update_mac(struct mtk_mac
-+ 		hw_stats->rx_checksum_errors +=
-+ 			mtk_r32(mac->hw, MT7628_SDM_CS_ERR);
-+ 	} else {
-++		const struct mtk_reg_map *reg_map = eth->soc->reg_map;
-+ 		unsigned int offs = hw_stats->reg_offset;
-+ 		u64 stats;
+++static SPINAND_OP_VARIANTS(read_cache_variants_2gq5,
+++		SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 4, NULL, 0),
+++		SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
+++		SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 2, NULL, 0),
+++		SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
+++		SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
+++		SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
+++
++ static SPINAND_OP_VARIANTS(write_cache_variants,
++ 		SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
++ 		SPINAND_PROG_LOAD(true, 0, NULL, 0));
++@@ -391,6 +399,46 @@ static const struct spinand_info gigadev
++ 					      &write_cache_variants,
++ 					      &update_cache_variants),
++ 		     SPINAND_HAS_QE_BIT,
+++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
+++				     gd5fxgq5xexxg_ecc_get_status)),
+++	SPINAND_INFO("GD5F2GQ5UExxG",
+++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x52),
+++		     NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
+++		     NAND_ECCREQ(4, 512),
+++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5,
+++					      &write_cache_variants,
+++					      &update_cache_variants),
+++		     SPINAND_HAS_QE_BIT,
+++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
+++				     gd5fxgq5xexxg_ecc_get_status)),
+++	SPINAND_INFO("GD5F2GQ5RExxG",
+++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x42),
+++		     NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
+++		     NAND_ECCREQ(4, 512),
+++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5,
+++					      &write_cache_variants,
+++					      &update_cache_variants),
+++		     SPINAND_HAS_QE_BIT,
+++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
+++				     gd5fxgq5xexxg_ecc_get_status)),
+++	SPINAND_INFO("GD5F4GQ6UExxG",
+++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x55),
+++		     NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 2, 1),
+++		     NAND_ECCREQ(4, 512),
+++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5,
+++					      &write_cache_variants,
+++					      &update_cache_variants),
+++		     SPINAND_HAS_QE_BIT,
+++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
+++				     gd5fxgq5xexxg_ecc_get_status)),
+++	SPINAND_INFO("GD5F4GQ6RExxG",
+++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x45),
+++		     NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 2, 1),
+++		     NAND_ECCREQ(4, 512),
+++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5,
+++					      &write_cache_variants,
+++					      &update_cache_variants),
+++		     SPINAND_HAS_QE_BIT,
++ 		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
++ 				     gd5fxgq5xexxg_ecc_get_status)),
++ };
+diff --git a/target/linux/generic/backport-5.15/420-v5.19-05-mtd-spinand-gigadevice-add-support-for-GD5FxGM7xExxG.patch b/target/linux/generic/backport-5.15/420-v5.19-05-mtd-spinand-gigadevice-add-support-for-GD5FxGM7xExxG.patch
+new file mode 100644
+index 0000000000..d63113e1a6
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/420-v5.19-05-mtd-spinand-gigadevice-add-support-for-GD5FxGM7xExxG.patch
+@@ -0,0 +1,91 @@
++From 54647cd003c08b714474a5b599a147ec6a160486 Mon Sep 17 00:00:00 2001
++From: Chuanhong Guo <gch981213@gmail.com>
++Date: Sun, 20 Mar 2022 18:00:01 +0800
++Subject: [PATCH 5/5] mtd: spinand: gigadevice: add support for GD5FxGM7xExxG
++
++Add support for:
++ GD5F{1,2}GM7{U,R}ExxG
++ GD5F4GM8{U,R}ExxG
++
++These are new 27nm counterparts for the GD5FxGQ4 chips from GigaDevice
++with 8b/512b on-die ECC capability.
++These chips (and currently supported GD5FxGQ5 chips) have QIO DTR
++instruction for reading page cache. It isn't added in this patch because
++I don't have a DTR spi controller for testing.
++
++Signed-off-by: Chuanhong Guo <gch981213@gmail.com>
++Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Link: https://lore.kernel.org/linux-mtd/20220320100001.247905-6-gch981213@gmail.com
++---
++ drivers/mtd/nand/spi/gigadevice.c | 60 +++++++++++++++++++++++++++++++
++ 1 file changed, 60 insertions(+)
++
++--- a/drivers/mtd/nand/spi/gigadevice.c
+++++ b/drivers/mtd/nand/spi/gigadevice.c
++@@ -441,6 +441,66 @@ static const struct spinand_info gigadev
++ 		     SPINAND_HAS_QE_BIT,
++ 		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
++ 				     gd5fxgq5xexxg_ecc_get_status)),
+++	SPINAND_INFO("GD5F1GM7UExxG",
+++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x91),
+++		     NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
+++		     NAND_ECCREQ(8, 512),
+++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
+++					      &write_cache_variants,
+++					      &update_cache_variants),
+++		     SPINAND_HAS_QE_BIT,
+++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
+++				     gd5fxgq4uexxg_ecc_get_status)),
+++	SPINAND_INFO("GD5F1GM7RExxG",
+++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x81),
+++		     NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
+++		     NAND_ECCREQ(8, 512),
+++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
+++					      &write_cache_variants,
+++					      &update_cache_variants),
+++		     SPINAND_HAS_QE_BIT,
+++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
+++				     gd5fxgq4uexxg_ecc_get_status)),
+++	SPINAND_INFO("GD5F2GM7UExxG",
+++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x92),
+++		     NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
+++		     NAND_ECCREQ(8, 512),
+++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
+++					      &write_cache_variants,
+++					      &update_cache_variants),
+++		     SPINAND_HAS_QE_BIT,
+++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
+++				     gd5fxgq4uexxg_ecc_get_status)),
+++	SPINAND_INFO("GD5F2GM7RExxG",
+++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x82),
+++		     NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
+++		     NAND_ECCREQ(8, 512),
+++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
+++					      &write_cache_variants,
+++					      &update_cache_variants),
+++		     SPINAND_HAS_QE_BIT,
+++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
+++				     gd5fxgq4uexxg_ecc_get_status)),
+++	SPINAND_INFO("GD5F4GM8UExxG",
+++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x95),
+++		     NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 1, 1, 1),
+++		     NAND_ECCREQ(8, 512),
+++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
+++					      &write_cache_variants,
+++					      &update_cache_variants),
+++		     SPINAND_HAS_QE_BIT,
+++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
+++				     gd5fxgq4uexxg_ecc_get_status)),
+++	SPINAND_INFO("GD5F4GM8RExxG",
+++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x85),
+++		     NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 1, 1, 1),
+++		     NAND_ECCREQ(8, 512),
+++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
+++					      &write_cache_variants,
+++					      &update_cache_variants),
+++		     SPINAND_HAS_QE_BIT,
+++		     SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
+++				     gd5fxgq4uexxg_ecc_get_status)),
++ };
 + 
-+-		hw_stats->rx_bytes += mtk_r32(mac->hw,
-+-					      MTK_GDM1_RX_GBCNT_L + offs);
-+-		stats = mtk_r32(mac->hw, MTK_GDM1_RX_GBCNT_H + offs);
-++		hw_stats->rx_bytes += mtk_r32(mac->hw, reg_map->gdm1_cnt + offs);
-++		stats = mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x4 + offs);
-+ 		if (stats)
-+ 			hw_stats->rx_bytes += (stats << 32);
-+ 		hw_stats->rx_packets +=
-+-			mtk_r32(mac->hw, MTK_GDM1_RX_GPCNT + offs);
-++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x8 + offs);
-+ 		hw_stats->rx_overflow +=
-+-			mtk_r32(mac->hw, MTK_GDM1_RX_OERCNT + offs);
-++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x10 + offs);
-+ 		hw_stats->rx_fcs_errors +=
-+-			mtk_r32(mac->hw, MTK_GDM1_RX_FERCNT + offs);
-++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x14 + offs);
-+ 		hw_stats->rx_short_errors +=
-+-			mtk_r32(mac->hw, MTK_GDM1_RX_SERCNT + offs);
-++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x18 + offs);
-+ 		hw_stats->rx_long_errors +=
-+-			mtk_r32(mac->hw, MTK_GDM1_RX_LENCNT + offs);
-++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x1c + offs);
-+ 		hw_stats->rx_checksum_errors +=
-+-			mtk_r32(mac->hw, MTK_GDM1_RX_CERCNT + offs);
-++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x20 + offs);
-+ 		hw_stats->rx_flow_control_packets +=
-+-			mtk_r32(mac->hw, MTK_GDM1_RX_FCCNT + offs);
-++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x24 + offs);
-+ 		hw_stats->tx_skip +=
-+-			mtk_r32(mac->hw, MTK_GDM1_TX_SKIPCNT + offs);
-++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x28 + offs);
-+ 		hw_stats->tx_collisions +=
-+-			mtk_r32(mac->hw, MTK_GDM1_TX_COLCNT + offs);
-++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x2c + offs);
-+ 		hw_stats->tx_bytes +=
-+-			mtk_r32(mac->hw, MTK_GDM1_TX_GBCNT_L + offs);
-+-		stats =  mtk_r32(mac->hw, MTK_GDM1_TX_GBCNT_H + offs);
-++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x30 + offs);
-++		stats =  mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x34 + offs);
-+ 		if (stats)
-+ 			hw_stats->tx_bytes += (stats << 32);
-+ 		hw_stats->tx_packets +=
-+-			mtk_r32(mac->hw, MTK_GDM1_TX_GPCNT + offs);
-++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x38 + offs);
-+ 	}
++ static const struct spinand_manufacturer_ops gigadevice_spinand_manuf_ops = {
+diff --git a/target/linux/generic/backport-5.15/421-v6.2-mtd-parsers-add-TP-Link-SafeLoader-partitions-table-.patch b/target/linux/generic/backport-5.15/421-v6.2-mtd-parsers-add-TP-Link-SafeLoader-partitions-table-.patch
+new file mode 100644
+index 0000000000..9f543365a5
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/421-v6.2-mtd-parsers-add-TP-Link-SafeLoader-partitions-table-.patch
+@@ -0,0 +1,229 @@
++From aec4d5f5ffd0f0092bd9dc21ea90e0bc237d4b74 Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Sat, 15 Oct 2022 11:29:50 +0200
++Subject: [PATCH] mtd: parsers: add TP-Link SafeLoader partitions table parser
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++This parser deals with most TP-Link home routers. It reads info about
++partitions and registers them in the MTD subsystem.
++
++Example from TP-Link Archer C5 V2:
++
++spi-nor spi0.0: s25fl128s1 (16384 Kbytes)
++15 tplink-safeloader partitions found on MTD device spi0.0
++Creating 15 MTD partitions on "spi0.0":
++0x000000000000-0x000000040000 : "fs-uboot"
++0x000000040000-0x000000440000 : "os-image"
++0x000000440000-0x000000e40000 : "rootfs"
++0x000000e40000-0x000000e40200 : "default-mac"
++0x000000e40200-0x000000e40400 : "pin"
++0x000000e40400-0x000000e40600 : "product-info"
++0x000000e50000-0x000000e60000 : "partition-table"
++0x000000e60000-0x000000e60200 : "soft-version"
++0x000000e61000-0x000000e70000 : "support-list"
++0x000000e70000-0x000000e80000 : "profile"
++0x000000e80000-0x000000e90000 : "default-config"
++0x000000e90000-0x000000ee0000 : "user-config"
++0x000000ee0000-0x000000fe0000 : "log"
++0x000000fe0000-0x000000ff0000 : "radio_bk"
++0x000000ff0000-0x000001000000 : "radio"
++
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
++Link: https://lore.kernel.org/linux-mtd/20221015092950.27467-2-zajec5@gmail.com
++---
++ drivers/mtd/parsers/Kconfig             |  15 +++
++ drivers/mtd/parsers/Makefile            |   1 +
++ drivers/mtd/parsers/tplink_safeloader.c | 150 ++++++++++++++++++++++++
++ 3 files changed, 166 insertions(+)
++ create mode 100644 drivers/mtd/parsers/tplink_safeloader.c
++
++--- a/drivers/mtd/parsers/Kconfig
+++++ b/drivers/mtd/parsers/Kconfig
++@@ -113,6 +113,21 @@ config MTD_AFS_PARTS
++ 	  for your particular device. It won't happen automatically. The
++ 	  'physmap' map driver (CONFIG_MTD_PHYSMAP) does this, for example.
 + 
-+ 	u64_stats_update_end(&hw_stats->syncp);
-+@@ -875,10 +928,10 @@ static int mtk_init_fq_dma(struct mtk_et
-+ 		txd->txd4 = 0;
-+ 	}
+++config MTD_PARSER_TPLINK_SAFELOADER
+++	tristate "TP-Link Safeloader partitions parser"
+++	depends on MTD && (ARCH_BCM_5301X || ATH79 || SOC_MT7620 || SOC_MT7621 || COMPILE_TEST)
+++	help
+++	  TP-Link home routers use flash partitions to store various data. Info
+++	  about flash space layout is stored in a partitions table using a
+++	  custom ASCII-based format.
+++
+++	  That format was first found in devices with SafeLoader bootloader and
+++	  was named after it. Later it was adapted to CFE and U-Boot
+++	  bootloaders.
+++
+++	  This driver reads partitions table, parses it and creates MTD
+++	  partitions.
+++
++ config MTD_PARSER_TRX
++ 	tristate "Parser for TRX format partitions"
++ 	depends on MTD && (BCM47XX || ARCH_BCM_5301X || ARCH_MEDIATEK || RALINK || COMPILE_TEST)
++--- a/drivers/mtd/parsers/Makefile
+++++ b/drivers/mtd/parsers/Makefile
++@@ -9,6 +9,7 @@ ofpart-$(CONFIG_MTD_OF_PARTS_BCM4908)	+=
++ ofpart-$(CONFIG_MTD_OF_PARTS_LINKSYS_NS)+= ofpart_linksys_ns.o
++ obj-$(CONFIG_MTD_PARSER_IMAGETAG)	+= parser_imagetag.o
++ obj-$(CONFIG_MTD_AFS_PARTS)		+= afs.o
+++obj-$(CONFIG_MTD_PARSER_TPLINK_SAFELOADER)	+= tplink_safeloader.o
++ obj-$(CONFIG_MTD_PARSER_TRX)		+= parser_trx.o
++ obj-$(CONFIG_MTD_SERCOMM_PARTS)		+= scpart.o
++ obj-$(CONFIG_MTD_SHARPSL_PARTS)		+= sharpslpart.o
++--- /dev/null
+++++ b/drivers/mtd/parsers/tplink_safeloader.c
++@@ -0,0 +1,150 @@
+++// SPDX-License-Identifier: GPL-2.0-only
+++/*
+++ * Copyright © 2022 Rafał Miłecki <rafal@milecki.pl>
+++ */
+++
+++#include <linux/kernel.h>
+++#include <linux/module.h>
+++#include <linux/mtd/mtd.h>
+++#include <linux/mtd/partitions.h>
+++#include <linux/of.h>
+++#include <linux/slab.h>
+++
+++#define TPLINK_SAFELOADER_DATA_OFFSET		4
+++#define TPLINK_SAFELOADER_MAX_PARTS		32
+++
+++struct safeloader_cmn_header {
+++	__be32 size;
+++	uint32_t unused;
+++} __packed;
+++
+++static void *mtd_parser_tplink_safeloader_read_table(struct mtd_info *mtd)
+++{
+++	struct safeloader_cmn_header hdr;
+++	struct device_node *np;
+++	size_t bytes_read;
+++	size_t offset;
+++	size_t size;
+++	char *buf;
+++	int err;
+++
+++	np = mtd_get_of_node(mtd);
+++	if (mtd_is_partition(mtd))
+++		of_node_get(np);
+++	else
+++		np = of_get_child_by_name(np, "partitions");
+++
+++	if (of_property_read_u32(np, "partitions-table-offset", (u32 *)&offset)) {
+++		pr_err("Failed to get partitions table offset\n");
+++		goto err_put;
+++	}
+++
+++	err = mtd_read(mtd, offset, sizeof(hdr), &bytes_read, (uint8_t *)&hdr);
+++	if (err && !mtd_is_bitflip(err)) {
+++		pr_err("Failed to read from %s at 0x%zx\n", mtd->name, offset);
+++		goto err_put;
+++	}
+++
+++	size = be32_to_cpu(hdr.size);
+++
+++	buf = kmalloc(size + 1, GFP_KERNEL);
+++	if (!buf)
+++		goto err_put;
+++
+++	err = mtd_read(mtd, offset + sizeof(hdr), size, &bytes_read, buf);
+++	if (err && !mtd_is_bitflip(err)) {
+++		pr_err("Failed to read from %s at 0x%zx\n", mtd->name, offset + sizeof(hdr));
+++		goto err_kfree;
+++	}
+++
+++	buf[size] = '\0';
+++
+++	of_node_put(np);
+++
+++	return buf;
+++
+++err_kfree:
+++	kfree(buf);
+++err_put:
+++	of_node_put(np);
+++	return NULL;
+++}
+++
+++static int mtd_parser_tplink_safeloader_parse(struct mtd_info *mtd,
+++					      const struct mtd_partition **pparts,
+++					      struct mtd_part_parser_data *data)
+++{
+++	struct mtd_partition *parts;
+++	char name[65];
+++	size_t offset;
+++	size_t bytes;
+++	char *buf;
+++	int idx;
+++	int err;
+++
+++	parts = kcalloc(TPLINK_SAFELOADER_MAX_PARTS, sizeof(*parts), GFP_KERNEL);
+++	if (!parts) {
+++		err = -ENOMEM;
+++		goto err_out;
+++	}
+++
+++	buf = mtd_parser_tplink_safeloader_read_table(mtd);
+++	if (!buf) {
+++		err = -ENOENT;
+++		goto err_out;
+++	}
+++
+++	for (idx = 0, offset = TPLINK_SAFELOADER_DATA_OFFSET;
+++	     idx < TPLINK_SAFELOADER_MAX_PARTS &&
+++	     sscanf(buf + offset, "partition %64s base 0x%llx size 0x%llx%zn\n",
+++		    name, &parts[idx].offset, &parts[idx].size, &bytes) == 3;
+++	     idx++, offset += bytes + 1) {
+++		parts[idx].name = kstrdup(name, GFP_KERNEL);
+++		if (!parts[idx].name) {
+++			err = -ENOMEM;
+++			goto err_free;
+++		}
+++	}
+++
+++	if (idx == TPLINK_SAFELOADER_MAX_PARTS)
+++		pr_warn("Reached maximum number of partitions!\n");
+++
+++	kfree(buf);
+++
+++	*pparts = parts;
+++
+++	return idx;
+++
+++err_free:
+++	for (idx -= 1; idx >= 0; idx--)
+++		kfree(parts[idx].name);
+++err_out:
+++	return err;
+++};
+++
+++static void mtd_parser_tplink_safeloader_cleanup(const struct mtd_partition *pparts,
+++						 int nr_parts)
+++{
+++	int i;
+++
+++	for (i = 0; i < nr_parts; i++)
+++		kfree(pparts[i].name);
+++
+++	kfree(pparts);
+++}
+++
+++static const struct of_device_id mtd_parser_tplink_safeloader_of_match_table[] = {
+++	{ .compatible = "tplink,safeloader-partitions" },
+++	{},
+++};
+++MODULE_DEVICE_TABLE(of, mtd_parser_tplink_safeloader_of_match_table);
+++
+++static struct mtd_part_parser mtd_parser_tplink_safeloader = {
+++	.parse_fn = mtd_parser_tplink_safeloader_parse,
+++	.cleanup = mtd_parser_tplink_safeloader_cleanup,
+++	.name = "tplink-safeloader",
+++	.of_match_table = mtd_parser_tplink_safeloader_of_match_table,
+++};
+++module_mtd_part_parser(mtd_parser_tplink_safeloader);
+++
+++MODULE_LICENSE("GPL");
+diff --git a/target/linux/generic/backport-5.15/600-v5.18-page_pool-Add-allocation-stats.patch b/target/linux/generic/backport-5.15/600-v5.18-page_pool-Add-allocation-stats.patch
+new file mode 100644
+index 0000000000..9e383de92c
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/600-v5.18-page_pool-Add-allocation-stats.patch
+@@ -0,0 +1,165 @@
++From 8610037e8106b48c79cfe0afb92b2b2466e51c3d Mon Sep 17 00:00:00 2001
++From: Joe Damato <jdamato@fastly.com>
++Date: Tue, 1 Mar 2022 23:55:47 -0800
++Subject: [PATCH] page_pool: Add allocation stats
++
++Add per-pool statistics counters for the allocation path of a page pool.
++These stats are incremented in softirq context, so no locking or per-cpu
++variables are needed.
++
++This code is disabled by default and a kernel config option is provided for
++users who wish to enable them.
++
++The statistics added are:
++	- fast: successful fast path allocations
++	- slow: slow path order-0 allocations
++	- slow_high_order: slow path high order allocations
++	- empty: ptr ring is empty, so a slow path allocation was forced.
++	- refill: an allocation which triggered a refill of the cache
++	- waive: pages obtained from the ptr ring that cannot be added to
++	  the cache due to a NUMA mismatch.
++
++Signed-off-by: Joe Damato <jdamato@fastly.com>
++Acked-by: Jesper Dangaard Brouer <brouer@redhat.com>
++Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ include/net/page_pool.h | 18 ++++++++++++++++++
++ net/Kconfig             | 13 +++++++++++++
++ net/core/page_pool.c    | 24 ++++++++++++++++++++----
++ 3 files changed, 51 insertions(+), 4 deletions(-)
++
++--- a/include/net/page_pool.h
+++++ b/include/net/page_pool.h
++@@ -82,6 +82,19 @@ struct page_pool_params {
++ 	unsigned int	offset;  /* DMA addr offset */
++ };
 + 
-+-	mtk_w32(eth, eth->phy_scratch_ring, MTK_QDMA_FQ_HEAD);
-+-	mtk_w32(eth, phy_ring_tail, MTK_QDMA_FQ_TAIL);
-+-	mtk_w32(eth, (cnt << 16) | cnt, MTK_QDMA_FQ_CNT);
-+-	mtk_w32(eth, MTK_QDMA_PAGE_SIZE << 16, MTK_QDMA_FQ_BLEN);
-++	mtk_w32(eth, eth->phy_scratch_ring, soc->reg_map->qdma.fq_head);
-++	mtk_w32(eth, phy_ring_tail, soc->reg_map->qdma.fq_tail);
-++	mtk_w32(eth, (cnt << 16) | cnt, soc->reg_map->qdma.fq_count);
-++	mtk_w32(eth, MTK_QDMA_PAGE_SIZE << 16, soc->reg_map->qdma.fq_blen);
+++#ifdef CONFIG_PAGE_POOL_STATS
+++struct page_pool_alloc_stats {
+++	u64 fast; /* fast path allocations */
+++	u64 slow; /* slow-path order 0 allocations */
+++	u64 slow_high_order; /* slow-path high order allocations */
+++	u64 empty; /* failed refills due to empty ptr ring, forcing
+++		    * slow path allocation
+++		    */
+++	u64 refill; /* allocations via successful refill */
+++	u64 waive;  /* failed refills due to numa zone mismatch */
+++};
+++#endif
+++
++ struct page_pool {
++ 	struct page_pool_params p;
 + 
-+ 	return 0;
-+ }
-+@@ -1122,7 +1175,7 @@ static int mtk_tx_map(struct sk_buff *sk
-+ 	if (MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
-+ 		if (netif_xmit_stopped(netdev_get_tx_queue(dev, 0)) ||
-+ 		    !netdev_xmit_more())
-+-			mtk_w32(eth, txd->txd2, MTK_QTX_CTX_PTR);
-++			mtk_w32(eth, txd->txd2, soc->reg_map->qdma.ctx_ptr);
-+ 	} else {
-+ 		int next_idx;
++@@ -132,6 +145,11 @@ struct page_pool {
++ 	refcount_t user_cnt;
 + 
-+@@ -1439,6 +1492,7 @@ rx_done:
-+ static int mtk_poll_tx_qdma(struct mtk_eth *eth, int budget,
-+ 			    unsigned int *done, unsigned int *bytes)
-+ {
-++	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
-+ 	struct mtk_tx_ring *ring = &eth->tx_ring;
-+ 	struct mtk_tx_dma *desc;
-+ 	struct sk_buff *skb;
-+@@ -1446,7 +1500,7 @@ static int mtk_poll_tx_qdma(struct mtk_e
-+ 	u32 cpu, dma;
++ 	u64 destroy_cnt;
+++
+++#ifdef CONFIG_PAGE_POOL_STATS
+++	/* these stats are incremented while in softirq context */
+++	struct page_pool_alloc_stats alloc_stats;
+++#endif
++ };
 + 
-+ 	cpu = ring->last_free_ptr;
-+-	dma = mtk_r32(eth, MTK_QTX_DRX_PTR);
-++	dma = mtk_r32(eth, reg_map->qdma.drx_ptr);
++ struct page *page_pool_alloc_pages(struct page_pool *pool, gfp_t gfp);
++--- a/net/Kconfig
+++++ b/net/Kconfig
++@@ -434,6 +434,19 @@ config NET_DEVLINK
++ config PAGE_POOL
++ 	bool
++ 
+++config PAGE_POOL_STATS
+++	default n
+++	bool "Page pool stats"
+++	depends on PAGE_POOL
+++	help
+++	  Enable page pool statistics to track page allocation and recycling
+++	  in page pools. This option incurs additional CPU cost in allocation
+++	  and recycle paths and additional memory cost to store the statistics.
+++	  These statistics are only available if this option is enabled and if
+++	  the driver using the page pool supports exporting this data.
+++
+++	  If unsure, say N.
+++
++ config FAILOVER
++ 	tristate "Generic failover module"
++ 	help
++--- a/net/core/page_pool.c
+++++ b/net/core/page_pool.c
++@@ -26,6 +26,13 @@
 + 
-+ 	desc = mtk_qdma_phys_to_virt(ring, cpu);
++ #define BIAS_MAX	LONG_MAX
 + 
-+@@ -1481,7 +1535,7 @@ static int mtk_poll_tx_qdma(struct mtk_e
-+ 	}
+++#ifdef CONFIG_PAGE_POOL_STATS
+++/* alloc_stat_inc is intended to be used in softirq context */
+++#define alloc_stat_inc(pool, __stat)	(pool->alloc_stats.__stat++)
+++#else
+++#define alloc_stat_inc(pool, __stat)
+++#endif
+++
++ static int page_pool_init(struct page_pool *pool,
++ 			  const struct page_pool_params *params)
++ {
++@@ -117,8 +124,10 @@ static struct page *page_pool_refill_all
++ 	int pref_nid; /* preferred NUMA node */
 + 
-+ 	ring->last_free_ptr = cpu;
-+-	mtk_w32(eth, cpu, MTK_QTX_CRX_PTR);
-++	mtk_w32(eth, cpu, reg_map->qdma.crx_ptr);
++ 	/* Quicker fallback, avoid locks when ring is empty */
++-	if (__ptr_ring_empty(r))
+++	if (__ptr_ring_empty(r)) {
+++		alloc_stat_inc(pool, empty);
++ 		return NULL;
+++	}
 + 
-+ 	return budget;
-+ }
-+@@ -1574,24 +1628,25 @@ static void mtk_handle_status_irq(struct
-+ static int mtk_napi_tx(struct napi_struct *napi, int budget)
-+ {
-+ 	struct mtk_eth *eth = container_of(napi, struct mtk_eth, tx_napi);
-++	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
-+ 	int tx_done = 0;
++ 	/* Softirq guarantee CPU and thus NUMA node is stable. This,
++ 	 * assumes CPU refilling driver RX-ring will also run RX-NAPI.
++@@ -148,14 +157,17 @@ static struct page *page_pool_refill_all
++ 			 * This limit stress on page buddy alloactor.
++ 			 */
++ 			page_pool_return_page(pool, page);
+++			alloc_stat_inc(pool, waive);
++ 			page = NULL;
++ 			break;
++ 		}
++ 	} while (pool->alloc.count < PP_ALLOC_CACHE_REFILL);
 + 
-+ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA))
-+ 		mtk_handle_status_irq(eth);
-+-	mtk_w32(eth, MTK_TX_DONE_INT, eth->tx_int_status_reg);
-++	mtk_w32(eth, MTK_TX_DONE_INT, reg_map->tx_irq_status);
-+ 	tx_done = mtk_poll_tx(eth, budget);
++ 	/* Return last page */
++-	if (likely(pool->alloc.count > 0))
+++	if (likely(pool->alloc.count > 0)) {
++ 		page = pool->alloc.cache[--pool->alloc.count];
+++		alloc_stat_inc(pool, refill);
+++	}
 + 
-+ 	if (unlikely(netif_msg_intr(eth))) {
-+ 		dev_info(eth->dev,
-+ 			 "done tx %d, intr 0x%08x/0x%x\n", tx_done,
-+-			 mtk_r32(eth, eth->tx_int_status_reg),
-+-			 mtk_r32(eth, eth->tx_int_mask_reg));
-++			 mtk_r32(eth, reg_map->tx_irq_status),
-++			 mtk_r32(eth, reg_map->tx_irq_mask));
++ 	spin_unlock(&r->consumer_lock);
++ 	return page;
++@@ -170,6 +182,7 @@ static struct page *__page_pool_get_cach
++ 	if (likely(pool->alloc.count)) {
++ 		/* Fast-path */
++ 		page = pool->alloc.cache[--pool->alloc.count];
+++		alloc_stat_inc(pool, fast);
++ 	} else {
++ 		page = page_pool_refill_alloc_cache(pool);
++ 	}
++@@ -241,6 +254,7 @@ static struct page *__page_pool_alloc_pa
++ 		return NULL;
 + 	}
 + 
-+ 	if (tx_done == budget)
-+ 		return budget;
-+ 
-+-	if (mtk_r32(eth, eth->tx_int_status_reg) & MTK_TX_DONE_INT)
-++	if (mtk_r32(eth, reg_map->tx_irq_status) & MTK_TX_DONE_INT)
-+ 		return budget;
+++	alloc_stat_inc(pool, slow_high_order);
++ 	page_pool_set_pp_info(pool, page);
 + 
-+ 	if (napi_complete_done(napi, tx_done))
-+@@ -1603,6 +1658,7 @@ static int mtk_napi_tx(struct napi_struc
-+ static int mtk_napi_rx(struct napi_struct *napi, int budget)
-+ {
-+ 	struct mtk_eth *eth = container_of(napi, struct mtk_eth, rx_napi);
-++	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
-+ 	int rx_done_total = 0;
++ 	/* Track how many pages are held 'in-flight' */
++@@ -295,10 +309,12 @@ static struct page *__page_pool_alloc_pa
++ 	}
 + 
-+ 	mtk_handle_status_irq(eth);
-+@@ -1610,21 +1666,21 @@ static int mtk_napi_rx(struct napi_struc
-+ 	do {
-+ 		int rx_done;
++ 	/* Return last page */
++-	if (likely(pool->alloc.count > 0))
+++	if (likely(pool->alloc.count > 0)) {
++ 		page = pool->alloc.cache[--pool->alloc.count];
++-	else
+++		alloc_stat_inc(pool, slow);
+++	} else {
++ 		page = NULL;
+++	}
 + 
-+-		mtk_w32(eth, MTK_RX_DONE_INT, MTK_PDMA_INT_STATUS);
-++		mtk_w32(eth, MTK_RX_DONE_INT, reg_map->pdma.irq_status);
-+ 		rx_done = mtk_poll_rx(napi, budget - rx_done_total, eth);
-+ 		rx_done_total += rx_done;
++ 	/* When page just alloc'ed is should/must have refcnt 1. */
++ 	return page;
+diff --git a/target/linux/generic/backport-5.15/601-v5.18-page_pool-Add-recycle-stats.patch b/target/linux/generic/backport-5.15/601-v5.18-page_pool-Add-recycle-stats.patch
+new file mode 100644
+index 0000000000..fb11f0035f
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/601-v5.18-page_pool-Add-recycle-stats.patch
+@@ -0,0 +1,140 @@
++From ad6fa1e1ab1b8164f1ba296b1b4dc556a483bcad Mon Sep 17 00:00:00 2001
++From: Joe Damato <jdamato@fastly.com>
++Date: Tue, 1 Mar 2022 23:55:48 -0800
++Subject: [PATCH 2/3] page_pool: Add recycle stats
++
++Add per-cpu stats tracking page pool recycling events:
++	- cached: recycling placed page in the page pool cache
++	- cache_full: page pool cache was full
++	- ring: page placed into the ptr ring
++	- ring_full: page released from page pool because the ptr ring was full
++	- released_refcnt: page released (and not recycled) because refcnt > 1
++
++Signed-off-by: Joe Damato <jdamato@fastly.com>
++Acked-by: Jesper Dangaard Brouer <brouer@redhat.com>
++Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ include/net/page_pool.h | 16 ++++++++++++++++
++ net/core/page_pool.c    | 30 ++++++++++++++++++++++++++++--
++ 2 files changed, 44 insertions(+), 2 deletions(-)
++
++--- a/include/net/page_pool.h
+++++ b/include/net/page_pool.h
++@@ -93,6 +93,18 @@ struct page_pool_alloc_stats {
++ 	u64 refill; /* allocations via successful refill */
++ 	u64 waive;  /* failed refills due to numa zone mismatch */
++ };
+++
+++struct page_pool_recycle_stats {
+++	u64 cached;	/* recycling placed page in the cache. */
+++	u64 cache_full; /* cache was full */
+++	u64 ring;	/* recycling placed page back into ptr ring */
+++	u64 ring_full;	/* page was released from page-pool because
+++			 * PTR ring was full.
+++			 */
+++	u64 released_refcnt; /* page released because of elevated
+++			      * refcnt
+++			      */
+++};
++ #endif
 + 
-+ 		if (unlikely(netif_msg_intr(eth))) {
-+ 			dev_info(eth->dev,
-+ 				 "done rx %d, intr 0x%08x/0x%x\n", rx_done,
-+-				 mtk_r32(eth, MTK_PDMA_INT_STATUS),
-+-				 mtk_r32(eth, MTK_PDMA_INT_MASK));
-++				 mtk_r32(eth, reg_map->pdma.irq_status),
-++				 mtk_r32(eth, reg_map->pdma.irq_mask));
-+ 		}
++ struct page_pool {
++@@ -136,6 +148,10 @@ struct page_pool {
++ 	 */
++ 	struct ptr_ring ring;
 + 
-+ 		if (rx_done_total == budget)
-+ 			return budget;
+++#ifdef CONFIG_PAGE_POOL_STATS
+++	/* recycle stats are per-cpu to avoid locking */
+++	struct page_pool_recycle_stats __percpu *recycle_stats;
+++#endif
++ 	atomic_t pages_state_release_cnt;
++ 
++ 	/* A page_pool is strictly tied to a single RX-queue being
++--- a/net/core/page_pool.c
+++++ b/net/core/page_pool.c
++@@ -29,8 +29,15 @@
++ #ifdef CONFIG_PAGE_POOL_STATS
++ /* alloc_stat_inc is intended to be used in softirq context */
++ #define alloc_stat_inc(pool, __stat)	(pool->alloc_stats.__stat++)
+++/* recycle_stat_inc is safe to use when preemption is possible. */
+++#define recycle_stat_inc(pool, __stat)							\
+++	do {										\
+++		struct page_pool_recycle_stats __percpu *s = pool->recycle_stats;	\
+++		this_cpu_inc(s->__stat);						\
+++	} while (0)
++ #else
++ #define alloc_stat_inc(pool, __stat)
+++#define recycle_stat_inc(pool, __stat)
++ #endif
 + 
-+-	} while (mtk_r32(eth, MTK_PDMA_INT_STATUS) & MTK_RX_DONE_INT);
-++	} while (mtk_r32(eth, reg_map->pdma.irq_status) & MTK_RX_DONE_INT);
++ static int page_pool_init(struct page_pool *pool,
++@@ -80,6 +87,12 @@ static int page_pool_init(struct page_po
++ 	    pool->p.flags & PP_FLAG_PAGE_FRAG)
++ 		return -EINVAL;
 + 
-+ 	if (napi_complete_done(napi, rx_done_total))
-+ 		mtk_rx_irq_enable(eth, MTK_RX_DONE_INT);
-+@@ -1687,20 +1743,20 @@ static int mtk_tx_alloc(struct mtk_eth *
-+ 	 */
-+ 	wmb();
+++#ifdef CONFIG_PAGE_POOL_STATS
+++	pool->recycle_stats = alloc_percpu(struct page_pool_recycle_stats);
+++	if (!pool->recycle_stats)
+++		return -ENOMEM;
+++#endif
+++
++ 	if (ptr_ring_init(&pool->ring, ring_qsize, GFP_KERNEL) < 0)
++ 		return -ENOMEM;
 + 
-+-	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
-+-		mtk_w32(eth, ring->phys, MTK_QTX_CTX_PTR);
-+-		mtk_w32(eth, ring->phys, MTK_QTX_DTX_PTR);
-++	if (MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
-++		mtk_w32(eth, ring->phys, soc->reg_map->qdma.ctx_ptr);
-++		mtk_w32(eth, ring->phys, soc->reg_map->qdma.dtx_ptr);
-+ 		mtk_w32(eth,
-+ 			ring->phys + ((MTK_DMA_SIZE - 1) * sz),
-+-			MTK_QTX_CRX_PTR);
-+-		mtk_w32(eth, ring->last_free_ptr, MTK_QTX_DRX_PTR);
-++			soc->reg_map->qdma.crx_ptr);
-++		mtk_w32(eth, ring->last_free_ptr, soc->reg_map->qdma.drx_ptr);
-+ 		mtk_w32(eth, (QDMA_RES_THRES << 8) | QDMA_RES_THRES,
-+-			MTK_QTX_CFG(0));
-++			soc->reg_map->qdma.qtx_cfg);
-+ 	} else {
-+ 		mtk_w32(eth, ring->phys_pdma, MT7628_TX_BASE_PTR0);
-+ 		mtk_w32(eth, MTK_DMA_SIZE, MT7628_TX_MAX_CNT0);
-+ 		mtk_w32(eth, 0, MT7628_TX_CTX_IDX0);
-+-		mtk_w32(eth, MT7628_PST_DTX_IDX0, MTK_PDMA_RST_IDX);
-++		mtk_w32(eth, MT7628_PST_DTX_IDX0, soc->reg_map->pdma.rst_idx);
-+ 	}
++@@ -412,7 +425,12 @@ static bool page_pool_recycle_in_ring(st
++ 	else
++ 		ret = ptr_ring_produce_bh(&pool->ring, page);
 + 
-+ 	return 0;
-+@@ -1739,6 +1795,7 @@ static void mtk_tx_clean(struct mtk_eth
++-	return (ret == 0) ? true : false;
+++	if (!ret) {
+++		recycle_stat_inc(pool, ring);
+++		return true;
+++	}
+++
+++	return false;
++ }
 + 
-+ static int mtk_rx_alloc(struct mtk_eth *eth, int ring_no, int rx_flag)
++ /* Only allow direct recycling in special circumstances, into the
++@@ -423,11 +441,14 @@ static bool page_pool_recycle_in_ring(st
++ static bool page_pool_recycle_in_cache(struct page *page,
++ 				       struct page_pool *pool)
 + {
-++	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
-+ 	struct mtk_rx_ring *ring;
-+ 	int rx_data_len, rx_dma_size;
-+ 	int i;
-+@@ -1807,16 +1864,18 @@ static int mtk_rx_alloc(struct mtk_eth *
-+ 	ring->dma_size = rx_dma_size;
-+ 	ring->calc_idx_update = false;
-+ 	ring->calc_idx = rx_dma_size - 1;
-+-	ring->crx_idx_reg = MTK_PRX_CRX_IDX_CFG(ring_no);
-++	ring->crx_idx_reg = reg_map->pdma.pcrx_ptr + ring_no * MTK_QRX_OFFSET;
-+ 	/* make sure that all changes to the dma ring are flushed before we
-+ 	 * continue
-+ 	 */
-+ 	wmb();
-+ 
-+-	mtk_w32(eth, ring->phys, MTK_PRX_BASE_PTR_CFG(ring_no) + offset);
-+-	mtk_w32(eth, rx_dma_size, MTK_PRX_MAX_CNT_CFG(ring_no) + offset);
-++	mtk_w32(eth, ring->phys,
-++		reg_map->pdma.rx_ptr + ring_no * MTK_QRX_OFFSET + offset);
-++	mtk_w32(eth, rx_dma_size,
-++		reg_map->pdma.rx_cnt_cfg + ring_no * MTK_QRX_OFFSET + offset);
-+ 	mtk_w32(eth, ring->calc_idx, ring->crx_idx_reg + offset);
-+-	mtk_w32(eth, MTK_PST_DRX_IDX_CFG(ring_no), MTK_PDMA_RST_IDX + offset);
-++	mtk_w32(eth, MTK_PST_DRX_IDX_CFG(ring_no), reg_map->pdma.rst_idx + offset);
++-	if (unlikely(pool->alloc.count == PP_ALLOC_CACHE_SIZE))
+++	if (unlikely(pool->alloc.count == PP_ALLOC_CACHE_SIZE)) {
+++		recycle_stat_inc(pool, cache_full);
++ 		return false;
+++	}
 + 
-+ 	return 0;
++ 	/* Caller MUST have verified/know (page_ref_count(page) == 1) */
++ 	pool->alloc.cache[pool->alloc.count++] = page;
+++	recycle_stat_inc(pool, cached);
++ 	return true;
 + }
-+@@ -2125,9 +2184,9 @@ static int mtk_dma_busy_wait(struct mtk_
-+ 	u32 val;
-+ 
-+ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA))
-+-		reg = MTK_QDMA_GLO_CFG;
-++		reg = eth->soc->reg_map->qdma.glo_cfg;
-+ 	else
-+-		reg = MTK_PDMA_GLO_CFG;
-++		reg = eth->soc->reg_map->pdma.glo_cfg;
 + 
-+ 	ret = readx_poll_timeout_atomic(__raw_readl, eth->base + reg, val,
-+ 					!(val & (MTK_RX_DMA_BUSY | MTK_TX_DMA_BUSY)),
-+@@ -2185,8 +2244,8 @@ static int mtk_dma_init(struct mtk_eth *
-+ 		 * automatically
-+ 		 */
-+ 		mtk_w32(eth, FC_THRES_DROP_MODE | FC_THRES_DROP_EN |
-+-			FC_THRES_MIN, MTK_QDMA_FC_THRES);
-+-		mtk_w32(eth, 0x0, MTK_QDMA_HRED2);
-++			FC_THRES_MIN, eth->soc->reg_map->qdma.fc_th);
-++		mtk_w32(eth, 0x0, eth->soc->reg_map->qdma.hred);
++@@ -482,6 +503,7 @@ __page_pool_put_page(struct page_pool *p
++ 	 * doing refcnt based recycle tricks, meaning another process
++ 	 * will be invoking put_page.
++ 	 */
+++	recycle_stat_inc(pool, released_refcnt);
++ 	/* Do not replace this with page_pool_return_page() */
++ 	page_pool_release_page(pool, page);
++ 	put_page(page);
++@@ -495,6 +517,7 @@ void page_pool_put_page(struct page_pool
++ 	page = __page_pool_put_page(pool, page, dma_sync_size, allow_direct);
++ 	if (page && !page_pool_recycle_in_ring(pool, page)) {
++ 		/* Cache full, fallback to free pages */
+++		recycle_stat_inc(pool, ring_full);
++ 		page_pool_return_page(pool, page);
 + 	}
++ }
++@@ -641,6 +664,9 @@ static void page_pool_free(struct page_p
++ 	if (pool->p.flags & PP_FLAG_DMA_MAP)
++ 		put_device(pool->p.dev);
 + 
-+ 	return 0;
-+@@ -2260,13 +2319,14 @@ static irqreturn_t mtk_handle_irq_tx(int
-+ static irqreturn_t mtk_handle_irq(int irq, void *_eth)
-+ {
-+ 	struct mtk_eth *eth = _eth;
-++	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
+++#ifdef CONFIG_PAGE_POOL_STATS
+++	free_percpu(pool->recycle_stats);
+++#endif
++ 	kfree(pool);
++ }
 + 
-+-	if (mtk_r32(eth, MTK_PDMA_INT_MASK) & MTK_RX_DONE_INT) {
-+-		if (mtk_r32(eth, MTK_PDMA_INT_STATUS) & MTK_RX_DONE_INT)
-++	if (mtk_r32(eth, reg_map->pdma.irq_mask) & MTK_RX_DONE_INT) {
-++		if (mtk_r32(eth, reg_map->pdma.irq_status) & MTK_RX_DONE_INT)
-+ 			mtk_handle_irq_rx(irq, _eth);
-+ 	}
-+-	if (mtk_r32(eth, eth->tx_int_mask_reg) & MTK_TX_DONE_INT) {
-+-		if (mtk_r32(eth, eth->tx_int_status_reg) & MTK_TX_DONE_INT)
-++	if (mtk_r32(eth, reg_map->tx_irq_mask) & MTK_TX_DONE_INT) {
-++		if (mtk_r32(eth, reg_map->tx_irq_status) & MTK_TX_DONE_INT)
-+ 			mtk_handle_irq_tx(irq, _eth);
-+ 	}
+diff --git a/target/linux/generic/backport-5.15/602-v5.18-page_pool-Add-function-to-batch-and-return-stats.patch b/target/linux/generic/backport-5.15/602-v5.18-page_pool-Add-function-to-batch-and-return-stats.patch
+new file mode 100644
+index 0000000000..41188fb7f2
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/602-v5.18-page_pool-Add-function-to-batch-and-return-stats.patch
+@@ -0,0 +1,77 @@
++From 6b95e3388b1ea0ca63500c5a6e39162dbf828433 Mon Sep 17 00:00:00 2001
++From: Joe Damato <jdamato@fastly.com>
++Date: Tue, 1 Mar 2022 23:55:49 -0800
++Subject: [PATCH 3/3] page_pool: Add function to batch and return stats
++
++Adds a function page_pool_get_stats which can be used by drivers to obtain
++stats for a specified page_pool.
++
++Signed-off-by: Joe Damato <jdamato@fastly.com>
++Acked-by: Jesper Dangaard Brouer <brouer@redhat.com>
++Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ include/net/page_pool.h | 17 +++++++++++++++++
++ net/core/page_pool.c    | 25 +++++++++++++++++++++++++
++ 2 files changed, 42 insertions(+)
++
++--- a/include/net/page_pool.h
+++++ b/include/net/page_pool.h
++@@ -105,6 +105,23 @@ struct page_pool_recycle_stats {
++ 			      * refcnt
++ 			      */
++ };
+++
+++/* This struct wraps the above stats structs so users of the
+++ * page_pool_get_stats API can pass a single argument when requesting the
+++ * stats for the page pool.
+++ */
+++struct page_pool_stats {
+++	struct page_pool_alloc_stats alloc_stats;
+++	struct page_pool_recycle_stats recycle_stats;
+++};
+++
+++/*
+++ * Drivers that wish to harvest page pool stats and report them to users
+++ * (perhaps via ethtool, debugfs, or another mechanism) can allocate a
+++ * struct page_pool_stats call page_pool_get_stats to get stats for the specified pool.
+++ */
+++bool page_pool_get_stats(struct page_pool *pool,
+++			 struct page_pool_stats *stats);
++ #endif
 + 
-+@@ -2290,6 +2350,7 @@ static void mtk_poll_controller(struct n
-+ static int mtk_start_dma(struct mtk_eth *eth)
++ struct page_pool {
++--- a/net/core/page_pool.c
+++++ b/net/core/page_pool.c
++@@ -35,6 +35,31 @@
++ 		struct page_pool_recycle_stats __percpu *s = pool->recycle_stats;	\
++ 		this_cpu_inc(s->__stat);						\
++ 	} while (0)
+++
+++bool page_pool_get_stats(struct page_pool *pool,
+++			 struct page_pool_stats *stats)
+++{
+++	int cpu = 0;
+++
+++	if (!stats)
+++		return false;
+++
+++	memcpy(&stats->alloc_stats, &pool->alloc_stats, sizeof(pool->alloc_stats));
+++
+++	for_each_possible_cpu(cpu) {
+++		const struct page_pool_recycle_stats *pcpu =
+++			per_cpu_ptr(pool->recycle_stats, cpu);
+++
+++		stats->recycle_stats.cached += pcpu->cached;
+++		stats->recycle_stats.cache_full += pcpu->cache_full;
+++		stats->recycle_stats.ring += pcpu->ring;
+++		stats->recycle_stats.ring_full += pcpu->ring_full;
+++		stats->recycle_stats.released_refcnt += pcpu->released_refcnt;
+++	}
+++
+++	return true;
+++}
+++EXPORT_SYMBOL(page_pool_get_stats);
++ #else
++ #define alloc_stat_inc(pool, __stat)
++ #define recycle_stat_inc(pool, __stat)
+diff --git a/target/linux/generic/backport-5.15/603-v5.19-page_pool-Add-recycle-stats-to-page_pool_put_page_bu.patch b/target/linux/generic/backport-5.15/603-v5.19-page_pool-Add-recycle-stats-to-page_pool_put_page_bu.patch
+new file mode 100644
+index 0000000000..6ae3fb7133
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/603-v5.19-page_pool-Add-recycle-stats-to-page_pool_put_page_bu.patch
+@@ -0,0 +1,55 @@
++From 590032a4d2133ecc10d3078a8db1d85a4842f12c Mon Sep 17 00:00:00 2001
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Mon, 11 Apr 2022 16:05:26 +0200
++Subject: [PATCH] page_pool: Add recycle stats to page_pool_put_page_bulk
++
++Add missing recycle stats to page_pool_put_page_bulk routine.
++
++Reviewed-by: Joe Damato <jdamato@fastly.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
++Link: https://lore.kernel.org/r/3712178b51c007cfaed910ea80e68f00c916b1fa.1649685634.git.lorenzo@kernel.org
++Signed-off-by: Paolo Abeni <pabeni@redhat.com>
++---
++ net/core/page_pool.c | 15 +++++++++++++--
++ 1 file changed, 13 insertions(+), 2 deletions(-)
++
++--- a/net/core/page_pool.c
+++++ b/net/core/page_pool.c
++@@ -36,6 +36,12 @@
++ 		this_cpu_inc(s->__stat);						\
++ 	} while (0)
++ 
+++#define recycle_stat_add(pool, __stat, val)						\
+++	do {										\
+++		struct page_pool_recycle_stats __percpu *s = pool->recycle_stats;	\
+++		this_cpu_add(s->__stat, val);						\
+++	} while (0)
+++
++ bool page_pool_get_stats(struct page_pool *pool,
++ 			 struct page_pool_stats *stats)
 + {
-+ 	u32 rx_2b_offset = (NET_IP_ALIGN == 2) ? MTK_RX_2B_OFFSET : 0;
-++	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
-+ 	int err;
++@@ -63,6 +69,7 @@ EXPORT_SYMBOL(page_pool_get_stats);
++ #else
++ #define alloc_stat_inc(pool, __stat)
++ #define recycle_stat_inc(pool, __stat)
+++#define recycle_stat_add(pool, __stat, val)
++ #endif
 + 
-+ 	err = mtk_dma_init(eth);
-+@@ -2304,16 +2365,15 @@ static int mtk_start_dma(struct mtk_eth
-+ 			MTK_TX_BT_32DWORDS | MTK_NDP_CO_PRO |
-+ 			MTK_RX_DMA_EN | MTK_RX_2B_OFFSET |
-+ 			MTK_RX_BT_32DWORDS,
-+-			MTK_QDMA_GLO_CFG);
-+-
-++			reg_map->qdma.glo_cfg);
-+ 		mtk_w32(eth,
-+ 			MTK_RX_DMA_EN | rx_2b_offset |
-+ 			MTK_RX_BT_32DWORDS | MTK_MULTI_EN,
-+-			MTK_PDMA_GLO_CFG);
-++			reg_map->pdma.glo_cfg);
-+ 	} else {
-+ 		mtk_w32(eth, MTK_TX_WB_DDONE | MTK_TX_DMA_EN | MTK_RX_DMA_EN |
-+ 			MTK_MULTI_EN | MTK_PDMA_SIZE_8DWORDS,
-+-			MTK_PDMA_GLO_CFG);
-++			reg_map->pdma.glo_cfg);
++ static int page_pool_init(struct page_pool *pool,
++@@ -569,9 +576,13 @@ void page_pool_put_page_bulk(struct page
++ 	/* Bulk producer into ptr_ring page_pool cache */
++ 	page_pool_ring_lock(pool);
++ 	for (i = 0; i < bulk_len; i++) {
++-		if (__ptr_ring_produce(&pool->ring, data[i]))
++-			break; /* ring full */
+++		if (__ptr_ring_produce(&pool->ring, data[i])) {
+++			/* ring full */
+++			recycle_stat_inc(pool, ring_full);
+++			break;
+++		}
 + 	}
+++	recycle_stat_add(pool, ring, i);
++ 	page_pool_ring_unlock(pool);
 + 
-+ 	return 0;
-+@@ -2437,8 +2497,8 @@ static int mtk_stop(struct net_device *d
-+ 	cancel_work_sync(&eth->tx_dim.work);
-+ 
-+ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA))
-+-		mtk_stop_dma(eth, MTK_QDMA_GLO_CFG);
-+-	mtk_stop_dma(eth, MTK_PDMA_GLO_CFG);
-++		mtk_stop_dma(eth, eth->soc->reg_map->qdma.glo_cfg);
-++	mtk_stop_dma(eth, eth->soc->reg_map->pdma.glo_cfg);
++ 	/* Hopefully all pages was return into ptr_ring */
+diff --git a/target/linux/generic/backport-5.15/604-v5.19-net-page_pool-introduce-ethtool-stats.patch b/target/linux/generic/backport-5.15/604-v5.19-net-page_pool-introduce-ethtool-stats.patch
+new file mode 100644
+index 0000000000..e5cf91ceee
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/604-v5.19-net-page_pool-introduce-ethtool-stats.patch
+@@ -0,0 +1,147 @@
++From f3c5264f452a5b0ac1de1f2f657efbabdea3c76a Mon Sep 17 00:00:00 2001
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Tue, 12 Apr 2022 18:31:58 +0200
++Subject: [PATCH] net: page_pool: introduce ethtool stats
++
++Introduce page_pool APIs to report stats through ethtool and reduce
++duplicated code in each driver.
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Reviewed-by: Jakub Kicinski <kuba@kernel.org>
++Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ include/net/page_pool.h | 21 ++++++++++++++
++ net/core/page_pool.c    | 63 ++++++++++++++++++++++++++++++++++++++++-
++ 2 files changed, 83 insertions(+), 1 deletion(-)
++
++--- a/include/net/page_pool.h
+++++ b/include/net/page_pool.h
++@@ -115,6 +115,10 @@ struct page_pool_stats {
++ 	struct page_pool_recycle_stats recycle_stats;
++ };
 + 
-+ 	mtk_dma_free(eth);
+++int page_pool_ethtool_stats_get_count(void);
+++u8 *page_pool_ethtool_stats_get_strings(u8 *data);
+++u64 *page_pool_ethtool_stats_get(u64 *data, void *stats);
+++
++ /*
++  * Drivers that wish to harvest page pool stats and report them to users
++  * (perhaps via ethtool, debugfs, or another mechanism) can allocate a
++@@ -122,6 +126,23 @@ struct page_pool_stats {
++  */
++ bool page_pool_get_stats(struct page_pool *pool,
++ 			 struct page_pool_stats *stats);
+++#else
+++
+++static inline int page_pool_ethtool_stats_get_count(void)
+++{
+++	return 0;
+++}
+++
+++static inline u8 *page_pool_ethtool_stats_get_strings(u8 *data)
+++{
+++	return data;
+++}
+++
+++static inline u64 *page_pool_ethtool_stats_get(u64 *data, void *stats)
+++{
+++	return data;
+++}
+++
++ #endif
 + 
-+@@ -2492,6 +2552,7 @@ static void mtk_dim_rx(struct work_struc
++ struct page_pool {
++--- a/net/core/page_pool.c
+++++ b/net/core/page_pool.c
++@@ -18,6 +18,7 @@
++ #include <linux/page-flags.h>
++ #include <linux/mm.h> /* for __put_page() */
++ #include <linux/poison.h>
+++#include <linux/ethtool.h>
++ 
++ #include <trace/events/page_pool.h>
++ 
++@@ -42,6 +43,20 @@
++ 		this_cpu_add(s->__stat, val);						\
++ 	} while (0)
++ 
+++static const char pp_stats[][ETH_GSTRING_LEN] = {
+++	"rx_pp_alloc_fast",
+++	"rx_pp_alloc_slow",
+++	"rx_pp_alloc_slow_ho",
+++	"rx_pp_alloc_empty",
+++	"rx_pp_alloc_refill",
+++	"rx_pp_alloc_waive",
+++	"rx_pp_recycle_cached",
+++	"rx_pp_recycle_cache_full",
+++	"rx_pp_recycle_ring",
+++	"rx_pp_recycle_ring_full",
+++	"rx_pp_recycle_released_ref",
+++};
+++
++ bool page_pool_get_stats(struct page_pool *pool,
++ 			 struct page_pool_stats *stats)
 + {
-+ 	struct dim *dim = container_of(work, struct dim, work);
-+ 	struct mtk_eth *eth = container_of(dim, struct mtk_eth, rx_dim);
-++	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
-+ 	struct dim_cq_moder cur_profile;
-+ 	u32 val, cur;
-+ 
-+@@ -2499,7 +2560,7 @@ static void mtk_dim_rx(struct work_struc
-+ 						dim->profile_ix);
-+ 	spin_lock_bh(&eth->dim_lock);
-+ 
-+-	val = mtk_r32(eth, MTK_PDMA_DELAY_INT);
-++	val = mtk_r32(eth, reg_map->pdma.delay_irq);
-+ 	val &= MTK_PDMA_DELAY_TX_MASK;
-+ 	val |= MTK_PDMA_DELAY_RX_EN;
-+ 
-+@@ -2509,9 +2570,9 @@ static void mtk_dim_rx(struct work_struc
-+ 	cur = min_t(u32, cur_profile.pkts, MTK_PDMA_DELAY_PINT_MASK);
-+ 	val |= cur << MTK_PDMA_DELAY_RX_PINT_SHIFT;
++@@ -50,7 +65,13 @@ bool page_pool_get_stats(struct page_poo
++ 	if (!stats)
++ 		return false;
 + 
-+-	mtk_w32(eth, val, MTK_PDMA_DELAY_INT);
-++	mtk_w32(eth, val, reg_map->pdma.delay_irq);
-+ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA))
-+-		mtk_w32(eth, val, MTK_QDMA_DELAY_INT);
-++		mtk_w32(eth, val, reg_map->qdma.delay_irq);
++-	memcpy(&stats->alloc_stats, &pool->alloc_stats, sizeof(pool->alloc_stats));
+++	/* The caller is responsible to initialize stats. */
+++	stats->alloc_stats.fast += pool->alloc_stats.fast;
+++	stats->alloc_stats.slow += pool->alloc_stats.slow;
+++	stats->alloc_stats.slow_high_order += pool->alloc_stats.slow_high_order;
+++	stats->alloc_stats.empty += pool->alloc_stats.empty;
+++	stats->alloc_stats.refill += pool->alloc_stats.refill;
+++	stats->alloc_stats.waive += pool->alloc_stats.waive;
++ 
++ 	for_each_possible_cpu(cpu) {
++ 		const struct page_pool_recycle_stats *pcpu =
++@@ -66,6 +87,46 @@ bool page_pool_get_stats(struct page_poo
++ 	return true;
++ }
++ EXPORT_SYMBOL(page_pool_get_stats);
+++
+++u8 *page_pool_ethtool_stats_get_strings(u8 *data)
+++{
+++	int i;
+++
+++	for (i = 0; i < ARRAY_SIZE(pp_stats); i++) {
+++		memcpy(data, pp_stats[i], ETH_GSTRING_LEN);
+++		data += ETH_GSTRING_LEN;
+++	}
+++
+++	return data;
+++}
+++EXPORT_SYMBOL(page_pool_ethtool_stats_get_strings);
+++
+++int page_pool_ethtool_stats_get_count(void)
+++{
+++	return ARRAY_SIZE(pp_stats);
+++}
+++EXPORT_SYMBOL(page_pool_ethtool_stats_get_count);
+++
+++u64 *page_pool_ethtool_stats_get(u64 *data, void *stats)
+++{
+++	struct page_pool_stats *pool_stats = stats;
+++
+++	*data++ = pool_stats->alloc_stats.fast;
+++	*data++ = pool_stats->alloc_stats.slow;
+++	*data++ = pool_stats->alloc_stats.slow_high_order;
+++	*data++ = pool_stats->alloc_stats.empty;
+++	*data++ = pool_stats->alloc_stats.refill;
+++	*data++ = pool_stats->alloc_stats.waive;
+++	*data++ = pool_stats->recycle_stats.cached;
+++	*data++ = pool_stats->recycle_stats.cache_full;
+++	*data++ = pool_stats->recycle_stats.ring;
+++	*data++ = pool_stats->recycle_stats.ring_full;
+++	*data++ = pool_stats->recycle_stats.released_refcnt;
+++
+++	return data;
+++}
+++EXPORT_SYMBOL(page_pool_ethtool_stats_get);
+++
++ #else
++ #define alloc_stat_inc(pool, __stat)
++ #define recycle_stat_inc(pool, __stat)
+diff --git a/target/linux/generic/backport-5.15/605-v5.18-xdp-introduce-flags-field-in-xdp_buff-xdp_frame.patch b/target/linux/generic/backport-5.15/605-v5.18-xdp-introduce-flags-field-in-xdp_buff-xdp_frame.patch
+new file mode 100644
+index 0000000000..4a914404a2
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/605-v5.18-xdp-introduce-flags-field-in-xdp_buff-xdp_frame.patch
+@@ -0,0 +1,99 @@
++From 2e88d4ff03013937028f5397268b21e10cf68713 Mon Sep 17 00:00:00 2001
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 21 Jan 2022 11:09:45 +0100
++Subject: [PATCH] xdp: introduce flags field in xdp_buff/xdp_frame
++
++Introduce flags field in xdp_frame and xdp_buffer data structures
++to define additional buffer features. At the moment the only
++supported buffer feature is frags bit (XDP_FLAGS_HAS_FRAGS).
++frags bit is used to specify if this is a linear buffer
++(XDP_FLAGS_HAS_FRAGS not set) or a frags frame (XDP_FLAGS_HAS_FRAGS
++set). In the latter case the driver is expected to initialize the
++skb_shared_info structure at the end of the first buffer to link together
++subsequent buffers belonging to the same frame.
++
++Acked-by: Toke Hoiland-Jorgensen <toke@redhat.com>
++Acked-by: John Fastabend <john.fastabend@gmail.com>
++Acked-by: Jesper Dangaard Brouer <brouer@redhat.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Link: https://lore.kernel.org/r/e389f14f3a162c0a5bc6a2e1aa8dd01a90be117d.1642758637.git.lorenzo@kernel.org
++Signed-off-by: Alexei Starovoitov <ast@kernel.org>
++---
++ include/net/xdp.h | 29 +++++++++++++++++++++++++++++
++ 1 file changed, 29 insertions(+)
++
++--- a/include/net/xdp.h
+++++ b/include/net/xdp.h
++@@ -66,6 +66,10 @@ struct xdp_txq_info {
++ 	struct net_device *dev;
++ };
 + 
-+ 	spin_unlock_bh(&eth->dim_lock);
+++enum xdp_buff_flags {
+++	XDP_FLAGS_HAS_FRAGS	= BIT(0), /* non-linear xdp buff */
+++};
+++
++ struct xdp_buff {
++ 	void *data;
++ 	void *data_end;
++@@ -74,13 +78,30 @@ struct xdp_buff {
++ 	struct xdp_rxq_info *rxq;
++ 	struct xdp_txq_info *txq;
++ 	u32 frame_sz; /* frame size to deduce data_hard_end/reserved tailroom*/
+++	u32 flags; /* supported values defined in xdp_buff_flags */
++ };
 + 
-+@@ -2522,6 +2583,7 @@ static void mtk_dim_tx(struct work_struc
+++static __always_inline bool xdp_buff_has_frags(struct xdp_buff *xdp)
+++{
+++	return !!(xdp->flags & XDP_FLAGS_HAS_FRAGS);
+++}
+++
+++static __always_inline void xdp_buff_set_frags_flag(struct xdp_buff *xdp)
+++{
+++	xdp->flags |= XDP_FLAGS_HAS_FRAGS;
+++}
+++
+++static __always_inline void xdp_buff_clear_frags_flag(struct xdp_buff *xdp)
+++{
+++	xdp->flags &= ~XDP_FLAGS_HAS_FRAGS;
+++}
+++
++ static __always_inline void
++ xdp_init_buff(struct xdp_buff *xdp, u32 frame_sz, struct xdp_rxq_info *rxq)
 + {
-+ 	struct dim *dim = container_of(work, struct dim, work);
-+ 	struct mtk_eth *eth = container_of(dim, struct mtk_eth, tx_dim);
-++	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
-+ 	struct dim_cq_moder cur_profile;
-+ 	u32 val, cur;
-+ 
-+@@ -2529,7 +2591,7 @@ static void mtk_dim_tx(struct work_struc
-+ 						dim->profile_ix);
-+ 	spin_lock_bh(&eth->dim_lock);
-+ 
-+-	val = mtk_r32(eth, MTK_PDMA_DELAY_INT);
-++	val = mtk_r32(eth, reg_map->pdma.delay_irq);
-+ 	val &= MTK_PDMA_DELAY_RX_MASK;
-+ 	val |= MTK_PDMA_DELAY_TX_EN;
++ 	xdp->frame_sz = frame_sz;
++ 	xdp->rxq = rxq;
+++	xdp->flags = 0;
++ }
 + 
-+@@ -2539,9 +2601,9 @@ static void mtk_dim_tx(struct work_struc
-+ 	cur = min_t(u32, cur_profile.pkts, MTK_PDMA_DELAY_PINT_MASK);
-+ 	val |= cur << MTK_PDMA_DELAY_TX_PINT_SHIFT;
++ static __always_inline void
++@@ -122,8 +143,14 @@ struct xdp_frame {
++ 	 */
++ 	struct xdp_mem_info mem;
++ 	struct net_device *dev_rx; /* used by cpumap */
+++	u32 flags; /* supported values defined in xdp_buff_flags */
++ };
 + 
-+-	mtk_w32(eth, val, MTK_PDMA_DELAY_INT);
-++	mtk_w32(eth, val, reg_map->pdma.delay_irq);
-+ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA))
-+-		mtk_w32(eth, val, MTK_QDMA_DELAY_INT);
-++		mtk_w32(eth, val, reg_map->qdma.delay_irq);
+++static __always_inline bool xdp_frame_has_frags(struct xdp_frame *frame)
+++{
+++	return !!(frame->flags & XDP_FLAGS_HAS_FRAGS);
+++}
+++
++ #define XDP_BULK_QUEUE_SIZE	16
++ struct xdp_frame_bulk {
++ 	int count;
++@@ -180,6 +207,7 @@ void xdp_convert_frame_to_buff(struct xd
++ 	xdp->data_end = frame->data + frame->len;
++ 	xdp->data_meta = frame->data - frame->metasize;
++ 	xdp->frame_sz = frame->frame_sz;
+++	xdp->flags = frame->flags;
++ }
 + 
-+ 	spin_unlock_bh(&eth->dim_lock);
++ static inline
++@@ -206,6 +234,7 @@ int xdp_update_frame_from_buff(struct xd
++ 	xdp_frame->headroom = headroom - sizeof(*xdp_frame);
++ 	xdp_frame->metasize = metasize;
++ 	xdp_frame->frame_sz = xdp->frame_sz;
+++	xdp_frame->flags = xdp->flags;
 + 
-+@@ -2552,6 +2614,7 @@ static int mtk_hw_init(struct mtk_eth *e
++ 	return 0;
++ }
+diff --git a/target/linux/generic/backport-5.15/606-v5.18-xdp-add-frags-support-to-xdp_return_-buff-frame.patch b/target/linux/generic/backport-5.15/606-v5.18-xdp-add-frags-support-to-xdp_return_-buff-frame.patch
+new file mode 100644
+index 0000000000..86d24367dd
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/606-v5.18-xdp-add-frags-support-to-xdp_return_-buff-frame.patch
+@@ -0,0 +1,137 @@
++From 7c48cb0176c6d6d3b55029f7ff4ffa05faee6446 Mon Sep 17 00:00:00 2001
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 21 Jan 2022 11:09:50 +0100
++Subject: [PATCH] xdp: add frags support to xdp_return_{buff/frame}
++
++Take into account if the received xdp_buff/xdp_frame is non-linear
++recycling/returning the frame memory to the allocator or into
++xdp_frame_bulk.
++
++Acked-by: Toke Hoiland-Jorgensen <toke@redhat.com>
++Acked-by: John Fastabend <john.fastabend@gmail.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Link: https://lore.kernel.org/r/a961069febc868508ce1bdf5e53a343eb4e57cb2.1642758637.git.lorenzo@kernel.org
++Signed-off-by: Alexei Starovoitov <ast@kernel.org>
++---
++ include/net/xdp.h | 18 ++++++++++++++--
++ net/core/xdp.c    | 54 ++++++++++++++++++++++++++++++++++++++++++++++-
++ 2 files changed, 69 insertions(+), 3 deletions(-)
++
++--- a/include/net/xdp.h
+++++ b/include/net/xdp.h
++@@ -275,10 +275,24 @@ void __xdp_release_frame(void *data, str
++ static inline void xdp_release_frame(struct xdp_frame *xdpf)
 + {
-+ 	u32 dma_mask = ETHSYS_DMA_AG_MAP_PDMA | ETHSYS_DMA_AG_MAP_QDMA |
-+ 		       ETHSYS_DMA_AG_MAP_PPE;
-++	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
-+ 	int i, val, ret;
++ 	struct xdp_mem_info *mem = &xdpf->mem;
+++	struct skb_shared_info *sinfo;
+++	int i;
 + 
-+ 	if (test_and_set_bit(MTK_HW_INIT, &eth->state))
-+@@ -2626,10 +2689,10 @@ static int mtk_hw_init(struct mtk_eth *e
-+ 	mtk_rx_irq_disable(eth, ~0);
++ 	/* Curr only page_pool needs this */
++-	if (mem->type == MEM_TYPE_PAGE_POOL)
++-		__xdp_release_frame(xdpf->data, mem);
+++	if (mem->type != MEM_TYPE_PAGE_POOL)
+++		return;
+++
+++	if (likely(!xdp_frame_has_frags(xdpf)))
+++		goto out;
+++
+++	sinfo = xdp_get_shared_info_from_frame(xdpf);
+++	for (i = 0; i < sinfo->nr_frags; i++) {
+++		struct page *page = skb_frag_page(&sinfo->frags[i]);
+++
+++		__xdp_release_frame(page_address(page), mem);
+++	}
+++out:
+++	__xdp_release_frame(xdpf->data, mem);
++ }
 + 
-+ 	/* FE int grouping */
-+-	mtk_w32(eth, MTK_TX_DONE_INT, MTK_PDMA_INT_GRP1);
-+-	mtk_w32(eth, MTK_RX_DONE_INT, MTK_PDMA_INT_GRP2);
-+-	mtk_w32(eth, MTK_TX_DONE_INT, MTK_QDMA_INT_GRP1);
-+-	mtk_w32(eth, MTK_RX_DONE_INT, MTK_QDMA_INT_GRP2);
-++	mtk_w32(eth, MTK_TX_DONE_INT, reg_map->pdma.int_grp);
-++	mtk_w32(eth, MTK_RX_DONE_INT, reg_map->pdma.int_grp + 4);
-++	mtk_w32(eth, MTK_TX_DONE_INT, reg_map->qdma.int_grp);
-++	mtk_w32(eth, MTK_RX_DONE_INT, reg_map->qdma.int_grp + 4);
-+ 	mtk_w32(eth, 0x21021000, MTK_FE_INT_GRP);
++ int xdp_rxq_info_reg(struct xdp_rxq_info *xdp_rxq,
++--- a/net/core/xdp.c
+++++ b/net/core/xdp.c
++@@ -376,12 +376,38 @@ static void __xdp_return(void *data, str
 + 
-+ 	return 0;
-+@@ -3168,14 +3231,6 @@ static int mtk_probe(struct platform_dev
-+ 	if (IS_ERR(eth->base))
-+ 		return PTR_ERR(eth->base);
++ void xdp_return_frame(struct xdp_frame *xdpf)
++ {
+++	struct skb_shared_info *sinfo;
+++	int i;
+++
+++	if (likely(!xdp_frame_has_frags(xdpf)))
+++		goto out;
+++
+++	sinfo = xdp_get_shared_info_from_frame(xdpf);
+++	for (i = 0; i < sinfo->nr_frags; i++) {
+++		struct page *page = skb_frag_page(&sinfo->frags[i]);
+++
+++		__xdp_return(page_address(page), &xdpf->mem, false, NULL);
+++	}
+++out:
++ 	__xdp_return(xdpf->data, &xdpf->mem, false, NULL);
++ }
++ EXPORT_SYMBOL_GPL(xdp_return_frame);
 + 
-+-	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
-+-		eth->tx_int_mask_reg = MTK_QDMA_INT_MASK;
-+-		eth->tx_int_status_reg = MTK_QDMA_INT_STATUS;
-+-	} else {
-+-		eth->tx_int_mask_reg = MTK_PDMA_INT_MASK;
-+-		eth->tx_int_status_reg = MTK_PDMA_INT_STATUS;
-+-	}
-+-
-+ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628)) {
-+ 		eth->rx_dma_l4_valid = RX_DMA_L4_VALID_PDMA;
-+ 		eth->ip_align = NET_IP_ALIGN;
-+@@ -3409,6 +3464,7 @@ static int mtk_remove(struct platform_de
++ void xdp_return_frame_rx_napi(struct xdp_frame *xdpf)
++ {
+++	struct skb_shared_info *sinfo;
+++	int i;
+++
+++	if (likely(!xdp_frame_has_frags(xdpf)))
+++		goto out;
+++
+++	sinfo = xdp_get_shared_info_from_frame(xdpf);
+++	for (i = 0; i < sinfo->nr_frags; i++) {
+++		struct page *page = skb_frag_page(&sinfo->frags[i]);
+++
+++		__xdp_return(page_address(page), &xdpf->mem, true, NULL);
+++	}
+++out:
++ 	__xdp_return(xdpf->data, &xdpf->mem, true, NULL);
 + }
++ EXPORT_SYMBOL_GPL(xdp_return_frame_rx_napi);
++@@ -417,7 +443,7 @@ void xdp_return_frame_bulk(struct xdp_fr
++ 	struct xdp_mem_allocator *xa;
 + 
-+ static const struct mtk_soc_data mt2701_data = {
-++	.reg_map = &mtk_reg_map,
-+ 	.caps = MT7623_CAPS | MTK_HWLRO,
-+ 	.hw_features = MTK_HW_FEATURES,
-+ 	.required_clks = MT7623_CLKS_BITMAP,
-+@@ -3420,6 +3476,7 @@ static const struct mtk_soc_data mt2701_
-+ };
++ 	if (mem->type != MEM_TYPE_PAGE_POOL) {
++-		__xdp_return(xdpf->data, &xdpf->mem, false, NULL);
+++		xdp_return_frame(xdpf);
++ 		return;
++ 	}
 + 
-+ static const struct mtk_soc_data mt7621_data = {
-++	.reg_map = &mtk_reg_map,
-+ 	.caps = MT7621_CAPS,
-+ 	.hw_features = MTK_HW_FEATURES,
-+ 	.required_clks = MT7621_CLKS_BITMAP,
-+@@ -3432,6 +3489,7 @@ static const struct mtk_soc_data mt7621_
-+ };
++@@ -436,12 +462,38 @@ void xdp_return_frame_bulk(struct xdp_fr
++ 		bq->xa = rhashtable_lookup(mem_id_ht, &mem->id, mem_id_rht_params);
++ 	}
 + 
-+ static const struct mtk_soc_data mt7622_data = {
-++	.reg_map = &mtk_reg_map,
-+ 	.ana_rgc3 = 0x2028,
-+ 	.caps = MT7622_CAPS | MTK_HWLRO,
-+ 	.hw_features = MTK_HW_FEATURES,
-+@@ -3445,6 +3503,7 @@ static const struct mtk_soc_data mt7622_
-+ };
+++	if (unlikely(xdp_frame_has_frags(xdpf))) {
+++		struct skb_shared_info *sinfo;
+++		int i;
+++
+++		sinfo = xdp_get_shared_info_from_frame(xdpf);
+++		for (i = 0; i < sinfo->nr_frags; i++) {
+++			skb_frag_t *frag = &sinfo->frags[i];
+++
+++			bq->q[bq->count++] = skb_frag_address(frag);
+++			if (bq->count == XDP_BULK_QUEUE_SIZE)
+++				xdp_flush_frame_bulk(bq);
+++		}
+++	}
++ 	bq->q[bq->count++] = xdpf->data;
++ }
++ EXPORT_SYMBOL_GPL(xdp_return_frame_bulk);
 + 
-+ static const struct mtk_soc_data mt7623_data = {
-++	.reg_map = &mtk_reg_map,
-+ 	.caps = MT7623_CAPS | MTK_HWLRO,
-+ 	.hw_features = MTK_HW_FEATURES,
-+ 	.required_clks = MT7623_CLKS_BITMAP,
-+@@ -3457,6 +3516,7 @@ static const struct mtk_soc_data mt7623_
-+ };
++ void xdp_return_buff(struct xdp_buff *xdp)
++ {
+++	struct skb_shared_info *sinfo;
+++	int i;
+++
+++	if (likely(!xdp_buff_has_frags(xdp)))
+++		goto out;
+++
+++	sinfo = xdp_get_shared_info_from_buff(xdp);
+++	for (i = 0; i < sinfo->nr_frags; i++) {
+++		struct page *page = skb_frag_page(&sinfo->frags[i]);
+++
+++		__xdp_return(page_address(page), &xdp->rxq->mem, true, xdp);
+++	}
+++out:
++ 	__xdp_return(xdp->data, &xdp->rxq->mem, true, xdp);
++ }
 + 
-+ static const struct mtk_soc_data mt7629_data = {
-++	.reg_map = &mtk_reg_map,
-+ 	.ana_rgc3 = 0x128,
-+ 	.caps = MT7629_CAPS | MTK_HWLRO,
-+ 	.hw_features = MTK_HW_FEATURES,
-+@@ -3469,6 +3529,7 @@ static const struct mtk_soc_data mt7629_
-+ };
+diff --git a/target/linux/generic/backport-5.15/607-v5.18-net-skbuff-add-size-metadata-to-skb_shared_info-for-.patch b/target/linux/generic/backport-5.15/607-v5.18-net-skbuff-add-size-metadata-to-skb_shared_info-for-.patch
+new file mode 100644
+index 0000000000..a0344a332a
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/607-v5.18-net-skbuff-add-size-metadata-to-skb_shared_info-for-.patch
+@@ -0,0 +1,31 @@
++From d16697cb6261d4cc23422e6b1cb2759df8aa76d0 Mon Sep 17 00:00:00 2001
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 21 Jan 2022 11:09:44 +0100
++Subject: [PATCH] net: skbuff: add size metadata to skb_shared_info for xdp
++
++Introduce xdp_frags_size field in skb_shared_info data structure
++to store xdp_buff/xdp_frame frame paged size (xdp_frags_size will
++be used in xdp frags support). In order to not increase
++skb_shared_info size we will use a hole due to skb_shared_info
++alignment.
++
++Acked-by: Toke Hoiland-Jorgensen <toke@redhat.com>
++Acked-by: John Fastabend <john.fastabend@gmail.com>
++Acked-by: Jesper Dangaard Brouer <brouer@redhat.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Link: https://lore.kernel.org/r/8a849819a3e0a143d540f78a3a5add76e17e980d.1642758637.git.lorenzo@kernel.org
++Signed-off-by: Alexei Starovoitov <ast@kernel.org>
++---
++ include/linux/skbuff.h | 1 +
++ 1 file changed, 1 insertion(+)
++
++--- a/include/linux/skbuff.h
+++++ b/include/linux/skbuff.h
++@@ -567,6 +567,7 @@ struct skb_shared_info {
++ 	 * Warning : all fields before dataref are cleared in __alloc_skb()
++ 	 */
++ 	atomic_t	dataref;
+++	unsigned int	xdp_frags_size;
 + 
-+ static const struct mtk_soc_data rt5350_data = {
-++	.reg_map = &mt7628_reg_map,
-+ 	.caps = MT7628_CAPS,
-+ 	.hw_features = MTK_HW_FEATURES_MT7628,
-+ 	.required_clks = MT7628_CLKS_BITMAP,
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-+@@ -48,6 +48,8 @@
-+ #define MTK_HW_FEATURES_MT7628	(NETIF_F_SG | NETIF_F_RXCSUM)
-+ #define NEXT_DESP_IDX(X, Y)	(((X) + 1) & ((Y) - 1))
++ 	/* Intermediate layers must ensure that destructor_arg
++ 	 * remains valid until skb destructor */
+diff --git a/target/linux/generic/backport-5.15/608-v5.18-net-veth-Account-total-xdp_frame-len-running-ndo_xdp.patch b/target/linux/generic/backport-5.15/608-v5.18-net-veth-Account-total-xdp_frame-len-running-ndo_xdp.patch
+new file mode 100644
+index 0000000000..3bdba8728c
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/608-v5.18-net-veth-Account-total-xdp_frame-len-running-ndo_xdp.patch
+@@ -0,0 +1,65 @@
++From 5142239a22219921a7863cf00c9ab853c00689d8 Mon Sep 17 00:00:00 2001
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 11 Mar 2022 10:14:18 +0100
++Subject: [PATCH] net: veth: Account total xdp_frame len running ndo_xdp_xmit
++
++Even if this is a theoretical issue since it is not possible to perform
++XDP_REDIRECT on a non-linear xdp_frame, veth driver does not account
++paged area in ndo_xdp_xmit function pointer.
++Introduce xdp_get_frame_len utility routine to get the xdp_frame full
++length and account total frame size running XDP_REDIRECT of a
++non-linear xdp frame into a veth device.
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
++Acked-by: Toke Hoiland-Jorgensen <toke@redhat.com>
++Acked-by: John Fastabend <john.fastabend@gmail.com>
++Link: https://lore.kernel.org/bpf/54f9fd3bb65d190daf2c0bbae2f852ff16cfbaa0.1646989407.git.lorenzo@kernel.org
++---
++ drivers/net/veth.c |  4 ++--
++ include/net/xdp.h  | 14 ++++++++++++++
++ 2 files changed, 16 insertions(+), 2 deletions(-)
++
++--- a/drivers/net/veth.c
+++++ b/drivers/net/veth.c
++@@ -501,7 +501,7 @@ static int veth_xdp_xmit(struct net_devi
++ 		struct xdp_frame *frame = frames[i];
++ 		void *ptr = veth_xdp_to_ptr(frame);
++ 
++-		if (unlikely(frame->len > max_len ||
+++		if (unlikely(xdp_get_frame_len(frame) > max_len ||
++ 			     __ptr_ring_produce(&rq->xdp_ring, ptr)))
++ 			break;
++ 		nxmit++;
++@@ -862,7 +862,7 @@ static int veth_xdp_rcv(struct veth_rq *
++ 			/* ndo_xdp_xmit */
++ 			struct xdp_frame *frame = veth_ptr_to_xdp(ptr);
++ 
++-			stats->xdp_bytes += frame->len;
+++			stats->xdp_bytes += xdp_get_frame_len(frame);
++ 			frame = veth_xdp_rcv_one(rq, frame, bq, stats);
++ 			if (frame) {
++ 				/* XDP_PASS */
++--- a/include/net/xdp.h
+++++ b/include/net/xdp.h
++@@ -295,6 +295,20 @@ out:
++ 	__xdp_release_frame(xdpf->data, mem);
++ }
 + 
-++#define MTK_QRX_OFFSET		0x10
+++static __always_inline unsigned int xdp_get_frame_len(struct xdp_frame *xdpf)
+++{
+++	struct skb_shared_info *sinfo;
+++	unsigned int len = xdpf->len;
 ++
-+ #define MTK_MAX_RX_RING_NUM	4
-+ #define MTK_HW_LRO_DMA_SIZE	8
-+ 
-+@@ -100,18 +102,6 @@
-+ /* Unicast Filter MAC Address Register - High */
-+ #define MTK_GDMA_MAC_ADRH(x)	(0x50C + (x * 0x1000))
-+ 
-+-/* PDMA RX Base Pointer Register */
-+-#define MTK_PRX_BASE_PTR0	0x900
-+-#define MTK_PRX_BASE_PTR_CFG(x)	(MTK_PRX_BASE_PTR0 + (x * 0x10))
-+-
-+-/* PDMA RX Maximum Count Register */
-+-#define MTK_PRX_MAX_CNT0	0x904
-+-#define MTK_PRX_MAX_CNT_CFG(x)	(MTK_PRX_MAX_CNT0 + (x * 0x10))
-+-
-+-/* PDMA RX CPU Pointer Register */
-+-#define MTK_PRX_CRX_IDX0	0x908
-+-#define MTK_PRX_CRX_IDX_CFG(x)	(MTK_PRX_CRX_IDX0 + (x * 0x10))
-+-
-+ /* PDMA HW LRO Control Registers */
-+ #define MTK_PDMA_LRO_CTRL_DW0	0x980
-+ #define MTK_LRO_EN			BIT(0)
-+@@ -126,18 +116,19 @@
-+ #define MTK_ADMA_MODE		BIT(15)
-+ #define MTK_LRO_MIN_RXD_SDL	(MTK_HW_LRO_SDL_REMAIN_ROOM << 16)
-+ 
-+-/* PDMA Global Configuration Register */
-+-#define MTK_PDMA_GLO_CFG	0xa04
-++#define MTK_RX_DMA_LRO_EN	BIT(8)
-+ #define MTK_MULTI_EN		BIT(10)
-+ #define MTK_PDMA_SIZE_8DWORDS	(1 << 4)
-+ 
-++/* PDMA Global Configuration Register */
-++#define MTK_PDMA_LRO_SDL	0x3000
-++#define MTK_RX_CFG_SDL_OFFSET	16
+++	if (likely(!xdp_frame_has_frags(xdpf)))
+++		goto out;
 ++
-+ /* PDMA Reset Index Register */
-+-#define MTK_PDMA_RST_IDX	0xa08
-+ #define MTK_PST_DRX_IDX0	BIT(16)
-+ #define MTK_PST_DRX_IDX_CFG(x)	(MTK_PST_DRX_IDX0 << (x))
-+ 
-+ /* PDMA Delay Interrupt Register */
-+-#define MTK_PDMA_DELAY_INT		0xa0c
-+ #define MTK_PDMA_DELAY_RX_MASK		GENMASK(15, 0)
-+ #define MTK_PDMA_DELAY_RX_EN		BIT(15)
-+ #define MTK_PDMA_DELAY_RX_PINT_SHIFT	8
-+@@ -151,19 +142,9 @@
-+ #define MTK_PDMA_DELAY_PINT_MASK	0x7f
-+ #define MTK_PDMA_DELAY_PTIME_MASK	0xff
-+ 
-+-/* PDMA Interrupt Status Register */
-+-#define MTK_PDMA_INT_STATUS	0xa20
-+-
-+-/* PDMA Interrupt Mask Register */
-+-#define MTK_PDMA_INT_MASK	0xa28
-+-
-+ /* PDMA HW LRO Alter Flow Delta Register */
-+ #define MTK_PDMA_LRO_ALT_SCORE_DELTA	0xa4c
-+ 
-+-/* PDMA Interrupt grouping registers */
-+-#define MTK_PDMA_INT_GRP1	0xa50
-+-#define MTK_PDMA_INT_GRP2	0xa54
-+-
-+ /* PDMA HW LRO IP Setting Registers */
-+ #define MTK_LRO_RX_RING0_DIP_DW0	0xb04
-+ #define MTK_LRO_DIP_DW0_CFG(x)		(MTK_LRO_RX_RING0_DIP_DW0 + (x * 0x40))
-+@@ -185,26 +166,9 @@
-+ #define MTK_RING_MAX_AGG_CNT_H		((MTK_HW_LRO_MAX_AGG_CNT >> 6) & 0x3)
-+ 
-+ /* QDMA TX Queue Configuration Registers */
-+-#define MTK_QTX_CFG(x)		(0x1800 + (x * 0x10))
-+ #define QDMA_RES_THRES		4
-+ 
-+-/* QDMA TX Queue Scheduler Registers */
-+-#define MTK_QTX_SCH(x)		(0x1804 + (x * 0x10))
-+-
-+-/* QDMA RX Base Pointer Register */
-+-#define MTK_QRX_BASE_PTR0	0x1900
-+-
-+-/* QDMA RX Maximum Count Register */
-+-#define MTK_QRX_MAX_CNT0	0x1904
-+-
-+-/* QDMA RX CPU Pointer Register */
-+-#define MTK_QRX_CRX_IDX0	0x1908
-+-
-+-/* QDMA RX DMA Pointer Register */
-+-#define MTK_QRX_DRX_IDX0	0x190C
-+-
-+ /* QDMA Global Configuration Register */
-+-#define MTK_QDMA_GLO_CFG	0x1A04
-+ #define MTK_RX_2B_OFFSET	BIT(31)
-+ #define MTK_RX_BT_32DWORDS	(3 << 11)
-+ #define MTK_NDP_CO_PRO		BIT(10)
-+@@ -216,20 +180,12 @@
-+ #define MTK_TX_DMA_EN		BIT(0)
-+ #define MTK_DMA_BUSY_TIMEOUT_US	1000000
-+ 
-+-/* QDMA Reset Index Register */
-+-#define MTK_QDMA_RST_IDX	0x1A08
-+-
-+-/* QDMA Delay Interrupt Register */
-+-#define MTK_QDMA_DELAY_INT	0x1A0C
-+-
-+ /* QDMA Flow Control Register */
-+-#define MTK_QDMA_FC_THRES	0x1A10
-+ #define FC_THRES_DROP_MODE	BIT(20)
-+ #define FC_THRES_DROP_EN	(7 << 16)
-+ #define FC_THRES_MIN		0x4444
-+ 
-+ /* QDMA Interrupt Status Register */
-+-#define MTK_QDMA_INT_STATUS	0x1A18
-+ #define MTK_RX_DONE_DLY		BIT(30)
-+ #define MTK_TX_DONE_DLY		BIT(28)
-+ #define MTK_RX_DONE_INT3	BIT(19)
-+@@ -244,55 +200,8 @@
-+ #define MTK_TX_DONE_INT		MTK_TX_DONE_DLY
-+ 
-+ /* QDMA Interrupt grouping registers */
-+-#define MTK_QDMA_INT_GRP1	0x1a20
-+-#define MTK_QDMA_INT_GRP2	0x1a24
-+ #define MTK_RLS_DONE_INT	BIT(0)
-+ 
-+-/* QDMA Interrupt Status Register */
-+-#define MTK_QDMA_INT_MASK	0x1A1C
-+-
-+-/* QDMA Interrupt Mask Register */
-+-#define MTK_QDMA_HRED2		0x1A44
-+-
-+-/* QDMA TX Forward CPU Pointer Register */
-+-#define MTK_QTX_CTX_PTR		0x1B00
-+-
-+-/* QDMA TX Forward DMA Pointer Register */
-+-#define MTK_QTX_DTX_PTR		0x1B04
-+-
-+-/* QDMA TX Release CPU Pointer Register */
-+-#define MTK_QTX_CRX_PTR		0x1B10
-+-
-+-/* QDMA TX Release DMA Pointer Register */
-+-#define MTK_QTX_DRX_PTR		0x1B14
-+-
-+-/* QDMA FQ Head Pointer Register */
-+-#define MTK_QDMA_FQ_HEAD	0x1B20
-+-
-+-/* QDMA FQ Head Pointer Register */
-+-#define MTK_QDMA_FQ_TAIL	0x1B24
-+-
-+-/* QDMA FQ Free Page Counter Register */
-+-#define MTK_QDMA_FQ_CNT		0x1B28
-+-
-+-/* QDMA FQ Free Page Buffer Length Register */
-+-#define MTK_QDMA_FQ_BLEN	0x1B2C
-+-
-+-/* GMA1 counter / statics register */
-+-#define MTK_GDM1_RX_GBCNT_L	0x2400
-+-#define MTK_GDM1_RX_GBCNT_H	0x2404
-+-#define MTK_GDM1_RX_GPCNT	0x2408
-+-#define MTK_GDM1_RX_OERCNT	0x2410
-+-#define MTK_GDM1_RX_FERCNT	0x2414
-+-#define MTK_GDM1_RX_SERCNT	0x2418
-+-#define MTK_GDM1_RX_LENCNT	0x241c
-+-#define MTK_GDM1_RX_CERCNT	0x2420
-+-#define MTK_GDM1_RX_FCCNT	0x2424
-+-#define MTK_GDM1_TX_SKIPCNT	0x2428
-+-#define MTK_GDM1_TX_COLCNT	0x242c
-+-#define MTK_GDM1_TX_GBCNT_L	0x2430
-+-#define MTK_GDM1_TX_GBCNT_H	0x2434
-+-#define MTK_GDM1_TX_GPCNT	0x2438
-+ #define MTK_STAT_OFFSET		0x40
-+ 
-+ #define MTK_WDMA0_BASE		0x2800
-+@@ -853,8 +762,46 @@ struct mtk_tx_dma_desc_info {
-+ 	u8		last:1;
-+ };
-+ 
-++struct mtk_reg_map {
-++	u32	tx_irq_mask;
-++	u32	tx_irq_status;
-++	struct {
-++		u32	rx_ptr;		/* rx base pointer */
-++		u32	rx_cnt_cfg;	/* rx max count configuration */
-++		u32	pcrx_ptr;	/* rx cpu pointer */
-++		u32	glo_cfg;	/* global configuration */
-++		u32	rst_idx;	/* reset index */
-++		u32	delay_irq;	/* delay interrupt */
-++		u32	irq_status;	/* interrupt status */
-++		u32	irq_mask;	/* interrupt mask */
-++		u32	int_grp;
-++	} pdma;
-++	struct {
-++		u32	qtx_cfg;	/* tx queue configuration */
-++		u32	rx_ptr;		/* rx base pointer */
-++		u32	rx_cnt_cfg;	/* rx max count configuration */
-++		u32	qcrx_ptr;	/* rx cpu pointer */
-++		u32	glo_cfg;	/* global configuration */
-++		u32	rst_idx;	/* reset index */
-++		u32	delay_irq;	/* delay interrupt */
-++		u32	fc_th;		/* flow control */
-++		u32	int_grp;
-++		u32	hred;		/* interrupt mask */
-++		u32	ctx_ptr;	/* tx acquire cpu pointer */
-++		u32	dtx_ptr;	/* tx acquire dma pointer */
-++		u32	crx_ptr;	/* tx release cpu pointer */
-++		u32	drx_ptr;	/* tx release dma pointer */
-++		u32	fq_head;	/* fq head pointer */
-++		u32	fq_tail;	/* fq tail pointer */
-++		u32	fq_count;	/* fq free page count */
-++		u32	fq_blen;	/* fq free page buffer length */
-++	} qdma;
-++	u32	gdm1_cnt;
-++};
+++	sinfo = xdp_get_shared_info_from_frame(xdpf);
+++	len += sinfo->xdp_frags_size;
+++out:
+++	return len;
+++}
 ++
-+ /* struct mtk_eth_data -	This is the structure holding all differences
-+  *				among various plaforms
-++ * @reg_map			Soc register map.
-+  * @ana_rgc3:                   The offset for register ANA_RGC3 related to
-+  *				sgmiisys syscon
-+  * @caps			Flags shown the extra capability for the SoC
-+@@ -867,6 +814,7 @@ struct mtk_tx_dma_desc_info {
-+  * @rxd_size			Rx DMA descriptor size.
-+  */
-+ struct mtk_soc_data {
-++	const struct mtk_reg_map *reg_map;
-+ 	u32             ana_rgc3;
-+ 	u32		caps;
-+ 	u32		required_clks;
-+@@ -994,8 +942,6 @@ struct mtk_eth {
-+ 	u32				tx_bytes;
-+ 	struct dim			tx_dim;
-+ 
-+-	u32				tx_int_mask_reg;
-+-	u32				tx_int_status_reg;
-+ 	u32				rx_dma_l4_valid;
-+ 	int				ip_align;
-+ 
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-27-net-ethernet-mtk_eth_soc-introduce-MTK_NETSYS_V2-sup.patch b/target/linux/generic/backport-5.15/702-v5.19-27-net-ethernet-mtk_eth_soc-introduce-MTK_NETSYS_V2-sup.patch
++ int xdp_rxq_info_reg(struct xdp_rxq_info *xdp_rxq,
++ 		     struct net_device *dev, u32 queue_index, unsigned int napi_id);
++ void xdp_rxq_info_unreg(struct xdp_rxq_info *xdp_rxq);
+diff --git a/target/linux/generic/backport-5.15/609-v5.18-veth-Allow-jumbo-frames-in-xdp-mode.patch b/target/linux/generic/backport-5.15/609-v5.18-veth-Allow-jumbo-frames-in-xdp-mode.patch
 new file mode 100644
-index 0000000000..e15854ecad
+index 0000000000..08744be524
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-27-net-ethernet-mtk_eth_soc-introduce-MTK_NETSYS_V2-sup.patch
-@@ -0,0 +1,917 @@
++++ b/target/linux/generic/backport-5.15/609-v5.18-veth-Allow-jumbo-frames-in-xdp-mode.patch
+@@ -0,0 +1,40 @@
++From 7cda76d858a4e71ac4a04066c093679a12e1312c Mon Sep 17 00:00:00 2001
 +From: Lorenzo Bianconi <lorenzo@kernel.org>
-+Date: Fri, 20 May 2022 20:11:36 +0200
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: introduce MTK_NETSYS_V2 support
++Date: Fri, 11 Mar 2022 10:14:20 +0100
++Subject: [PATCH] veth: Allow jumbo frames in xdp mode
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
 +
-+Introduce MTK_NETSYS_V2 support. MTK_NETSYS_V2 defines 32B TX/RX DMA
-+descriptors.
-+This is a preliminary patch to add mt7986 ethernet support.
++Allow increasing the MTU over page boundaries on veth devices
++if the attached xdp program declares to support xdp fragments.
 +
-+Tested-by: Sam Shih <sam.shih@mediatek.com>
 +Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
++Acked-by: Toke Høiland-Jørgensen <toke@redhat.com>
++Acked-by: John Fastabend <john.fastabend@gmail.com>
++Link: https://lore.kernel.org/bpf/d5dc039c3d4123426e7023a488c449181a7bc57f.1646989407.git.lorenzo@kernel.org
++---
++ drivers/net/veth.c | 11 ++++++++---
++ 1 file changed, 8 insertions(+), 3 deletions(-)
++
++--- a/drivers/net/veth.c
+++++ b/drivers/net/veth.c
++@@ -1470,9 +1470,14 @@ static int veth_xdp_set(struct net_devic
++ 			goto err;
++ 		}
++ 
++-		max_mtu = PAGE_SIZE - VETH_XDP_HEADROOM -
++-			  peer->hard_header_len -
++-			  SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
+++		max_mtu = SKB_WITH_OVERHEAD(PAGE_SIZE - VETH_XDP_HEADROOM) -
+++			  peer->hard_header_len;
+++		/* Allow increasing the max_mtu if the program supports
+++		 * XDP fragments.
+++		 */
+++		//if (prog->aux->xdp_has_frags)
+++		max_mtu += PAGE_SIZE * MAX_SKB_FRAGS;
+++
++ 		if (peer->mtu > max_mtu) {
++ 			NL_SET_ERR_MSG_MOD(extack, "Peer MTU is too large to set XDP");
++ 			err = -ERANGE;
+diff --git a/target/linux/generic/backport-5.15/700-v5.17-net-dsa-introduce-tagger-owned-storage-for-private.patch b/target/linux/generic/backport-5.15/700-v5.17-net-dsa-introduce-tagger-owned-storage-for-private.patch
+new file mode 100644
+index 0000000000..f2b651f0c6
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/700-v5.17-net-dsa-introduce-tagger-owned-storage-for-private.patch
+@@ -0,0 +1,279 @@
++From dc452a471dbae8aca8257c565174212620880093 Mon Sep 17 00:00:00 2001
++From: Vladimir Oltean <vladimir.oltean@nxp.com>
++Date: Fri, 10 Dec 2021 01:34:37 +0200
++Subject: net: dsa: introduce tagger-owned storage for private and shared data
++
++Ansuel is working on register access over Ethernet for the qca8k switch
++family. This requires the qca8k tagging protocol driver to receive
++frames which aren't intended for the network stack, but instead for the
++qca8k switch driver itself.
++
++The dp->priv is currently the prevailing method for passing data back
++and forth between the tagging protocol driver and the switch driver.
++However, this method is riddled with caveats.
++
++The DSA design allows in principle for any switch driver to return any
++protocol it desires in ->get_tag_protocol(). The dsa_loop driver can be
++modified to do just that. But in the current design, the memory behind
++dp->priv has to be allocated by the switch driver, so if the tagging
++protocol is paired to an unexpected switch driver, we may end up in NULL
++pointer dereferences inside the kernel, or worse (a switch driver may
++allocate dp->priv according to the expectations of a different tagger).
++
++The latter possibility is even more plausible considering that DSA
++switches can dynamically change tagging protocols in certain cases
++(dsa <-> edsa, ocelot <-> ocelot-8021q), and the current design lends
++itself to mistakes that are all too easy to make.
++
++This patch proposes that the tagging protocol driver should manage its
++own memory, instead of relying on the switch driver to do so.
++After analyzing the different in-tree needs, it can be observed that the
++required tagger storage is per switch, therefore a ds->tagger_data
++pointer is introduced. In principle, per-port storage could also be
++introduced, although there is no need for it at the moment. Future
++changes will replace the current usage of dp->priv with ds->tagger_data.
++
++We define a "binding" event between the DSA switch tree and the tagging
++protocol. During this binding event, the tagging protocol's ->connect()
++method is called first, and this may allocate some memory for each
++switch of the tree. Then a cross-chip notifier is emitted for the
++switches within that tree, and they are given the opportunity to fix up
++the tagger's memory (for example, they might set up some function
++pointers that represent virtual methods for consuming packets).
++Because the memory is owned by the tagger, there exists a ->disconnect()
++method for the tagger (which is the place to free the resources), but
++there doesn't exist a ->disconnect() method for the switch driver.
++This is part of the design. The switch driver should make minimal use of
++the public part of the tagger data, and only after type-checking it
++using the supplied "proto" argument.
++
++In the code there are in fact two binding events, one is the initial
++event in dsa_switch_setup_tag_protocol(). At this stage, the cross chip
++notifier chains aren't initialized, so we call each switch's connect()
++method by hand. Then there is dsa_tree_bind_tag_proto() during
++dsa_tree_change_tag_proto(), and here we have an old protocol and a new
++one. We first connect to the new one before disconnecting from the old
++one, to simplify error handling a bit and to ensure we remain in a valid
++state at all times.
++
++Co-developed-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
 +Signed-off-by: David S. Miller <davem@davemloft.net>
 +---
++ include/net/dsa.h  | 12 +++++++++
++ net/dsa/dsa2.c     | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++---
++ net/dsa/dsa_priv.h |  1 +
++ net/dsa/switch.c   | 14 +++++++++++
++ 4 files changed, 96 insertions(+), 4 deletions(-)
 +
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -862,8 +862,8 @@ static inline int mtk_max_buf_size(int f
-+ 	return buf_size;
-+ }
++--- a/include/net/dsa.h
+++++ b/include/net/dsa.h
++@@ -80,12 +80,15 @@ enum dsa_tag_protocol {
++ };
 + 
-+-static inline bool mtk_rx_get_desc(struct mtk_rx_dma *rxd,
-+-				   struct mtk_rx_dma *dma_rxd)
-++static bool mtk_rx_get_desc(struct mtk_eth *eth, struct mtk_rx_dma_v2 *rxd,
-++			    struct mtk_rx_dma_v2 *dma_rxd)
++ struct dsa_switch;
+++struct dsa_switch_tree;
++ 
++ struct dsa_device_ops {
++ 	struct sk_buff *(*xmit)(struct sk_buff *skb, struct net_device *dev);
++ 	struct sk_buff *(*rcv)(struct sk_buff *skb, struct net_device *dev);
++ 	void (*flow_dissect)(const struct sk_buff *skb, __be16 *proto,
++ 			     int *offset);
+++	int (*connect)(struct dsa_switch_tree *dst);
+++	void (*disconnect)(struct dsa_switch_tree *dst);
++ 	unsigned int needed_headroom;
++ 	unsigned int needed_tailroom;
++ 	const char *name;
++@@ -329,6 +332,8 @@ struct dsa_switch {
++ 	 */
++ 	void *priv;
++ 
+++	void *tagger_data;
+++
++ 	/*
++ 	 * Configuration data for this switch.
++ 	 */
++@@ -584,6 +589,13 @@ struct dsa_switch_ops {
++ 						  enum dsa_tag_protocol mprot);
++ 	int	(*change_tag_protocol)(struct dsa_switch *ds, int port,
++ 				       enum dsa_tag_protocol proto);
+++	/*
+++	 * Method for switch drivers to connect to the tagging protocol driver
+++	 * in current use. The switch driver can provide handlers for certain
+++	 * types of packets for switch management.
+++	 */
+++	int	(*connect_tag_protocol)(struct dsa_switch *ds,
+++					enum dsa_tag_protocol proto);
++ 
++ 	/* Optional switch-wide initialization and destruction methods */
++ 	int	(*setup)(struct dsa_switch *ds);
++--- a/net/dsa/dsa2.c
+++++ b/net/dsa/dsa2.c
++@@ -230,8 +230,12 @@ static struct dsa_switch_tree *dsa_tree_
++ 
++ static void dsa_tree_free(struct dsa_switch_tree *dst)
 + {
-+ 	rxd->rxd2 = READ_ONCE(dma_rxd->rxd2);
-+ 	if (!(rxd->rxd2 & RX_DMA_DONE))
-+@@ -872,6 +872,10 @@ static inline bool mtk_rx_get_desc(struc
-+ 	rxd->rxd1 = READ_ONCE(dma_rxd->rxd1);
-+ 	rxd->rxd3 = READ_ONCE(dma_rxd->rxd3);
-+ 	rxd->rxd4 = READ_ONCE(dma_rxd->rxd4);
-++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
-++		rxd->rxd5 = READ_ONCE(dma_rxd->rxd5);
-++		rxd->rxd6 = READ_ONCE(dma_rxd->rxd6);
++-	if (dst->tag_ops)
+++	if (dst->tag_ops) {
+++		if (dst->tag_ops->disconnect)
+++			dst->tag_ops->disconnect(dst);
+++
++ 		dsa_tag_driver_put(dst->tag_ops);
 ++	}
-+ 
-+ 	return true;
++ 	list_del(&dst->list);
++ 	kfree(dst);
 + }
-+@@ -916,7 +920,7 @@ static int mtk_init_fq_dma(struct mtk_et
-+ 	phy_ring_tail = eth->phy_scratch_ring + soc->txrx.txd_size * (cnt - 1);
-+ 
-+ 	for (i = 0; i < cnt; i++) {
-+-		struct mtk_tx_dma *txd;
-++		struct mtk_tx_dma_v2 *txd;
++@@ -805,7 +809,7 @@ static int dsa_switch_setup_tag_protocol
++ 	int port, err;
 + 
-+ 		txd = (void *)eth->scratch_ring + i * soc->txrx.txd_size;
-+ 		txd->txd1 = dma_addr + i * MTK_QDMA_PAGE_SIZE;
-+@@ -926,6 +930,12 @@ static int mtk_init_fq_dma(struct mtk_et
++ 	if (tag_ops->proto == dst->default_proto)
++-		return 0;
+++		goto connect;
 + 
-+ 		txd->txd3 = TX_DMA_PLEN0(MTK_QDMA_PAGE_SIZE);
-+ 		txd->txd4 = 0;
-++		if (MTK_HAS_CAPS(soc->caps, MTK_NETSYS_V2)) {
-++			txd->txd5 = 0;
-++			txd->txd6 = 0;
-++			txd->txd7 = 0;
-++			txd->txd8 = 0;
-++		}
++ 	for (port = 0; port < ds->num_ports; port++) {
++ 		if (!dsa_is_cpu_port(ds, port))
++@@ -821,6 +825,17 @@ static int dsa_switch_setup_tag_protocol
++ 		}
 + 	}
 + 
-+ 	mtk_w32(eth, eth->phy_scratch_ring, soc->reg_map->qdma.fq_head);
-+@@ -1029,10 +1039,12 @@ static void setup_tx_buf(struct mtk_eth
-+ 	}
+++connect:
+++	if (ds->ops->connect_tag_protocol) {
+++		err = ds->ops->connect_tag_protocol(ds, tag_ops->proto);
+++		if (err) {
+++			dev_err(ds->dev,
+++				"Unable to connect to tag protocol \"%s\": %pe\n",
+++				tag_ops->name, ERR_PTR(err));
+++			return err;
+++		}
+++	}
+++
++ 	return 0;
 + }
 + 
-+-static void mtk_tx_set_dma_desc(struct net_device *dev, struct mtk_tx_dma *desc,
-+-				struct mtk_tx_dma_desc_info *info)
-++static void mtk_tx_set_dma_desc_v1(struct net_device *dev, void *txd,
-++				   struct mtk_tx_dma_desc_info *info)
-+ {
-+ 	struct mtk_mac *mac = netdev_priv(dev);
-++	struct mtk_eth *eth = mac->hw;
-++	struct mtk_tx_dma *desc = txd;
-+ 	u32 data;
-+ 
-+ 	WRITE_ONCE(desc->txd1, info->addr);
-+@@ -1056,6 +1068,59 @@ static void mtk_tx_set_dma_desc(struct n
-+ 	WRITE_ONCE(desc->txd4, data);
++@@ -1132,6 +1147,46 @@ static void dsa_tree_teardown(struct dsa
++ 	dst->setup = false;
 + }
 + 
-++static void mtk_tx_set_dma_desc_v2(struct net_device *dev, void *txd,
-++				   struct mtk_tx_dma_desc_info *info)
+++static int dsa_tree_bind_tag_proto(struct dsa_switch_tree *dst,
+++				   const struct dsa_device_ops *tag_ops)
 ++{
-++	struct mtk_mac *mac = netdev_priv(dev);
-++	struct mtk_tx_dma_v2 *desc = txd;
-++	struct mtk_eth *eth = mac->hw;
-++	u32 data;
+++	const struct dsa_device_ops *old_tag_ops = dst->tag_ops;
+++	struct dsa_notifier_tag_proto_info info;
+++	int err;
 ++
-++	WRITE_ONCE(desc->txd1, info->addr);
+++	dst->tag_ops = tag_ops;
 ++
-++	data = TX_DMA_PLEN0(info->size);
-++	if (info->last)
-++		data |= TX_DMA_LS0;
-++	WRITE_ONCE(desc->txd3, data);
+++	/* Notify the new tagger about the connection to this tree */
+++	if (tag_ops->connect) {
+++		err = tag_ops->connect(dst);
+++		if (err)
+++			goto out_revert;
+++	}
 ++
-++	if (!info->qid && mac->id)
-++		info->qid = MTK_QDMA_GMAC2_QID;
+++	/* Notify the switches from this tree about the connection
+++	 * to the new tagger
+++	 */
+++	info.tag_ops = tag_ops;
+++	err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_CONNECT, &info);
+++	if (err && err != -EOPNOTSUPP)
+++		goto out_disconnect;
 ++
-++	data = (mac->id + 1) << TX_DMA_FPORT_SHIFT_V2; /* forward port */
-++	data |= TX_DMA_SWC_V2 | QID_BITS_V2(info->qid);
-++	WRITE_ONCE(desc->txd4, data);
+++	/* Notify the old tagger about the disconnection from this tree */
+++	if (old_tag_ops->disconnect)
+++		old_tag_ops->disconnect(dst);
 ++
-++	data = 0;
-++	if (info->first) {
-++		if (info->gso)
-++			data |= TX_DMA_TSO_V2;
-++		/* tx checksum offload */
-++		if (info->csum)
-++			data |= TX_DMA_CHKSUM_V2;
-++	}
-++	WRITE_ONCE(desc->txd5, data);
-++
-++	data = 0;
-++	if (info->first && info->vlan)
-++		data |= TX_DMA_INS_VLAN_V2 | info->vlan_tci;
-++	WRITE_ONCE(desc->txd6, data);
-++
-++	WRITE_ONCE(desc->txd7, 0);
-++	WRITE_ONCE(desc->txd8, 0);
-++}
+++	return 0;
 ++
-++static void mtk_tx_set_dma_desc(struct net_device *dev, void *txd,
-++				struct mtk_tx_dma_desc_info *info)
-++{
-++	struct mtk_mac *mac = netdev_priv(dev);
-++	struct mtk_eth *eth = mac->hw;
+++out_disconnect:
+++	/* Revert the new tagger's connection to this tree */
+++	if (tag_ops->disconnect)
+++		tag_ops->disconnect(dst);
+++out_revert:
+++	dst->tag_ops = old_tag_ops;
 ++
-++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
-++		mtk_tx_set_dma_desc_v2(dev, txd, info);
-++	else
-++		mtk_tx_set_dma_desc_v1(dev, txd, info);
+++	return err;
 ++}
 ++
-+ static int mtk_tx_map(struct sk_buff *skb, struct net_device *dev,
-+ 		      int tx_num, struct mtk_tx_ring *ring, bool gso)
-+ {
-+@@ -1064,6 +1129,7 @@ static int mtk_tx_map(struct sk_buff *sk
-+ 		.gso = gso,
-+ 		.csum = skb->ip_summed == CHECKSUM_PARTIAL,
-+ 		.vlan = skb_vlan_tag_present(skb),
-++		.qid = skb->mark & MTK_QDMA_TX_MASK,
-+ 		.vlan_tci = skb_vlan_tag_get(skb),
-+ 		.first = true,
-+ 		.last = !skb_is_nonlinear(skb),
-+@@ -1123,7 +1189,9 @@ static int mtk_tx_map(struct sk_buff *sk
-+ 			}
++ /* Since the dsa/tagging sysfs device attribute is per master, the assumption
++  * is that all DSA switches within a tree share the same tagger, otherwise
++  * they would have formed disjoint trees (different "dsa,member" values).
++@@ -1164,12 +1219,15 @@ int dsa_tree_change_tag_proto(struct dsa
++ 			goto out_unlock;
++ 	}
 + 
-+ 			memset(&txd_info, 0, sizeof(struct mtk_tx_dma_desc_info));
-+-			txd_info.size = min(frag_size, MTK_TX_DMA_BUF_LEN);
-++			txd_info.size = min_t(unsigned int, frag_size,
-++					      soc->txrx.dma_max_len);
-++			txd_info.qid = skb->mark & MTK_QDMA_TX_MASK;
-+ 			txd_info.last = i == skb_shinfo(skb)->nr_frags - 1 &&
-+ 					!(frag_size - txd_info.size);
-+ 			txd_info.addr = skb_frag_dma_map(eth->dma_dev, frag,
-+@@ -1204,17 +1272,16 @@ err_dma:
-+ 	return -ENOMEM;
-+ }
+++	/* Notify the tag protocol change */
++ 	info.tag_ops = tag_ops;
++ 	err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO, &info);
++ 	if (err)
++-		goto out_unwind_tagger;
+++		return err;
 + 
-+-static inline int mtk_cal_txd_req(struct sk_buff *skb)
-++static int mtk_cal_txd_req(struct mtk_eth *eth, struct sk_buff *skb)
-+ {
-+-	int i, nfrags;
-++	int i, nfrags = 1;
-+ 	skb_frag_t *frag;
++-	dst->tag_ops = tag_ops;
+++	err = dsa_tree_bind_tag_proto(dst, tag_ops);
+++	if (err)
+++		goto out_unwind_tagger;
 + 
-+-	nfrags = 1;
-+ 	if (skb_is_gso(skb)) {
-+ 		for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
-+ 			frag = &skb_shinfo(skb)->frags[i];
-+ 			nfrags += DIV_ROUND_UP(skb_frag_size(frag),
-+-						MTK_TX_DMA_BUF_LEN);
-++					       eth->soc->txrx.dma_max_len);
-+ 		}
++ 	rtnl_unlock();
++ 
++@@ -1257,6 +1315,7 @@ static int dsa_port_parse_cpu(struct dsa
++ 	struct dsa_switch *ds = dp->ds;
++ 	struct dsa_switch_tree *dst = ds->dst;
++ 	enum dsa_tag_protocol default_proto;
+++	int err;
++ 
++ 	/* Find out which protocol the switch would prefer. */
++ 	default_proto = dsa_get_tag_protocol(dp, master);
++@@ -1311,6 +1370,12 @@ static int dsa_port_parse_cpu(struct dsa
++ 		 */
++ 		dsa_tag_driver_put(tag_ops);
 + 	} else {
-+ 		nfrags += skb_shinfo(skb)->nr_frags;
-+@@ -1266,7 +1333,7 @@ static netdev_tx_t mtk_start_xmit(struct
-+ 	if (unlikely(test_bit(MTK_RESETTING, &eth->state)))
-+ 		goto drop;
+++		if (tag_ops->connect) {
+++			err = tag_ops->connect(dst);
+++			if (err)
+++				return err;
+++		}
+++
++ 		dst->tag_ops = tag_ops;
++ 	}
 + 
-+-	tx_num = mtk_cal_txd_req(skb);
-++	tx_num = mtk_cal_txd_req(eth, skb);
-+ 	if (unlikely(atomic_read(&ring->free_count) <= tx_num)) {
-+ 		netif_stop_queue(dev);
-+ 		netif_err(eth, tx_queued, dev,
-+@@ -1358,7 +1425,7 @@ static int mtk_poll_rx(struct napi_struc
-+ 	int idx;
-+ 	struct sk_buff *skb;
-+ 	u8 *data, *new_data;
-+-	struct mtk_rx_dma *rxd, trxd;
-++	struct mtk_rx_dma_v2 *rxd, trxd;
-+ 	int done = 0, bytes = 0;
++--- a/net/dsa/dsa_priv.h
+++++ b/net/dsa/dsa_priv.h
++@@ -37,6 +37,7 @@ enum {
++ 	DSA_NOTIFIER_VLAN_DEL,
++ 	DSA_NOTIFIER_MTU,
++ 	DSA_NOTIFIER_TAG_PROTO,
+++	DSA_NOTIFIER_TAG_PROTO_CONNECT,
++ 	DSA_NOTIFIER_MRP_ADD,
++ 	DSA_NOTIFIER_MRP_DEL,
++ 	DSA_NOTIFIER_MRP_ADD_RING_ROLE,
++--- a/net/dsa/switch.c
+++++ b/net/dsa/switch.c
++@@ -616,6 +616,17 @@ static int dsa_switch_change_tag_proto(s
++ 	return 0;
++ }
 + 
-+ 	while (done < budget) {
-+@@ -1366,7 +1433,7 @@ static int mtk_poll_rx(struct napi_struc
-+ 		unsigned int pktlen;
-+ 		dma_addr_t dma_addr;
-+ 		u32 hash, reason;
-+-		int mac;
-++		int mac = 0;
+++static int dsa_switch_connect_tag_proto(struct dsa_switch *ds,
+++					struct dsa_notifier_tag_proto_info *info)
+++{
+++	const struct dsa_device_ops *tag_ops = info->tag_ops;
+++
+++	if (!ds->ops->connect_tag_protocol)
+++		return -EOPNOTSUPP;
+++
+++	return ds->ops->connect_tag_protocol(ds, tag_ops->proto);
+++}
+++
++ static int dsa_switch_mrp_add(struct dsa_switch *ds,
++ 			      struct dsa_notifier_mrp_info *info)
++ {
++@@ -735,6 +746,9 @@ static int dsa_switch_event(struct notif
++ 	case DSA_NOTIFIER_TAG_PROTO:
++ 		err = dsa_switch_change_tag_proto(ds, info);
++ 		break;
+++	case DSA_NOTIFIER_TAG_PROTO_CONNECT:
+++		err = dsa_switch_connect_tag_proto(ds, info);
+++		break;
++ 	case DSA_NOTIFIER_MRP_ADD:
++ 		err = dsa_switch_mrp_add(ds, info);
++ 		break;
+diff --git a/target/linux/generic/backport-5.15/701-v5.17-dsa-make-tagging-protocols-connect-to-individual-switches.patch b/target/linux/generic/backport-5.15/701-v5.17-dsa-make-tagging-protocols-connect-to-individual-switches.patch
+new file mode 100644
+index 0000000000..0c50ae6fb9
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/701-v5.17-dsa-make-tagging-protocols-connect-to-individual-switches.patch
+@@ -0,0 +1,274 @@
++From 7f2973149c22e7a6fee4c0c9fa6b8e4108e9c208 Mon Sep 17 00:00:00 2001
++From: Vladimir Oltean <vladimir.oltean@nxp.com>
++Date: Tue, 14 Dec 2021 03:45:36 +0200
++Subject: net: dsa: make tagging protocols connect to individual switches from
++ a tree
++
++On the NXP Bluebox 3 board which uses a multi-switch setup with sja1105,
++the mechanism through which the tagger connects to the switch tree is
++broken, due to improper DSA code design. At the time when tag_ops->connect()
++is called in dsa_port_parse_cpu(), DSA hasn't finished "touching" all
++the ports, so it doesn't know how large the tree is and how many ports
++it has. It has just seen the first CPU port by this time. As a result,
++this function will call the tagger's ->connect method too early, and the
++tagger will connect only to the first switch from the tree.
++
++This could be perhaps addressed a bit more simply by just moving the
++tag_ops->connect(dst) call a bit later (for example in dsa_tree_setup),
++but there is already a design inconsistency at present: on the switch
++side, the notification is on a per-switch basis, but on the tagger side,
++it is on a per-tree basis. Furthermore, the persistent storage itself is
++per switch (ds->tagger_data). And the tagger connect and disconnect
++procedures (at least the ones that exist currently) could see a fair bit
++of simplification if they didn't have to iterate through the switches of
++a tree.
++
++To fix the issue, this change transforms tag_ops->connect(dst) into
++tag_ops->connect(ds) and moves it somewhere where we already iterate
++over all switches of a tree. That is in dsa_switch_setup_tag_protocol(),
++which is a good placement because we already have there the connection
++call to the switch side of things.
++
++As for the dsa_tree_bind_tag_proto() method (called from the code path
++that changes the tag protocol), things are a bit more complicated
++because we receive the tree as argument, yet when we unwind on errors,
++it would be nice to not call tag_ops->disconnect(ds) where we didn't
++previously call tag_ops->connect(ds). We didn't have this problem before
++because the tag_ops connection operations passed the entire dst before,
++and this is more fine grained now. To solve the error rewind case using
++the new API, we have to create yet one more cross-chip notifier for
++disconnection, and stay connected with the old tag protocol to all the
++switches in the tree until we've succeeded to connect with the new one
++as well. So if something fails half way, the whole tree is still
++connected to the old tagger. But there may still be leaks if the tagger
++fails to connect to the 2nd out of 3 switches in a tree: somebody needs
++to tell the tagger to disconnect from the first switch. Nothing comes
++for free, and this was previously handled privately by the tagging
++protocol driver before, but now we need to emit a disconnect cross-chip
++notifier for that, because DSA has to take care of the unwind path. We
++assume that the tagging protocol has connected to a switch if it has set
++ds->tagger_data to something, otherwise we avoid calling its
++disconnection method in the error rewind path.
++
++The rest of the changes are in the tagging protocol drivers, and have to
++do with the replacement of dst with ds. The iteration is removed and the
++error unwind path is simplified, as mentioned above.
++
++Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ include/net/dsa.h          |  5 ++--
++ net/dsa/dsa2.c             | 44 +++++++++++++-----------------
++ net/dsa/dsa_priv.h         |  1 +
++ net/dsa/switch.c           | 52 ++++++++++++++++++++++++++++++++---
++ net/dsa/tag_ocelot_8021q.c | 53 +++++++++++-------------------------
++ net/dsa/tag_sja1105.c      | 67 ++++++++++++++++------------------------------
++ 6 files changed, 109 insertions(+), 113 deletions(-)
++
++--- a/include/net/dsa.h
+++++ b/include/net/dsa.h
++@@ -80,15 +80,14 @@ enum dsa_tag_protocol {
++ };
 + 
-+ 		ring = mtk_get_rx_ring(eth);
-+ 		if (unlikely(!ring))
-+@@ -1376,16 +1443,15 @@ static int mtk_poll_rx(struct napi_struc
-+ 		rxd = (void *)ring->dma + idx * eth->soc->txrx.rxd_size;
-+ 		data = ring->data[idx];
++ struct dsa_switch;
++-struct dsa_switch_tree;
 + 
-+-		if (!mtk_rx_get_desc(&trxd, rxd))
-++		if (!mtk_rx_get_desc(eth, &trxd, rxd))
-+ 			break;
++ struct dsa_device_ops {
++ 	struct sk_buff *(*xmit)(struct sk_buff *skb, struct net_device *dev);
++ 	struct sk_buff *(*rcv)(struct sk_buff *skb, struct net_device *dev);
++ 	void (*flow_dissect)(const struct sk_buff *skb, __be16 *proto,
++ 			     int *offset);
++-	int (*connect)(struct dsa_switch_tree *dst);
++-	void (*disconnect)(struct dsa_switch_tree *dst);
+++	int (*connect)(struct dsa_switch *ds);
+++	void (*disconnect)(struct dsa_switch *ds);
++ 	unsigned int needed_headroom;
++ 	unsigned int needed_tailroom;
++ 	const char *name;
++--- a/net/dsa/dsa2.c
+++++ b/net/dsa/dsa2.c
++@@ -230,12 +230,8 @@ static struct dsa_switch_tree *dsa_tree_
 + 
-+ 		/* find out which mac the packet come from. values start at 1 */
-+-		if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628) ||
-+-		    (trxd.rxd4 & RX_DMA_SPECIAL_TAG))
-+-			mac = 0;
-+-		else
-+-			mac = ((trxd.rxd4 >> RX_DMA_FPORT_SHIFT) &
-+-			       RX_DMA_FPORT_MASK) - 1;
-++		if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
-++			mac = RX_DMA_GET_SPORT_V2(trxd.rxd5) - 1;
-++		else if (!MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628) &&
-++			 !(trxd.rxd4 & RX_DMA_SPECIAL_TAG))
-++			mac = RX_DMA_GET_SPORT(trxd.rxd4) - 1;
++ static void dsa_tree_free(struct dsa_switch_tree *dst)
++ {
++-	if (dst->tag_ops) {
++-		if (dst->tag_ops->disconnect)
++-			dst->tag_ops->disconnect(dst);
++-
+++	if (dst->tag_ops)
++ 		dsa_tag_driver_put(dst->tag_ops);
++-	}
++ 	list_del(&dst->list);
++ 	kfree(dst);
++ }
++@@ -826,17 +822,29 @@ static int dsa_switch_setup_tag_protocol
++ 	}
 + 
-+ 		if (unlikely(mac < 0 || mac >= MTK_MAC_COUNT ||
-+ 			     !eth->netdev[mac]))
-+@@ -1431,7 +1497,7 @@ static int mtk_poll_rx(struct napi_struc
-+ 		pktlen = RX_DMA_GET_PLEN0(trxd.rxd2);
-+ 		skb->dev = netdev;
-+ 		skb_put(skb, pktlen);
-+-		if (trxd.rxd4 & eth->rx_dma_l4_valid)
-++		if (trxd.rxd4 & eth->soc->txrx.rx_dma_l4_valid)
-+ 			skb->ip_summed = CHECKSUM_UNNECESSARY;
-+ 		else
-+ 			skb_checksum_none_assert(skb);
-+@@ -1449,10 +1515,25 @@ static int mtk_poll_rx(struct napi_struc
-+ 			mtk_ppe_check_skb(eth->ppe, skb,
-+ 					  trxd.rxd4 & MTK_RXD4_FOE_ENTRY);
++ connect:
+++	if (tag_ops->connect) {
+++		err = tag_ops->connect(ds);
+++		if (err)
+++			return err;
+++	}
+++
++ 	if (ds->ops->connect_tag_protocol) {
++ 		err = ds->ops->connect_tag_protocol(ds, tag_ops->proto);
++ 		if (err) {
++ 			dev_err(ds->dev,
++ 				"Unable to connect to tag protocol \"%s\": %pe\n",
++ 				tag_ops->name, ERR_PTR(err));
++-			return err;
+++			goto disconnect;
++ 		}
++ 	}
 + 
-+-		if (netdev->features & NETIF_F_HW_VLAN_CTAG_RX &&
-+-		    (trxd.rxd2 & RX_DMA_VTAG))
-+-			__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q),
-+-					       RX_DMA_VID(trxd.rxd3));
-++		if (netdev->features & NETIF_F_HW_VLAN_CTAG_RX) {
-++			if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
-++				if (trxd.rxd3 & RX_DMA_VTAG_V2)
-++					__vlan_hwaccel_put_tag(skb,
-++						htons(RX_DMA_VPID(trxd.rxd4)),
-++						RX_DMA_VID(trxd.rxd4));
-++			} else if (trxd.rxd2 & RX_DMA_VTAG) {
-++				__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q),
-++						       RX_DMA_VID(trxd.rxd3));
-++			}
++ 	return 0;
 ++
-++			/* If the device is attached to a dsa switch, the special
-++			 * tag inserted in VLAN field by hw switch can * be offloaded
-++			 * by RX HW VLAN offload. Clear vlan info.
-++			 */
-++			if (netdev_uses_dsa(netdev))
-++				__vlan_hwaccel_clear_tag(skb);
-++		}
+++disconnect:
+++	if (tag_ops->disconnect)
+++		tag_ops->disconnect(ds);
 ++
-+ 		skb_record_rx_queue(skb, 0);
-+ 		napi_gro_receive(napi, skb);
-+ 
-+@@ -1464,7 +1545,7 @@ release_desc:
-+ 		if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628))
-+ 			rxd->rxd2 = RX_DMA_LSO;
-+ 		else
-+-			rxd->rxd2 = RX_DMA_PLEN0(ring->buf_size);
-++			rxd->rxd2 = RX_DMA_PREP_PLEN0(ring->buf_size);
+++	return err;
++ }
 + 
-+ 		ring->calc_idx = idx;
++ static int dsa_switch_setup(struct dsa_switch *ds)
++@@ -1156,13 +1164,6 @@ static int dsa_tree_bind_tag_proto(struc
 + 
-+@@ -1666,7 +1747,8 @@ static int mtk_napi_rx(struct napi_struc
-+ 	do {
-+ 		int rx_done;
++ 	dst->tag_ops = tag_ops;
 + 
-+-		mtk_w32(eth, MTK_RX_DONE_INT, reg_map->pdma.irq_status);
-++		mtk_w32(eth, eth->soc->txrx.rx_irq_done_mask,
-++			reg_map->pdma.irq_status);
-+ 		rx_done = mtk_poll_rx(napi, budget - rx_done_total, eth);
-+ 		rx_done_total += rx_done;
++-	/* Notify the new tagger about the connection to this tree */
++-	if (tag_ops->connect) {
++-		err = tag_ops->connect(dst);
++-		if (err)
++-			goto out_revert;
++-	}
++-
++ 	/* Notify the switches from this tree about the connection
++ 	 * to the new tagger
++ 	 */
++@@ -1172,16 +1173,14 @@ static int dsa_tree_bind_tag_proto(struc
++ 		goto out_disconnect;
 + 
-+@@ -1680,10 +1762,11 @@ static int mtk_napi_rx(struct napi_struc
-+ 		if (rx_done_total == budget)
-+ 			return budget;
++ 	/* Notify the old tagger about the disconnection from this tree */
++-	if (old_tag_ops->disconnect)
++-		old_tag_ops->disconnect(dst);
+++	info.tag_ops = old_tag_ops;
+++	dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_DISCONNECT, &info);
 + 
-+-	} while (mtk_r32(eth, reg_map->pdma.irq_status) & MTK_RX_DONE_INT);
-++	} while (mtk_r32(eth, reg_map->pdma.irq_status) &
-++		 eth->soc->txrx.rx_irq_done_mask);
++ 	return 0;
 + 
-+ 	if (napi_complete_done(napi, rx_done_total))
-+-		mtk_rx_irq_enable(eth, MTK_RX_DONE_INT);
-++		mtk_rx_irq_enable(eth, eth->soc->txrx.rx_irq_done_mask);
++ out_disconnect:
++-	/* Revert the new tagger's connection to this tree */
++-	if (tag_ops->disconnect)
++-		tag_ops->disconnect(dst);
++-out_revert:
+++	info.tag_ops = tag_ops;
+++	dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_DISCONNECT, &info);
++ 	dst->tag_ops = old_tag_ops;
 + 
-+ 	return rx_done_total;
-+ }
-+@@ -1693,7 +1776,7 @@ static int mtk_tx_alloc(struct mtk_eth *
-+ 	const struct mtk_soc_data *soc = eth->soc;
-+ 	struct mtk_tx_ring *ring = &eth->tx_ring;
-+ 	int i, sz = soc->txrx.txd_size;
-+-	struct mtk_tx_dma *txd;
-++	struct mtk_tx_dma_v2 *txd;
++ 	return err;
++@@ -1315,7 +1314,6 @@ static int dsa_port_parse_cpu(struct dsa
++ 	struct dsa_switch *ds = dp->ds;
++ 	struct dsa_switch_tree *dst = ds->dst;
++ 	enum dsa_tag_protocol default_proto;
++-	int err;
 + 
-+ 	ring->buf = kcalloc(MTK_DMA_SIZE, sizeof(*ring->buf),
-+ 			       GFP_KERNEL);
-+@@ -1713,13 +1796,19 @@ static int mtk_tx_alloc(struct mtk_eth *
-+ 		txd->txd2 = next_ptr;
-+ 		txd->txd3 = TX_DMA_LS0 | TX_DMA_OWNER_CPU;
-+ 		txd->txd4 = 0;
-++		if (MTK_HAS_CAPS(soc->caps, MTK_NETSYS_V2)) {
-++			txd->txd5 = 0;
-++			txd->txd6 = 0;
-++			txd->txd7 = 0;
-++			txd->txd8 = 0;
-++		}
++ 	/* Find out which protocol the switch would prefer. */
++ 	default_proto = dsa_get_tag_protocol(dp, master);
++@@ -1370,12 +1368,6 @@ static int dsa_port_parse_cpu(struct dsa
++ 		 */
++ 		dsa_tag_driver_put(tag_ops);
++ 	} else {
++-		if (tag_ops->connect) {
++-			err = tag_ops->connect(dst);
++-			if (err)
++-				return err;
++-		}
++-
++ 		dst->tag_ops = tag_ops;
 + 	}
 + 
-+ 	/* On MT7688 (PDMA only) this driver uses the ring->dma structs
-+ 	 * only as the framework. The real HW descriptors are the PDMA
-+ 	 * descriptors in ring->dma_pdma.
-+ 	 */
-+-	if (!MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
-++	if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
-+ 		ring->dma_pdma = dma_alloc_coherent(eth->dma_dev, MTK_DMA_SIZE * sz,
-+ 						    &ring->phys_pdma, GFP_KERNEL);
-+ 		if (!ring->dma_pdma)
-+@@ -1799,13 +1888,11 @@ static int mtk_rx_alloc(struct mtk_eth *
-+ 	struct mtk_rx_ring *ring;
-+ 	int rx_data_len, rx_dma_size;
-+ 	int i;
-+-	u32 offset = 0;
++--- a/net/dsa/dsa_priv.h
+++++ b/net/dsa/dsa_priv.h
++@@ -38,6 +38,7 @@ enum {
++ 	DSA_NOTIFIER_MTU,
++ 	DSA_NOTIFIER_TAG_PROTO,
++ 	DSA_NOTIFIER_TAG_PROTO_CONNECT,
+++	DSA_NOTIFIER_TAG_PROTO_DISCONNECT,
++ 	DSA_NOTIFIER_MRP_ADD,
++ 	DSA_NOTIFIER_MRP_DEL,
++ 	DSA_NOTIFIER_MRP_ADD_RING_ROLE,
++--- a/net/dsa/switch.c
+++++ b/net/dsa/switch.c
++@@ -616,15 +616,58 @@ static int dsa_switch_change_tag_proto(s
++ 	return 0;
++ }
 + 
-+ 	if (rx_flag == MTK_RX_FLAGS_QDMA) {
-+ 		if (ring_no)
-+ 			return -EINVAL;
-+ 		ring = &eth->rx_ring_qdma;
-+-		offset = 0x1000;
-+ 	} else {
-+ 		ring = &eth->rx_ring[ring_no];
-+ 	}
-+@@ -1841,7 +1928,7 @@ static int mtk_rx_alloc(struct mtk_eth *
-+ 		return -ENOMEM;
++-static int dsa_switch_connect_tag_proto(struct dsa_switch *ds,
++-					struct dsa_notifier_tag_proto_info *info)
+++/* We use the same cross-chip notifiers to inform both the tagger side, as well
+++ * as the switch side, of connection and disconnection events.
+++ * Since ds->tagger_data is owned by the tagger, it isn't a hard error if the
+++ * switch side doesn't support connecting to this tagger, and therefore, the
+++ * fact that we don't disconnect the tagger side doesn't constitute a memory
+++ * leak: the tagger will still operate with persistent per-switch memory, just
+++ * with the switch side unconnected to it. What does constitute a hard error is
+++ * when the switch side supports connecting but fails.
+++ */
+++static int
+++dsa_switch_connect_tag_proto(struct dsa_switch *ds,
+++			     struct dsa_notifier_tag_proto_info *info)
++ {
++ 	const struct dsa_device_ops *tag_ops = info->tag_ops;
+++	int err;
+++
+++	/* Notify the new tagger about the connection to this switch */
+++	if (tag_ops->connect) {
+++		err = tag_ops->connect(ds);
+++		if (err)
+++			return err;
+++	}
 + 
-+ 	for (i = 0; i < rx_dma_size; i++) {
-+-		struct mtk_rx_dma *rxd;
-++		struct mtk_rx_dma_v2 *rxd;
++ 	if (!ds->ops->connect_tag_protocol)
++ 		return -EOPNOTSUPP;
 + 
-+ 		dma_addr_t dma_addr = dma_map_single(eth->dma_dev,
-+ 				ring->data[i] + NET_SKB_PAD + eth->ip_align,
-+@@ -1856,26 +1943,47 @@ static int mtk_rx_alloc(struct mtk_eth *
-+ 		if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628))
-+ 			rxd->rxd2 = RX_DMA_LSO;
-+ 		else
-+-			rxd->rxd2 = RX_DMA_PLEN0(ring->buf_size);
-++			rxd->rxd2 = RX_DMA_PREP_PLEN0(ring->buf_size);
-+ 
-+ 		rxd->rxd3 = 0;
-+ 		rxd->rxd4 = 0;
-++		if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
-++			rxd->rxd5 = 0;
-++			rxd->rxd6 = 0;
-++			rxd->rxd7 = 0;
-++			rxd->rxd8 = 0;
-++		}
-+ 	}
-+ 	ring->dma_size = rx_dma_size;
-+ 	ring->calc_idx_update = false;
-+ 	ring->calc_idx = rx_dma_size - 1;
-+-	ring->crx_idx_reg = reg_map->pdma.pcrx_ptr + ring_no * MTK_QRX_OFFSET;
-++	if (rx_flag == MTK_RX_FLAGS_QDMA)
-++		ring->crx_idx_reg = reg_map->qdma.qcrx_ptr +
-++				    ring_no * MTK_QRX_OFFSET;
-++	else
-++		ring->crx_idx_reg = reg_map->pdma.pcrx_ptr +
-++				    ring_no * MTK_QRX_OFFSET;
-+ 	/* make sure that all changes to the dma ring are flushed before we
-+ 	 * continue
-+ 	 */
-+ 	wmb();
-+ 
-+-	mtk_w32(eth, ring->phys,
-+-		reg_map->pdma.rx_ptr + ring_no * MTK_QRX_OFFSET + offset);
-+-	mtk_w32(eth, rx_dma_size,
-+-		reg_map->pdma.rx_cnt_cfg + ring_no * MTK_QRX_OFFSET + offset);
-+-	mtk_w32(eth, ring->calc_idx, ring->crx_idx_reg + offset);
-+-	mtk_w32(eth, MTK_PST_DRX_IDX_CFG(ring_no), reg_map->pdma.rst_idx + offset);
-++	if (rx_flag == MTK_RX_FLAGS_QDMA) {
-++		mtk_w32(eth, ring->phys,
-++			reg_map->qdma.rx_ptr + ring_no * MTK_QRX_OFFSET);
-++		mtk_w32(eth, rx_dma_size,
-++			reg_map->qdma.rx_cnt_cfg + ring_no * MTK_QRX_OFFSET);
-++		mtk_w32(eth, MTK_PST_DRX_IDX_CFG(ring_no),
-++			reg_map->qdma.rst_idx);
-++	} else {
-++		mtk_w32(eth, ring->phys,
-++			reg_map->pdma.rx_ptr + ring_no * MTK_QRX_OFFSET);
-++		mtk_w32(eth, rx_dma_size,
-++			reg_map->pdma.rx_cnt_cfg + ring_no * MTK_QRX_OFFSET);
-++		mtk_w32(eth, MTK_PST_DRX_IDX_CFG(ring_no),
-++			reg_map->pdma.rst_idx);
++-	return ds->ops->connect_tag_protocol(ds, tag_ops->proto);
+++	/* Notify the switch about the connection to the new tagger */
+++	err = ds->ops->connect_tag_protocol(ds, tag_ops->proto);
+++	if (err) {
+++		/* Revert the new tagger's connection to this tree */
+++		if (tag_ops->disconnect)
+++			tag_ops->disconnect(ds);
+++		return err;
 ++	}
-++	mtk_w32(eth, ring->calc_idx, ring->crx_idx_reg);
-+ 
-+ 	return 0;
+++
+++	return 0;
+++}
+++
+++static int
+++dsa_switch_disconnect_tag_proto(struct dsa_switch *ds,
+++				struct dsa_notifier_tag_proto_info *info)
+++{
+++	const struct dsa_device_ops *tag_ops = info->tag_ops;
+++
+++	/* Notify the tagger about the disconnection from this switch */
+++	if (tag_ops->disconnect && ds->tagger_data)
+++		tag_ops->disconnect(ds);
+++
+++	/* No need to notify the switch, since it shouldn't have any
+++	 * resources to tear down
+++	 */
+++	return 0;
 + }
-+@@ -2297,7 +2405,7 @@ static irqreturn_t mtk_handle_irq_rx(int
-+ 	eth->rx_events++;
-+ 	if (likely(napi_schedule_prep(&eth->rx_napi))) {
-+ 		__napi_schedule(&eth->rx_napi);
-+-		mtk_rx_irq_disable(eth, MTK_RX_DONE_INT);
-++		mtk_rx_irq_disable(eth, eth->soc->txrx.rx_irq_done_mask);
-+ 	}
 + 
-+ 	return IRQ_HANDLED;
-+@@ -2321,8 +2429,10 @@ static irqreturn_t mtk_handle_irq(int ir
-+ 	struct mtk_eth *eth = _eth;
-+ 	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
++ static int dsa_switch_mrp_add(struct dsa_switch *ds,
++@@ -749,6 +792,9 @@ static int dsa_switch_event(struct notif
++ 	case DSA_NOTIFIER_TAG_PROTO_CONNECT:
++ 		err = dsa_switch_connect_tag_proto(ds, info);
++ 		break;
+++	case DSA_NOTIFIER_TAG_PROTO_DISCONNECT:
+++		err = dsa_switch_disconnect_tag_proto(ds, info);
+++		break;
++ 	case DSA_NOTIFIER_MRP_ADD:
++ 		err = dsa_switch_mrp_add(ds, info);
++ 		break;
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-00-net-ethernet-mtk_eth_soc-add-support-for-coherent-DM.patch b/target/linux/generic/backport-5.15/702-v5.19-00-net-ethernet-mtk_eth_soc-add-support-for-coherent-DM.patch
+new file mode 100644
+index 0000000000..afd78fdda2
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-00-net-ethernet-mtk_eth_soc-add-support-for-coherent-DM.patch
+@@ -0,0 +1,327 @@
++From: Felix Fietkau <nbd@nbd.name>
++Date: Sat, 5 Feb 2022 17:59:07 +0100
++Subject: [PATCH] net: ethernet: mtk_eth_soc: add support for coherent
++ DMA
++
++It improves performance by eliminating the need for a cache flush on rx and tx
++In preparation for supporting WED (Wireless Ethernet Dispatch), also add a
++function for disabling coherent DMA at runtime.
++
++Signed-off-by: Felix Fietkau <nbd@nbd.name>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -9,6 +9,7 @@
++ #include <linux/of_device.h>
++ #include <linux/of_mdio.h>
++ #include <linux/of_net.h>
+++#include <linux/of_address.h>
++ #include <linux/mfd/syscon.h>
++ #include <linux/regmap.h>
++ #include <linux/clk.h>
++@@ -839,7 +840,7 @@ static int mtk_init_fq_dma(struct mtk_et
++ 	dma_addr_t dma_addr;
++ 	int i;
 + 
-+-	if (mtk_r32(eth, reg_map->pdma.irq_mask) & MTK_RX_DONE_INT) {
-+-		if (mtk_r32(eth, reg_map->pdma.irq_status) & MTK_RX_DONE_INT)
-++	if (mtk_r32(eth, reg_map->pdma.irq_mask) &
-++	    eth->soc->txrx.rx_irq_done_mask) {
-++		if (mtk_r32(eth, reg_map->pdma.irq_status) &
-++		    eth->soc->txrx.rx_irq_done_mask)
-+ 			mtk_handle_irq_rx(irq, _eth);
-+ 	}
-+ 	if (mtk_r32(eth, reg_map->tx_irq_mask) & MTK_TX_DONE_INT) {
-+@@ -2340,16 +2450,16 @@ static void mtk_poll_controller(struct n
-+ 	struct mtk_eth *eth = mac->hw;
++-	eth->scratch_ring = dma_alloc_coherent(eth->dev,
+++	eth->scratch_ring = dma_alloc_coherent(eth->dma_dev,
++ 					       cnt * sizeof(struct mtk_tx_dma),
++ 					       &eth->phy_scratch_ring,
++ 					       GFP_ATOMIC);
++@@ -851,10 +852,10 @@ static int mtk_init_fq_dma(struct mtk_et
++ 	if (unlikely(!eth->scratch_head))
++ 		return -ENOMEM;
 + 
-+ 	mtk_tx_irq_disable(eth, MTK_TX_DONE_INT);
-+-	mtk_rx_irq_disable(eth, MTK_RX_DONE_INT);
-++	mtk_rx_irq_disable(eth, eth->soc->txrx.rx_irq_done_mask);
-+ 	mtk_handle_irq_rx(eth->irq[2], dev);
-+ 	mtk_tx_irq_enable(eth, MTK_TX_DONE_INT);
-+-	mtk_rx_irq_enable(eth, MTK_RX_DONE_INT);
-++	mtk_rx_irq_enable(eth, eth->soc->txrx.rx_irq_done_mask);
-+ }
-+ #endif
++-	dma_addr = dma_map_single(eth->dev,
+++	dma_addr = dma_map_single(eth->dma_dev,
++ 				  eth->scratch_head, cnt * MTK_QDMA_PAGE_SIZE,
++ 				  DMA_FROM_DEVICE);
++-	if (unlikely(dma_mapping_error(eth->dev, dma_addr)))
+++	if (unlikely(dma_mapping_error(eth->dma_dev, dma_addr)))
++ 		return -ENOMEM;
 + 
-+ static int mtk_start_dma(struct mtk_eth *eth)
++ 	phy_ring_tail = eth->phy_scratch_ring +
++@@ -908,26 +909,26 @@ static void mtk_tx_unmap(struct mtk_eth
 + {
-+-	u32 rx_2b_offset = (NET_IP_ALIGN == 2) ? MTK_RX_2B_OFFSET : 0;
-++	u32 val, rx_2b_offset = (NET_IP_ALIGN == 2) ? MTK_RX_2B_OFFSET : 0;
-+ 	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
-+ 	int err;
++ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
++ 		if (tx_buf->flags & MTK_TX_FLAGS_SINGLE0) {
++-			dma_unmap_single(eth->dev,
+++			dma_unmap_single(eth->dma_dev,
++ 					 dma_unmap_addr(tx_buf, dma_addr0),
++ 					 dma_unmap_len(tx_buf, dma_len0),
++ 					 DMA_TO_DEVICE);
++ 		} else if (tx_buf->flags & MTK_TX_FLAGS_PAGE0) {
++-			dma_unmap_page(eth->dev,
+++			dma_unmap_page(eth->dma_dev,
++ 				       dma_unmap_addr(tx_buf, dma_addr0),
++ 				       dma_unmap_len(tx_buf, dma_len0),
++ 				       DMA_TO_DEVICE);
++ 		}
++ 	} else {
++ 		if (dma_unmap_len(tx_buf, dma_len0)) {
++-			dma_unmap_page(eth->dev,
+++			dma_unmap_page(eth->dma_dev,
++ 				       dma_unmap_addr(tx_buf, dma_addr0),
++ 				       dma_unmap_len(tx_buf, dma_len0),
++ 				       DMA_TO_DEVICE);
++ 		}
 + 
-+@@ -2360,12 +2470,19 @@ static int mtk_start_dma(struct mtk_eth
++ 		if (dma_unmap_len(tx_buf, dma_len1)) {
++-			dma_unmap_page(eth->dev,
+++			dma_unmap_page(eth->dma_dev,
++ 				       dma_unmap_addr(tx_buf, dma_addr1),
++ 				       dma_unmap_len(tx_buf, dma_len1),
++ 				       DMA_TO_DEVICE);
++@@ -1005,9 +1006,9 @@ static int mtk_tx_map(struct sk_buff *sk
++ 	if (skb_vlan_tag_present(skb))
++ 		txd4 |= TX_DMA_INS_VLAN | skb_vlan_tag_get(skb);
++ 
++-	mapped_addr = dma_map_single(eth->dev, skb->data,
+++	mapped_addr = dma_map_single(eth->dma_dev, skb->data,
++ 				     skb_headlen(skb), DMA_TO_DEVICE);
++-	if (unlikely(dma_mapping_error(eth->dev, mapped_addr)))
+++	if (unlikely(dma_mapping_error(eth->dma_dev, mapped_addr)))
++ 		return -ENOMEM;
++ 
++ 	WRITE_ONCE(itxd->txd1, mapped_addr);
++@@ -1046,10 +1047,10 @@ static int mtk_tx_map(struct sk_buff *sk
++ 
++ 
++ 			frag_map_size = min(frag_size, MTK_TX_DMA_BUF_LEN);
++-			mapped_addr = skb_frag_dma_map(eth->dev, frag, offset,
+++			mapped_addr = skb_frag_dma_map(eth->dma_dev, frag, offset,
++ 						       frag_map_size,
++ 						       DMA_TO_DEVICE);
++-			if (unlikely(dma_mapping_error(eth->dev, mapped_addr)))
+++			if (unlikely(dma_mapping_error(eth->dma_dev, mapped_addr)))
++ 				goto err_dma;
++ 
++ 			if (i == nr_frags - 1 &&
++@@ -1330,18 +1331,18 @@ static int mtk_poll_rx(struct napi_struc
++ 			netdev->stats.rx_dropped++;
++ 			goto release_desc;
++ 		}
++-		dma_addr = dma_map_single(eth->dev,
+++		dma_addr = dma_map_single(eth->dma_dev,
++ 					  new_data + NET_SKB_PAD +
++ 					  eth->ip_align,
++ 					  ring->buf_size,
++ 					  DMA_FROM_DEVICE);
++-		if (unlikely(dma_mapping_error(eth->dev, dma_addr))) {
+++		if (unlikely(dma_mapping_error(eth->dma_dev, dma_addr))) {
++ 			skb_free_frag(new_data);
++ 			netdev->stats.rx_dropped++;
++ 			goto release_desc;
++ 		}
++ 
++-		dma_unmap_single(eth->dev, trxd.rxd1,
+++		dma_unmap_single(eth->dma_dev, trxd.rxd1,
++ 				 ring->buf_size, DMA_FROM_DEVICE);
++ 
++ 		/* receive data */
++@@ -1614,7 +1615,7 @@ static int mtk_tx_alloc(struct mtk_eth *
++ 	if (!ring->buf)
++ 		goto no_tx_mem;
++ 
++-	ring->dma = dma_alloc_coherent(eth->dev, MTK_DMA_SIZE * sz,
+++	ring->dma = dma_alloc_coherent(eth->dma_dev, MTK_DMA_SIZE * sz,
++ 				       &ring->phys, GFP_ATOMIC);
++ 	if (!ring->dma)
++ 		goto no_tx_mem;
++@@ -1632,7 +1633,7 @@ static int mtk_tx_alloc(struct mtk_eth *
++ 	 * descriptors in ring->dma_pdma.
++ 	 */
++ 	if (!MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
++-		ring->dma_pdma = dma_alloc_coherent(eth->dev, MTK_DMA_SIZE * sz,
+++		ring->dma_pdma = dma_alloc_coherent(eth->dma_dev, MTK_DMA_SIZE * sz,
++ 						    &ring->phys_pdma,
++ 						    GFP_ATOMIC);
++ 		if (!ring->dma_pdma)
++@@ -1691,7 +1692,7 @@ static void mtk_tx_clean(struct mtk_eth
 + 	}
 + 
-+ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
-+-		mtk_w32(eth,
-+-			MTK_TX_WB_DDONE | MTK_TX_DMA_EN |
-+-			MTK_TX_BT_32DWORDS | MTK_NDP_CO_PRO |
-+-			MTK_RX_DMA_EN | MTK_RX_2B_OFFSET |
-+-			MTK_RX_BT_32DWORDS,
-+-			reg_map->qdma.glo_cfg);
-++		val = mtk_r32(eth, reg_map->qdma.glo_cfg);
-++		val |= MTK_TX_DMA_EN | MTK_RX_DMA_EN |
-++		       MTK_TX_BT_32DWORDS | MTK_NDP_CO_PRO |
-++		       MTK_RX_2B_OFFSET | MTK_TX_WB_DDONE;
-++
-++		if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
-++			val |= MTK_MUTLI_CNT | MTK_RESV_BUF |
-++			       MTK_WCOMP_EN | MTK_DMAD_WR_WDONE |
-++			       MTK_CHK_DDONE_EN;
-++		else
-++			val |= MTK_RX_BT_32DWORDS;
-++		mtk_w32(eth, val, reg_map->qdma.glo_cfg);
-++
-+ 		mtk_w32(eth,
-+ 			MTK_RX_DMA_EN | rx_2b_offset |
-+ 			MTK_RX_BT_32DWORDS | MTK_MULTI_EN,
-+@@ -2437,7 +2554,7 @@ static int mtk_open(struct net_device *d
-+ 		napi_enable(&eth->tx_napi);
-+ 		napi_enable(&eth->rx_napi);
-+ 		mtk_tx_irq_enable(eth, MTK_TX_DONE_INT);
-+-		mtk_rx_irq_enable(eth, MTK_RX_DONE_INT);
-++		mtk_rx_irq_enable(eth, eth->soc->txrx.rx_irq_done_mask);
-+ 		refcount_set(&eth->dma_refcnt, 1);
++ 	if (ring->dma) {
++-		dma_free_coherent(eth->dev,
+++		dma_free_coherent(eth->dma_dev,
++ 				  MTK_DMA_SIZE * sizeof(*ring->dma),
++ 				  ring->dma,
++ 				  ring->phys);
++@@ -1699,7 +1700,7 @@ static void mtk_tx_clean(struct mtk_eth
 + 	}
-+ 	else
-+@@ -2489,7 +2606,7 @@ static int mtk_stop(struct net_device *d
-+ 	mtk_gdm_config(eth, MTK_GDMA_DROP_ALL);
 + 
-+ 	mtk_tx_irq_disable(eth, MTK_TX_DONE_INT);
-+-	mtk_rx_irq_disable(eth, MTK_RX_DONE_INT);
-++	mtk_rx_irq_disable(eth, eth->soc->txrx.rx_irq_done_mask);
-+ 	napi_disable(&eth->tx_napi);
-+ 	napi_disable(&eth->rx_napi);
++ 	if (ring->dma_pdma) {
++-		dma_free_coherent(eth->dev,
+++		dma_free_coherent(eth->dma_dev,
++ 				  MTK_DMA_SIZE * sizeof(*ring->dma_pdma),
++ 				  ring->dma_pdma,
++ 				  ring->phys_pdma);
++@@ -1747,18 +1748,18 @@ static int mtk_rx_alloc(struct mtk_eth *
++ 			return -ENOMEM;
++ 	}
 + 
-+@@ -2649,9 +2766,25 @@ static int mtk_hw_init(struct mtk_eth *e
-+ 		return 0;
++-	ring->dma = dma_alloc_coherent(eth->dev,
+++	ring->dma = dma_alloc_coherent(eth->dma_dev,
++ 				       rx_dma_size * sizeof(*ring->dma),
++ 				       &ring->phys, GFP_ATOMIC);
++ 	if (!ring->dma)
++ 		return -ENOMEM;
++ 
++ 	for (i = 0; i < rx_dma_size; i++) {
++-		dma_addr_t dma_addr = dma_map_single(eth->dev,
+++		dma_addr_t dma_addr = dma_map_single(eth->dma_dev,
++ 				ring->data[i] + NET_SKB_PAD + eth->ip_align,
++ 				ring->buf_size,
++ 				DMA_FROM_DEVICE);
++-		if (unlikely(dma_mapping_error(eth->dev, dma_addr)))
+++		if (unlikely(dma_mapping_error(eth->dma_dev, dma_addr)))
++ 			return -ENOMEM;
++ 		ring->dma[i].rxd1 = (unsigned int)dma_addr;
++ 
++@@ -1794,7 +1795,7 @@ static void mtk_rx_clean(struct mtk_eth
++ 				continue;
++ 			if (!ring->dma[i].rxd1)
++ 				continue;
++-			dma_unmap_single(eth->dev,
+++			dma_unmap_single(eth->dma_dev,
++ 					 ring->dma[i].rxd1,
++ 					 ring->buf_size,
++ 					 DMA_FROM_DEVICE);
++@@ -1805,7 +1806,7 @@ static void mtk_rx_clean(struct mtk_eth
 + 	}
 + 
-+-	/* Non-MT7628 handling... */
-+-	ethsys_reset(eth, RSTCTRL_FE);
-+-	ethsys_reset(eth, RSTCTRL_PPE);
-++	val = RSTCTRL_FE | RSTCTRL_PPE;
-++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
-++		regmap_write(eth->ethsys, ETHSYS_FE_RST_CHK_IDLE_EN, 0);
++ 	if (ring->dma) {
++-		dma_free_coherent(eth->dev,
+++		dma_free_coherent(eth->dma_dev,
++ 				  ring->dma_size * sizeof(*ring->dma),
++ 				  ring->dma,
++ 				  ring->phys);
++@@ -2161,7 +2162,7 @@ static void mtk_dma_free(struct mtk_eth
++ 		if (eth->netdev[i])
++ 			netdev_reset_queue(eth->netdev[i]);
++ 	if (eth->scratch_ring) {
++-		dma_free_coherent(eth->dev,
+++		dma_free_coherent(eth->dma_dev,
++ 				  MTK_DMA_SIZE * sizeof(struct mtk_tx_dma),
++ 				  eth->scratch_ring,
++ 				  eth->phy_scratch_ring);
++@@ -2511,6 +2512,8 @@ static void mtk_dim_tx(struct work_struc
++ 
++ static int mtk_hw_init(struct mtk_eth *eth)
++ {
+++	u32 dma_mask = ETHSYS_DMA_AG_MAP_PDMA | ETHSYS_DMA_AG_MAP_QDMA |
+++		       ETHSYS_DMA_AG_MAP_PPE;
++ 	int i, val, ret;
++ 
++ 	if (test_and_set_bit(MTK_HW_INIT, &eth->state))
++@@ -2523,6 +2526,10 @@ static int mtk_hw_init(struct mtk_eth *e
++ 	if (ret)
++ 		goto err_disable_pm;
++ 
+++	if (eth->ethsys)
+++		regmap_update_bits(eth->ethsys, ETHSYS_DMA_AG_MAP, dma_mask,
+++				   of_dma_is_coherent(eth->dma_dev->of_node) * dma_mask);
 ++
-++		val |= RSTCTRL_ETH;
-++		if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
-++			val |= RSTCTRL_PPE1;
-++	}
++ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628)) {
++ 		ret = device_reset(eth->dev);
++ 		if (ret) {
++@@ -3076,6 +3083,35 @@ free_netdev:
++ 	return err;
++ }
++ 
+++void mtk_eth_set_dma_device(struct mtk_eth *eth, struct device *dma_dev)
+++{
+++	struct net_device *dev, *tmp;
+++	LIST_HEAD(dev_list);
+++	int i;
 ++
-++	ethsys_reset(eth, val);
+++	rtnl_lock();
 ++
-++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
-++		regmap_write(eth->ethsys, ETHSYS_FE_RST_CHK_IDLE_EN,
-++			     0x3ffffff);
+++	for (i = 0; i < MTK_MAC_COUNT; i++) {
+++		dev = eth->netdev[i];
 ++
-++		/* Set FE to PDMAv2 if necessary */
-++		val = mtk_r32(eth, MTK_FE_GLO_MISC);
-++		mtk_w32(eth,  val | BIT(4), MTK_FE_GLO_MISC);
-++	}
-+ 
-+ 	if (eth->pctl) {
-+ 		/* Set GE2 driving and slew rate */
-+@@ -2690,11 +2823,47 @@ static int mtk_hw_init(struct mtk_eth *e
-+ 
-+ 	/* FE int grouping */
-+ 	mtk_w32(eth, MTK_TX_DONE_INT, reg_map->pdma.int_grp);
-+-	mtk_w32(eth, MTK_RX_DONE_INT, reg_map->pdma.int_grp + 4);
-++	mtk_w32(eth, eth->soc->txrx.rx_irq_done_mask, reg_map->pdma.int_grp + 4);
-+ 	mtk_w32(eth, MTK_TX_DONE_INT, reg_map->qdma.int_grp);
-+-	mtk_w32(eth, MTK_RX_DONE_INT, reg_map->qdma.int_grp + 4);
-++	mtk_w32(eth, eth->soc->txrx.rx_irq_done_mask, reg_map->qdma.int_grp + 4);
-+ 	mtk_w32(eth, 0x21021000, MTK_FE_INT_GRP);
-+ 
-++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
-++		/* PSE should not drop port8 and port9 packets */
-++		mtk_w32(eth, 0x00000300, PSE_DROP_CFG);
+++		if (!dev || !(dev->flags & IFF_UP))
+++			continue;
 ++
-++		/* PSE Free Queue Flow Control  */
-++		mtk_w32(eth, 0x01fa01f4, PSE_FQFC_CFG2);
+++		list_add_tail(&dev->close_list, &dev_list);
+++	}
 ++
-++		/* PSE config input queue threshold */
-++		mtk_w32(eth, 0x001a000e, PSE_IQ_REV(1));
-++		mtk_w32(eth, 0x01ff001a, PSE_IQ_REV(2));
-++		mtk_w32(eth, 0x000e01ff, PSE_IQ_REV(3));
-++		mtk_w32(eth, 0x000e000e, PSE_IQ_REV(4));
-++		mtk_w32(eth, 0x000e000e, PSE_IQ_REV(5));
-++		mtk_w32(eth, 0x000e000e, PSE_IQ_REV(6));
-++		mtk_w32(eth, 0x000e000e, PSE_IQ_REV(7));
-++		mtk_w32(eth, 0x000e000e, PSE_IQ_REV(8));
+++	dev_close_many(&dev_list, false);
 ++
-++		/* PSE config output queue threshold */
-++		mtk_w32(eth, 0x000f000a, PSE_OQ_TH(1));
-++		mtk_w32(eth, 0x001a000f, PSE_OQ_TH(2));
-++		mtk_w32(eth, 0x000f001a, PSE_OQ_TH(3));
-++		mtk_w32(eth, 0x01ff000f, PSE_OQ_TH(4));
-++		mtk_w32(eth, 0x000f000f, PSE_OQ_TH(5));
-++		mtk_w32(eth, 0x0006000f, PSE_OQ_TH(6));
-++		mtk_w32(eth, 0x00060006, PSE_OQ_TH(7));
-++		mtk_w32(eth, 0x00060006, PSE_OQ_TH(8));
+++	eth->dma_dev = dma_dev;
 ++
-++		/* GDM and CDM Threshold */
-++		mtk_w32(eth, 0x00000004, MTK_GDM2_THRES);
-++		mtk_w32(eth, 0x00000004, MTK_CDMW0_THRES);
-++		mtk_w32(eth, 0x00000004, MTK_CDMW1_THRES);
-++		mtk_w32(eth, 0x00000004, MTK_CDME0_THRES);
-++		mtk_w32(eth, 0x00000004, MTK_CDME1_THRES);
-++		mtk_w32(eth, 0x00000004, MTK_CDMM_THRES);
+++	list_for_each_entry_safe(dev, tmp, &dev_list, close_list) {
+++		list_del_init(&dev->close_list);
+++		dev_open(dev, NULL);
 ++	}
 ++
-+ 	return 0;
+++	rtnl_unlock();
+++}
+++
++ static int mtk_probe(struct platform_device *pdev)
++ {
++ 	struct device_node *mac_np;
++@@ -3089,6 +3125,7 @@ static int mtk_probe(struct platform_dev
++ 	eth->soc = of_device_get_match_data(&pdev->dev);
 + 
-+ err_disable_pm:
-+@@ -3231,12 +3400,8 @@ static int mtk_probe(struct platform_dev
++ 	eth->dev = &pdev->dev;
+++	eth->dma_dev = &pdev->dev;
++ 	eth->base = devm_platform_ioremap_resource(pdev, 0);
 + 	if (IS_ERR(eth->base))
 + 		return PTR_ERR(eth->base);
++@@ -3137,6 +3174,16 @@ static int mtk_probe(struct platform_dev
++ 		}
++ 	}
 + 
-+-	if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628)) {
-+-		eth->rx_dma_l4_valid = RX_DMA_L4_VALID_PDMA;
-++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628))
-+ 		eth->ip_align = NET_IP_ALIGN;
-+-	} else {
-+-		eth->rx_dma_l4_valid = RX_DMA_L4_VALID;
-+-	}
-+ 
-+ 	spin_lock_init(&eth->page_lock);
-+ 	spin_lock_init(&eth->tx_irq_lock);
-+@@ -3472,6 +3637,10 @@ static const struct mtk_soc_data mt2701_
-+ 	.txrx = {
-+ 		.txd_size = sizeof(struct mtk_tx_dma),
-+ 		.rxd_size = sizeof(struct mtk_rx_dma),
-++		.rx_irq_done_mask = MTK_RX_DONE_INT,
-++		.rx_dma_l4_valid = RX_DMA_L4_VALID,
-++		.dma_max_len = MTK_TX_DMA_BUF_LEN,
-++		.dma_len_offset = 16,
-+ 	},
-+ };
-+ 
-+@@ -3485,6 +3654,10 @@ static const struct mtk_soc_data mt7621_
-+ 	.txrx = {
-+ 		.txd_size = sizeof(struct mtk_tx_dma),
-+ 		.rxd_size = sizeof(struct mtk_rx_dma),
-++		.rx_irq_done_mask = MTK_RX_DONE_INT,
-++		.rx_dma_l4_valid = RX_DMA_L4_VALID,
-++		.dma_max_len = MTK_TX_DMA_BUF_LEN,
-++		.dma_len_offset = 16,
-+ 	},
-+ };
+++	if (of_dma_is_coherent(pdev->dev.of_node)) {
+++		struct regmap *cci;
+++
+++		cci = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+++						      "mediatek,cci-control");
+++		/* enable CPU/bus coherency */
+++		if (!IS_ERR(cci))
+++			regmap_write(cci, 0, 3);
+++	}
+++
++ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_SGMII)) {
++ 		eth->sgmii = devm_kzalloc(eth->dev, sizeof(*eth->sgmii),
++ 					  GFP_KERNEL);
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -462,6 +462,12 @@
++ #define RSTCTRL_FE		BIT(6)
++ #define RSTCTRL_PPE		BIT(31)
 + 
-+@@ -3499,6 +3672,10 @@ static const struct mtk_soc_data mt7622_
-+ 	.txrx = {
-+ 		.txd_size = sizeof(struct mtk_tx_dma),
-+ 		.rxd_size = sizeof(struct mtk_rx_dma),
-++		.rx_irq_done_mask = MTK_RX_DONE_INT,
-++		.rx_dma_l4_valid = RX_DMA_L4_VALID,
-++		.dma_max_len = MTK_TX_DMA_BUF_LEN,
-++		.dma_len_offset = 16,
-+ 	},
-+ };
+++/* ethernet dma channel agent map */
+++#define ETHSYS_DMA_AG_MAP	0x408
+++#define ETHSYS_DMA_AG_MAP_PDMA	BIT(0)
+++#define ETHSYS_DMA_AG_MAP_QDMA	BIT(1)
+++#define ETHSYS_DMA_AG_MAP_PPE	BIT(2)
+++
++ /* SGMII subsystem config registers */
++ /* Register to auto-negotiation restart */
++ #define SGMSYS_PCS_CONTROL_1	0x0
++@@ -879,6 +885,7 @@ struct mtk_sgmii {
++ /* struct mtk_eth -	This is the main datasructure for holding the state
++  *			of the driver
++  * @dev:		The device pointer
+++ * @dev:		The device pointer used for dma mapping/alloc
++  * @base:		The mapped register i/o base
++  * @page_lock:		Make sure that register operations are atomic
++  * @tx_irq__lock:	Make sure that IRQ register operations are atomic
++@@ -922,6 +929,7 @@ struct mtk_sgmii {
 + 
-+@@ -3512,6 +3689,10 @@ static const struct mtk_soc_data mt7623_
-+ 	.txrx = {
-+ 		.txd_size = sizeof(struct mtk_tx_dma),
-+ 		.rxd_size = sizeof(struct mtk_rx_dma),
-++		.rx_irq_done_mask = MTK_RX_DONE_INT,
-++		.rx_dma_l4_valid = RX_DMA_L4_VALID,
-++		.dma_max_len = MTK_TX_DMA_BUF_LEN,
-++		.dma_len_offset = 16,
-+ 	},
-+ };
++ struct mtk_eth {
++ 	struct device			*dev;
+++	struct device			*dma_dev;
++ 	void __iomem			*base;
++ 	spinlock_t			page_lock;
++ 	spinlock_t			tx_irq_lock;
++@@ -1020,6 +1028,7 @@ int mtk_gmac_rgmii_path_setup(struct mtk
++ int mtk_eth_offload_init(struct mtk_eth *eth);
++ int mtk_eth_setup_tc(struct net_device *dev, enum tc_setup_type type,
++ 		     void *type_data);
+++void mtk_eth_set_dma_device(struct mtk_eth *eth, struct device *dma_dev);
 + 
-+@@ -3525,6 +3706,10 @@ static const struct mtk_soc_data mt7629_
-+ 	.txrx = {
-+ 		.txd_size = sizeof(struct mtk_tx_dma),
-+ 		.rxd_size = sizeof(struct mtk_rx_dma),
-++		.rx_irq_done_mask = MTK_RX_DONE_INT,
-++		.rx_dma_l4_valid = RX_DMA_L4_VALID,
-++		.dma_max_len = MTK_TX_DMA_BUF_LEN,
-++		.dma_len_offset = 16,
-+ 	},
-+ };
 + 
-+@@ -3537,6 +3722,10 @@ static const struct mtk_soc_data rt5350_
-+ 	.txrx = {
-+ 		.txd_size = sizeof(struct mtk_tx_dma),
-+ 		.rxd_size = sizeof(struct mtk_rx_dma),
-++		.rx_irq_done_mask = MTK_RX_DONE_INT,
-++		.rx_dma_l4_valid = RX_DMA_L4_VALID_PDMA,
-++		.dma_max_len = MTK_TX_DMA_BUF_LEN,
-++		.dma_len_offset = 16,
-+ 	},
-+ };
++ #endif /* MTK_ETH_H */
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-01-arm64-dts-mediatek-mt7622-add-support-for-coherent-D.patch b/target/linux/generic/backport-5.15/702-v5.19-01-arm64-dts-mediatek-mt7622-add-support-for-coherent-D.patch
+new file mode 100644
+index 0000000000..d9015d4805
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-01-arm64-dts-mediatek-mt7622-add-support-for-coherent-D.patch
+@@ -0,0 +1,30 @@
++From: Felix Fietkau <nbd@nbd.name>
++Date: Mon, 7 Feb 2022 10:27:22 +0100
++Subject: [PATCH] arm64: dts: mediatek: mt7622: add support for coherent
++ DMA
++
++It improves performance by eliminating the need for a cache flush on rx and tx
++
++Signed-off-by: Felix Fietkau <nbd@nbd.name>
++---
++
++--- a/arch/arm64/boot/dts/mediatek/mt7622.dtsi
+++++ b/arch/arm64/boot/dts/mediatek/mt7622.dtsi
++@@ -357,7 +357,7 @@
++ 		};
 + 
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-+@@ -23,6 +23,7 @@
-+ #define MTK_MAX_RX_LENGTH	1536
-+ #define MTK_MAX_RX_LENGTH_2K	2048
-+ #define MTK_TX_DMA_BUF_LEN	0x3fff
-++#define MTK_TX_DMA_BUF_LEN_V2	0xffff
-+ #define MTK_DMA_SIZE		512
-+ #define MTK_NAPI_WEIGHT		64
-+ #define MTK_MAC_COUNT		2
-+@@ -83,6 +84,10 @@
-+ #define MTK_CDMQ_IG_CTRL	0x1400
-+ #define MTK_CDMQ_STAG_EN	BIT(0)
++ 		cci_control2: slave-if@5000 {
++-			compatible = "arm,cci-400-ctrl-if";
+++			compatible = "arm,cci-400-ctrl-if", "syscon";
++ 			interface-type = "ace";
++ 			reg = <0x5000 0x1000>;
++ 		};
++@@ -937,6 +937,8 @@
++ 		power-domains = <&scpsys MT7622_POWER_DOMAIN_ETHSYS>;
++ 		mediatek,ethsys = <&ethsys>;
++ 		mediatek,sgmiisys = <&sgmiisys>;
+++		mediatek,cci-control = <&cci_control2>;
+++		dma-coherent;
++ 		#address-cells = <1>;
++ 		#size-cells = <0>;
++ 		status = "disabled";
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-02-net-ethernet-mtk_eth_soc-add-support-for-Wireless-Et.patch b/target/linux/generic/backport-5.15/702-v5.19-02-net-ethernet-mtk_eth_soc-add-support-for-Wireless-Et.patch
+new file mode 100644
+index 0000000000..502d1d0e9b
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-02-net-ethernet-mtk_eth_soc-add-support-for-Wireless-Et.patch
+@@ -0,0 +1,1679 @@
++From: Felix Fietkau <nbd@nbd.name>
++Date: Sat, 5 Feb 2022 17:56:08 +0100
++Subject: [PATCH] net: ethernet: mtk_eth_soc: add support for Wireless
++ Ethernet Dispatch (WED)
++
++The Wireless Ethernet Dispatch subsystem on the MT7622 SoC can be
++configured to intercept and handle access to the DMA queues and
++PCIe interrupts for a MT7615/MT7915 wireless card.
++It can manage the internal WDMA (Wireless DMA) controller, which allows
++ethernet packets to be passed from the packet switch engine (PSE) to the
++wireless card, bypassing the CPU entirely.
++This can be used to implement hardware flow offloading from ethernet to
++WLAN.
++
++Signed-off-by: Felix Fietkau <nbd@nbd.name>
++---
++ create mode 100644 drivers/net/ethernet/mediatek/mtk_wed.c
++ create mode 100644 drivers/net/ethernet/mediatek/mtk_wed.h
++ create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
++ create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_ops.c
++ create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_regs.h
++ create mode 100644 include/linux/soc/mediatek/mtk_wed.h
++
++--- a/drivers/net/ethernet/mediatek/Kconfig
+++++ b/drivers/net/ethernet/mediatek/Kconfig
++@@ -7,6 +7,10 @@ config NET_VENDOR_MEDIATEK
 + 
-++/* CDMP Ingress Control Register */
-++#define MTK_CDMP_IG_CTRL	0x400
-++#define MTK_CDMP_STAG_EN	BIT(0)
++ if NET_VENDOR_MEDIATEK
++ 
+++config NET_MEDIATEK_SOC_WED
+++	depends on ARCH_MEDIATEK || COMPILE_TEST
+++	def_bool NET_MEDIATEK_SOC != n
 ++
-+ /* CDMP Exgress Control Register */
-+ #define MTK_CDMP_EG_CTRL	0x404
++ config NET_MEDIATEK_SOC
++ 	tristate "MediaTek SoC Gigabit Ethernet support"
++ 	depends on NET_DSA || !NET_DSA
++--- a/drivers/net/ethernet/mediatek/Makefile
+++++ b/drivers/net/ethernet/mediatek/Makefile
++@@ -5,4 +5,9 @@
 + 
-+@@ -102,13 +107,38 @@
-+ /* Unicast Filter MAC Address Register - High */
-+ #define MTK_GDMA_MAC_ADRH(x)	(0x50C + (x * 0x1000))
++ obj-$(CONFIG_NET_MEDIATEK_SOC) += mtk_eth.o
++ mtk_eth-y := mtk_eth_soc.o mtk_sgmii.o mtk_eth_path.o mtk_ppe.o mtk_ppe_debugfs.o mtk_ppe_offload.o
+++mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed.o
+++ifdef CONFIG_DEBUG_FS
+++mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed_debugfs.o
+++endif
+++obj-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed_ops.o
++ obj-$(CONFIG_NET_MEDIATEK_STAR_EMAC) += mtk_star_emac.o
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -24,6 +24,7 @@
++ #include <net/dsa.h>
 + 
-++/* FE global misc reg*/
-++#define MTK_FE_GLO_MISC         0x124
++ #include "mtk_eth_soc.h"
+++#include "mtk_wed.h"
++ 
++ static int mtk_msg_level = -1;
++ module_param_named(msg_level, mtk_msg_level, int, 0);
++@@ -3206,6 +3207,22 @@ static int mtk_probe(struct platform_dev
++ 		}
++ 	}
++ 
+++	for (i = 0;; i++) {
+++		struct device_node *np = of_parse_phandle(pdev->dev.of_node,
+++							  "mediatek,wed", i);
+++		static const u32 wdma_regs[] = {
+++			MTK_WDMA0_BASE,
+++			MTK_WDMA1_BASE
+++		};
+++		void __iomem *wdma;
 ++
-++/* PSE Free Queue Flow Control  */
-++#define PSE_FQFC_CFG1		0x100
-++#define PSE_FQFC_CFG2		0x104
-++#define PSE_DROP_CFG		0x108
+++		if (!np || i >= ARRAY_SIZE(wdma_regs))
+++			break;
 ++
-++/* PSE Input Queue Reservation Register*/
-++#define PSE_IQ_REV(x)		(0x140 + (((x) - 1) << 2))
+++		wdma = eth->base + wdma_regs[i];
+++		mtk_wed_add_hw(np, eth, wdma, i);
+++	}
 ++
-++/* PSE Output Queue Threshold Register*/
-++#define PSE_OQ_TH(x)		(0x160 + (((x) - 1) << 2))
++ 	for (i = 0; i < 3; i++) {
++ 		if (MTK_HAS_CAPS(eth->soc->caps, MTK_SHARED_INT) && i > 0)
++ 			eth->irq[i] = eth->irq[0];
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -295,6 +295,9 @@
++ #define MTK_GDM1_TX_GPCNT	0x2438
++ #define MTK_STAT_OFFSET		0x40
++ 
+++#define MTK_WDMA0_BASE		0x2800
+++#define MTK_WDMA1_BASE		0x2c00
 ++
-++/* GDM and CDM Threshold */
-++#define MTK_GDM2_THRES		0x1530
-++#define MTK_CDMW0_THRES		0x164c
-++#define MTK_CDMW1_THRES		0x1650
-++#define MTK_CDME0_THRES		0x1654
-++#define MTK_CDME1_THRES		0x1658
-++#define MTK_CDMM_THRES		0x165c
++ /* QDMA descriptor txd4 */
++ #define TX_DMA_CHKSUM		(0x7 << 29)
++ #define TX_DMA_TSO		BIT(28)
++--- /dev/null
+++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
++@@ -0,0 +1,875 @@
+++// SPDX-License-Identifier: GPL-2.0-only
+++/* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name> */
 ++
-+ /* PDMA HW LRO Control Registers */
-+ #define MTK_PDMA_LRO_CTRL_DW0	0x980
-+ #define MTK_LRO_EN			BIT(0)
-+ #define MTK_L3_CKS_UPD_EN		BIT(7)
-++#define MTK_L3_CKS_UPD_EN_V2		BIT(19)
-+ #define MTK_LRO_ALT_PKT_CNT_MODE	BIT(21)
-+ #define MTK_LRO_RING_RELINQUISH_REQ	(0x7 << 26)
-++#define MTK_LRO_RING_RELINQUISH_REQ_V2	(0xf << 24)
-+ #define MTK_LRO_RING_RELINQUISH_DONE	(0x7 << 29)
-++#define MTK_LRO_RING_RELINQUISH_DONE_V2	(0xf << 28)
-+ 
-+ #define MTK_PDMA_LRO_CTRL_DW1	0x984
-+ #define MTK_PDMA_LRO_CTRL_DW2	0x988
-+@@ -180,6 +210,13 @@
-+ #define MTK_TX_DMA_EN		BIT(0)
-+ #define MTK_DMA_BUSY_TIMEOUT_US	1000000
-+ 
-++/* QDMA V2 Global Configuration Register */
-++#define MTK_CHK_DDONE_EN	BIT(28)
-++#define MTK_DMAD_WR_WDONE	BIT(26)
-++#define MTK_WCOMP_EN		BIT(24)
-++#define MTK_RESV_BUF		(0x40 << 16)
-++#define MTK_MUTLI_CNT		(0x4 << 12)
+++#include <linux/kernel.h>
+++#include <linux/slab.h>
+++#include <linux/module.h>
+++#include <linux/bitfield.h>
+++#include <linux/dma-mapping.h>
+++#include <linux/skbuff.h>
+++#include <linux/of_platform.h>
+++#include <linux/of_address.h>
+++#include <linux/mfd/syscon.h>
+++#include <linux/debugfs.h>
+++#include <linux/soc/mediatek/mtk_wed.h>
+++#include "mtk_eth_soc.h"
+++#include "mtk_wed_regs.h"
+++#include "mtk_wed.h"
+++#include "mtk_ppe.h"
 ++
-+ /* QDMA Flow Control Register */
-+ #define FC_THRES_DROP_MODE	BIT(20)
-+ #define FC_THRES_DROP_EN	(7 << 16)
-+@@ -199,11 +236,32 @@
-+ #define MTK_RX_DONE_INT		MTK_RX_DONE_DLY
-+ #define MTK_TX_DONE_INT		MTK_TX_DONE_DLY
-+ 
-++#define MTK_RX_DONE_INT_V2	BIT(14)
+++#define MTK_PCIE_BASE(n)		(0x1a143000 + (n) * 0x2000)
 ++
-+ /* QDMA Interrupt grouping registers */
-+ #define MTK_RLS_DONE_INT	BIT(0)
-+ 
-+ #define MTK_STAT_OFFSET		0x40
-+ 
-++/* QDMA TX NUM */
-++#define MTK_QDMA_TX_NUM		16
-++#define MTK_QDMA_TX_MASK	(MTK_QDMA_TX_NUM - 1)
-++#define QID_BITS_V2(x)		(((x) & 0x3f) << 16)
-++#define MTK_QDMA_GMAC2_QID	8
+++#define MTK_WED_PKT_SIZE		1900
+++#define MTK_WED_BUF_SIZE		2048
+++#define MTK_WED_BUF_PER_PAGE		(PAGE_SIZE / 2048)
 ++
-++#define MTK_TX_DMA_BUF_SHIFT	8
+++#define MTK_WED_TX_RING_SIZE		2048
+++#define MTK_WED_WDMA_RING_SIZE		1024
 ++
-++/* QDMA V2 descriptor txd6 */
-++#define TX_DMA_INS_VLAN_V2	BIT(16)
-++/* QDMA V2 descriptor txd5 */
-++#define TX_DMA_CHKSUM_V2	(0x7 << 28)
-++#define TX_DMA_TSO_V2		BIT(31)
+++static struct mtk_wed_hw *hw_list[2];
+++static DEFINE_MUTEX(hw_lock);
 ++
-++/* QDMA V2 descriptor txd4 */
-++#define TX_DMA_FPORT_SHIFT_V2	8
-++#define TX_DMA_FPORT_MASK_V2	0xf
-++#define TX_DMA_SWC_V2		BIT(30)
+++static void
+++wed_m32(struct mtk_wed_device *dev, u32 reg, u32 mask, u32 val)
+++{
+++	regmap_update_bits(dev->hw->regs, reg, mask | val, val);
+++}
 ++
-+ #define MTK_WDMA0_BASE		0x2800
-+ #define MTK_WDMA1_BASE		0x2c00
-+ 
-+@@ -217,10 +275,9 @@
-+ /* QDMA descriptor txd3 */
-+ #define TX_DMA_OWNER_CPU	BIT(31)
-+ #define TX_DMA_LS0		BIT(30)
-+-#define TX_DMA_PLEN0(_x)	(((_x) & MTK_TX_DMA_BUF_LEN) << 16)
-+-#define TX_DMA_PLEN1(_x)	((_x) & MTK_TX_DMA_BUF_LEN)
-++#define TX_DMA_PLEN0(x)		(((x) & eth->soc->txrx.dma_max_len) << eth->soc->txrx.dma_len_offset)
-++#define TX_DMA_PLEN1(x)		((x) & eth->soc->txrx.dma_max_len)
-+ #define TX_DMA_SWC		BIT(14)
-+-#define TX_DMA_SDL(_x)		(((_x) & 0x3fff) << 16)
-+ 
-+ /* PDMA on MT7628 */
-+ #define TX_DMA_DONE		BIT(31)
-+@@ -230,12 +287,14 @@
-+ /* QDMA descriptor rxd2 */
-+ #define RX_DMA_DONE		BIT(31)
-+ #define RX_DMA_LSO		BIT(30)
-+-#define RX_DMA_PLEN0(_x)	(((_x) & 0x3fff) << 16)
-+-#define RX_DMA_GET_PLEN0(_x)	(((_x) >> 16) & 0x3fff)
-++#define RX_DMA_PREP_PLEN0(x)	(((x) & eth->soc->txrx.dma_max_len) << eth->soc->txrx.dma_len_offset)
-++#define RX_DMA_GET_PLEN0(x)	(((x) >> eth->soc->txrx.dma_len_offset) & eth->soc->txrx.dma_max_len)
-+ #define RX_DMA_VTAG		BIT(15)
-+ 
-+ /* QDMA descriptor rxd3 */
-+-#define RX_DMA_VID(_x)		((_x) & 0xfff)
-++#define RX_DMA_VID(x)		((x) & VLAN_VID_MASK)
-++#define RX_DMA_TCI(x)		((x) & (VLAN_PRIO_MASK | VLAN_VID_MASK))
-++#define RX_DMA_VPID(x)		(((x) >> 16) & 0xffff)
-+ 
-+ /* QDMA descriptor rxd4 */
-+ #define MTK_RXD4_FOE_ENTRY	GENMASK(13, 0)
-+@@ -246,10 +305,15 @@
-+ /* QDMA descriptor rxd4 */
-+ #define RX_DMA_L4_VALID		BIT(24)
-+ #define RX_DMA_L4_VALID_PDMA	BIT(30)		/* when PDMA is used */
-+-#define RX_DMA_FPORT_SHIFT	19
-+-#define RX_DMA_FPORT_MASK	0x7
-+ #define RX_DMA_SPECIAL_TAG	BIT(22)
-+ 
-++#define RX_DMA_GET_SPORT(x)	(((x) >> 19) & 0xf)
-++#define RX_DMA_GET_SPORT_V2(x)	(((x) >> 26) & 0x7)
+++static void
+++wed_set(struct mtk_wed_device *dev, u32 reg, u32 mask)
+++{
+++	return wed_m32(dev, reg, 0, mask);
+++}
 ++
-++/* PDMA V2 descriptor rxd3 */
-++#define RX_DMA_VTAG_V2		BIT(0)
-++#define RX_DMA_L4_VALID_V2	BIT(2)
+++static void
+++wed_clr(struct mtk_wed_device *dev, u32 reg, u32 mask)
+++{
+++	return wed_m32(dev, reg, mask, 0);
+++}
 ++
-+ /* PHY Indirect Access Control registers */
-+ #define MTK_PHY_IAC		0x10004
-+ #define PHY_IAC_ACCESS		BIT(31)
-+@@ -370,6 +434,16 @@
-+ #define ETHSYS_TRGMII_MT7621_DDR_PLL	BIT(5)
-+ 
-+ /* ethernet reset control register */
-++#define ETHSYS_RSTCTRL			0x34
-++#define RSTCTRL_FE			BIT(6)
-++#define RSTCTRL_PPE			BIT(31)
-++#define RSTCTRL_PPE1			BIT(30)
-++#define RSTCTRL_ETH			BIT(23)
+++static void
+++wdma_m32(struct mtk_wed_device *dev, u32 reg, u32 mask, u32 val)
+++{
+++	wdma_w32(dev, reg, (wdma_r32(dev, reg) & ~mask) | val);
+++}
 ++
-++/* ethernet reset check idle register */
-++#define ETHSYS_FE_RST_CHK_IDLE_EN	0x28
+++static void
+++wdma_set(struct mtk_wed_device *dev, u32 reg, u32 mask)
+++{
+++	wdma_m32(dev, reg, 0, mask);
+++}
 ++
-++/* ethernet reset control register */
-+ #define ETHSYS_RSTCTRL		0x34
-+ #define RSTCTRL_FE		BIT(6)
-+ #define RSTCTRL_PPE		BIT(31)
-+@@ -453,6 +527,17 @@ struct mtk_rx_dma {
-+ 	unsigned int rxd4;
-+ } __packed __aligned(4);
-+ 
-++struct mtk_rx_dma_v2 {
-++	unsigned int rxd1;
-++	unsigned int rxd2;
-++	unsigned int rxd3;
-++	unsigned int rxd4;
-++	unsigned int rxd5;
-++	unsigned int rxd6;
-++	unsigned int rxd7;
-++	unsigned int rxd8;
-++} __packed __aligned(4);
+++static u32
+++mtk_wed_read_reset(struct mtk_wed_device *dev)
+++{
+++	return wed_r32(dev, MTK_WED_RESET);
+++}
 ++
-+ struct mtk_tx_dma {
-+ 	unsigned int txd1;
-+ 	unsigned int txd2;
-+@@ -460,6 +545,17 @@ struct mtk_tx_dma {
-+ 	unsigned int txd4;
-+ } __packed __aligned(4);
-+ 
-++struct mtk_tx_dma_v2 {
-++	unsigned int txd1;
-++	unsigned int txd2;
-++	unsigned int txd3;
-++	unsigned int txd4;
-++	unsigned int txd5;
-++	unsigned int txd6;
-++	unsigned int txd7;
-++	unsigned int txd8;
-++} __packed __aligned(4);
+++static void
+++mtk_wed_reset(struct mtk_wed_device *dev, u32 mask)
+++{
+++	u32 status;
 ++
-+ struct mtk_eth;
-+ struct mtk_mac;
-+ 
-+@@ -646,7 +742,9 @@ enum mkt_eth_capabilities {
-+ 	MTK_SHARED_INT_BIT,
-+ 	MTK_TRGMII_MT7621_CLK_BIT,
-+ 	MTK_QDMA_BIT,
-++	MTK_NETSYS_V2_BIT,
-+ 	MTK_SOC_MT7628_BIT,
-++	MTK_RSTCTRL_PPE1_BIT,
-+ 
-+ 	/* MUX BITS*/
-+ 	MTK_ETH_MUX_GDM1_TO_GMAC1_ESW_BIT,
-+@@ -678,7 +776,9 @@ enum mkt_eth_capabilities {
-+ #define MTK_SHARED_INT		BIT(MTK_SHARED_INT_BIT)
-+ #define MTK_TRGMII_MT7621_CLK	BIT(MTK_TRGMII_MT7621_CLK_BIT)
-+ #define MTK_QDMA		BIT(MTK_QDMA_BIT)
-++#define MTK_NETSYS_V2		BIT(MTK_NETSYS_V2_BIT)
-+ #define MTK_SOC_MT7628		BIT(MTK_SOC_MT7628_BIT)
-++#define MTK_RSTCTRL_PPE1	BIT(MTK_RSTCTRL_PPE1_BIT)
-+ 
-+ #define MTK_ETH_MUX_GDM1_TO_GMAC1_ESW		\
-+ 	BIT(MTK_ETH_MUX_GDM1_TO_GMAC1_ESW_BIT)
-+@@ -755,6 +855,7 @@ struct mtk_tx_dma_desc_info {
-+ 	dma_addr_t	addr;
-+ 	u32		size;
-+ 	u16		vlan_tci;
-++	u16		qid;
-+ 	u8		gso:1;
-+ 	u8		csum:1;
-+ 	u8		vlan:1;
-+@@ -812,6 +913,10 @@ struct mtk_reg_map {
-+  *				the extra setup for those pins used by GMAC.
-+  * @txd_size			Tx DMA descriptor size.
-+  * @rxd_size			Rx DMA descriptor size.
-++ * @rx_irq_done_mask		Rx irq done register mask.
-++ * @rx_dma_l4_valid		Rx DMA valid register mask.
-++ * @dma_max_len			Max DMA tx/rx buffer length.
-++ * @dma_len_offset		Tx/Rx DMA length field offset.
-+  */
-+ struct mtk_soc_data {
-+ 	const struct mtk_reg_map *reg_map;
-+@@ -824,6 +929,10 @@ struct mtk_soc_data {
-+ 	struct {
-+ 		u32	txd_size;
-+ 		u32	rxd_size;
-++		u32	rx_irq_done_mask;
-++		u32	rx_dma_l4_valid;
-++		u32	dma_max_len;
-++		u32	dma_len_offset;
-+ 	} txrx;
-+ };
-+ 
-+@@ -942,7 +1051,6 @@ struct mtk_eth {
-+ 	u32				tx_bytes;
-+ 	struct dim			tx_dim;
-+ 
-+-	u32				rx_dma_l4_valid;
-+ 	int				ip_align;
-+ 
-+ 	struct mtk_ppe			*ppe;
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-28-net-ethernet-mtk_eth_soc-convert-ring-dma-pointer-to.patch b/target/linux/generic/backport-5.15/702-v5.19-28-net-ethernet-mtk_eth_soc-convert-ring-dma-pointer-to.patch
-new file mode 100644
-index 0000000000..a956639239
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-28-net-ethernet-mtk_eth_soc-convert-ring-dma-pointer-to.patch
-@@ -0,0 +1,135 @@
-+From: Lorenzo Bianconi <lorenzo@kernel.org>
-+Date: Fri, 20 May 2022 20:11:37 +0200
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: convert ring dma pointer to void
-+
-+Simplify the code converting {tx,rx} ring dma pointer to void
-+
-+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -946,18 +946,15 @@ static int mtk_init_fq_dma(struct mtk_et
-+ 	return 0;
-+ }
-+ 
-+-static inline void *mtk_qdma_phys_to_virt(struct mtk_tx_ring *ring, u32 desc)
-++static void *mtk_qdma_phys_to_virt(struct mtk_tx_ring *ring, u32 desc)
-+ {
-+-	void *ret = ring->dma;
-+-
-+-	return ret + (desc - ring->phys);
-++	return ring->dma + (desc - ring->phys);
-+ }
-+ 
-+ static struct mtk_tx_buf *mtk_desc_to_tx_buf(struct mtk_tx_ring *ring,
-+-					     struct mtk_tx_dma *txd,
-+-					     u32 txd_size)
-++					     void *txd, u32 txd_size)
-+ {
-+-	int idx = ((void *)txd - (void *)ring->dma) / txd_size;
-++	int idx = (txd - ring->dma) / txd_size;
-+ 
-+ 	return &ring->buf[idx];
-+ }
-+@@ -965,13 +962,12 @@ static struct mtk_tx_buf *mtk_desc_to_tx
-+ static struct mtk_tx_dma *qdma_to_pdma(struct mtk_tx_ring *ring,
-+ 				       struct mtk_tx_dma *dma)
-+ {
-+-	return ring->dma_pdma - ring->dma + dma;
-++	return ring->dma_pdma - (struct mtk_tx_dma *)ring->dma + dma;
-+ }
-+ 
-+-static int txd_to_idx(struct mtk_tx_ring *ring, struct mtk_tx_dma *dma,
-+-		      u32 txd_size)
-++static int txd_to_idx(struct mtk_tx_ring *ring, void *dma, u32 txd_size)
-+ {
-+-	return ((void *)dma - (void *)ring->dma) / txd_size;
-++	return (dma - ring->dma) / txd_size;
-+ }
-+ 
-+ static void mtk_tx_unmap(struct mtk_eth *eth, struct mtk_tx_buf *tx_buf,
-+@@ -1388,7 +1384,7 @@ static struct mtk_rx_ring *mtk_get_rx_ri
-+ 
-+ 		ring = &eth->rx_ring[i];
-+ 		idx = NEXT_DESP_IDX(ring->calc_idx, ring->dma_size);
-+-		rxd = (void *)ring->dma + idx * eth->soc->txrx.rxd_size;
-++		rxd = ring->dma + idx * eth->soc->txrx.rxd_size;
-+ 		if (rxd->rxd2 & RX_DMA_DONE) {
-+ 			ring->calc_idx_update = true;
-+ 			return ring;
-+@@ -1440,7 +1436,7 @@ static int mtk_poll_rx(struct napi_struc
-+ 			goto rx_done;
-+ 
-+ 		idx = NEXT_DESP_IDX(ring->calc_idx, ring->dma_size);
-+-		rxd = (void *)ring->dma + idx * eth->soc->txrx.rxd_size;
-++		rxd = ring->dma + idx * eth->soc->txrx.rxd_size;
-+ 		data = ring->data[idx];
-+ 
-+ 		if (!mtk_rx_get_desc(eth, &trxd, rxd))
-+@@ -1647,7 +1643,7 @@ static int mtk_poll_tx_pdma(struct mtk_e
-+ 
-+ 		mtk_tx_unmap(eth, tx_buf, true);
-+ 
-+-		desc = (void *)ring->dma + cpu * eth->soc->txrx.txd_size;
-++		desc = ring->dma + cpu * eth->soc->txrx.txd_size;
-+ 		ring->last_free = desc;
-+ 		atomic_inc(&ring->free_count);
-+ 
-+@@ -1792,7 +1788,7 @@ static int mtk_tx_alloc(struct mtk_eth *
-+ 		int next = (i + 1) % MTK_DMA_SIZE;
-+ 		u32 next_ptr = ring->phys + next * sz;
-+ 
-+-		txd = (void *)ring->dma + i * sz;
-++		txd = ring->dma + i * sz;
-+ 		txd->txd2 = next_ptr;
-+ 		txd->txd3 = TX_DMA_LS0 | TX_DMA_OWNER_CPU;
-+ 		txd->txd4 = 0;
-+@@ -1822,7 +1818,7 @@ static int mtk_tx_alloc(struct mtk_eth *
-+ 
-+ 	ring->dma_size = MTK_DMA_SIZE;
-+ 	atomic_set(&ring->free_count, MTK_DMA_SIZE - 2);
-+-	ring->next_free = &ring->dma[0];
-++	ring->next_free = ring->dma;
-+ 	ring->last_free = (void *)txd;
-+ 	ring->last_free_ptr = (u32)(ring->phys + ((MTK_DMA_SIZE - 1) * sz));
-+ 	ring->thresh = MAX_SKB_FRAGS;
-+@@ -1937,7 +1933,7 @@ static int mtk_rx_alloc(struct mtk_eth *
-+ 		if (unlikely(dma_mapping_error(eth->dma_dev, dma_addr)))
-+ 			return -ENOMEM;
-+ 
-+-		rxd = (void *)ring->dma + i * eth->soc->txrx.rxd_size;
-++		rxd = ring->dma + i * eth->soc->txrx.rxd_size;
-+ 		rxd->rxd1 = (unsigned int)dma_addr;
-+ 
-+ 		if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628))
-+@@ -1999,7 +1995,7 @@ static void mtk_rx_clean(struct mtk_eth
-+ 			if (!ring->data[i])
-+ 				continue;
-+ 
-+-			rxd = (void *)ring->dma + i * eth->soc->txrx.rxd_size;
-++			rxd = ring->dma + i * eth->soc->txrx.rxd_size;
-+ 			if (!rxd->rxd1)
-+ 				continue;
-+ 
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-+@@ -688,7 +688,7 @@ struct mtk_tx_buf {
-+  *			are present
-+  */
-+ struct mtk_tx_ring {
-+-	struct mtk_tx_dma *dma;
-++	void *dma;
-+ 	struct mtk_tx_buf *buf;
-+ 	dma_addr_t phys;
-+ 	struct mtk_tx_dma *next_free;
-+@@ -718,7 +718,7 @@ enum mtk_rx_flags {
-+  * @calc_idx:		The current head of ring
-+  */
-+ struct mtk_rx_ring {
-+-	struct mtk_rx_dma *dma;
-++	void *dma;
-+ 	u8 **data;
-+ 	dma_addr_t phys;
-+ 	u16 frag_size;
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-29-net-ethernet-mtk_eth_soc-convert-scratch_ring-pointe.patch b/target/linux/generic/backport-5.15/702-v5.19-29-net-ethernet-mtk_eth_soc-convert-scratch_ring-pointe.patch
-new file mode 100644
-index 0000000000..459ffd5406
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-29-net-ethernet-mtk_eth_soc-convert-scratch_ring-pointe.patch
-@@ -0,0 +1,33 @@
-+From: Lorenzo Bianconi <lorenzo@kernel.org>
-+Date: Fri, 20 May 2022 20:11:38 +0200
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: convert scratch_ring pointer to
-+ void
-+
-+Simplify the code converting scratch_ring pointer to void
-+
-+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -922,7 +922,7 @@ static int mtk_init_fq_dma(struct mtk_et
-+ 	for (i = 0; i < cnt; i++) {
-+ 		struct mtk_tx_dma_v2 *txd;
-+ 
-+-		txd = (void *)eth->scratch_ring + i * soc->txrx.txd_size;
-++		txd = eth->scratch_ring + i * soc->txrx.txd_size;
-+ 		txd->txd1 = dma_addr + i * MTK_QDMA_PAGE_SIZE;
-+ 		if (i < cnt - 1)
-+ 			txd->txd2 = eth->phy_scratch_ring +
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-+@@ -1028,7 +1028,7 @@ struct mtk_eth {
-+ 	struct mtk_rx_ring		rx_ring_qdma;
-+ 	struct napi_struct		tx_napi;
-+ 	struct napi_struct		rx_napi;
-+-	struct mtk_tx_dma		*scratch_ring;
-++	void				*scratch_ring;
-+ 	dma_addr_t			phy_scratch_ring;
-+ 	void				*scratch_head;
-+ 	struct clk			*clks[MTK_CLK_MAX];
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-30-net-ethernet-mtk_eth_soc-introduce-support-for-mt798.patch b/target/linux/generic/backport-5.15/702-v5.19-30-net-ethernet-mtk_eth_soc-introduce-support-for-mt798.patch
-new file mode 100644
-index 0000000000..4baeb2244f
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-30-net-ethernet-mtk_eth_soc-introduce-support-for-mt798.patch
-@@ -0,0 +1,138 @@
-+From: Lorenzo Bianconi <lorenzo@kernel.org>
-+Date: Fri, 20 May 2022 20:11:39 +0200
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: introduce support for mt7986
-+ chipset
-+
-+Add support for mt7986-eth driver available on mt7986 soc.
-+
-+Tested-by: Sam Shih <sam.shih@mediatek.com>
-+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -87,6 +87,43 @@ static const struct mtk_reg_map mt7628_r
-+ 	},
-+ };
-+ 
-++static const struct mtk_reg_map mt7986_reg_map = {
-++	.tx_irq_mask		= 0x461c,
-++	.tx_irq_status		= 0x4618,
-++	.pdma = {
-++		.rx_ptr		= 0x6100,
-++		.rx_cnt_cfg	= 0x6104,
-++		.pcrx_ptr	= 0x6108,
-++		.glo_cfg	= 0x6204,
-++		.rst_idx	= 0x6208,
-++		.delay_irq	= 0x620c,
-++		.irq_status	= 0x6220,
-++		.irq_mask	= 0x6228,
-++		.int_grp	= 0x6250,
-++	},
-++	.qdma = {
-++		.qtx_cfg	= 0x4400,
-++		.rx_ptr		= 0x4500,
-++		.rx_cnt_cfg	= 0x4504,
-++		.qcrx_ptr	= 0x4508,
-++		.glo_cfg	= 0x4604,
-++		.rst_idx	= 0x4608,
-++		.delay_irq	= 0x460c,
-++		.fc_th		= 0x4610,
-++		.int_grp	= 0x4620,
-++		.hred		= 0x4644,
-++		.ctx_ptr	= 0x4700,
-++		.dtx_ptr	= 0x4704,
-++		.crx_ptr	= 0x4710,
-++		.drx_ptr	= 0x4714,
-++		.fq_head	= 0x4720,
-++		.fq_tail	= 0x4724,
-++		.fq_count	= 0x4728,
-++		.fq_blen	= 0x472c,
-++	},
-++	.gdm1_cnt		= 0x1c00,
-++};
+++	wed_w32(dev, MTK_WED_RESET, mask);
+++	if (readx_poll_timeout(mtk_wed_read_reset, dev, status,
+++			       !(status & mask), 0, 1000))
+++		WARN_ON_ONCE(1);
+++}
 ++
-+ /* strings used by ethtool */
-+ static const struct mtk_ethtool_stats {
-+ 	char str[ETH_GSTRING_LEN];
-+@@ -110,7 +147,7 @@ static const char * const mtk_clks_sourc
-+ 	"ethif", "sgmiitop", "esw", "gp0", "gp1", "gp2", "fe", "trgpll",
-+ 	"sgmii_tx250m", "sgmii_rx250m", "sgmii_cdr_ref", "sgmii_cdr_fb",
-+ 	"sgmii2_tx250m", "sgmii2_rx250m", "sgmii2_cdr_ref", "sgmii2_cdr_fb",
-+-	"sgmii_ck", "eth2pll",
-++	"sgmii_ck", "eth2pll", "wocpu0", "wocpu1", "netsys0", "netsys1"
-+ };
-+ 
-+ void mtk_w32(struct mtk_eth *eth, u32 val, unsigned reg)
-+@@ -3709,6 +3746,21 @@ static const struct mtk_soc_data mt7629_
-+ 	},
-+ };
-+ 
-++static const struct mtk_soc_data mt7986_data = {
-++	.reg_map = &mt7986_reg_map,
-++	.ana_rgc3 = 0x128,
-++	.caps = MT7986_CAPS,
-++	.required_clks = MT7986_CLKS_BITMAP,
-++	.required_pctl = false,
-++	.txrx = {
-++		.txd_size = sizeof(struct mtk_tx_dma_v2),
-++		.rxd_size = sizeof(struct mtk_rx_dma_v2),
-++		.rx_irq_done_mask = MTK_RX_DONE_INT_V2,
-++		.dma_max_len = MTK_TX_DMA_BUF_LEN_V2,
-++		.dma_len_offset = 8,
-++	},
-++};
+++static struct mtk_wed_hw *
+++mtk_wed_assign(struct mtk_wed_device *dev)
+++{
+++	struct mtk_wed_hw *hw;
 ++
-+ static const struct mtk_soc_data rt5350_data = {
-+ 	.reg_map = &mt7628_reg_map,
-+ 	.caps = MT7628_CAPS,
-+@@ -3731,6 +3783,7 @@ const struct of_device_id of_mtk_match[]
-+ 	{ .compatible = "mediatek,mt7622-eth", .data = &mt7622_data},
-+ 	{ .compatible = "mediatek,mt7623-eth", .data = &mt7623_data},
-+ 	{ .compatible = "mediatek,mt7629-eth", .data = &mt7629_data},
-++	{ .compatible = "mediatek,mt7986-eth", .data = &mt7986_data},
-+ 	{ .compatible = "ralink,rt5350-eth", .data = &rt5350_data},
-+ 	{},
-+ };
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
-+@@ -623,6 +623,10 @@ enum mtk_clks_map {
-+ 	MTK_CLK_SGMII2_CDR_FB,
-+ 	MTK_CLK_SGMII_CK,
-+ 	MTK_CLK_ETH2PLL,
-++	MTK_CLK_WOCPU0,
-++	MTK_CLK_WOCPU1,
-++	MTK_CLK_NETSYS0,
-++	MTK_CLK_NETSYS1,
-+ 	MTK_CLK_MAX
-+ };
-+ 
-+@@ -653,6 +657,16 @@ enum mtk_clks_map {
-+ 				 BIT(MTK_CLK_SGMII2_CDR_FB) | \
-+ 				 BIT(MTK_CLK_SGMII_CK) | \
-+ 				 BIT(MTK_CLK_ETH2PLL) | BIT(MTK_CLK_SGMIITOP))
-++#define MT7986_CLKS_BITMAP	(BIT(MTK_CLK_FE) | BIT(MTK_CLK_GP2) | BIT(MTK_CLK_GP1) | \
-++				 BIT(MTK_CLK_WOCPU1) | BIT(MTK_CLK_WOCPU0) | \
-++				 BIT(MTK_CLK_SGMII_TX_250M) | \
-++				 BIT(MTK_CLK_SGMII_RX_250M) | \
-++				 BIT(MTK_CLK_SGMII_CDR_REF) | \
-++				 BIT(MTK_CLK_SGMII_CDR_FB) | \
-++				 BIT(MTK_CLK_SGMII2_TX_250M) | \
-++				 BIT(MTK_CLK_SGMII2_RX_250M) | \
-++				 BIT(MTK_CLK_SGMII2_CDR_REF) | \
-++				 BIT(MTK_CLK_SGMII2_CDR_FB))
-+ 
-+ enum mtk_dev_state {
-+ 	MTK_HW_INIT,
-+@@ -851,6 +865,10 @@ enum mkt_eth_capabilities {
-+ 		      MTK_MUX_U3_GMAC2_TO_QPHY | \
-+ 		      MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA)
-+ 
-++#define MT7986_CAPS  (MTK_GMAC1_SGMII | MTK_GMAC2_SGMII | \
-++		      MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA | \
-++		      MTK_NETSYS_V2 | MTK_RSTCTRL_PPE1)
+++	hw = hw_list[pci_domain_nr(dev->wlan.pci_dev->bus)];
+++	if (!hw || hw->wed_dev)
+++		return NULL;
 ++
-+ struct mtk_tx_dma_desc_info {
-+ 	dma_addr_t	addr;
-+ 	u32		size;
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-31-net-ethernet-mtk_eth_soc-fix-error-code-in-mtk_flow_.patch b/target/linux/generic/backport-5.15/702-v5.19-31-net-ethernet-mtk_eth_soc-fix-error-code-in-mtk_flow_.patch
-new file mode 100644
-index 0000000000..e490333a9b
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-31-net-ethernet-mtk_eth_soc-fix-error-code-in-mtk_flow_.patch
-@@ -0,0 +1,25 @@
-+From: Dan Carpenter <dan.carpenter@oracle.com>
-+Date: Thu, 19 May 2022 17:08:00 +0300
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: fix error code in
-+ mtk_flow_offload_replace()
-+
-+Preserve the error code from mtk_foe_entry_commit().  Do not return
-+success.
-+
-+Fixes: c4f033d9e03e ("net: ethernet: mtk_eth_soc: rework hardware flow table management")
-+Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
-+@@ -434,7 +434,8 @@ mtk_flow_offload_replace(struct mtk_eth
-+ 	memcpy(&entry->data, &foe, sizeof(entry->data));
-+ 	entry->wed_index = wed_index;
-+ 
-+-	if (mtk_foe_entry_commit(eth->ppe, entry) < 0)
-++	err = mtk_foe_entry_commit(eth->ppe, entry);
-++	if (err < 0)
-+ 		goto free;
-+ 
-+ 	err = rhashtable_insert_fast(&eth->flow_table, &entry->node,
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-33-net-ethernet-mtk_eth_soc-enable-rx-cksum-offload-for.patch b/target/linux/generic/backport-5.15/702-v5.19-33-net-ethernet-mtk_eth_soc-enable-rx-cksum-offload-for.patch
-new file mode 100644
-index 0000000000..2e3ad698b5
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-33-net-ethernet-mtk_eth_soc-enable-rx-cksum-offload-for.patch
-@@ -0,0 +1,47 @@
-+From: Lorenzo Bianconi <lorenzo@kernel.org>
-+Date: Mon, 6 Jun 2022 21:49:00 +0200
-+Subject: [PATCH] net: ethernet: mtk_eth_soc: enable rx cksum offload for
-+ MTK_NETSYS_V2
-+
-+Enable rx checksum offload for mt7986 chipset.
-+
-+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
-+Link: https://lore.kernel.org/r/c8699805c18f7fd38315fcb8da2787676d83a32c.1654544585.git.lorenzo@kernel.org
-+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-+---
-+
-+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-+@@ -1462,8 +1462,8 @@ static int mtk_poll_rx(struct napi_struc
-+ 	int done = 0, bytes = 0;
-+ 
-+ 	while (done < budget) {
-++		unsigned int pktlen, *rxdcsum;
-+ 		struct net_device *netdev;
-+-		unsigned int pktlen;
-+ 		dma_addr_t dma_addr;
-+ 		u32 hash, reason;
-+ 		int mac = 0;
-+@@ -1530,7 +1530,13 @@ static int mtk_poll_rx(struct napi_struc
-+ 		pktlen = RX_DMA_GET_PLEN0(trxd.rxd2);
-+ 		skb->dev = netdev;
-+ 		skb_put(skb, pktlen);
-+-		if (trxd.rxd4 & eth->soc->txrx.rx_dma_l4_valid)
+++	hw->wed_dev = dev;
+++	return hw;
+++}
 ++
-++		if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
-++			rxdcsum = &trxd.rxd3;
-++		else
-++			rxdcsum = &trxd.rxd4;
+++static int
+++mtk_wed_buffer_alloc(struct mtk_wed_device *dev)
+++{
+++	struct mtk_wdma_desc *desc;
+++	dma_addr_t desc_phys;
+++	void **page_list;
+++	int token = dev->wlan.token_start;
+++	int ring_size;
+++	int n_pages;
+++	int i, page_idx;
 ++
-++		if (*rxdcsum & eth->soc->txrx.rx_dma_l4_valid)
-+ 			skb->ip_summed = CHECKSUM_UNNECESSARY;
-+ 		else
-+ 			skb_checksum_none_assert(skb);
-+@@ -3756,6 +3762,7 @@ static const struct mtk_soc_data mt7986_
-+ 		.txd_size = sizeof(struct mtk_tx_dma_v2),
-+ 		.rxd_size = sizeof(struct mtk_rx_dma_v2),
-+ 		.rx_irq_done_mask = MTK_RX_DONE_INT_V2,
-++		.rx_dma_l4_valid = RX_DMA_L4_VALID_V2,
-+ 		.dma_max_len = MTK_TX_DMA_BUF_LEN_V2,
-+ 		.dma_len_offset = 8,
-+ 	},
-diff --git a/target/linux/generic/backport-5.15/702-v5.19-34-eth-mtk_ppe-fix-up-after-merge.patch b/target/linux/generic/backport-5.15/702-v5.19-34-eth-mtk_ppe-fix-up-after-merge.patch
-new file mode 100644
-index 0000000000..5303ca48a7
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/702-v5.19-34-eth-mtk_ppe-fix-up-after-merge.patch
-@@ -0,0 +1,28 @@
-+From: Jakub Kicinski <kuba@kernel.org>
-+Date: Thu, 19 May 2022 18:25:55 -0700
-+Subject: [PATCH] eth: mtk_ppe: fix up after merge
-+
-+I missed this in the barrage of GCC 12 warnings. Commit cf2df74e202d
-+("net: fix dev_fill_forward_path with pppoe + bridge") changed
-+the pointer into an array.
-+
-+Fixes: d7e6f5836038 ("Merge git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net")
-+Link: https://lore.kernel.org/r/20220520012555.2262461-1-kuba@kernel.org
-+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-+---
-+
-+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
-++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
-+@@ -90,10 +90,11 @@ mtk_flow_get_wdma_info(struct net_device
-+ {
-+ 	struct net_device_path_ctx ctx = {
-+ 		.dev = dev,
-+-		.daddr = addr,
-+ 	};
-+ 	struct net_device_path path = {};
-+ 
-++	memcpy(ctx.daddr, addr, sizeof(ctx.daddr));
+++	ring_size = dev->wlan.nbuf & ~(MTK_WED_BUF_PER_PAGE - 1);
+++	n_pages = ring_size / MTK_WED_BUF_PER_PAGE;
 ++
-+ 	if (!IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED))
-+ 		return -1;
-+ 
-diff --git a/target/linux/generic/backport-5.15/734-v5.16-0001-net-bgmac-improve-handling-PHY.patch b/target/linux/generic/backport-5.15/734-v5.16-0001-net-bgmac-improve-handling-PHY.patch
-new file mode 100644
-index 0000000000..6788a2ec35
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/734-v5.16-0001-net-bgmac-improve-handling-PHY.patch
-@@ -0,0 +1,84 @@
-+From b5375509184dc23d2b7fa0c5ed8763899ccc9674 Mon Sep 17 00:00:00 2001
-+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
-+Date: Sat, 2 Oct 2021 19:58:11 +0200
-+Subject: [PATCH] net: bgmac: improve handling PHY
-+MIME-Version: 1.0
-+Content-Type: text/plain; charset=UTF-8
-+Content-Transfer-Encoding: 8bit
-+
-+1. Use info from DT if available
-+
-+It allows describing for example a fixed link. It's more accurate than
-+just guessing there may be one (depending on a chipset).
-+
-+2. Verify PHY ID before trying to connect PHY
-+
-+PHY addr 0x1e (30) is special in Broadcom routers and means a switch
-+connected as MDIO devices instead of a real PHY. Don't try connecting to
-+it.
-+
-+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/ethernet/broadcom/bgmac-bcma.c | 33 ++++++++++++++--------
-+ 1 file changed, 21 insertions(+), 12 deletions(-)
-+
-+--- a/drivers/net/ethernet/broadcom/bgmac-bcma.c
-++++ b/drivers/net/ethernet/broadcom/bgmac-bcma.c
-+@@ -11,6 +11,7 @@
-+ #include <linux/bcma/bcma.h>
-+ #include <linux/brcmphy.h>
-+ #include <linux/etherdevice.h>
-++#include <linux/of_mdio.h>
-+ #include <linux/of_net.h>
-+ #include "bgmac.h"
-+ 
-+@@ -86,17 +87,28 @@ static int bcma_phy_connect(struct bgmac
-+ 	struct phy_device *phy_dev;
-+ 	char bus_id[MII_BUS_ID_SIZE + 3];
-+ 
-++	/* DT info should be the most accurate */
-++	phy_dev = of_phy_get_and_connect(bgmac->net_dev, bgmac->dev->of_node,
-++					 bgmac_adjust_link);
-++	if (phy_dev)
-++		return 0;
+++	page_list = kcalloc(n_pages, sizeof(*page_list), GFP_KERNEL);
+++	if (!page_list)
+++		return -ENOMEM;
 ++
-+ 	/* Connect to the PHY */
-+-	snprintf(bus_id, sizeof(bus_id), PHY_ID_FMT, bgmac->mii_bus->id,
-+-		 bgmac->phyaddr);
-+-	phy_dev = phy_connect(bgmac->net_dev, bus_id, bgmac_adjust_link,
-+-			      PHY_INTERFACE_MODE_MII);
-+-	if (IS_ERR(phy_dev)) {
-+-		dev_err(bgmac->dev, "PHY connection failed\n");
-+-		return PTR_ERR(phy_dev);
-++	if (bgmac->mii_bus && bgmac->phyaddr != BGMAC_PHY_NOREGS) {
-++		snprintf(bus_id, sizeof(bus_id), PHY_ID_FMT, bgmac->mii_bus->id,
-++			 bgmac->phyaddr);
-++		phy_dev = phy_connect(bgmac->net_dev, bus_id, bgmac_adjust_link,
-++				      PHY_INTERFACE_MODE_MII);
-++		if (IS_ERR(phy_dev)) {
-++			dev_err(bgmac->dev, "PHY connection failed\n");
-++			return PTR_ERR(phy_dev);
+++	dev->buf_ring.size = ring_size;
+++	dev->buf_ring.pages = page_list;
+++
+++	desc = dma_alloc_coherent(dev->hw->dev, ring_size * sizeof(*desc),
+++				  &desc_phys, GFP_KERNEL);
+++	if (!desc)
+++		return -ENOMEM;
+++
+++	dev->buf_ring.desc = desc;
+++	dev->buf_ring.desc_phys = desc_phys;
+++
+++	for (i = 0, page_idx = 0; i < ring_size; i += MTK_WED_BUF_PER_PAGE) {
+++		dma_addr_t page_phys, buf_phys;
+++		struct page *page;
+++		void *buf;
+++		int s;
+++
+++		page = __dev_alloc_pages(GFP_KERNEL, 0);
+++		if (!page)
+++			return -ENOMEM;
+++
+++		page_phys = dma_map_page(dev->hw->dev, page, 0, PAGE_SIZE,
+++					 DMA_BIDIRECTIONAL);
+++		if (dma_mapping_error(dev->hw->dev, page_phys)) {
+++			__free_page(page);
+++			return -ENOMEM;
 ++		}
 ++
-++		return 0;
-+ 	}
-+ 
-+-	return 0;
-++	/* Assume a fixed link to the switch port */
-++	return bgmac_phy_connect_direct(bgmac);
-+ }
-+ 
-+ static const struct bcma_device_id bgmac_bcma_tbl[] = {
-+@@ -297,10 +309,7 @@ static int bgmac_probe(struct bcma_devic
-+ 	bgmac->cco_ctl_maskset = bcma_bgmac_cco_ctl_maskset;
-+ 	bgmac->get_bus_clock = bcma_bgmac_get_bus_clock;
-+ 	bgmac->cmn_maskset32 = bcma_bgmac_cmn_maskset32;
-+-	if (bgmac->mii_bus)
-+-		bgmac->phy_connect = bcma_phy_connect;
-+-	else
-+-		bgmac->phy_connect = bgmac_phy_connect_direct;
-++	bgmac->phy_connect = bcma_phy_connect;
-+ 
-+ 	err = bgmac_enet_probe(bgmac);
-+ 	if (err)
-diff --git a/target/linux/generic/backport-5.15/734-v5.16-0002-net-bgmac-support-MDIO-described-in-DT.patch b/target/linux/generic/backport-5.15/734-v5.16-0002-net-bgmac-support-MDIO-described-in-DT.patch
-new file mode 100644
-index 0000000000..f134828273
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/734-v5.16-0002-net-bgmac-support-MDIO-described-in-DT.patch
-@@ -0,0 +1,54 @@
-+From 45c9d966688e7fad7f24bfc450547d91e4304d0b Mon Sep 17 00:00:00 2001
-+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
-+Date: Sat, 2 Oct 2021 19:58:12 +0200
-+Subject: [PATCH] net: bgmac: support MDIO described in DT
-+MIME-Version: 1.0
-+Content-Type: text/plain; charset=UTF-8
-+Content-Transfer-Encoding: 8bit
-+
-+Check ethernet controller DT node for "mdio" subnode and use it with
-+of_mdiobus_register() when present. That allows specifying MDIO and its
-+PHY devices in a standard DT based way.
-+
-+This is required for BCM53573 SoC support. That family is sometimes
-+called Northstar (by marketing?) but is quite different from it. It uses
-+different CPU(s) and many different hw blocks.
-+
-+One of shared blocks in BCM53573 is Ethernet controller. Switch however
-+is not SRAB accessible (as it Northstar) but is MDIO attached.
-+
-+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/ethernet/broadcom/bgmac-bcma-mdio.c | 6 +++++-
-+ 1 file changed, 5 insertions(+), 1 deletion(-)
-+
-+--- a/drivers/net/ethernet/broadcom/bgmac-bcma-mdio.c
-++++ b/drivers/net/ethernet/broadcom/bgmac-bcma-mdio.c
-+@@ -10,6 +10,7 @@
-+ 
-+ #include <linux/bcma/bcma.h>
-+ #include <linux/brcmphy.h>
-++#include <linux/of_mdio.h>
-+ #include "bgmac.h"
-+ 
-+ static bool bcma_mdio_wait_value(struct bcma_device *core, u16 reg, u32 mask,
-+@@ -211,6 +212,7 @@ struct mii_bus *bcma_mdio_mii_register(s
-+ {
-+ 	struct bcma_device *core = bgmac->bcma.core;
-+ 	struct mii_bus *mii_bus;
-++	struct device_node *np;
-+ 	int err;
-+ 
-+ 	mii_bus = mdiobus_alloc();
-+@@ -229,7 +231,9 @@ struct mii_bus *bcma_mdio_mii_register(s
-+ 	mii_bus->parent = &core->dev;
-+ 	mii_bus->phy_mask = ~(1 << bgmac->phyaddr);
-+ 
-+-	err = mdiobus_register(mii_bus);
-++	np = of_get_child_by_name(core->dev.of_node, "mdio");
+++		page_list[page_idx++] = page;
+++		dma_sync_single_for_cpu(dev->hw->dev, page_phys, PAGE_SIZE,
+++					DMA_BIDIRECTIONAL);
 ++
-++	err = of_mdiobus_register(mii_bus, np);
-+ 	if (err) {
-+ 		dev_err(&core->dev, "Registration of mii bus failed\n");
-+ 		goto err_free_bus;
-diff --git a/target/linux/generic/backport-5.15/742-v5.16-net-phy-at803x-add-support-for-qca-8327-internal-phy.patch b/target/linux/generic/backport-5.15/742-v5.16-net-phy-at803x-add-support-for-qca-8327-internal-phy.patch
-new file mode 100644
-index 0000000000..8f000ba918
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/742-v5.16-net-phy-at803x-add-support-for-qca-8327-internal-phy.patch
-@@ -0,0 +1,48 @@
-+From 0ccf8511182436183c031e8a2f740ae91a02c625 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Tue, 14 Sep 2021 14:33:45 +0200
-+Subject: net: phy: at803x: add support for qca 8327 internal phy
-+
-+Add support for qca8327 internal phy needed for correct init of the
-+switch port. It does use the same qca8337 function and reg just with a
-+different id.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Tested-by: Rosen Penev <rosenp@gmail.com>
-+Tested-by: Andrew Lunn <andrew@lunn.ch>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/phy/at803x.c | 15 +++++++++++++++
-+ 1 file changed, 15 insertions(+)
-+
-+--- a/drivers/net/phy/at803x.c
-++++ b/drivers/net/phy/at803x.c
-+@@ -1412,6 +1412,19 @@ static struct phy_driver at803x_driver[]
-+ 	.get_sset_count = at803x_get_sset_count,
-+ 	.get_strings = at803x_get_strings,
-+ 	.get_stats = at803x_get_stats,
-++}, {
-++	/* QCA8327 */
-++	.phy_id = QCA8327_PHY_ID,
-++	.phy_id_mask = QCA8K_PHY_ID_MASK,
-++	.name = "QCA PHY 8327",
-++	/* PHY_GBIT_FEATURES */
-++	.probe = at803x_probe,
-++	.flags = PHY_IS_INTERNAL,
-++	.config_init = qca83xx_config_init,
-++	.soft_reset = genphy_soft_reset,
-++	.get_sset_count = at803x_get_sset_count,
-++	.get_strings = at803x_get_strings,
-++	.get_stats = at803x_get_stats,
-+ }, };
-+ 
-+ module_phy_driver(at803x_driver);
-+@@ -1422,6 +1435,8 @@ static struct mdio_device_id __maybe_unu
-+ 	{ PHY_ID_MATCH_EXACT(ATH8032_PHY_ID) },
-+ 	{ PHY_ID_MATCH_EXACT(ATH8035_PHY_ID) },
-+ 	{ PHY_ID_MATCH_EXACT(ATH9331_PHY_ID) },
-++	{ PHY_ID_MATCH_EXACT(QCA8337_PHY_ID) },
-++	{ PHY_ID_MATCH_EXACT(QCA8327_PHY_ID) },
-+ 	{ }
-+ };
-+ 
-diff --git a/target/linux/generic/backport-5.15/743-v5.16-0001-net-dsa-b53-Include-all-ports-in-enabled_ports.patch b/target/linux/generic/backport-5.15/743-v5.16-0001-net-dsa-b53-Include-all-ports-in-enabled_ports.patch
-new file mode 100644
-index 0000000000..eb84b45b5c
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/743-v5.16-0001-net-dsa-b53-Include-all-ports-in-enabled_ports.patch
-@@ -0,0 +1,131 @@
-+From 983d96a9116a328668601555d96736261d33170c Mon Sep 17 00:00:00 2001
-+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
-+Date: Thu, 16 Sep 2021 14:03:51 +0200
-+Subject: [PATCH] net: dsa: b53: Include all ports in "enabled_ports"
-+MIME-Version: 1.0
-+Content-Type: text/plain; charset=UTF-8
-+Content-Transfer-Encoding: 8bit
-+
-+Make "enabled_ports" bitfield contain all available switch ports
-+including a CPU port. This way there is no need for fixup during
-+initialization.
-+
-+For BCM53010, BCM53018 and BCM53019 include also other available ports.
-+
-+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Tested-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-+---
-+ drivers/net/dsa/b53/b53_common.c | 23 +++++++++++------------
-+ 1 file changed, 11 insertions(+), 12 deletions(-)
-+
-+--- a/drivers/net/dsa/b53/b53_common.c
-++++ b/drivers/net/dsa/b53/b53_common.c
-+@@ -2302,7 +2302,7 @@ static const struct b53_chip_data b53_sw
-+ 		.chip_id = BCM5325_DEVICE_ID,
-+ 		.dev_name = "BCM5325",
-+ 		.vlans = 16,
-+-		.enabled_ports = 0x1f,
-++		.enabled_ports = 0x3f,
-+ 		.arl_bins = 2,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 5,
-+@@ -2313,7 +2313,7 @@ static const struct b53_chip_data b53_sw
-+ 		.chip_id = BCM5365_DEVICE_ID,
-+ 		.dev_name = "BCM5365",
-+ 		.vlans = 256,
-+-		.enabled_ports = 0x1f,
-++		.enabled_ports = 0x3f,
-+ 		.arl_bins = 2,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 5,
-+@@ -2324,7 +2324,7 @@ static const struct b53_chip_data b53_sw
-+ 		.chip_id = BCM5389_DEVICE_ID,
-+ 		.dev_name = "BCM5389",
-+ 		.vlans = 4096,
-+-		.enabled_ports = 0x1f,
-++		.enabled_ports = 0x11f,
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+@@ -2338,7 +2338,7 @@ static const struct b53_chip_data b53_sw
-+ 		.chip_id = BCM5395_DEVICE_ID,
-+ 		.dev_name = "BCM5395",
-+ 		.vlans = 4096,
-+-		.enabled_ports = 0x1f,
-++		.enabled_ports = 0x11f,
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+@@ -2352,7 +2352,7 @@ static const struct b53_chip_data b53_sw
-+ 		.chip_id = BCM5397_DEVICE_ID,
-+ 		.dev_name = "BCM5397",
-+ 		.vlans = 4096,
-+-		.enabled_ports = 0x1f,
-++		.enabled_ports = 0x11f,
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+@@ -2366,7 +2366,7 @@ static const struct b53_chip_data b53_sw
-+ 		.chip_id = BCM5398_DEVICE_ID,
-+ 		.dev_name = "BCM5398",
-+ 		.vlans = 4096,
-+-		.enabled_ports = 0x7f,
-++		.enabled_ports = 0x17f,
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+@@ -2380,7 +2380,7 @@ static const struct b53_chip_data b53_sw
-+ 		.chip_id = BCM53115_DEVICE_ID,
-+ 		.dev_name = "BCM53115",
-+ 		.vlans = 4096,
-+-		.enabled_ports = 0x1f,
-++		.enabled_ports = 0x11f,
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.vta_regs = B53_VTA_REGS,
-+@@ -2394,7 +2394,7 @@ static const struct b53_chip_data b53_sw
-+ 		.chip_id = BCM53125_DEVICE_ID,
-+ 		.dev_name = "BCM53125",
-+ 		.vlans = 4096,
-+-		.enabled_ports = 0xff,
-++		.enabled_ports = 0x1ff,
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+@@ -2436,7 +2436,7 @@ static const struct b53_chip_data b53_sw
-+ 		.chip_id = BCM53010_DEVICE_ID,
-+ 		.dev_name = "BCM53010",
-+ 		.vlans = 4096,
-+-		.enabled_ports = 0x1f,
-++		.enabled_ports = 0x1bf,
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+@@ -2478,7 +2478,7 @@ static const struct b53_chip_data b53_sw
-+ 		.chip_id = BCM53018_DEVICE_ID,
-+ 		.dev_name = "BCM53018",
-+ 		.vlans = 4096,
-+-		.enabled_ports = 0x1f,
-++		.enabled_ports = 0x1bf,
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+@@ -2492,7 +2492,7 @@ static const struct b53_chip_data b53_sw
-+ 		.chip_id = BCM53019_DEVICE_ID,
-+ 		.dev_name = "BCM53019",
-+ 		.vlans = 4096,
-+-		.enabled_ports = 0x1f,
-++		.enabled_ports = 0x1bf,
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+@@ -2634,7 +2634,6 @@ static int b53_switch_init(struct b53_de
-+ 			dev->cpu_port = 5;
-+ 	}
-+ 
-+-	dev->enabled_ports |= BIT(dev->cpu_port);
-+ 	dev->num_ports = fls(dev->enabled_ports);
-+ 
-+ 	dev->ds->num_ports = min_t(unsigned int, dev->num_ports, DSA_MAX_PORTS);
-diff --git a/target/linux/generic/backport-5.15/743-v5.16-0002-net-dsa-b53-Drop-BCM5301x-workaround-for-a-wrong-CPU.patch b/target/linux/generic/backport-5.15/743-v5.16-0002-net-dsa-b53-Drop-BCM5301x-workaround-for-a-wrong-CPU.patch
-new file mode 100644
-index 0000000000..23805a9027
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/743-v5.16-0002-net-dsa-b53-Drop-BCM5301x-workaround-for-a-wrong-CPU.patch
-@@ -0,0 +1,42 @@
-+From b290c6384afabbca5ae6e2af72fb1b2bc37922be Mon Sep 17 00:00:00 2001
-+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
-+Date: Thu, 16 Sep 2021 14:03:52 +0200
-+Subject: [PATCH] net: dsa: b53: Drop BCM5301x workaround for a wrong CPU/IMP
-+ port
-+MIME-Version: 1.0
-+Content-Type: text/plain; charset=UTF-8
-+Content-Transfer-Encoding: 8bit
-+
-+On BCM5301x port 8 requires a fixed link when used.
-+
-+Years ago when b53 was an OpenWrt downstream driver (with configuration
-+based on sometimes bugged NVRAM) there was a need for a fixup. In case
-+of forcing fixed link for (incorrectly specified) port 5 the code had to
-+actually setup port 8 link.
-+
-+For upstream b53 driver with setup based on DT there is no need for that
-+workaround. In DT we have and require correct ports setup.
-+
-+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Tested-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-+---
-+ drivers/net/dsa/b53/b53_common.c | 6 ------
-+ 1 file changed, 6 deletions(-)
-+
-+--- a/drivers/net/dsa/b53/b53_common.c
-++++ b/drivers/net/dsa/b53/b53_common.c
-+@@ -1291,12 +1291,6 @@ static void b53_adjust_link(struct dsa_s
-+ 				return;
-+ 			}
-+ 		}
-+-	} else if (is5301x(dev)) {
-+-		if (port != dev->cpu_port) {
-+-			b53_force_port_config(dev, dev->cpu_port, 2000,
-+-					      DUPLEX_FULL, true, true);
-+-			b53_force_link(dev, dev->cpu_port, 1);
-+-		}
-+ 	}
-+ 
-+ 	/* Re-negotiate EEE if it was enabled already */
-diff --git a/target/linux/generic/backport-5.15/743-v5.16-0003-net-dsa-b53-Improve-flow-control-setup-on-BCM5301x.patch b/target/linux/generic/backport-5.15/743-v5.16-0003-net-dsa-b53-Improve-flow-control-setup-on-BCM5301x.patch
-new file mode 100644
-index 0000000000..941fa23eb4
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/743-v5.16-0003-net-dsa-b53-Improve-flow-control-setup-on-BCM5301x.patch
-@@ -0,0 +1,32 @@
-+From 3ff26b29230c54fea2353b63124c589b61953e14 Mon Sep 17 00:00:00 2001
-+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
-+Date: Thu, 16 Sep 2021 14:03:53 +0200
-+Subject: [PATCH] net: dsa: b53: Improve flow control setup on BCM5301x
-+MIME-Version: 1.0
-+Content-Type: text/plain; charset=UTF-8
-+Content-Transfer-Encoding: 8bit
-+
-+According to the Broadcom's reference driver flow control needs to be
-+enabled for any CPU switch port (5, 7 or 8 - depending on which one is
-+used). Current code makes it work only for the port 5. Use
-+dsa_is_cpu_port() which solved that problem.
-+
-+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Tested-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-+---
-+ drivers/net/dsa/b53/b53_common.c | 2 +-
-+ 1 file changed, 1 insertion(+), 1 deletion(-)
-+
-+--- a/drivers/net/dsa/b53/b53_common.c
-++++ b/drivers/net/dsa/b53/b53_common.c
-+@@ -1222,7 +1222,7 @@ static void b53_adjust_link(struct dsa_s
-+ 		return;
-+ 
-+ 	/* Enable flow control on BCM5301x's CPU port */
-+-	if (is5301x(dev) && port == dev->cpu_port)
-++	if (is5301x(dev) && dsa_is_cpu_port(ds, port))
-+ 		tx_pause = rx_pause = true;
-+ 
-+ 	if (phydev->pause) {
-diff --git a/target/linux/generic/backport-5.15/743-v5.16-0004-net-dsa-b53-Drop-unused-cpu_port-field.patch b/target/linux/generic/backport-5.15/743-v5.16-0004-net-dsa-b53-Drop-unused-cpu_port-field.patch
-new file mode 100644
-index 0000000000..746a1e3978
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/743-v5.16-0004-net-dsa-b53-Drop-unused-cpu_port-field.patch
-@@ -0,0 +1,205 @@
-+From 7d5af56418d7d01e43247a33b6fe6492ea871923 Mon Sep 17 00:00:00 2001
-+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
-+Date: Thu, 16 Sep 2021 14:03:54 +0200
-+Subject: [PATCH] net: dsa: b53: Drop unused "cpu_port" field
-+MIME-Version: 1.0
-+Content-Type: text/plain; charset=UTF-8
-+Content-Transfer-Encoding: 8bit
-+
-+It's set but never used anymore.
-+
-+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Tested-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-+---
-+ drivers/net/dsa/b53/b53_common.c | 28 ----------------------------
-+ drivers/net/dsa/b53/b53_priv.h   |  1 -
-+ 2 files changed, 29 deletions(-)
-+
-+--- a/drivers/net/dsa/b53/b53_common.c
-++++ b/drivers/net/dsa/b53/b53_common.c
-+@@ -2300,7 +2300,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_bins = 2,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 5,
-+-		.cpu_port = B53_CPU_PORT_25,
-+ 		.duplex_reg = B53_DUPLEX_STAT_FE,
-+ 	},
-+ 	{
-+@@ -2311,7 +2310,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_bins = 2,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 5,
-+-		.cpu_port = B53_CPU_PORT_25,
-+ 		.duplex_reg = B53_DUPLEX_STAT_FE,
-+ 	},
-+ 	{
-+@@ -2322,7 +2320,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+-		.cpu_port = B53_CPU_PORT,
-+ 		.vta_regs = B53_VTA_REGS,
-+ 		.duplex_reg = B53_DUPLEX_STAT_GE,
-+ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
-+@@ -2336,7 +2333,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+-		.cpu_port = B53_CPU_PORT,
-+ 		.vta_regs = B53_VTA_REGS,
-+ 		.duplex_reg = B53_DUPLEX_STAT_GE,
-+ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
-+@@ -2350,7 +2346,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+-		.cpu_port = B53_CPU_PORT,
-+ 		.vta_regs = B53_VTA_REGS_9798,
-+ 		.duplex_reg = B53_DUPLEX_STAT_GE,
-+ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
-+@@ -2364,7 +2359,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+-		.cpu_port = B53_CPU_PORT,
-+ 		.vta_regs = B53_VTA_REGS_9798,
-+ 		.duplex_reg = B53_DUPLEX_STAT_GE,
-+ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
-+@@ -2379,7 +2373,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_buckets = 1024,
-+ 		.vta_regs = B53_VTA_REGS,
-+ 		.imp_port = 8,
-+-		.cpu_port = B53_CPU_PORT,
-+ 		.duplex_reg = B53_DUPLEX_STAT_GE,
-+ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
-+ 		.jumbo_size_reg = B53_JUMBO_MAX_SIZE,
-+@@ -2392,7 +2385,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+-		.cpu_port = B53_CPU_PORT,
-+ 		.vta_regs = B53_VTA_REGS,
-+ 		.duplex_reg = B53_DUPLEX_STAT_GE,
-+ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
-+@@ -2406,7 +2398,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+-		.cpu_port = B53_CPU_PORT,
-+ 		.vta_regs = B53_VTA_REGS,
-+ 		.duplex_reg = B53_DUPLEX_STAT_GE,
-+ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
-+@@ -2420,7 +2411,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+-		.cpu_port = B53_CPU_PORT,
-+ 		.vta_regs = B53_VTA_REGS_63XX,
-+ 		.duplex_reg = B53_DUPLEX_STAT_63XX,
-+ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK_63XX,
-+@@ -2434,7 +2424,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+-		.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */
-+ 		.vta_regs = B53_VTA_REGS,
-+ 		.duplex_reg = B53_DUPLEX_STAT_GE,
-+ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
-+@@ -2448,7 +2437,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+-		.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */
-+ 		.vta_regs = B53_VTA_REGS,
-+ 		.duplex_reg = B53_DUPLEX_STAT_GE,
-+ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
-+@@ -2462,7 +2450,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+-		.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */
-+ 		.vta_regs = B53_VTA_REGS,
-+ 		.duplex_reg = B53_DUPLEX_STAT_GE,
-+ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
-+@@ -2476,7 +2463,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+-		.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */
-+ 		.vta_regs = B53_VTA_REGS,
-+ 		.duplex_reg = B53_DUPLEX_STAT_GE,
-+ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
-+@@ -2490,7 +2476,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+-		.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */
-+ 		.vta_regs = B53_VTA_REGS,
-+ 		.duplex_reg = B53_DUPLEX_STAT_GE,
-+ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
-+@@ -2504,7 +2489,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+-		.cpu_port = B53_CPU_PORT,
-+ 		.vta_regs = B53_VTA_REGS,
-+ 		.duplex_reg = B53_DUPLEX_STAT_GE,
-+ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
-+@@ -2518,7 +2502,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+-		.cpu_port = B53_CPU_PORT,
-+ 		.vta_regs = B53_VTA_REGS,
-+ 		.duplex_reg = B53_DUPLEX_STAT_GE,
-+ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
-+@@ -2547,7 +2530,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 1024,
-+ 		.imp_port = 8,
-+-		.cpu_port = B53_CPU_PORT,
-+ 		.vta_regs = B53_VTA_REGS,
-+ 		.duplex_reg = B53_DUPLEX_STAT_GE,
-+ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
-+@@ -2561,7 +2543,6 @@ static const struct b53_chip_data b53_sw
-+ 		.arl_bins = 4,
-+ 		.arl_buckets = 256,
-+ 		.imp_port = 8,
-+-		.cpu_port = B53_CPU_PORT,
-+ 		.vta_regs = B53_VTA_REGS,
-+ 		.duplex_reg = B53_DUPLEX_STAT_GE,
-+ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
-+@@ -2587,7 +2568,6 @@ static int b53_switch_init(struct b53_de
-+ 			dev->vta_regs[2] = chip->vta_regs[2];
-+ 			dev->jumbo_pm_reg = chip->jumbo_pm_reg;
-+ 			dev->imp_port = chip->imp_port;
-+-			dev->cpu_port = chip->cpu_port;
-+ 			dev->num_vlans = chip->vlans;
-+ 			dev->num_arl_bins = chip->arl_bins;
-+ 			dev->num_arl_buckets = chip->arl_buckets;
-+@@ -2619,13 +2599,6 @@ static int b53_switch_init(struct b53_de
-+ 			break;
-+ #endif
-+ 		}
-+-	} else if (dev->chip_id == BCM53115_DEVICE_ID) {
-+-		u64 strap_value;
-+-
-+-		b53_read48(dev, B53_STAT_PAGE, B53_STRAP_VALUE, &strap_value);
-+-		/* use second IMP port if GMII is enabled */
-+-		if (strap_value & SV_GMII_CTRL_115)
-+-			dev->cpu_port = 5;
-+ 	}
-+ 
-+ 	dev->num_ports = fls(dev->enabled_ports);
-+--- a/drivers/net/dsa/b53/b53_priv.h
-++++ b/drivers/net/dsa/b53/b53_priv.h
-+@@ -124,7 +124,6 @@ struct b53_device {
-+ 	/* used ports mask */
-+ 	u16 enabled_ports;
-+ 	unsigned int imp_port;
-+-	unsigned int cpu_port;
-+ 
-+ 	/* connect specific data */
-+ 	u8 current_page;
-diff --git a/target/linux/generic/backport-5.15/745-v5.16-01-net-phy-at803x-add-support-for-qca-8327-A-variant.patch b/target/linux/generic/backport-5.15/745-v5.16-01-net-phy-at803x-add-support-for-qca-8327-A-variant.patch
-new file mode 100644
-index 0000000000..99d91dfa76
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/745-v5.16-01-net-phy-at803x-add-support-for-qca-8327-A-variant.patch
-@@ -0,0 +1,65 @@
-+From b4df02b562f4aa14ff6811f30e1b4d2159585c59 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Sun, 19 Sep 2021 18:28:15 +0200
-+Subject: net: phy: at803x: add support for qca 8327 A variant internal phy
-+
-+For qca8327 internal phy there are 2 different switch variant with 2
-+different phy id. Add this missing variant so the internal phy can be
-+correctly identified and fixed.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/phy/at803x.c | 25 ++++++++++++++++++++-----
-+ 1 file changed, 20 insertions(+), 5 deletions(-)
-+
-+--- a/drivers/net/phy/at803x.c
-++++ b/drivers/net/phy/at803x.c
-+@@ -150,7 +150,8 @@
-+ #define ATH8035_PHY_ID				0x004dd072
-+ #define AT8030_PHY_ID_MASK			0xffffffef
-+ 
-+-#define QCA8327_PHY_ID				0x004dd034
-++#define QCA8327_A_PHY_ID			0x004dd033
-++#define QCA8327_B_PHY_ID			0x004dd034
-+ #define QCA8337_PHY_ID				0x004dd036
-+ #define QCA8K_PHY_ID_MASK			0xffffffff
-+ 
-+@@ -1413,10 +1414,23 @@ static struct phy_driver at803x_driver[]
-+ 	.get_strings = at803x_get_strings,
-+ 	.get_stats = at803x_get_stats,
-+ }, {
-+-	/* QCA8327 */
-+-	.phy_id = QCA8327_PHY_ID,
-++	/* QCA8327-A from switch QCA8327-AL1A */
-++	.phy_id = QCA8327_A_PHY_ID,
-+ 	.phy_id_mask = QCA8K_PHY_ID_MASK,
-+-	.name = "QCA PHY 8327",
-++	.name = "QCA PHY 8327-A",
-++	/* PHY_GBIT_FEATURES */
-++	.probe = at803x_probe,
-++	.flags = PHY_IS_INTERNAL,
-++	.config_init = qca83xx_config_init,
-++	.soft_reset = genphy_soft_reset,
-++	.get_sset_count = at803x_get_sset_count,
-++	.get_strings = at803x_get_strings,
-++	.get_stats = at803x_get_stats,
-++}, {
-++	/* QCA8327-B from switch QCA8327-BL1A */
-++	.phy_id = QCA8327_B_PHY_ID,
-++	.phy_id_mask = QCA8K_PHY_ID_MASK,
-++	.name = "QCA PHY 8327-B",
-+ 	/* PHY_GBIT_FEATURES */
-+ 	.probe = at803x_probe,
-+ 	.flags = PHY_IS_INTERNAL,
-+@@ -1436,7 +1450,8 @@ static struct mdio_device_id __maybe_unu
-+ 	{ PHY_ID_MATCH_EXACT(ATH8035_PHY_ID) },
-+ 	{ PHY_ID_MATCH_EXACT(ATH9331_PHY_ID) },
-+ 	{ PHY_ID_MATCH_EXACT(QCA8337_PHY_ID) },
-+-	{ PHY_ID_MATCH_EXACT(QCA8327_PHY_ID) },
-++	{ PHY_ID_MATCH_EXACT(QCA8327_A_PHY_ID) },
-++	{ PHY_ID_MATCH_EXACT(QCA8327_B_PHY_ID) },
-+ 	{ }
-+ };
-+ 
-diff --git a/target/linux/generic/backport-5.15/745-v5.16-02-net-phy-at803x-add-resume-suspend-function-to-qca83x.patch b/target/linux/generic/backport-5.15/745-v5.16-02-net-phy-at803x-add-resume-suspend-function-to-qca83x.patch
-new file mode 100644
-index 0000000000..cd83fac83c
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/745-v5.16-02-net-phy-at803x-add-resume-suspend-function-to-qca83x.patch
-@@ -0,0 +1,45 @@
-+From 15b9df4ece17d084f14eb0ca1cf05f2ad497e425 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Sun, 19 Sep 2021 18:28:16 +0200
-+Subject: net: phy: at803x: add resume/suspend function to qca83xx phy
-+
-+Add resume/suspend function to qca83xx internal phy.
-+We can't use the at803x generic function as the documentation lacks of
-+any support for WoL regs.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/phy/at803x.c | 6 ++++++
-+ 1 file changed, 6 insertions(+)
-+
-+--- a/drivers/net/phy/at803x.c
-++++ b/drivers/net/phy/at803x.c
-+@@ -1413,6 +1413,8 @@ static struct phy_driver at803x_driver[]
-+ 	.get_sset_count = at803x_get_sset_count,
-+ 	.get_strings = at803x_get_strings,
-+ 	.get_stats = at803x_get_stats,
-++	.suspend		= genphy_suspend,
-++	.resume			= genphy_resume,
-+ }, {
-+ 	/* QCA8327-A from switch QCA8327-AL1A */
-+ 	.phy_id = QCA8327_A_PHY_ID,
-+@@ -1426,6 +1428,8 @@ static struct phy_driver at803x_driver[]
-+ 	.get_sset_count = at803x_get_sset_count,
-+ 	.get_strings = at803x_get_strings,
-+ 	.get_stats = at803x_get_stats,
-++	.suspend		= genphy_suspend,
-++	.resume			= genphy_resume,
-+ }, {
-+ 	/* QCA8327-B from switch QCA8327-BL1A */
-+ 	.phy_id = QCA8327_B_PHY_ID,
-+@@ -1439,6 +1443,8 @@ static struct phy_driver at803x_driver[]
-+ 	.get_sset_count = at803x_get_sset_count,
-+ 	.get_strings = at803x_get_strings,
-+ 	.get_stats = at803x_get_stats,
-++	.suspend		= genphy_suspend,
-++	.resume			= genphy_resume,
-+ }, };
-+ 
-+ module_phy_driver(at803x_driver);
-diff --git a/target/linux/generic/backport-5.15/745-v5.16-03-net-phy-at803x-fix-spacing-and-improve-name-for-83xx.patch b/target/linux/generic/backport-5.15/745-v5.16-03-net-phy-at803x-fix-spacing-and-improve-name-for-83xx.patch
-new file mode 100644
-index 0000000000..586d8953b2
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/745-v5.16-03-net-phy-at803x-fix-spacing-and-improve-name-for-83xx.patch
-@@ -0,0 +1,95 @@
-+From d44fd8604a4ab92119adb35f05fd87612af722b5 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Sun, 19 Sep 2021 18:28:17 +0200
-+Subject: net: phy: at803x: fix spacing and improve name for 83xx phy
-+
-+Fix spacing and improve name for 83xx phy following other phy in the
-+same driver.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/phy/at803x.c | 60 ++++++++++++++++++++++++------------------------
-+ 1 file changed, 30 insertions(+), 30 deletions(-)
-+
-+--- a/drivers/net/phy/at803x.c
-++++ b/drivers/net/phy/at803x.c
-+@@ -1402,47 +1402,47 @@ static struct phy_driver at803x_driver[]
-+ 	.config_aneg		= at803x_config_aneg,
-+ }, {
-+ 	/* QCA8337 */
-+-	.phy_id = QCA8337_PHY_ID,
-+-	.phy_id_mask = QCA8K_PHY_ID_MASK,
-+-	.name = "QCA PHY 8337",
-++	.phy_id			= QCA8337_PHY_ID,
-++	.phy_id_mask		= QCA8K_PHY_ID_MASK,
-++	.name			= "Qualcomm Atheros 8337 internal PHY",
-+ 	/* PHY_GBIT_FEATURES */
-+-	.probe = at803x_probe,
-+-	.flags = PHY_IS_INTERNAL,
-+-	.config_init = qca83xx_config_init,
-+-	.soft_reset = genphy_soft_reset,
-+-	.get_sset_count = at803x_get_sset_count,
-+-	.get_strings = at803x_get_strings,
-+-	.get_stats = at803x_get_stats,
-++	.probe			= at803x_probe,
-++	.flags			= PHY_IS_INTERNAL,
-++	.config_init		= qca83xx_config_init,
-++	.soft_reset		= genphy_soft_reset,
-++	.get_sset_count		= at803x_get_sset_count,
-++	.get_strings		= at803x_get_strings,
-++	.get_stats		= at803x_get_stats,
-+ 	.suspend		= genphy_suspend,
-+ 	.resume			= genphy_resume,
-+ }, {
-+ 	/* QCA8327-A from switch QCA8327-AL1A */
-+-	.phy_id = QCA8327_A_PHY_ID,
-+-	.phy_id_mask = QCA8K_PHY_ID_MASK,
-+-	.name = "QCA PHY 8327-A",
-++	.phy_id			= QCA8327_A_PHY_ID,
-++	.phy_id_mask		= QCA8K_PHY_ID_MASK,
-++	.name			= "Qualcomm Atheros 8327-A internal PHY",
-+ 	/* PHY_GBIT_FEATURES */
-+-	.probe = at803x_probe,
-+-	.flags = PHY_IS_INTERNAL,
-+-	.config_init = qca83xx_config_init,
-+-	.soft_reset = genphy_soft_reset,
-+-	.get_sset_count = at803x_get_sset_count,
-+-	.get_strings = at803x_get_strings,
-+-	.get_stats = at803x_get_stats,
-++	.probe			= at803x_probe,
-++	.flags			= PHY_IS_INTERNAL,
-++	.config_init		= qca83xx_config_init,
-++	.soft_reset		= genphy_soft_reset,
-++	.get_sset_count		= at803x_get_sset_count,
-++	.get_strings		= at803x_get_strings,
-++	.get_stats		= at803x_get_stats,
-+ 	.suspend		= genphy_suspend,
-+ 	.resume			= genphy_resume,
-+ }, {
-+ 	/* QCA8327-B from switch QCA8327-BL1A */
-+-	.phy_id = QCA8327_B_PHY_ID,
-+-	.phy_id_mask = QCA8K_PHY_ID_MASK,
-+-	.name = "QCA PHY 8327-B",
-++	.phy_id			= QCA8327_B_PHY_ID,
-++	.phy_id_mask		= QCA8K_PHY_ID_MASK,
-++	.name			= "Qualcomm Atheros 8327-B internal PHY",
-+ 	/* PHY_GBIT_FEATURES */
-+-	.probe = at803x_probe,
-+-	.flags = PHY_IS_INTERNAL,
-+-	.config_init = qca83xx_config_init,
-+-	.soft_reset = genphy_soft_reset,
-+-	.get_sset_count = at803x_get_sset_count,
-+-	.get_strings = at803x_get_strings,
-+-	.get_stats = at803x_get_stats,
-++	.probe			= at803x_probe,
-++	.flags			= PHY_IS_INTERNAL,
-++	.config_init		= qca83xx_config_init,
-++	.soft_reset		= genphy_soft_reset,
-++	.get_sset_count		= at803x_get_sset_count,
-++	.get_strings		= at803x_get_strings,
-++	.get_stats		= at803x_get_stats,
-+ 	.suspend		= genphy_suspend,
-+ 	.resume			= genphy_resume,
-+ }, };
-diff --git a/target/linux/generic/backport-5.15/746-v5.16-01-net-phy-at803x-fix-resume-for-QCA8327-phy.patch b/target/linux/generic/backport-5.15/746-v5.16-01-net-phy-at803x-fix-resume-for-QCA8327-phy.patch
-new file mode 100644
-index 0000000000..09797ae83b
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/746-v5.16-01-net-phy-at803x-fix-resume-for-QCA8327-phy.patch
-@@ -0,0 +1,131 @@
-+From ba3c01ee02ed0d821c9f241f179bbc9457542b8f Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Sun, 10 Oct 2021 00:46:15 +0200
-+Subject: net: phy: at803x: fix resume for QCA8327 phy
-+
-+From Documentation phy resume triggers phy reset and restart
-+auto-negotiation. Add a dedicated function to wait reset to finish as
-+it was notice a regression where port sometime are not reliable after a
-+suspend/resume session. The reset wait logic is copied from phy_poll_reset.
-+Add dedicated suspend function to use genphy_suspend only with QCA8337
-+phy and set only additional debug settings for QCA8327. With more test
-+it was reported that QCA8327 doesn't proprely support this mode and
-+using this cause the unreliability of the switch ports, especially the
-+malfunction of the port0.
-+
-+Fixes: 15b9df4ece17 ("net: phy: at803x: add resume/suspend function to qca83xx phy")
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/phy/at803x.c | 69 +++++++++++++++++++++++++++++++++++++++++++-----
-+ 1 file changed, 63 insertions(+), 6 deletions(-)
-+
-+--- a/drivers/net/phy/at803x.c
-++++ b/drivers/net/phy/at803x.c
-+@@ -92,9 +92,14 @@
-+ #define AT803X_DEBUG_REG_5			0x05
-+ #define AT803X_DEBUG_TX_CLK_DLY_EN		BIT(8)
-+ 
-++#define AT803X_DEBUG_REG_HIB_CTRL		0x0b
-++#define   AT803X_DEBUG_HIB_CTRL_SEL_RST_80U	BIT(10)
-++#define   AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE	BIT(13)
-++
-+ #define AT803X_DEBUG_REG_3C			0x3C
-+ 
-+ #define AT803X_DEBUG_REG_3D			0x3D
-++#define   AT803X_DEBUG_GATE_CLK_IN1000		BIT(6)
-+ 
-+ #define AT803X_DEBUG_REG_1F			0x1F
-+ #define AT803X_DEBUG_PLL_ON			BIT(2)
-+@@ -1304,6 +1309,58 @@ static int qca83xx_config_init(struct ph
-+ 	return 0;
-+ }
-+ 
-++static int qca83xx_resume(struct phy_device *phydev)
-++{
-++	int ret, val;
+++		buf = page_to_virt(page);
+++		buf_phys = page_phys;
 ++
-++	/* Skip reset if not suspended */
-++	if (!phydev->suspended)
-++		return 0;
+++		for (s = 0; s < MTK_WED_BUF_PER_PAGE; s++) {
+++			u32 txd_size;
 ++
-++	/* Reinit the port, reset values set by suspend */
-++	qca83xx_config_init(phydev);
+++			txd_size = dev->wlan.init_buf(buf, buf_phys, token++);
 ++
-++	/* Reset the port on port resume */
-++	phy_set_bits(phydev, MII_BMCR, BMCR_RESET | BMCR_ANENABLE);
+++			desc->buf0 = buf_phys;
+++			desc->buf1 = buf_phys + txd_size;
+++			desc->ctrl = FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN0,
+++						txd_size) |
+++				     FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN1,
+++						MTK_WED_BUF_SIZE - txd_size) |
+++				     MTK_WDMA_DESC_CTRL_LAST_SEG1;
+++			desc->info = 0;
+++			desc++;
 ++
-++	/* On resume from suspend the switch execute a reset and
-++	 * restart auto-negotiation. Wait for reset to complete.
-++	 */
-++	ret = phy_read_poll_timeout(phydev, MII_BMCR, val, !(val & BMCR_RESET),
-++				    50000, 600000, true);
-++	if (ret)
-++		return ret;
+++			buf += MTK_WED_BUF_SIZE;
+++			buf_phys += MTK_WED_BUF_SIZE;
+++		}
 ++
-++	msleep(1);
+++		dma_sync_single_for_device(dev->hw->dev, page_phys, PAGE_SIZE,
+++					   DMA_BIDIRECTIONAL);
+++	}
 ++
 ++	return 0;
 ++}
 ++
-++static int qca83xx_suspend(struct phy_device *phydev)
+++static void
+++mtk_wed_free_buffer(struct mtk_wed_device *dev)
 ++{
-++	u16 mask = 0;
+++	struct mtk_wdma_desc *desc = dev->buf_ring.desc;
+++	void **page_list = dev->buf_ring.pages;
+++	int page_idx;
+++	int i;
 ++
-++	/* Only QCA8337 support actual suspend.
-++	 * QCA8327 cause port unreliability when phy suspend
-++	 * is set.
-++	 */
-++	if (phydev->drv->phy_id == QCA8337_PHY_ID) {
-++		genphy_suspend(phydev);
-++	} else {
-++		mask |= ~(BMCR_SPEED1000 | BMCR_FULLDPLX);
-++		phy_modify(phydev, MII_BMCR, mask, 0);
+++	if (!page_list)
+++		return;
+++
+++	if (!desc)
+++		goto free_pagelist;
+++
+++	for (i = 0, page_idx = 0; i < dev->buf_ring.size; i += MTK_WED_BUF_PER_PAGE) {
+++		void *page = page_list[page_idx++];
+++
+++		if (!page)
+++			break;
+++
+++		dma_unmap_page(dev->hw->dev, desc[i].buf0,
+++			       PAGE_SIZE, DMA_BIDIRECTIONAL);
+++		__free_page(page);
 ++	}
 ++
-++	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_3D,
-++			      AT803X_DEBUG_GATE_CLK_IN1000, 0);
+++	dma_free_coherent(dev->hw->dev, dev->buf_ring.size * sizeof(*desc),
+++			  desc, dev->buf_ring.desc_phys);
 ++
-++	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_HIB_CTRL,
-++			      AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE |
-++			      AT803X_DEBUG_HIB_CTRL_SEL_RST_80U, 0);
+++free_pagelist:
+++	kfree(page_list);
+++}
 ++
-++	return 0;
+++static void
+++mtk_wed_free_ring(struct mtk_wed_device *dev, struct mtk_wed_ring *ring)
+++{
+++	if (!ring->desc)
+++		return;
+++
+++	dma_free_coherent(dev->hw->dev, ring->size * sizeof(*ring->desc),
+++			  ring->desc, ring->desc_phys);
 ++}
 ++
-+ static struct phy_driver at803x_driver[] = {
-+ {
-+ 	/* Qualcomm Atheros AR8035 */
-+@@ -1413,8 +1470,8 @@ static struct phy_driver at803x_driver[]
-+ 	.get_sset_count		= at803x_get_sset_count,
-+ 	.get_strings		= at803x_get_strings,
-+ 	.get_stats		= at803x_get_stats,
-+-	.suspend		= genphy_suspend,
-+-	.resume			= genphy_resume,
-++	.suspend		= qca83xx_suspend,
-++	.resume			= qca83xx_resume,
-+ }, {
-+ 	/* QCA8327-A from switch QCA8327-AL1A */
-+ 	.phy_id			= QCA8327_A_PHY_ID,
-+@@ -1428,8 +1485,8 @@ static struct phy_driver at803x_driver[]
-+ 	.get_sset_count		= at803x_get_sset_count,
-+ 	.get_strings		= at803x_get_strings,
-+ 	.get_stats		= at803x_get_stats,
-+-	.suspend		= genphy_suspend,
-+-	.resume			= genphy_resume,
-++	.suspend		= qca83xx_suspend,
-++	.resume			= qca83xx_resume,
-+ }, {
-+ 	/* QCA8327-B from switch QCA8327-BL1A */
-+ 	.phy_id			= QCA8327_B_PHY_ID,
-+@@ -1443,8 +1500,8 @@ static struct phy_driver at803x_driver[]
-+ 	.get_sset_count		= at803x_get_sset_count,
-+ 	.get_strings		= at803x_get_strings,
-+ 	.get_stats		= at803x_get_stats,
-+-	.suspend		= genphy_suspend,
-+-	.resume			= genphy_resume,
-++	.suspend		= qca83xx_suspend,
-++	.resume			= qca83xx_resume,
-+ }, };
-+ 
-+ module_phy_driver(at803x_driver);
-diff --git a/target/linux/generic/backport-5.15/746-v5.16-02-net-phy-at803x-add-DAC-amplitude-fix-for-8327-phy.patch b/target/linux/generic/backport-5.15/746-v5.16-02-net-phy-at803x-add-DAC-amplitude-fix-for-8327-phy.patch
-new file mode 100644
-index 0000000000..c504c37c84
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/746-v5.16-02-net-phy-at803x-add-DAC-amplitude-fix-for-8327-phy.patch
-@@ -0,0 +1,91 @@
-+From 1ca8311949aec5c9447645731ef1c6bc5bd71350 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Sun, 10 Oct 2021 00:46:16 +0200
-+Subject: net: phy: at803x: add DAC amplitude fix for 8327 phy
-+
-+QCA8327 internal phy require DAC amplitude adjustement set to +6% with
-+100m speed. Also add additional define to report a change of the same
-+reg in QCA8337. (different scope it does set 1000m voltage)
-+Add link_change_notify function to set the proper amplitude adjustement
-+on PHY_RUNNING state and disable on any other state.
-+
-+Fixes: b4df02b562f4 ("net: phy: at803x: add support for qca 8327 A variant internal phy")
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/phy/at803x.c | 33 +++++++++++++++++++++++++++++++++
-+ 1 file changed, 33 insertions(+)
-+
-+--- a/drivers/net/phy/at803x.c
-++++ b/drivers/net/phy/at803x.c
-+@@ -87,6 +87,8 @@
-+ #define AT803X_PSSR_MR_AN_COMPLETE		0x0200
-+ 
-+ #define AT803X_DEBUG_REG_0			0x00
-++#define QCA8327_DEBUG_MANU_CTRL_EN		BIT(2)
-++#define QCA8337_DEBUG_MANU_CTRL_EN		GENMASK(3, 2)
-+ #define AT803X_DEBUG_RX_CLK_DLY_EN		BIT(15)
-+ 
-+ #define AT803X_DEBUG_REG_5			0x05
-+@@ -1306,9 +1308,37 @@ static int qca83xx_config_init(struct ph
-+ 		break;
-+ 	}
-+ 
-++	/* QCA8327 require DAC amplitude adjustment for 100m set to +6%.
-++	 * Disable on init and enable only with 100m speed following
-++	 * qca original source code.
-++	 */
-++	if (phydev->drv->phy_id == QCA8327_A_PHY_ID ||
-++	    phydev->drv->phy_id == QCA8327_B_PHY_ID)
-++		at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0,
-++				      QCA8327_DEBUG_MANU_CTRL_EN, 0);
-++
-+ 	return 0;
-+ }
-+ 
-++static void qca83xx_link_change_notify(struct phy_device *phydev)
+++static void
+++mtk_wed_free_tx_rings(struct mtk_wed_device *dev)
 ++{
-++	/* QCA8337 doesn't require DAC Amplitude adjustement */
-++	if (phydev->drv->phy_id == QCA8337_PHY_ID)
-++		return;
+++	int i;
 ++
-++	/* Set DAC Amplitude adjustment to +6% for 100m on link running */
-++	if (phydev->state == PHY_RUNNING) {
-++		if (phydev->speed == SPEED_100)
-++			at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0,
-++					      QCA8327_DEBUG_MANU_CTRL_EN,
-++					      QCA8327_DEBUG_MANU_CTRL_EN);
-++	} else {
-++		/* Reset DAC Amplitude adjustment */
-++		at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0,
-++				      QCA8327_DEBUG_MANU_CTRL_EN, 0);
-++	}
+++	for (i = 0; i < ARRAY_SIZE(dev->tx_ring); i++)
+++		mtk_wed_free_ring(dev, &dev->tx_ring[i]);
+++	for (i = 0; i < ARRAY_SIZE(dev->tx_wdma); i++)
+++		mtk_wed_free_ring(dev, &dev->tx_wdma[i]);
 ++}
 ++
-+ static int qca83xx_resume(struct phy_device *phydev)
-+ {
-+ 	int ret, val;
-+@@ -1463,6 +1493,7 @@ static struct phy_driver at803x_driver[]
-+ 	.phy_id_mask		= QCA8K_PHY_ID_MASK,
-+ 	.name			= "Qualcomm Atheros 8337 internal PHY",
-+ 	/* PHY_GBIT_FEATURES */
-++	.link_change_notify	= qca83xx_link_change_notify,
-+ 	.probe			= at803x_probe,
-+ 	.flags			= PHY_IS_INTERNAL,
-+ 	.config_init		= qca83xx_config_init,
-+@@ -1478,6 +1509,7 @@ static struct phy_driver at803x_driver[]
-+ 	.phy_id_mask		= QCA8K_PHY_ID_MASK,
-+ 	.name			= "Qualcomm Atheros 8327-A internal PHY",
-+ 	/* PHY_GBIT_FEATURES */
-++	.link_change_notify	= qca83xx_link_change_notify,
-+ 	.probe			= at803x_probe,
-+ 	.flags			= PHY_IS_INTERNAL,
-+ 	.config_init		= qca83xx_config_init,
-+@@ -1493,6 +1525,7 @@ static struct phy_driver at803x_driver[]
-+ 	.phy_id_mask		= QCA8K_PHY_ID_MASK,
-+ 	.name			= "Qualcomm Atheros 8327-B internal PHY",
-+ 	/* PHY_GBIT_FEATURES */
-++	.link_change_notify	= qca83xx_link_change_notify,
-+ 	.probe			= at803x_probe,
-+ 	.flags			= PHY_IS_INTERNAL,
-+ 	.config_init		= qca83xx_config_init,
-diff --git a/target/linux/generic/backport-5.15/746-v5.16-03-net-phy-at803x-enable-prefer-master-for-83xx-interna.patch b/target/linux/generic/backport-5.15/746-v5.16-03-net-phy-at803x-enable-prefer-master-for-83xx-interna.patch
-new file mode 100644
-index 0000000000..9f880593f1
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/746-v5.16-03-net-phy-at803x-enable-prefer-master-for-83xx-interna.patch
-@@ -0,0 +1,27 @@
-+From 9d1c29b4028557a496be9c5eb2b4b86063700636 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Sun, 10 Oct 2021 00:46:17 +0200
-+Subject: net: phy: at803x: enable prefer master for 83xx internal phy
-+
-+From original QCA source code the port was set to prefer master as port
-+type in 1000BASE-T mode. Apply the same settings also here.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/phy/at803x.c | 3 +++
-+ 1 file changed, 3 insertions(+)
-+
-+--- a/drivers/net/phy/at803x.c
-++++ b/drivers/net/phy/at803x.c
-+@@ -1317,6 +1317,9 @@ static int qca83xx_config_init(struct ph
-+ 		at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0,
-+ 				      QCA8327_DEBUG_MANU_CTRL_EN, 0);
-+ 
-++	/* Following original QCA sourcecode set port to prefer master */
-++	phy_set_bits(phydev, MII_CTRL1000, CTL1000_PREFER_MASTER);
-++
-+ 	return 0;
-+ }
-+ 
-diff --git a/target/linux/generic/backport-5.15/746-v5.16-04-net-phy-at803x-better-describe-debug-regs.patch b/target/linux/generic/backport-5.15/746-v5.16-04-net-phy-at803x-better-describe-debug-regs.patch
-new file mode 100644
-index 0000000000..89e9b3f662
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/746-v5.16-04-net-phy-at803x-better-describe-debug-regs.patch
-@@ -0,0 +1,127 @@
-+From 67999555ff42e91de7654488d9a7735bd9e84555 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Sun, 10 Oct 2021 00:46:18 +0200
-+Subject: net: phy: at803x: better describe debug regs
-+
-+Give a name to known debug regs from Documentation instead of using
-+unknown hex values.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/phy/at803x.c | 30 +++++++++++++++---------------
-+ 1 file changed, 15 insertions(+), 15 deletions(-)
-+
-+--- a/drivers/net/phy/at803x.c
-++++ b/drivers/net/phy/at803x.c
-+@@ -86,12 +86,12 @@
-+ #define AT803X_PSSR				0x11	/*PHY-Specific Status Register*/
-+ #define AT803X_PSSR_MR_AN_COMPLETE		0x0200
-+ 
-+-#define AT803X_DEBUG_REG_0			0x00
-++#define AT803X_DEBUG_ANALOG_TEST_CTRL		0x00
-+ #define QCA8327_DEBUG_MANU_CTRL_EN		BIT(2)
-+ #define QCA8337_DEBUG_MANU_CTRL_EN		GENMASK(3, 2)
-+ #define AT803X_DEBUG_RX_CLK_DLY_EN		BIT(15)
-+ 
-+-#define AT803X_DEBUG_REG_5			0x05
-++#define AT803X_DEBUG_SYSTEM_CTRL_MODE		0x05
-+ #define AT803X_DEBUG_TX_CLK_DLY_EN		BIT(8)
-+ 
-+ #define AT803X_DEBUG_REG_HIB_CTRL		0x0b
-+@@ -100,7 +100,7 @@
-+ 
-+ #define AT803X_DEBUG_REG_3C			0x3C
-+ 
-+-#define AT803X_DEBUG_REG_3D			0x3D
-++#define AT803X_DEBUG_REG_GREEN			0x3D
-+ #define   AT803X_DEBUG_GATE_CLK_IN1000		BIT(6)
-+ 
-+ #define AT803X_DEBUG_REG_1F			0x1F
-+@@ -284,25 +284,25 @@ static int at803x_read_page(struct phy_d
-+ 
-+ static int at803x_enable_rx_delay(struct phy_device *phydev)
-+ {
-+-	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0, 0,
-++	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, 0,
-+ 				     AT803X_DEBUG_RX_CLK_DLY_EN);
-+ }
-+ 
-+ static int at803x_enable_tx_delay(struct phy_device *phydev)
-+ {
-+-	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_5, 0,
-++	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE, 0,
-+ 				     AT803X_DEBUG_TX_CLK_DLY_EN);
-+ }
-+ 
-+ static int at803x_disable_rx_delay(struct phy_device *phydev)
-+ {
-+-	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0,
-++	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
-+ 				     AT803X_DEBUG_RX_CLK_DLY_EN, 0);
-+ }
-+ 
-+ static int at803x_disable_tx_delay(struct phy_device *phydev)
-+ {
-+-	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_5,
-++	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE,
-+ 				     AT803X_DEBUG_TX_CLK_DLY_EN, 0);
-+ }
-+ 
-+@@ -1292,9 +1292,9 @@ static int qca83xx_config_init(struct ph
-+ 	switch (switch_revision) {
-+ 	case 1:
-+ 		/* For 100M waveform */
-+-		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_0, 0x02ea);
-++		at803x_debug_reg_write(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, 0x02ea);
-+ 		/* Turn on Gigabit clock */
-+-		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_3D, 0x68a0);
-++		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x68a0);
-+ 		break;
-+ 
-+ 	case 2:
-+@@ -1302,8 +1302,8 @@ static int qca83xx_config_init(struct ph
-+ 		fallthrough;
-+ 	case 4:
-+ 		phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_AZ_DEBUG, 0x803f);
-+-		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_3D, 0x6860);
-+-		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_5, 0x2c46);
-++		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x6860);
-++		at803x_debug_reg_write(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE, 0x2c46);
-+ 		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_3C, 0x6000);
-+ 		break;
-+ 	}
-+@@ -1314,7 +1314,7 @@ static int qca83xx_config_init(struct ph
-+ 	 */
-+ 	if (phydev->drv->phy_id == QCA8327_A_PHY_ID ||
-+ 	    phydev->drv->phy_id == QCA8327_B_PHY_ID)
-+-		at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0,
-++		at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
-+ 				      QCA8327_DEBUG_MANU_CTRL_EN, 0);
-+ 
-+ 	/* Following original QCA sourcecode set port to prefer master */
-+@@ -1332,12 +1332,12 @@ static void qca83xx_link_change_notify(s
-+ 	/* Set DAC Amplitude adjustment to +6% for 100m on link running */
-+ 	if (phydev->state == PHY_RUNNING) {
-+ 		if (phydev->speed == SPEED_100)
-+-			at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0,
-++			at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
-+ 					      QCA8327_DEBUG_MANU_CTRL_EN,
-+ 					      QCA8327_DEBUG_MANU_CTRL_EN);
-+ 	} else {
-+ 		/* Reset DAC Amplitude adjustment */
-+-		at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0,
-++		at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
-+ 				      QCA8327_DEBUG_MANU_CTRL_EN, 0);
-+ 	}
-+ }
-+@@ -1384,7 +1384,7 @@ static int qca83xx_suspend(struct phy_de
-+ 		phy_modify(phydev, MII_BMCR, mask, 0);
-+ 	}
-+ 
-+-	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_3D,
-++	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_GREEN,
-+ 			      AT803X_DEBUG_GATE_CLK_IN1000, 0);
-+ 
-+ 	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_HIB_CTRL,
-diff --git a/target/linux/generic/backport-5.15/747-v5.16-01-dsa-qca8k-add-mac-power-sel-support.patch b/target/linux/generic/backport-5.15/747-v5.16-01-dsa-qca8k-add-mac-power-sel-support.patch
-new file mode 100644
-index 0000000000..c8d424de38
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/747-v5.16-01-dsa-qca8k-add-mac-power-sel-support.patch
-@@ -0,0 +1,80 @@
-+From d8b6f5bae6d3b648a67b6958cb98e4e97256d652 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Thu, 14 Oct 2021 00:39:06 +0200
-+Subject: dsa: qca8k: add mac_power_sel support
-+
-+Add missing mac power sel support needed for ipq8064/5 SoC that require
-+1.8v for the internal regulator port instead of the default 1.5v.
-+If other device needs this, consider adding a dedicated binding to
-+support this.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 31 +++++++++++++++++++++++++++++++
-+ drivers/net/dsa/qca8k.h |  5 +++++
-+ 2 files changed, 36 insertions(+)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -951,6 +951,33 @@ qca8k_setup_of_rgmii_delay(struct qca8k_
-+ }
-+ 
-+ static int
-++qca8k_setup_mac_pwr_sel(struct qca8k_priv *priv)
+++static void
+++mtk_wed_set_ext_int(struct mtk_wed_device *dev, bool en)
 ++{
-++	u32 mask = 0;
-++	int ret = 0;
+++	u32 mask = MTK_WED_EXT_INT_STATUS_ERROR_MASK;
 ++
-++	/* SoC specific settings for ipq8064.
-++	 * If more device require this consider adding
-++	 * a dedicated binding.
-++	 */
-++	if (of_machine_is_compatible("qcom,ipq8064"))
-++		mask |= QCA8K_MAC_PWR_RGMII0_1_8V;
+++	if (!dev->hw->num_flows)
+++		mask &= ~MTK_WED_EXT_INT_STATUS_TKID_WO_PYLD;
 ++
-++	/* SoC specific settings for ipq8065 */
-++	if (of_machine_is_compatible("qcom,ipq8065"))
-++		mask |= QCA8K_MAC_PWR_RGMII1_1_8V;
+++	wed_w32(dev, MTK_WED_EXT_INT_MASK, en ? mask : 0);
+++	wed_r32(dev, MTK_WED_EXT_INT_MASK);
+++}
 ++
-++	if (mask) {
-++		ret = qca8k_rmw(priv, QCA8K_REG_MAC_PWR_SEL,
-++				QCA8K_MAC_PWR_RGMII0_1_8V |
-++				QCA8K_MAC_PWR_RGMII1_1_8V,
-++				mask);
-++	}
+++static void
+++mtk_wed_stop(struct mtk_wed_device *dev)
+++{
+++	regmap_write(dev->hw->mirror, dev->hw->index * 4, 0);
+++	mtk_wed_set_ext_int(dev, false);
 ++
-++	return ret;
+++	wed_clr(dev, MTK_WED_CTRL,
+++		MTK_WED_CTRL_WDMA_INT_AGENT_EN |
+++		MTK_WED_CTRL_WPDMA_INT_AGENT_EN |
+++		MTK_WED_CTRL_WED_TX_BM_EN |
+++		MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
+++	wed_w32(dev, MTK_WED_WPDMA_INT_TRIGGER, 0);
+++	wed_w32(dev, MTK_WED_WDMA_INT_TRIGGER, 0);
+++	wdma_w32(dev, MTK_WDMA_INT_MASK, 0);
+++	wdma_w32(dev, MTK_WDMA_INT_GRP2, 0);
+++	wed_w32(dev, MTK_WED_WPDMA_INT_MASK, 0);
+++
+++	wed_clr(dev, MTK_WED_GLO_CFG,
+++		MTK_WED_GLO_CFG_TX_DMA_EN |
+++		MTK_WED_GLO_CFG_RX_DMA_EN);
+++	wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
+++		MTK_WED_WPDMA_GLO_CFG_TX_DRV_EN |
+++		MTK_WED_WPDMA_GLO_CFG_RX_DRV_EN);
+++	wed_clr(dev, MTK_WED_WDMA_GLO_CFG,
+++		MTK_WED_WDMA_GLO_CFG_RX_DRV_EN);
 ++}
 ++
-++static int
-+ qca8k_setup(struct dsa_switch *ds)
-+ {
-+ 	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
-+@@ -979,6 +1006,10 @@ qca8k_setup(struct dsa_switch *ds)
-+ 	if (ret)
-+ 		return ret;
-+ 
-++	ret = qca8k_setup_mac_pwr_sel(priv);
-++	if (ret)
-++		return ret;
+++static void
+++mtk_wed_detach(struct mtk_wed_device *dev)
+++{
+++	struct device_node *wlan_node = dev->wlan.pci_dev->dev.of_node;
+++	struct mtk_wed_hw *hw = dev->hw;
 ++
-+ 	/* Enable CPU Port */
-+ 	ret = qca8k_reg_set(priv, QCA8K_REG_GLOBAL_FW_CTRL0,
-+ 			    QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -100,6 +100,11 @@
-+ #define   QCA8K_SGMII_MODE_CTRL_PHY			(1 << 22)
-+ #define   QCA8K_SGMII_MODE_CTRL_MAC			(2 << 22)
-+ 
-++/* MAC_PWR_SEL registers */
-++#define QCA8K_REG_MAC_PWR_SEL				0x0e4
-++#define   QCA8K_MAC_PWR_RGMII1_1_8V			BIT(18)
-++#define   QCA8K_MAC_PWR_RGMII0_1_8V			BIT(19)
+++	mutex_lock(&hw_lock);
 ++
-+ /* EEE control registers */
-+ #define QCA8K_REG_EEE_CTRL				0x100
-+ #define  QCA8K_REG_EEE_CTRL_LPI_EN(_i)			((_i + 1) * 2)
-diff --git a/target/linux/generic/backport-5.15/747-v5.16-02-dt-bindings-net-dsa-qca8k-Add-SGMII-clock-phase-prop.patch b/target/linux/generic/backport-5.15/747-v5.16-02-dt-bindings-net-dsa-qca8k-Add-SGMII-clock-phase-prop.patch
-new file mode 100644
-index 0000000000..bd768ec27d
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/747-v5.16-02-dt-bindings-net-dsa-qca8k-Add-SGMII-clock-phase-prop.patch
-@@ -0,0 +1,30 @@
-+From fdbf35df9c091db9c46e57e9938e3f7a4f603a7c Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Thu, 14 Oct 2021 00:39:07 +0200
-+Subject: dt-bindings: net: dsa: qca8k: Add SGMII clock phase properties
-+
-+Add names and descriptions of additional PORT0_PAD_CTRL properties.
-+qca,sgmii-(rx|tx)clk-falling-edge are for setting the respective clock
-+phase to failling edge.
-+
-+Co-developed-by: Matthew Hagan <mnhagan88@gmail.com>
-+Signed-off-by: Matthew Hagan <mnhagan88@gmail.com>
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ Documentation/devicetree/bindings/net/dsa/qca8k.txt | 4 ++++
-+ 1 file changed, 4 insertions(+)
-+
-+--- a/Documentation/devicetree/bindings/net/dsa/qca8k.txt
-++++ b/Documentation/devicetree/bindings/net/dsa/qca8k.txt
-+@@ -37,6 +37,10 @@ A CPU port node has the following option
-+                           managed entity. See
-+                           Documentation/devicetree/bindings/net/fixed-link.txt
-+                           for details.
-++- qca,sgmii-rxclk-falling-edge: Set the receive clock phase to falling edge.
-++                                Mostly used in qca8327 with CPU port 0 set to
-++                                sgmii.
-++- qca,sgmii-txclk-falling-edge: Set the transmit clock phase to falling edge.
-+ 
-+ For QCA8K the 'fixed-link' sub-node supports only the following properties:
-+ 
-diff --git a/target/linux/generic/backport-5.15/747-v5.16-03-net-dsa-qca8k-add-support-for-sgmii-falling-edge.patch b/target/linux/generic/backport-5.15/747-v5.16-03-net-dsa-qca8k-add-support-for-sgmii-falling-edge.patch
-new file mode 100644
-index 0000000000..e464452d82
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/747-v5.16-03-net-dsa-qca8k-add-support-for-sgmii-falling-edge.patch
-@@ -0,0 +1,127 @@
-+From 6c43809bf1bee76c434e365a26546a92a5fbec14 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Thu, 14 Oct 2021 00:39:08 +0200
-+Subject: net: dsa: qca8k: add support for sgmii falling edge
-+
-+Add support for this in the qca8k driver. Also add support for SGMII
-+rx/tx clock falling edge. This is only present for pad0, pad5 and
-+pad6 have these bit reserved from Documentation. Add a comment that this
-+is hardcoded to PAD0 as qca8327/28/34/37 have an unique sgmii line and
-+setting falling in port0 applies to both configuration with sgmii used
-+for port0 or port6.
-+
-+Co-developed-by: Matthew Hagan <mnhagan88@gmail.com>
-+Signed-off-by: Matthew Hagan <mnhagan88@gmail.com>
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++
-+ drivers/net/dsa/qca8k.h |  4 ++++
-+ 2 files changed, 67 insertions(+)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -978,6 +978,42 @@ qca8k_setup_mac_pwr_sel(struct qca8k_pri
-+ }
-+ 
-+ static int
-++qca8k_parse_port_config(struct qca8k_priv *priv)
-++{
-++	struct device_node *port_dn;
-++	phy_interface_t mode;
-++	struct dsa_port *dp;
-++	int port, ret;
+++	mtk_wed_stop(dev);
 ++
-++	/* We have 2 CPU port. Check them */
-++	for (port = 0; port < QCA8K_NUM_PORTS; port++) {
-++		/* Skip every other port */
-++		if (port != 0 && port != 6)
-++			continue;
+++	wdma_w32(dev, MTK_WDMA_RESET_IDX, MTK_WDMA_RESET_IDX_RX);
+++	wdma_w32(dev, MTK_WDMA_RESET_IDX, 0);
 ++
-++		dp = dsa_to_port(priv->ds, port);
-++		port_dn = dp->dn;
+++	mtk_wed_reset(dev, MTK_WED_RESET_WED);
 ++
-++		if (!of_device_is_available(port_dn))
-++			continue;
+++	mtk_wed_free_buffer(dev);
+++	mtk_wed_free_tx_rings(dev);
 ++
-++		ret = of_get_phy_mode(port_dn, &mode);
-++		if (ret)
-++			continue;
+++	if (of_dma_is_coherent(wlan_node))
+++		regmap_update_bits(hw->hifsys, HIFSYS_DMA_AG_MAP,
+++				   BIT(hw->index), BIT(hw->index));
 ++
-++		if (mode == PHY_INTERFACE_MODE_SGMII) {
-++			if (of_property_read_bool(port_dn, "qca,sgmii-txclk-falling-edge"))
-++				priv->sgmii_tx_clk_falling_edge = true;
+++	if (!hw_list[!hw->index]->wed_dev &&
+++	    hw->eth->dma_dev != hw->eth->dev)
+++		mtk_eth_set_dma_device(hw->eth, hw->eth->dev);
 ++
-++			if (of_property_read_bool(port_dn, "qca,sgmii-rxclk-falling-edge"))
-++				priv->sgmii_rx_clk_falling_edge = true;
-++		}
-++	}
+++	memset(dev, 0, sizeof(*dev));
+++	module_put(THIS_MODULE);
 ++
-++	return 0;
+++	hw->wed_dev = NULL;
+++	mutex_unlock(&hw_lock);
 ++}
 ++
-++static int
-+ qca8k_setup(struct dsa_switch *ds)
-+ {
-+ 	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
-+@@ -990,6 +1026,11 @@ qca8k_setup(struct dsa_switch *ds)
-+ 		return -EINVAL;
-+ 	}
-+ 
-++	/* Parse CPU port config to be later used in phy_link mac_config */
-++	ret = qca8k_parse_port_config(priv);
-++	if (ret)
-++		return ret;
-++
-+ 	mutex_init(&priv->reg_mutex);
-+ 
-+ 	/* Start by setting up the register mapping */
-+@@ -1274,6 +1315,28 @@ qca8k_phylink_mac_config(struct dsa_swit
-+ 		}
-+ 
-+ 		qca8k_write(priv, QCA8K_REG_SGMII_CTRL, val);
+++static void
+++mtk_wed_hw_init_early(struct mtk_wed_device *dev)
+++{
+++	u32 mask, set;
+++	u32 offset;
 ++
-++		/* For qca8327/qca8328/qca8334/qca8338 sgmii is unique and
-++		 * falling edge is set writing in the PORT0 PAD reg
-++		 */
-++		if (priv->switch_id == QCA8K_ID_QCA8327 ||
-++		    priv->switch_id == QCA8K_ID_QCA8337)
-++			reg = QCA8K_REG_PORT0_PAD_CTRL;
+++	mtk_wed_stop(dev);
+++	mtk_wed_reset(dev, MTK_WED_RESET_WED);
 ++
-++		val = 0;
+++	mask = MTK_WED_WDMA_GLO_CFG_BT_SIZE |
+++	       MTK_WED_WDMA_GLO_CFG_DYNAMIC_DMAD_RECYCLE |
+++	       MTK_WED_WDMA_GLO_CFG_RX_DIS_FSM_AUTO_IDLE;
+++	set = FIELD_PREP(MTK_WED_WDMA_GLO_CFG_BT_SIZE, 2) |
+++	      MTK_WED_WDMA_GLO_CFG_DYNAMIC_SKIP_DMAD_PREP |
+++	      MTK_WED_WDMA_GLO_CFG_IDLE_DMAD_SUPPLY;
+++	wed_m32(dev, MTK_WED_WDMA_GLO_CFG, mask, set);
 ++
-++		/* SGMII Clock phase configuration */
-++		if (priv->sgmii_rx_clk_falling_edge)
-++			val |= QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE;
+++	wdma_set(dev, MTK_WDMA_GLO_CFG, MTK_WDMA_GLO_CFG_RX_INFO_PRERES);
 ++
-++		if (priv->sgmii_tx_clk_falling_edge)
-++			val |= QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE;
+++	offset = dev->hw->index ? 0x04000400 : 0;
+++	wed_w32(dev, MTK_WED_WDMA_OFFSET0, 0x2a042a20 + offset);
+++	wed_w32(dev, MTK_WED_WDMA_OFFSET1, 0x29002800 + offset);
 ++
-++		if (val)
-++			ret = qca8k_rmw(priv, reg,
-++					QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE |
-++					QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE,
-++					val);
-+ 		break;
-+ 	default:
-+ 		dev_err(ds->dev, "xMII mode %s not supported for port %d\n",
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -35,6 +35,8 @@
-+ #define   QCA8K_MASK_CTRL_DEVICE_ID_MASK		GENMASK(15, 8)
-+ #define   QCA8K_MASK_CTRL_DEVICE_ID(x)			((x) >> 8)
-+ #define QCA8K_REG_PORT0_PAD_CTRL			0x004
-++#define   QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE	BIT(19)
-++#define   QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE	BIT(18)
-+ #define QCA8K_REG_PORT5_PAD_CTRL			0x008
-+ #define QCA8K_REG_PORT6_PAD_CTRL			0x00c
-+ #define   QCA8K_PORT_PAD_RGMII_EN			BIT(26)
-+@@ -260,6 +262,8 @@ struct qca8k_priv {
-+ 	u8 switch_revision;
-+ 	u8 rgmii_tx_delay;
-+ 	u8 rgmii_rx_delay;
-++	bool sgmii_rx_clk_falling_edge;
-++	bool sgmii_tx_clk_falling_edge;
-+ 	bool legacy_phy_port_mapping;
-+ 	struct regmap *regmap;
-+ 	struct mii_bus *bus;
-diff --git a/target/linux/generic/backport-5.15/747-v5.16-04-dt-bindings-net-dsa-qca8k-Document-support-for-CPU-p.patch b/target/linux/generic/backport-5.15/747-v5.16-04-dt-bindings-net-dsa-qca8k-Document-support-for-CPU-p.patch
-new file mode 100644
-index 0000000000..606ac0af3d
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/747-v5.16-04-dt-bindings-net-dsa-qca8k-Document-support-for-CPU-p.patch
-@@ -0,0 +1,29 @@
-+From 731d613338ec6de482053ffa3f71be2325b0f8eb Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Thu, 14 Oct 2021 00:39:09 +0200
-+Subject: dt-bindings: net: dsa: qca8k: Document support for CPU port 6
-+
-+The switch now support CPU port to be set 6 instead of be hardcoded to
-+0. Document support for it and describe logic selection.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ Documentation/devicetree/bindings/net/dsa/qca8k.txt | 6 +++++-
-+ 1 file changed, 5 insertions(+), 1 deletion(-)
-+
-+--- a/Documentation/devicetree/bindings/net/dsa/qca8k.txt
-++++ b/Documentation/devicetree/bindings/net/dsa/qca8k.txt
-+@@ -29,7 +29,11 @@ the mdio MASTER is used as communication
-+ Don't use mixed external and internal mdio-bus configurations, as this is
-+ not supported by the hardware.
-+ 
-+-The CPU port of this switch is always port 0.
-++This switch support 2 CPU port. Normally and advised configuration is with
-++CPU port set to port 0. It is also possible to set the CPU port to port 6
-++if the device requires it. The driver will configure the switch to the defined
-++port. With both CPU port declared the first CPU port is selected as primary
-++and the secondary CPU ignored.
-+ 
-+ A CPU port node has the following optional node:
-+ 
-diff --git a/target/linux/generic/backport-5.15/747-v5.16-05-net-dsa-qca8k-add-support-for-cpu-port-6.patch b/target/linux/generic/backport-5.15/747-v5.16-05-net-dsa-qca8k-add-support-for-cpu-port-6.patch
-new file mode 100644
-index 0000000000..320db8fa9f
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/747-v5.16-05-net-dsa-qca8k-add-support-for-cpu-port-6.patch
-@@ -0,0 +1,153 @@
-+From 3fcf734aa482487df83cf8f18608438fcf59127f Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Thu, 14 Oct 2021 00:39:10 +0200
-+Subject: net: dsa: qca8k: add support for cpu port 6
-+
-+Currently CPU port is always hardcoded to port 0. This switch have 2 CPU
-+ports. The original intention of this driver seems to be use the
-+mac06_exchange bit to swap MAC0 with MAC6 in the strange configuration
-+where device have connected only the CPU port 6. To skip the
-+introduction of a new binding, rework the driver to address the
-+secondary CPU port as primary and drop any reference of hardcoded port.
-+With configuration of mac06 exchange, just skip the definition of port0
-+and define the CPU port as a secondary. The driver will autoconfigure
-+the switch to use that as the primary CPU port.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 51 ++++++++++++++++++++++++++++++++++---------------
-+ drivers/net/dsa/qca8k.h |  2 --
-+ 2 files changed, 36 insertions(+), 17 deletions(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -977,6 +977,22 @@ qca8k_setup_mac_pwr_sel(struct qca8k_pri
-+ 	return ret;
-+ }
-+ 
-++static int qca8k_find_cpu_port(struct dsa_switch *ds)
+++	wed_w32(dev, MTK_WED_PCIE_CFG_BASE, MTK_PCIE_BASE(dev->hw->index));
+++	wed_w32(dev, MTK_WED_WPDMA_CFG_BASE, dev->wlan.wpdma_phys);
+++}
+++
+++static void
+++mtk_wed_hw_init(struct mtk_wed_device *dev)
 ++{
-++	struct qca8k_priv *priv = ds->priv;
+++	if (dev->init_done)
+++		return;
 ++
-++	/* Find the connected cpu port. Valid port are 0 or 6 */
-++	if (dsa_is_cpu_port(ds, 0))
-++		return 0;
+++	dev->init_done = true;
+++	mtk_wed_set_ext_int(dev, false);
+++	wed_w32(dev, MTK_WED_TX_BM_CTRL,
+++		MTK_WED_TX_BM_CTRL_PAUSE |
+++		FIELD_PREP(MTK_WED_TX_BM_CTRL_VLD_GRP_NUM,
+++			   dev->buf_ring.size / 128) |
+++		FIELD_PREP(MTK_WED_TX_BM_CTRL_RSV_GRP_NUM,
+++			   MTK_WED_TX_RING_SIZE / 256));
 ++
-++	dev_dbg(priv->dev, "port 0 is not the CPU port. Checking port 6");
+++	wed_w32(dev, MTK_WED_TX_BM_BASE, dev->buf_ring.desc_phys);
 ++
-++	if (dsa_is_cpu_port(ds, 6))
-++		return 6;
+++	wed_w32(dev, MTK_WED_TX_BM_TKID,
+++		FIELD_PREP(MTK_WED_TX_BM_TKID_START,
+++			   dev->wlan.token_start) |
+++		FIELD_PREP(MTK_WED_TX_BM_TKID_END,
+++			   dev->wlan.token_start + dev->wlan.nbuf - 1));
 ++
-++	return -EINVAL;
+++	wed_w32(dev, MTK_WED_TX_BM_BUF_LEN, MTK_WED_PKT_SIZE);
+++
+++	wed_w32(dev, MTK_WED_TX_BM_DYN_THR,
+++		FIELD_PREP(MTK_WED_TX_BM_DYN_THR_LO, 1) |
+++		MTK_WED_TX_BM_DYN_THR_HI);
+++
+++	mtk_wed_reset(dev, MTK_WED_RESET_TX_BM);
+++
+++	wed_set(dev, MTK_WED_CTRL,
+++		MTK_WED_CTRL_WED_TX_BM_EN |
+++		MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
+++
+++	wed_clr(dev, MTK_WED_TX_BM_CTRL, MTK_WED_TX_BM_CTRL_PAUSE);
 ++}
 ++
-+ static int
-+ qca8k_parse_port_config(struct qca8k_priv *priv)
-+ {
-+@@ -1017,13 +1033,13 @@ static int
-+ qca8k_setup(struct dsa_switch *ds)
-+ {
-+ 	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
-+-	int ret, i;
-++	int cpu_port, ret, i;
-+ 	u32 mask;
-+ 
-+-	/* Make sure that port 0 is the cpu port */
-+-	if (!dsa_is_cpu_port(ds, 0)) {
-+-		dev_err(priv->dev, "port 0 is not the CPU port");
-+-		return -EINVAL;
-++	cpu_port = qca8k_find_cpu_port(ds);
-++	if (cpu_port < 0) {
-++		dev_err(priv->dev, "No cpu port configured in both cpu port0 and port6");
-++		return cpu_port;
-+ 	}
-+ 
-+ 	/* Parse CPU port config to be later used in phy_link mac_config */
-+@@ -1065,7 +1081,7 @@ qca8k_setup(struct dsa_switch *ds)
-+ 		dev_warn(priv->dev, "mib init failed");
-+ 
-+ 	/* Enable QCA header mode on the cpu port */
-+-	ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(QCA8K_CPU_PORT),
-++	ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(cpu_port),
-+ 			  QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_TX_S |
-+ 			  QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_RX_S);
-+ 	if (ret) {
-+@@ -1087,10 +1103,10 @@ qca8k_setup(struct dsa_switch *ds)
-+ 
-+ 	/* Forward all unknown frames to CPU port for Linux processing */
-+ 	ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1,
-+-			  BIT(0) << QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S |
-+-			  BIT(0) << QCA8K_GLOBAL_FW_CTRL1_BC_DP_S |
-+-			  BIT(0) << QCA8K_GLOBAL_FW_CTRL1_MC_DP_S |
-+-			  BIT(0) << QCA8K_GLOBAL_FW_CTRL1_UC_DP_S);
-++			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S |
-++			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_BC_DP_S |
-++			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_MC_DP_S |
-++			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_UC_DP_S);
-+ 	if (ret)
-+ 		return ret;
-+ 
-+@@ -1098,7 +1114,7 @@ qca8k_setup(struct dsa_switch *ds)
-+ 	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
-+ 		/* CPU port gets connected to all user ports of the switch */
-+ 		if (dsa_is_cpu_port(ds, i)) {
-+-			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(QCA8K_CPU_PORT),
-++			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(cpu_port),
-+ 					QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds));
-+ 			if (ret)
-+ 				return ret;
-+@@ -1110,7 +1126,7 @@ qca8k_setup(struct dsa_switch *ds)
-+ 
-+ 			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
-+ 					QCA8K_PORT_LOOKUP_MEMBER,
-+-					BIT(QCA8K_CPU_PORT));
-++					BIT(cpu_port));
-+ 			if (ret)
-+ 				return ret;
-+ 
-+@@ -1616,9 +1632,12 @@ static int
-+ qca8k_port_bridge_join(struct dsa_switch *ds, int port, struct net_device *br)
-+ {
-+ 	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
-+-	int port_mask = BIT(QCA8K_CPU_PORT);
-++	int port_mask, cpu_port;
-+ 	int i, ret;
-+ 
-++	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
-++	port_mask = BIT(cpu_port);
+++static void
+++mtk_wed_ring_reset(struct mtk_wdma_desc *desc, int size)
+++{
+++	int i;
 ++
-+ 	for (i = 1; i < QCA8K_NUM_PORTS; i++) {
-+ 		if (dsa_to_port(ds, i)->bridge_dev != br)
-+ 			continue;
-+@@ -1645,7 +1664,9 @@ static void
-+ qca8k_port_bridge_leave(struct dsa_switch *ds, int port, struct net_device *br)
-+ {
-+ 	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
-+-	int i;
-++	int cpu_port, i;
+++	for (i = 0; i < size; i++) {
+++		desc[i].buf0 = 0;
+++		desc[i].ctrl = cpu_to_le32(MTK_WDMA_DESC_CTRL_DMA_DONE);
+++		desc[i].buf1 = 0;
+++		desc[i].info = 0;
+++	}
+++}
 ++
-++	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
-+ 
-+ 	for (i = 1; i < QCA8K_NUM_PORTS; i++) {
-+ 		if (dsa_to_port(ds, i)->bridge_dev != br)
-+@@ -1662,7 +1683,7 @@ qca8k_port_bridge_leave(struct dsa_switc
-+ 	 * this port
-+ 	 */
-+ 	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
-+-		  QCA8K_PORT_LOOKUP_MEMBER, BIT(QCA8K_CPU_PORT));
-++		  QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port));
-+ }
-+ 
-+ static int
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -24,8 +24,6 @@
-+ 
-+ #define QCA8K_NUM_FDB_RECORDS				2048
-+ 
-+-#define QCA8K_CPU_PORT					0
-+-
-+ #define QCA8K_PORT_VID_DEF				1
-+ 
-+ /* Global control registers */
-diff --git a/target/linux/generic/backport-5.15/747-v5.16-06-net-dsa-qca8k-rework-rgmii-delay-logic-and-scan-for-.patch b/target/linux/generic/backport-5.15/747-v5.16-06-net-dsa-qca8k-rework-rgmii-delay-logic-and-scan-for-.patch
-new file mode 100644
-index 0000000000..de201764f9
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/747-v5.16-06-net-dsa-qca8k-rework-rgmii-delay-logic-and-scan-for-.patch
-@@ -0,0 +1,295 @@
-+From 5654ec78dd7e64b1e04777b24007344329e6a63b Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Thu, 14 Oct 2021 00:39:11 +0200
-+Subject: net: dsa: qca8k: rework rgmii delay logic and scan for cpu port 6
-+
-+Future proof commit. This switch have 2 CPU ports and one valid
-+configuration is first CPU port set to sgmii and second CPU port set to
-+rgmii-id. The current implementation detects delay only for CPU port
-+zero set to rgmii and doesn't count any delay set in a secondary CPU
-+port. Drop the current delay scan function and move it to the sgmii
-+parser function to generalize and implicitly add support for secondary
-+CPU port set to rgmii-id. Introduce new logic where delay is enabled
-+also with internal delay binding declared and rgmii set as PHY mode.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 165 ++++++++++++++++++++++++------------------------
-+ drivers/net/dsa/qca8k.h |  10 ++-
-+ 2 files changed, 89 insertions(+), 86 deletions(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -889,68 +889,6 @@ qca8k_setup_mdio_bus(struct qca8k_priv *
-+ }
-+ 
-+ static int
-+-qca8k_setup_of_rgmii_delay(struct qca8k_priv *priv)
-+-{
-+-	struct device_node *port_dn;
-+-	phy_interface_t mode;
-+-	struct dsa_port *dp;
-+-	u32 val;
-+-
-+-	/* CPU port is already checked */
-+-	dp = dsa_to_port(priv->ds, 0);
-+-
-+-	port_dn = dp->dn;
-+-
-+-	/* Check if port 0 is set to the correct type */
-+-	of_get_phy_mode(port_dn, &mode);
-+-	if (mode != PHY_INTERFACE_MODE_RGMII_ID &&
-+-	    mode != PHY_INTERFACE_MODE_RGMII_RXID &&
-+-	    mode != PHY_INTERFACE_MODE_RGMII_TXID) {
-+-		return 0;
-+-	}
-+-
-+-	switch (mode) {
-+-	case PHY_INTERFACE_MODE_RGMII_ID:
-+-	case PHY_INTERFACE_MODE_RGMII_RXID:
-+-		if (of_property_read_u32(port_dn, "rx-internal-delay-ps", &val))
-+-			val = 2;
-+-		else
-+-			/* Switch regs accept value in ns, convert ps to ns */
-+-			val = val / 1000;
-+-
-+-		if (val > QCA8K_MAX_DELAY) {
-+-			dev_err(priv->dev, "rgmii rx delay is limited to a max value of 3ns, setting to the max value");
-+-			val = 3;
-+-		}
-+-
-+-		priv->rgmii_rx_delay = val;
-+-		/* Stop here if we need to check only for rx delay */
-+-		if (mode != PHY_INTERFACE_MODE_RGMII_ID)
-+-			break;
-+-
-+-		fallthrough;
-+-	case PHY_INTERFACE_MODE_RGMII_TXID:
-+-		if (of_property_read_u32(port_dn, "tx-internal-delay-ps", &val))
-+-			val = 1;
-+-		else
-+-			/* Switch regs accept value in ns, convert ps to ns */
-+-			val = val / 1000;
-+-
-+-		if (val > QCA8K_MAX_DELAY) {
-+-			dev_err(priv->dev, "rgmii tx delay is limited to a max value of 3ns, setting to the max value");
-+-			val = 3;
-+-		}
-+-
-+-		priv->rgmii_tx_delay = val;
-+-		break;
-+-	default:
-+-		return 0;
-+-	}
-+-
-+-	return 0;
-+-}
-+-
-+-static int
-+ qca8k_setup_mac_pwr_sel(struct qca8k_priv *priv)
-+ {
-+ 	u32 mask = 0;
-+@@ -996,19 +934,21 @@ static int qca8k_find_cpu_port(struct ds
-+ static int
-+ qca8k_parse_port_config(struct qca8k_priv *priv)
-+ {
-++	int port, cpu_port_index = 0, ret;
-+ 	struct device_node *port_dn;
-+ 	phy_interface_t mode;
-+ 	struct dsa_port *dp;
-+-	int port, ret;
-++	u32 delay;
-+ 
-+ 	/* We have 2 CPU port. Check them */
-+-	for (port = 0; port < QCA8K_NUM_PORTS; port++) {
-++	for (port = 0; port < QCA8K_NUM_PORTS && cpu_port_index < QCA8K_NUM_CPU_PORTS; port++) {
-+ 		/* Skip every other port */
-+ 		if (port != 0 && port != 6)
-+ 			continue;
-+ 
-+ 		dp = dsa_to_port(priv->ds, port);
-+ 		port_dn = dp->dn;
-++		cpu_port_index++;
-+ 
-+ 		if (!of_device_is_available(port_dn))
-+ 			continue;
-+@@ -1017,12 +957,54 @@ qca8k_parse_port_config(struct qca8k_pri
-+ 		if (ret)
-+ 			continue;
-+ 
-+-		if (mode == PHY_INTERFACE_MODE_SGMII) {
-++		switch (mode) {
-++		case PHY_INTERFACE_MODE_RGMII:
-++		case PHY_INTERFACE_MODE_RGMII_ID:
-++		case PHY_INTERFACE_MODE_RGMII_TXID:
-++		case PHY_INTERFACE_MODE_RGMII_RXID:
-++			delay = 0;
+++static u32
+++mtk_wed_check_busy(struct mtk_wed_device *dev)
+++{
+++	if (wed_r32(dev, MTK_WED_GLO_CFG) & MTK_WED_GLO_CFG_TX_DMA_BUSY)
+++		return true;
 ++
-++			if (!of_property_read_u32(port_dn, "tx-internal-delay-ps", &delay))
-++				/* Switch regs accept value in ns, convert ps to ns */
-++				delay = delay / 1000;
-++			else if (mode == PHY_INTERFACE_MODE_RGMII_ID ||
-++				 mode == PHY_INTERFACE_MODE_RGMII_TXID)
-++				delay = 1;
+++	if (wed_r32(dev, MTK_WED_WPDMA_GLO_CFG) &
+++	    MTK_WED_WPDMA_GLO_CFG_TX_DRV_BUSY)
+++		return true;
 ++
-++			if (delay > QCA8K_MAX_DELAY) {
-++				dev_err(priv->dev, "rgmii tx delay is limited to a max value of 3ns, setting to the max value");
-++				delay = 3;
-++			}
+++	if (wed_r32(dev, MTK_WED_CTRL) & MTK_WED_CTRL_WDMA_INT_AGENT_BUSY)
+++		return true;
 ++
-++			priv->rgmii_tx_delay[cpu_port_index] = delay;
+++	if (wed_r32(dev, MTK_WED_WDMA_GLO_CFG) &
+++	    MTK_WED_WDMA_GLO_CFG_RX_DRV_BUSY)
+++		return true;
 ++
-++			delay = 0;
+++	if (wdma_r32(dev, MTK_WDMA_GLO_CFG) &
+++	    MTK_WED_WDMA_GLO_CFG_RX_DRV_BUSY)
+++		return true;
 ++
-++			if (!of_property_read_u32(port_dn, "rx-internal-delay-ps", &delay))
-++				/* Switch regs accept value in ns, convert ps to ns */
-++				delay = delay / 1000;
-++			else if (mode == PHY_INTERFACE_MODE_RGMII_ID ||
-++				 mode == PHY_INTERFACE_MODE_RGMII_RXID)
-++				delay = 2;
+++	if (wed_r32(dev, MTK_WED_CTRL) &
+++	    (MTK_WED_CTRL_WED_TX_BM_BUSY | MTK_WED_CTRL_WED_TX_FREE_AGENT_BUSY))
+++		return true;
 ++
-++			if (delay > QCA8K_MAX_DELAY) {
-++				dev_err(priv->dev, "rgmii rx delay is limited to a max value of 3ns, setting to the max value");
-++				delay = 3;
-++			}
+++	return false;
+++}
 ++
-++			priv->rgmii_rx_delay[cpu_port_index] = delay;
+++static int
+++mtk_wed_poll_busy(struct mtk_wed_device *dev)
+++{
+++	int sleep = 15000;
+++	int timeout = 100 * sleep;
+++	u32 val;
 ++
-++			break;
-++		case PHY_INTERFACE_MODE_SGMII:
-+ 			if (of_property_read_bool(port_dn, "qca,sgmii-txclk-falling-edge"))
-+ 				priv->sgmii_tx_clk_falling_edge = true;
-+ 
-+ 			if (of_property_read_bool(port_dn, "qca,sgmii-rxclk-falling-edge"))
-+ 				priv->sgmii_rx_clk_falling_edge = true;
+++	return read_poll_timeout(mtk_wed_check_busy, val, !val, sleep,
+++				 timeout, false, dev);
+++}
 ++
-++			break;
-++		default:
+++static void
+++mtk_wed_reset_dma(struct mtk_wed_device *dev)
+++{
+++	bool busy = false;
+++	u32 val;
+++	int i;
+++
+++	for (i = 0; i < ARRAY_SIZE(dev->tx_ring); i++) {
+++		struct mtk_wdma_desc *desc = dev->tx_ring[i].desc;
+++
+++		if (!desc)
 ++			continue;
-+ 		}
-+ 	}
-+ 
-+@@ -1059,10 +1041,6 @@ qca8k_setup(struct dsa_switch *ds)
-+ 	if (ret)
-+ 		return ret;
-+ 
-+-	ret = qca8k_setup_of_rgmii_delay(priv);
-+-	if (ret)
-+-		return ret;
-+-
-+ 	ret = qca8k_setup_mac_pwr_sel(priv);
-+ 	if (ret)
-+ 		return ret;
-+@@ -1229,8 +1207,8 @@ qca8k_phylink_mac_config(struct dsa_swit
-+ 			 const struct phylink_link_state *state)
-+ {
-+ 	struct qca8k_priv *priv = ds->priv;
-+-	u32 reg, val;
-+-	int ret;
-++	int cpu_port_index, ret;
-++	u32 reg, val, delay;
-+ 
-+ 	switch (port) {
-+ 	case 0: /* 1st CPU port */
-+@@ -1242,6 +1220,7 @@ qca8k_phylink_mac_config(struct dsa_swit
-+ 			return;
-+ 
-+ 		reg = QCA8K_REG_PORT0_PAD_CTRL;
-++		cpu_port_index = QCA8K_CPU_PORT0;
-+ 		break;
-+ 	case 1:
-+ 	case 2:
-+@@ -1260,6 +1239,7 @@ qca8k_phylink_mac_config(struct dsa_swit
-+ 			return;
-+ 
-+ 		reg = QCA8K_REG_PORT6_PAD_CTRL;
-++		cpu_port_index = QCA8K_CPU_PORT6;
-+ 		break;
-+ 	default:
-+ 		dev_err(ds->dev, "%s: unsupported port: %i\n", __func__, port);
-+@@ -1274,23 +1254,40 @@ qca8k_phylink_mac_config(struct dsa_swit
-+ 
-+ 	switch (state->interface) {
-+ 	case PHY_INTERFACE_MODE_RGMII:
-+-		/* RGMII mode means no delay so don't enable the delay */
-+-		qca8k_write(priv, reg, QCA8K_PORT_PAD_RGMII_EN);
-+-		break;
-+ 	case PHY_INTERFACE_MODE_RGMII_ID:
-+ 	case PHY_INTERFACE_MODE_RGMII_TXID:
-+ 	case PHY_INTERFACE_MODE_RGMII_RXID:
-+-		/* RGMII_ID needs internal delay. This is enabled through
-+-		 * PORT5_PAD_CTRL for all ports, rather than individual port
-+-		 * registers
-++		val = QCA8K_PORT_PAD_RGMII_EN;
 ++
-++		/* Delay can be declared in 3 different way.
-++		 * Mode to rgmii and internal-delay standard binding defined
-++		 * rgmii-id or rgmii-tx/rx phy mode set.
-++		 * The parse logic set a delay different than 0 only when one
-++		 * of the 3 different way is used. In all other case delay is
-++		 * not enabled. With ID or TX/RXID delay is enabled and set
-++		 * to the default and recommended value.
-++		 */
-++		if (priv->rgmii_tx_delay[cpu_port_index]) {
-++			delay = priv->rgmii_tx_delay[cpu_port_index];
+++		mtk_wed_ring_reset(desc, MTK_WED_TX_RING_SIZE);
+++	}
 ++
-++			val |= QCA8K_PORT_PAD_RGMII_TX_DELAY(delay) |
-++			       QCA8K_PORT_PAD_RGMII_TX_DELAY_EN;
-++		}
+++	if (mtk_wed_poll_busy(dev))
+++		busy = mtk_wed_check_busy(dev);
 ++
-++		if (priv->rgmii_rx_delay[cpu_port_index]) {
-++			delay = priv->rgmii_rx_delay[cpu_port_index];
+++	if (busy) {
+++		mtk_wed_reset(dev, MTK_WED_RESET_WED_TX_DMA);
+++	} else {
+++		wed_w32(dev, MTK_WED_RESET_IDX,
+++			MTK_WED_RESET_IDX_TX |
+++			MTK_WED_RESET_IDX_RX);
+++		wed_w32(dev, MTK_WED_RESET_IDX, 0);
+++	}
 ++
-++			val |= QCA8K_PORT_PAD_RGMII_RX_DELAY(delay) |
-++			       QCA8K_PORT_PAD_RGMII_RX_DELAY_EN;
-++		}
+++	wdma_w32(dev, MTK_WDMA_RESET_IDX, MTK_WDMA_RESET_IDX_RX);
+++	wdma_w32(dev, MTK_WDMA_RESET_IDX, 0);
 ++
-++		/* Set RGMII delay based on the selected values */
-++		qca8k_write(priv, reg, val);
+++	if (busy) {
+++		mtk_wed_reset(dev, MTK_WED_RESET_WDMA_INT_AGENT);
+++		mtk_wed_reset(dev, MTK_WED_RESET_WDMA_RX_DRV);
+++	} else {
+++		wed_w32(dev, MTK_WED_WDMA_RESET_IDX,
+++			MTK_WED_WDMA_RESET_IDX_RX | MTK_WED_WDMA_RESET_IDX_DRV);
+++		wed_w32(dev, MTK_WED_WDMA_RESET_IDX, 0);
 ++
-++		/* QCA8337 requires to set rgmii rx delay for all ports.
-++		 * This is enabled through PORT5_PAD_CTRL for all ports,
-++		 * rather than individual port registers.
-+ 		 */
-+-		qca8k_write(priv, reg,
-+-			    QCA8K_PORT_PAD_RGMII_EN |
-+-			    QCA8K_PORT_PAD_RGMII_TX_DELAY(priv->rgmii_tx_delay) |
-+-			    QCA8K_PORT_PAD_RGMII_RX_DELAY(priv->rgmii_rx_delay) |
-+-			    QCA8K_PORT_PAD_RGMII_TX_DELAY_EN |
-+-			    QCA8K_PORT_PAD_RGMII_RX_DELAY_EN);
-+-		/* QCA8337 requires to set rgmii rx delay */
-+ 		if (priv->switch_id == QCA8K_ID_QCA8337)
-+ 			qca8k_write(priv, QCA8K_REG_PORT5_PAD_CTRL,
-+ 				    QCA8K_PORT_PAD_RGMII_RX_DELAY_EN);
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -13,6 +13,7 @@
-+ #include <linux/gpio.h>
-+ 
-+ #define QCA8K_NUM_PORTS					7
-++#define QCA8K_NUM_CPU_PORTS				2
-+ #define QCA8K_MAX_MTU					9000
-+ 
-+ #define PHY_ID_QCA8327					0x004dd034
-+@@ -255,13 +256,18 @@ struct qca8k_match_data {
-+ 	u8 id;
-+ };
-+ 
-++enum {
-++	QCA8K_CPU_PORT0,
-++	QCA8K_CPU_PORT6,
-++};
+++		wed_set(dev, MTK_WED_WDMA_GLO_CFG,
+++			MTK_WED_WDMA_GLO_CFG_RST_INIT_COMPLETE);
 ++
-+ struct qca8k_priv {
-+ 	u8 switch_id;
-+ 	u8 switch_revision;
-+-	u8 rgmii_tx_delay;
-+-	u8 rgmii_rx_delay;
-+ 	bool sgmii_rx_clk_falling_edge;
-+ 	bool sgmii_tx_clk_falling_edge;
-++	u8 rgmii_rx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
-++	u8 rgmii_tx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
-+ 	bool legacy_phy_port_mapping;
-+ 	struct regmap *regmap;
-+ 	struct mii_bus *bus;
-diff --git a/target/linux/generic/backport-5.15/747-v5.16-07-dt-bindings-net-dsa-qca8k-Document-qca-sgmii-enable-.patch b/target/linux/generic/backport-5.15/747-v5.16-07-dt-bindings-net-dsa-qca8k-Document-qca-sgmii-enable-.patch
-new file mode 100644
-index 0000000000..8abd264e79
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/747-v5.16-07-dt-bindings-net-dsa-qca8k-Document-qca-sgmii-enable-.patch
-@@ -0,0 +1,33 @@
-+From 13ad5ccc093ff448b99ac7e138e91e78796adb48 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Thu, 14 Oct 2021 00:39:12 +0200
-+Subject: dt-bindings: net: dsa: qca8k: Document qca,sgmii-enable-pll
-+
-+Document qca,sgmii-enable-pll binding used in the CPU nodes to
-+enable SGMII PLL on MAC config.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ Documentation/devicetree/bindings/net/dsa/qca8k.txt | 10 ++++++++++
-+ 1 file changed, 10 insertions(+)
-+
-+--- a/Documentation/devicetree/bindings/net/dsa/qca8k.txt
-++++ b/Documentation/devicetree/bindings/net/dsa/qca8k.txt
-+@@ -45,6 +45,16 @@ A CPU port node has the following option
-+                                 Mostly used in qca8327 with CPU port 0 set to
-+                                 sgmii.
-+ - qca,sgmii-txclk-falling-edge: Set the transmit clock phase to falling edge.
-++- qca,sgmii-enable-pll  : For SGMII CPU port, explicitly enable PLL, TX and RX
-++                          chain along with Signal Detection.
-++                          This should NOT be enabled for qca8327. If enabled with
-++                          qca8327 the sgmii port won't correctly init and an err
-++                          is printed.
-++                          This can be required for qca8337 switch with revision 2.
-++                          A warning is displayed when used with revision greater
-++                          2.
-++                          With CPU port set to sgmii and qca8337 it is advised
-++                          to set this unless a communication problem is observed.
-+ 
-+ For QCA8K the 'fixed-link' sub-node supports only the following properties:
-+ 
-diff --git a/target/linux/generic/backport-5.15/747-v5.16-08-net-dsa-qca8k-add-explicit-SGMII-PLL-enable.patch b/target/linux/generic/backport-5.15/747-v5.16-08-net-dsa-qca8k-add-explicit-SGMII-PLL-enable.patch
-new file mode 100644
-index 0000000000..2b5a84a1b0
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/747-v5.16-08-net-dsa-qca8k-add-explicit-SGMII-PLL-enable.patch
-@@ -0,0 +1,65 @@
-+From bbc4799e8bb6c397e3b3fec13de68e179f5db9ff Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Thu, 14 Oct 2021 00:39:13 +0200
-+Subject: net: dsa: qca8k: add explicit SGMII PLL enable
-+
-+Support enabling PLL on the SGMII CPU port. Some device require this
-+special configuration or no traffic is transmitted and the switch
-+doesn't work at all. A dedicated binding is added to the CPU node
-+port to apply the correct reg on mac config.
-+Fail to correctly configure sgmii with qca8327 switch and warn if pll is
-+used on qca8337 with a revision greater than 1.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 19 +++++++++++++++++--
-+ drivers/net/dsa/qca8k.h |  1 +
-+ 2 files changed, 18 insertions(+), 2 deletions(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -1002,6 +1002,18 @@ qca8k_parse_port_config(struct qca8k_pri
-+ 			if (of_property_read_bool(port_dn, "qca,sgmii-rxclk-falling-edge"))
-+ 				priv->sgmii_rx_clk_falling_edge = true;
-+ 
-++			if (of_property_read_bool(port_dn, "qca,sgmii-enable-pll")) {
-++				priv->sgmii_enable_pll = true;
+++		wed_clr(dev, MTK_WED_WDMA_GLO_CFG,
+++			MTK_WED_WDMA_GLO_CFG_RST_INIT_COMPLETE);
+++	}
 ++
-++				if (priv->switch_id == QCA8K_ID_QCA8327) {
-++					dev_err(priv->dev, "SGMII PLL should NOT be enabled for qca8327. Aborting enabling");
-++					priv->sgmii_enable_pll = false;
-++				}
+++	for (i = 0; i < 100; i++) {
+++		val = wed_r32(dev, MTK_WED_TX_BM_INTF);
+++		if (FIELD_GET(MTK_WED_TX_BM_INTF_TKFIFO_FDEP, val) == 0x40)
+++			break;
+++	}
 ++
-++				if (priv->switch_revision < 2)
-++					dev_warn(priv->dev, "SGMII PLL should NOT be enabled for qca8337 with revision 2 or more.");
-++			}
+++	mtk_wed_reset(dev, MTK_WED_RESET_TX_FREE_AGENT);
+++	mtk_wed_reset(dev, MTK_WED_RESET_TX_BM);
 ++
-+ 			break;
-+ 		default:
-+ 			continue;
-+@@ -1312,8 +1324,11 @@ qca8k_phylink_mac_config(struct dsa_swit
-+ 		if (ret)
-+ 			return;
-+ 
-+-		val |= QCA8K_SGMII_EN_PLL | QCA8K_SGMII_EN_RX |
-+-			QCA8K_SGMII_EN_TX | QCA8K_SGMII_EN_SD;
-++		val |= QCA8K_SGMII_EN_SD;
+++	if (busy) {
+++		mtk_wed_reset(dev, MTK_WED_RESET_WPDMA_INT_AGENT);
+++		mtk_wed_reset(dev, MTK_WED_RESET_WPDMA_TX_DRV);
+++		mtk_wed_reset(dev, MTK_WED_RESET_WPDMA_RX_DRV);
+++	} else {
+++		wed_w32(dev, MTK_WED_WPDMA_RESET_IDX,
+++			MTK_WED_WPDMA_RESET_IDX_TX |
+++			MTK_WED_WPDMA_RESET_IDX_RX);
+++		wed_w32(dev, MTK_WED_WPDMA_RESET_IDX, 0);
+++	}
 ++
-++		if (priv->sgmii_enable_pll)
-++			val |= QCA8K_SGMII_EN_PLL | QCA8K_SGMII_EN_RX |
-++			       QCA8K_SGMII_EN_TX;
-+ 
-+ 		if (dsa_is_cpu_port(ds, port)) {
-+ 			/* CPU port, we're talking to the CPU MAC, be a PHY */
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -266,6 +266,7 @@ struct qca8k_priv {
-+ 	u8 switch_revision;
-+ 	bool sgmii_rx_clk_falling_edge;
-+ 	bool sgmii_tx_clk_falling_edge;
-++	bool sgmii_enable_pll;
-+ 	u8 rgmii_rx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
-+ 	u8 rgmii_tx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
-+ 	bool legacy_phy_port_mapping;
-diff --git a/target/linux/generic/backport-5.15/747-v5.16-09-dt-bindings-net-dsa-qca8k-Document-qca-led-open-drai.patch b/target/linux/generic/backport-5.15/747-v5.16-09-dt-bindings-net-dsa-qca8k-Document-qca-led-open-drai.patch
-new file mode 100644
-index 0000000000..38dc954e8c
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/747-v5.16-09-dt-bindings-net-dsa-qca8k-Document-qca-led-open-drai.patch
-@@ -0,0 +1,37 @@
-+From 924087c5c3d41553700b0eb83ca2a53b91643dca Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Thu, 14 Oct 2021 00:39:14 +0200
-+Subject: dt-bindings: net: dsa: qca8k: Document qca,led-open-drain binding
-+
-+Document new binding qca,ignore-power-on-sel used to ignore
-+power on strapping and use sw regs instead.
-+Document qca,led-open.drain to set led to open drain mode, the
-+qca,ignore-power-on-sel is mandatory with this enabled or an error will
-+be reported.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ Documentation/devicetree/bindings/net/dsa/qca8k.txt | 11 +++++++++++
-+ 1 file changed, 11 insertions(+)
-+
-+--- a/Documentation/devicetree/bindings/net/dsa/qca8k.txt
-++++ b/Documentation/devicetree/bindings/net/dsa/qca8k.txt
-+@@ -13,6 +13,17 @@ Required properties:
-+ Optional properties:
-+ 
-+ - reset-gpios: GPIO to be used to reset the whole device
-++- qca,ignore-power-on-sel: Ignore power on pin strapping to configure led open
-++                           drain or eeprom presence. This is needed for broken
-++                           devices that have wrong configuration or when the oem
-++                           decided to not use pin strapping and fallback to sw
-++                           regs.
-++- qca,led-open-drain: Set leds to open-drain mode. This requires the
-++                      qca,ignore-power-on-sel to be set or the driver will fail
-++                      to probe. This is needed if the oem doesn't use pin
-++                      strapping to set this mode and prefers to set it using sw
-++                      regs. The pin strapping related to led open drain mode is
-++                      the pin B68 for QCA832x and B49 for QCA833x
-+ 
-+ Subnodes:
-+ 
-diff --git a/target/linux/generic/backport-5.15/747-v5.16-10-net-dsa-qca8k-add-support-for-pws-config-reg.patch b/target/linux/generic/backport-5.15/747-v5.16-10-net-dsa-qca8k-add-support-for-pws-config-reg.patch
-new file mode 100644
-index 0000000000..aa5d92a4fd
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/747-v5.16-10-net-dsa-qca8k-add-support-for-pws-config-reg.patch
-@@ -0,0 +1,92 @@
-+From 362bb238d8bf1470424214a8a5968d9c6cce68fa Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Thu, 14 Oct 2021 00:39:15 +0200
-+Subject: net: dsa: qca8k: add support for pws config reg
-+
-+Some qca8327 switch require to force the ignore of power on sel
-+strapping. Some switch require to set the led open drain mode in regs
-+instead of using strapping. While most of the device implements this
-+using the correct way using pin strapping, there are still some broken
-+device that require to be set using sw regs.
-+Introduce a new binding and support these special configuration.
-+As led open drain require to ignore pin strapping to work, the probe
-+fails with EINVAL error with incorrect configuration.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 39 +++++++++++++++++++++++++++++++++++++++
-+ drivers/net/dsa/qca8k.h |  6 ++++++
-+ 2 files changed, 45 insertions(+)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -932,6 +932,41 @@ static int qca8k_find_cpu_port(struct ds
-+ }
-+ 
-+ static int
-++qca8k_setup_of_pws_reg(struct qca8k_priv *priv)
+++}
+++
+++static int
+++mtk_wed_ring_alloc(struct mtk_wed_device *dev, struct mtk_wed_ring *ring,
+++		   int size)
 ++{
-++	struct device_node *node = priv->dev->of_node;
-++	u32 val = 0;
-++	int ret;
+++	ring->desc = dma_alloc_coherent(dev->hw->dev,
+++					size * sizeof(*ring->desc),
+++					&ring->desc_phys, GFP_KERNEL);
+++	if (!ring->desc)
+++		return -ENOMEM;
 ++
-++	/* QCA8327 require to set to the correct mode.
-++	 * His bigger brother QCA8328 have the 172 pin layout.
-++	 * Should be applied by default but we set this just to make sure.
-++	 */
-++	if (priv->switch_id == QCA8K_ID_QCA8327) {
-++		ret = qca8k_rmw(priv, QCA8K_REG_PWS, QCA8327_PWS_PACKAGE148_EN,
-++				QCA8327_PWS_PACKAGE148_EN);
-++		if (ret)
-++			return ret;
-++	}
+++	ring->size = size;
+++	mtk_wed_ring_reset(ring->desc, size);
 ++
-++	if (of_property_read_bool(node, "qca,ignore-power-on-sel"))
-++		val |= QCA8K_PWS_POWER_ON_SEL;
+++	return 0;
+++}
 ++
-++	if (of_property_read_bool(node, "qca,led-open-drain")) {
-++		if (!(val & QCA8K_PWS_POWER_ON_SEL)) {
-++			dev_err(priv->dev, "qca,led-open-drain require qca,ignore-power-on-sel to be set.");
-++			return -EINVAL;
-++		}
+++static int
+++mtk_wed_wdma_ring_setup(struct mtk_wed_device *dev, int idx, int size)
+++{
+++	struct mtk_wed_ring *wdma = &dev->tx_wdma[idx];
 ++
-++		val |= QCA8K_PWS_LED_OPEN_EN_CSR;
-++	}
+++	if (mtk_wed_ring_alloc(dev, wdma, MTK_WED_WDMA_RING_SIZE))
+++		return -ENOMEM;
 ++
-++	return qca8k_rmw(priv, QCA8K_REG_PWS,
-++			QCA8K_PWS_LED_OPEN_EN_CSR | QCA8K_PWS_POWER_ON_SEL,
-++			val);
+++	wdma_w32(dev, MTK_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_BASE,
+++		 wdma->desc_phys);
+++	wdma_w32(dev, MTK_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_COUNT,
+++		 size);
+++	wdma_w32(dev, MTK_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_CPU_IDX, 0);
+++
+++	wed_w32(dev, MTK_WED_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_BASE,
+++		wdma->desc_phys);
+++	wed_w32(dev, MTK_WED_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_COUNT,
+++		size);
+++
+++	return 0;
 ++}
 ++
-++static int
-+ qca8k_parse_port_config(struct qca8k_priv *priv)
-+ {
-+ 	int port, cpu_port_index = 0, ret;
-+@@ -1053,6 +1088,10 @@ qca8k_setup(struct dsa_switch *ds)
-+ 	if (ret)
-+ 		return ret;
-+ 
-++	ret = qca8k_setup_of_pws_reg(priv);
-++	if (ret)
-++		return ret;
+++static void
+++mtk_wed_start(struct mtk_wed_device *dev, u32 irq_mask)
+++{
+++	u32 wdma_mask;
+++	u32 val;
+++	int i;
 ++
-+ 	ret = qca8k_setup_mac_pwr_sel(priv);
-+ 	if (ret)
-+ 		return ret;
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -46,6 +46,12 @@
-+ #define   QCA8K_MAX_DELAY				3
-+ #define   QCA8K_PORT_PAD_SGMII_EN			BIT(7)
-+ #define QCA8K_REG_PWS					0x010
-++#define   QCA8K_PWS_POWER_ON_SEL			BIT(31)
-++/* This reg is only valid for QCA832x and toggle the package
-++ * type from 176 pin (by default) to 148 pin used on QCA8327
-++ */
-++#define   QCA8327_PWS_PACKAGE148_EN			BIT(30)
-++#define   QCA8K_PWS_LED_OPEN_EN_CSR			BIT(24)
-+ #define   QCA8K_PWS_SERDES_AEN_DIS			BIT(7)
-+ #define QCA8K_REG_MODULE_EN				0x030
-+ #define   QCA8K_MODULE_EN_MIB				BIT(0)
-diff --git a/target/linux/generic/backport-5.15/747-v5.16-11-dt-bindings-net-dsa-qca8k-document-support-for-qca83.patch b/target/linux/generic/backport-5.15/747-v5.16-11-dt-bindings-net-dsa-qca8k-document-support-for-qca83.patch
-new file mode 100644
-index 0000000000..1bfb00c5b2
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/747-v5.16-11-dt-bindings-net-dsa-qca8k-document-support-for-qca83.patch
-@@ -0,0 +1,32 @@
-+From ed7988d77fbfb79366b68f9e7fa60a6080da23d4 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Thu, 14 Oct 2021 00:39:16 +0200
-+Subject: dt-bindings: net: dsa: qca8k: document support for qca8328
-+
-+QCA8328 is the bigger brother of qca8327. Document the new compatible
-+binding and add some information to understand the various switch
-+compatible.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ Documentation/devicetree/bindings/net/dsa/qca8k.txt | 7 ++++---
-+ 1 file changed, 4 insertions(+), 3 deletions(-)
-+
-+--- a/Documentation/devicetree/bindings/net/dsa/qca8k.txt
-++++ b/Documentation/devicetree/bindings/net/dsa/qca8k.txt
-+@@ -3,9 +3,10 @@
-+ Required properties:
-+ 
-+ - compatible: should be one of:
-+-    "qca,qca8327"
-+-    "qca,qca8334"
-+-    "qca,qca8337"
-++    "qca,qca8328": referenced as AR8328(N)-AK1(A/B) QFN 176 pin package
-++    "qca,qca8327": referenced as AR8327(N)-AL1A DR-QFN 148 pin package
-++    "qca,qca8334": referenced as QCA8334-AL3C QFN 88 pin package
-++    "qca,qca8337": referenced as QCA8337N-AL3(B/C) DR-QFN 148 pin package
-+ 
-+ - #size-cells: must be 0
-+ - #address-cells: must be 1
-diff --git a/target/linux/generic/backport-5.15/747-v5.16-12-net-dsa-qca8k-add-support-for-QCA8328.patch b/target/linux/generic/backport-5.15/747-v5.16-12-net-dsa-qca8k-add-support-for-QCA8328.patch
-new file mode 100644
-index 0000000000..70f227fb69
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/747-v5.16-12-net-dsa-qca8k-add-support-for-QCA8328.patch
-@@ -0,0 +1,78 @@
-+From f477d1c8bdbef4f400718238e350f16f521d2a3e Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Thu, 14 Oct 2021 00:39:17 +0200
-+Subject: net: dsa: qca8k: add support for QCA8328
-+
-+QCA8328 switch is the bigger brother of the qca8327. Same regs different
-+chip. Change the function to set the correct pin layout and introduce a
-+new match_data to differentiate the 2 switch as they have the same ID
-+and their internal PHY have the same ID.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 19 ++++++++++++++++---
-+ drivers/net/dsa/qca8k.h |  1 +
-+ 2 files changed, 17 insertions(+), 3 deletions(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -935,6 +935,7 @@ static int
-+ qca8k_setup_of_pws_reg(struct qca8k_priv *priv)
-+ {
-+ 	struct device_node *node = priv->dev->of_node;
-++	const struct qca8k_match_data *data;
-+ 	u32 val = 0;
-+ 	int ret;
-+ 
-+@@ -943,8 +944,14 @@ qca8k_setup_of_pws_reg(struct qca8k_priv
-+ 	 * Should be applied by default but we set this just to make sure.
-+ 	 */
-+ 	if (priv->switch_id == QCA8K_ID_QCA8327) {
-++		data = of_device_get_match_data(priv->dev);
+++	for (i = 0; i < ARRAY_SIZE(dev->tx_wdma); i++)
+++		if (!dev->tx_wdma[i].desc)
+++			mtk_wed_wdma_ring_setup(dev, i, 16);
 ++
-++		/* Set the correct package of 148 pin for QCA8327 */
-++		if (data->reduced_package)
-++			val |= QCA8327_PWS_PACKAGE148_EN;
+++	wdma_mask = FIELD_PREP(MTK_WDMA_INT_MASK_RX_DONE, GENMASK(1, 0));
 ++
-+ 		ret = qca8k_rmw(priv, QCA8K_REG_PWS, QCA8327_PWS_PACKAGE148_EN,
-+-				QCA8327_PWS_PACKAGE148_EN);
-++				val);
-+ 		if (ret)
-+ 			return ret;
-+ 	}
-+@@ -2105,7 +2112,12 @@ static int qca8k_resume(struct device *d
-+ static SIMPLE_DEV_PM_OPS(qca8k_pm_ops,
-+ 			 qca8k_suspend, qca8k_resume);
-+ 
-+-static const struct qca8k_match_data qca832x = {
-++static const struct qca8k_match_data qca8327 = {
-++	.id = QCA8K_ID_QCA8327,
-++	.reduced_package = true,
-++};
+++	mtk_wed_hw_init(dev);
 ++
-++static const struct qca8k_match_data qca8328 = {
-+ 	.id = QCA8K_ID_QCA8327,
-+ };
-+ 
-+@@ -2114,7 +2126,8 @@ static const struct qca8k_match_data qca
-+ };
-+ 
-+ static const struct of_device_id qca8k_of_match[] = {
-+-	{ .compatible = "qca,qca8327", .data = &qca832x },
-++	{ .compatible = "qca,qca8327", .data = &qca8327 },
-++	{ .compatible = "qca,qca8328", .data = &qca8328 },
-+ 	{ .compatible = "qca,qca8334", .data = &qca833x },
-+ 	{ .compatible = "qca,qca8337", .data = &qca833x },
-+ 	{ /* sentinel */ },
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -260,6 +260,7 @@ struct ar8xxx_port_status {
-+ 
-+ struct qca8k_match_data {
-+ 	u8 id;
-++	bool reduced_package;
-+ };
-+ 
-+ enum {
-diff --git a/target/linux/generic/backport-5.15/747-v5.16-13-net-dsa-qca8k-set-internal-delay-also-for-sgmii.patch b/target/linux/generic/backport-5.15/747-v5.16-13-net-dsa-qca8k-set-internal-delay-also-for-sgmii.patch
-new file mode 100644
-index 0000000000..27f94dca02
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/747-v5.16-13-net-dsa-qca8k-set-internal-delay-also-for-sgmii.patch
-@@ -0,0 +1,159 @@
-+From cef08115846e581f80ff99abf7bf218da1840616 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Thu, 14 Oct 2021 00:39:18 +0200
-+Subject: net: dsa: qca8k: set internal delay also for sgmii
-+
-+QCA original code report port instability and sa that SGMII also require
-+to set internal delay. Generalize the rgmii delay function and apply the
-+advised value if they are not defined in DT.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 88 +++++++++++++++++++++++++++++++++----------------
-+ drivers/net/dsa/qca8k.h |  2 ++
-+ 2 files changed, 62 insertions(+), 28 deletions(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -1004,6 +1004,7 @@ qca8k_parse_port_config(struct qca8k_pri
-+ 		case PHY_INTERFACE_MODE_RGMII_ID:
-+ 		case PHY_INTERFACE_MODE_RGMII_TXID:
-+ 		case PHY_INTERFACE_MODE_RGMII_RXID:
-++		case PHY_INTERFACE_MODE_SGMII:
-+ 			delay = 0;
-+ 
-+ 			if (!of_property_read_u32(port_dn, "tx-internal-delay-ps", &delay))
-+@@ -1036,8 +1037,13 @@ qca8k_parse_port_config(struct qca8k_pri
-+ 
-+ 			priv->rgmii_rx_delay[cpu_port_index] = delay;
-+ 
-+-			break;
-+-		case PHY_INTERFACE_MODE_SGMII:
-++			/* Skip sgmii parsing for rgmii* mode */
-++			if (mode == PHY_INTERFACE_MODE_RGMII ||
-++			    mode == PHY_INTERFACE_MODE_RGMII_ID ||
-++			    mode == PHY_INTERFACE_MODE_RGMII_TXID ||
-++			    mode == PHY_INTERFACE_MODE_RGMII_RXID)
-++				break;
+++	wed_set(dev, MTK_WED_CTRL,
+++		MTK_WED_CTRL_WDMA_INT_AGENT_EN |
+++		MTK_WED_CTRL_WPDMA_INT_AGENT_EN |
+++		MTK_WED_CTRL_WED_TX_BM_EN |
+++		MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
 ++
-+ 			if (of_property_read_bool(port_dn, "qca,sgmii-txclk-falling-edge"))
-+ 				priv->sgmii_tx_clk_falling_edge = true;
-+ 
-+@@ -1261,12 +1267,53 @@ qca8k_setup(struct dsa_switch *ds)
-+ }
-+ 
-+ static void
-++qca8k_mac_config_setup_internal_delay(struct qca8k_priv *priv, int cpu_port_index,
-++				      u32 reg)
+++	wed_w32(dev, MTK_WED_PCIE_INT_TRIGGER, MTK_WED_PCIE_INT_TRIGGER_STATUS);
+++
+++	wed_w32(dev, MTK_WED_WPDMA_INT_TRIGGER,
+++		MTK_WED_WPDMA_INT_TRIGGER_RX_DONE |
+++		MTK_WED_WPDMA_INT_TRIGGER_TX_DONE);
+++
+++	wed_set(dev, MTK_WED_WPDMA_INT_CTRL,
+++		MTK_WED_WPDMA_INT_CTRL_SUBRT_ADV);
+++
+++	wed_w32(dev, MTK_WED_WDMA_INT_TRIGGER, wdma_mask);
+++	wed_clr(dev, MTK_WED_WDMA_INT_CTRL, wdma_mask);
+++
+++	wdma_w32(dev, MTK_WDMA_INT_MASK, wdma_mask);
+++	wdma_w32(dev, MTK_WDMA_INT_GRP2, wdma_mask);
+++
+++	wed_w32(dev, MTK_WED_WPDMA_INT_MASK, irq_mask);
+++	wed_w32(dev, MTK_WED_INT_MASK, irq_mask);
+++
+++	wed_set(dev, MTK_WED_GLO_CFG,
+++		MTK_WED_GLO_CFG_TX_DMA_EN |
+++		MTK_WED_GLO_CFG_RX_DMA_EN);
+++	wed_set(dev, MTK_WED_WPDMA_GLO_CFG,
+++		MTK_WED_WPDMA_GLO_CFG_TX_DRV_EN |
+++		MTK_WED_WPDMA_GLO_CFG_RX_DRV_EN);
+++	wed_set(dev, MTK_WED_WDMA_GLO_CFG,
+++		MTK_WED_WDMA_GLO_CFG_RX_DRV_EN);
+++
+++	mtk_wed_set_ext_int(dev, true);
+++	val = dev->wlan.wpdma_phys |
+++	      MTK_PCIE_MIRROR_MAP_EN |
+++	      FIELD_PREP(MTK_PCIE_MIRROR_MAP_WED_ID, dev->hw->index);
+++
+++	if (dev->hw->index)
+++		val |= BIT(1);
+++	val |= BIT(0);
+++	regmap_write(dev->hw->mirror, dev->hw->index * 4, val);
+++
+++	dev->running = true;
+++}
+++
+++static int
+++mtk_wed_attach(struct mtk_wed_device *dev)
+++	__releases(RCU)
 ++{
-++	u32 delay, val = 0;
-++	int ret;
+++	struct mtk_wed_hw *hw;
+++	int ret = 0;
 ++
-++	/* Delay can be declared in 3 different way.
-++	 * Mode to rgmii and internal-delay standard binding defined
-++	 * rgmii-id or rgmii-tx/rx phy mode set.
-++	 * The parse logic set a delay different than 0 only when one
-++	 * of the 3 different way is used. In all other case delay is
-++	 * not enabled. With ID or TX/RXID delay is enabled and set
-++	 * to the default and recommended value.
-++	 */
-++	if (priv->rgmii_tx_delay[cpu_port_index]) {
-++		delay = priv->rgmii_tx_delay[cpu_port_index];
+++	RCU_LOCKDEP_WARN(!rcu_read_lock_held(),
+++			 "mtk_wed_attach without holding the RCU read lock");
 ++
-++		val |= QCA8K_PORT_PAD_RGMII_TX_DELAY(delay) |
-++			QCA8K_PORT_PAD_RGMII_TX_DELAY_EN;
+++	if (pci_domain_nr(dev->wlan.pci_dev->bus) > 1 ||
+++	    !try_module_get(THIS_MODULE))
+++		ret = -ENODEV;
+++
+++	rcu_read_unlock();
+++
+++	if (ret)
+++		return ret;
+++
+++	mutex_lock(&hw_lock);
+++
+++	hw = mtk_wed_assign(dev);
+++	if (!hw) {
+++		module_put(THIS_MODULE);
+++		ret = -ENODEV;
+++		goto out;
 ++	}
 ++
-++	if (priv->rgmii_rx_delay[cpu_port_index]) {
-++		delay = priv->rgmii_rx_delay[cpu_port_index];
+++	dev_info(&dev->wlan.pci_dev->dev, "attaching wed device %d\n", hw->index);
 ++
-++		val |= QCA8K_PORT_PAD_RGMII_RX_DELAY(delay) |
-++			QCA8K_PORT_PAD_RGMII_RX_DELAY_EN;
+++	dev->hw = hw;
+++	dev->dev = hw->dev;
+++	dev->irq = hw->irq;
+++	dev->wdma_idx = hw->index;
+++
+++	if (hw->eth->dma_dev == hw->eth->dev &&
+++	    of_dma_is_coherent(hw->eth->dev->of_node))
+++		mtk_eth_set_dma_device(hw->eth, hw->dev);
+++
+++	ret = mtk_wed_buffer_alloc(dev);
+++	if (ret) {
+++		mtk_wed_detach(dev);
+++		goto out;
 ++	}
 ++
-++	/* Set RGMII delay based on the selected values */
-++	ret = qca8k_rmw(priv, reg,
-++			QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK |
-++			QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK |
-++			QCA8K_PORT_PAD_RGMII_TX_DELAY_EN |
-++			QCA8K_PORT_PAD_RGMII_RX_DELAY_EN,
-++			val);
-++	if (ret)
-++		dev_err(priv->dev, "Failed to set internal delay for CPU port%d",
-++			cpu_port_index == QCA8K_CPU_PORT0 ? 0 : 6);
+++	mtk_wed_hw_init_early(dev);
+++	regmap_update_bits(hw->hifsys, HIFSYS_DMA_AG_MAP, BIT(hw->index), 0);
+++
+++out:
+++	mutex_unlock(&hw_lock);
+++
+++	return ret;
+++}
+++
+++static int
+++mtk_wed_tx_ring_setup(struct mtk_wed_device *dev, int idx, void __iomem *regs)
+++{
+++	struct mtk_wed_ring *ring = &dev->tx_ring[idx];
+++
+++	/*
+++	 * Tx ring redirection:
+++	 * Instead of configuring the WLAN PDMA TX ring directly, the WLAN
+++	 * driver allocated DMA ring gets configured into WED MTK_WED_RING_TX(n)
+++	 * registers.
+++	 *
+++	 * WED driver posts its own DMA ring as WLAN PDMA TX and configures it
+++	 * into MTK_WED_WPDMA_RING_TX(n) registers.
+++	 * It gets filled with packets picked up from WED TX ring and from
+++	 * WDMA RX.
+++	 */
+++
+++	BUG_ON(idx > ARRAY_SIZE(dev->tx_ring));
+++
+++	if (mtk_wed_ring_alloc(dev, ring, MTK_WED_TX_RING_SIZE))
+++		return -ENOMEM;
+++
+++	if (mtk_wed_wdma_ring_setup(dev, idx, MTK_WED_WDMA_RING_SIZE))
+++		return -ENOMEM;
+++
+++	ring->reg_base = MTK_WED_RING_TX(idx);
+++	ring->wpdma = regs;
+++
+++	/* WED -> WPDMA */
+++	wpdma_tx_w32(dev, idx, MTK_WED_RING_OFS_BASE, ring->desc_phys);
+++	wpdma_tx_w32(dev, idx, MTK_WED_RING_OFS_COUNT, MTK_WED_TX_RING_SIZE);
+++	wpdma_tx_w32(dev, idx, MTK_WED_RING_OFS_CPU_IDX, 0);
+++
+++	wed_w32(dev, MTK_WED_WPDMA_RING_TX(idx) + MTK_WED_RING_OFS_BASE,
+++		ring->desc_phys);
+++	wed_w32(dev, MTK_WED_WPDMA_RING_TX(idx) + MTK_WED_RING_OFS_COUNT,
+++		MTK_WED_TX_RING_SIZE);
+++	wed_w32(dev, MTK_WED_WPDMA_RING_TX(idx) + MTK_WED_RING_OFS_CPU_IDX, 0);
+++
+++	return 0;
+++}
+++
+++static int
+++mtk_wed_txfree_ring_setup(struct mtk_wed_device *dev, void __iomem *regs)
+++{
+++	struct mtk_wed_ring *ring = &dev->txfree_ring;
+++	int i;
+++
+++	/*
+++	 * For txfree event handling, the same DMA ring is shared between WED
+++	 * and WLAN. The WLAN driver accesses the ring index registers through
+++	 * WED
+++	 */
+++	ring->reg_base = MTK_WED_RING_RX(1);
+++	ring->wpdma = regs;
+++
+++	for (i = 0; i < 12; i += 4) {
+++		u32 val = readl(regs + i);
+++
+++		wed_w32(dev, MTK_WED_RING_RX(1) + i, val);
+++		wed_w32(dev, MTK_WED_WPDMA_RING_RX(1) + i, val);
+++	}
+++
+++	return 0;
+++}
+++
+++static u32
+++mtk_wed_irq_get(struct mtk_wed_device *dev, u32 mask)
+++{
+++	u32 val;
+++
+++	val = wed_r32(dev, MTK_WED_EXT_INT_STATUS);
+++	wed_w32(dev, MTK_WED_EXT_INT_STATUS, val);
+++	val &= MTK_WED_EXT_INT_STATUS_ERROR_MASK;
+++	if (!dev->hw->num_flows)
+++		val &= ~MTK_WED_EXT_INT_STATUS_TKID_WO_PYLD;
+++	if (val && net_ratelimit())
+++		pr_err("mtk_wed%d: error status=%08x\n", dev->hw->index, val);
+++
+++	val = wed_r32(dev, MTK_WED_INT_STATUS);
+++	val &= mask;
+++	wed_w32(dev, MTK_WED_INT_STATUS, val); /* ACK */
+++
+++	return val;
 ++}
 ++
 ++static void
-+ qca8k_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode,
-+ 			 const struct phylink_link_state *state)
-+ {
-+ 	struct qca8k_priv *priv = ds->priv;
-+ 	int cpu_port_index, ret;
-+-	u32 reg, val, delay;
-++	u32 reg, val;
-+ 
-+ 	switch (port) {
-+ 	case 0: /* 1st CPU port */
-+@@ -1315,32 +1362,10 @@ qca8k_phylink_mac_config(struct dsa_swit
-+ 	case PHY_INTERFACE_MODE_RGMII_ID:
-+ 	case PHY_INTERFACE_MODE_RGMII_TXID:
-+ 	case PHY_INTERFACE_MODE_RGMII_RXID:
-+-		val = QCA8K_PORT_PAD_RGMII_EN;
-+-
-+-		/* Delay can be declared in 3 different way.
-+-		 * Mode to rgmii and internal-delay standard binding defined
-+-		 * rgmii-id or rgmii-tx/rx phy mode set.
-+-		 * The parse logic set a delay different than 0 only when one
-+-		 * of the 3 different way is used. In all other case delay is
-+-		 * not enabled. With ID or TX/RXID delay is enabled and set
-+-		 * to the default and recommended value.
-+-		 */
-+-		if (priv->rgmii_tx_delay[cpu_port_index]) {
-+-			delay = priv->rgmii_tx_delay[cpu_port_index];
-+-
-+-			val |= QCA8K_PORT_PAD_RGMII_TX_DELAY(delay) |
-+-			       QCA8K_PORT_PAD_RGMII_TX_DELAY_EN;
-+-		}
-+-
-+-		if (priv->rgmii_rx_delay[cpu_port_index]) {
-+-			delay = priv->rgmii_rx_delay[cpu_port_index];
-+-
-+-			val |= QCA8K_PORT_PAD_RGMII_RX_DELAY(delay) |
-+-			       QCA8K_PORT_PAD_RGMII_RX_DELAY_EN;
-+-		}
-++		qca8k_write(priv, reg, QCA8K_PORT_PAD_RGMII_EN);
-+ 
-+-		/* Set RGMII delay based on the selected values */
-+-		qca8k_write(priv, reg, val);
-++		/* Configure rgmii delay */
-++		qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg);
-+ 
-+ 		/* QCA8337 requires to set rgmii rx delay for all ports.
-+ 		 * This is enabled through PORT5_PAD_CTRL for all ports,
-+@@ -1411,6 +1436,13 @@ qca8k_phylink_mac_config(struct dsa_swit
-+ 					QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE |
-+ 					QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE,
-+ 					val);
+++mtk_wed_irq_set_mask(struct mtk_wed_device *dev, u32 mask)
+++{
+++	if (!dev->running)
+++		return;
 ++
-++		/* From original code is reported port instability as SGMII also
-++		 * require delay set. Apply advised values here or take them from DT.
-++		 */
-++		if (state->interface == PHY_INTERFACE_MODE_SGMII)
-++			qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg);
+++	mtk_wed_set_ext_int(dev, !!mask);
+++	wed_w32(dev, MTK_WED_INT_MASK, mask);
+++}
 ++
-+ 		break;
-+ 	default:
-+ 		dev_err(ds->dev, "xMII mode %s not supported for port %d\n",
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -39,7 +39,9 @@
-+ #define QCA8K_REG_PORT5_PAD_CTRL			0x008
-+ #define QCA8K_REG_PORT6_PAD_CTRL			0x00c
-+ #define   QCA8K_PORT_PAD_RGMII_EN			BIT(26)
-++#define   QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK		GENMASK(23, 22)
-+ #define   QCA8K_PORT_PAD_RGMII_TX_DELAY(x)		((x) << 22)
-++#define   QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK		GENMASK(21, 20)
-+ #define   QCA8K_PORT_PAD_RGMII_RX_DELAY(x)		((x) << 20)
-+ #define	  QCA8K_PORT_PAD_RGMII_TX_DELAY_EN		BIT(25)
-+ #define   QCA8K_PORT_PAD_RGMII_RX_DELAY_EN		BIT(24)
-diff --git a/target/linux/generic/backport-5.15/747-v5.16-14-net-dsa-qca8k-move-port-config-to-dedicated-struct.patch b/target/linux/generic/backport-5.15/747-v5.16-14-net-dsa-qca8k-move-port-config-to-dedicated-struct.patch
-new file mode 100644
-index 0000000000..b991798c87
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/747-v5.16-14-net-dsa-qca8k-move-port-config-to-dedicated-struct.patch
-@@ -0,0 +1,124 @@
-+From fd0bb28c547f7c8affb1691128cece38f5b626a1 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Thu, 14 Oct 2021 00:39:19 +0200
-+Subject: net: dsa: qca8k: move port config to dedicated struct
-+
-+Move ports related config to dedicated struct to keep things organized.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 26 +++++++++++++-------------
-+ drivers/net/dsa/qca8k.h | 10 +++++++---
-+ 2 files changed, 20 insertions(+), 16 deletions(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -1019,7 +1019,7 @@ qca8k_parse_port_config(struct qca8k_pri
-+ 				delay = 3;
-+ 			}
-+ 
-+-			priv->rgmii_tx_delay[cpu_port_index] = delay;
-++			priv->ports_config.rgmii_tx_delay[cpu_port_index] = delay;
-+ 
-+ 			delay = 0;
-+ 
-+@@ -1035,7 +1035,7 @@ qca8k_parse_port_config(struct qca8k_pri
-+ 				delay = 3;
-+ 			}
-+ 
-+-			priv->rgmii_rx_delay[cpu_port_index] = delay;
-++			priv->ports_config.rgmii_rx_delay[cpu_port_index] = delay;
-+ 
-+ 			/* Skip sgmii parsing for rgmii* mode */
-+ 			if (mode == PHY_INTERFACE_MODE_RGMII ||
-+@@ -1045,17 +1045,17 @@ qca8k_parse_port_config(struct qca8k_pri
-+ 				break;
-+ 
-+ 			if (of_property_read_bool(port_dn, "qca,sgmii-txclk-falling-edge"))
-+-				priv->sgmii_tx_clk_falling_edge = true;
-++				priv->ports_config.sgmii_tx_clk_falling_edge = true;
-+ 
-+ 			if (of_property_read_bool(port_dn, "qca,sgmii-rxclk-falling-edge"))
-+-				priv->sgmii_rx_clk_falling_edge = true;
-++				priv->ports_config.sgmii_rx_clk_falling_edge = true;
-+ 
-+ 			if (of_property_read_bool(port_dn, "qca,sgmii-enable-pll")) {
-+-				priv->sgmii_enable_pll = true;
-++				priv->ports_config.sgmii_enable_pll = true;
-+ 
-+ 				if (priv->switch_id == QCA8K_ID_QCA8327) {
-+ 					dev_err(priv->dev, "SGMII PLL should NOT be enabled for qca8327. Aborting enabling");
-+-					priv->sgmii_enable_pll = false;
-++					priv->ports_config.sgmii_enable_pll = false;
-+ 				}
-+ 
-+ 				if (priv->switch_revision < 2)
-+@@ -1281,15 +1281,15 @@ qca8k_mac_config_setup_internal_delay(st
-+ 	 * not enabled. With ID or TX/RXID delay is enabled and set
-+ 	 * to the default and recommended value.
-+ 	 */
-+-	if (priv->rgmii_tx_delay[cpu_port_index]) {
-+-		delay = priv->rgmii_tx_delay[cpu_port_index];
-++	if (priv->ports_config.rgmii_tx_delay[cpu_port_index]) {
-++		delay = priv->ports_config.rgmii_tx_delay[cpu_port_index];
-+ 
-+ 		val |= QCA8K_PORT_PAD_RGMII_TX_DELAY(delay) |
-+ 			QCA8K_PORT_PAD_RGMII_TX_DELAY_EN;
-+ 	}
-+ 
-+-	if (priv->rgmii_rx_delay[cpu_port_index]) {
-+-		delay = priv->rgmii_rx_delay[cpu_port_index];
-++	if (priv->ports_config.rgmii_rx_delay[cpu_port_index]) {
-++		delay = priv->ports_config.rgmii_rx_delay[cpu_port_index];
-+ 
-+ 		val |= QCA8K_PORT_PAD_RGMII_RX_DELAY(delay) |
-+ 			QCA8K_PORT_PAD_RGMII_RX_DELAY_EN;
-+@@ -1397,7 +1397,7 @@ qca8k_phylink_mac_config(struct dsa_swit
-+ 
-+ 		val |= QCA8K_SGMII_EN_SD;
-+ 
-+-		if (priv->sgmii_enable_pll)
-++		if (priv->ports_config.sgmii_enable_pll)
-+ 			val |= QCA8K_SGMII_EN_PLL | QCA8K_SGMII_EN_RX |
-+ 			       QCA8K_SGMII_EN_TX;
-+ 
-+@@ -1425,10 +1425,10 @@ qca8k_phylink_mac_config(struct dsa_swit
-+ 		val = 0;
-+ 
-+ 		/* SGMII Clock phase configuration */
-+-		if (priv->sgmii_rx_clk_falling_edge)
-++		if (priv->ports_config.sgmii_rx_clk_falling_edge)
-+ 			val |= QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE;
-+ 
-+-		if (priv->sgmii_tx_clk_falling_edge)
-++		if (priv->ports_config.sgmii_tx_clk_falling_edge)
-+ 			val |= QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE;
-+ 
-+ 		if (val)
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -270,15 +270,19 @@ enum {
-+ 	QCA8K_CPU_PORT6,
-+ };
-+ 
-+-struct qca8k_priv {
-+-	u8 switch_id;
-+-	u8 switch_revision;
-++struct qca8k_ports_config {
-+ 	bool sgmii_rx_clk_falling_edge;
-+ 	bool sgmii_tx_clk_falling_edge;
-+ 	bool sgmii_enable_pll;
-+ 	u8 rgmii_rx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
-+ 	u8 rgmii_tx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
-++};
+++int mtk_wed_flow_add(int index)
+++{
+++	struct mtk_wed_hw *hw = hw_list[index];
+++	int ret;
 ++
-++struct qca8k_priv {
-++	u8 switch_id;
-++	u8 switch_revision;
-+ 	bool legacy_phy_port_mapping;
-++	struct qca8k_ports_config ports_config;
-+ 	struct regmap *regmap;
-+ 	struct mii_bus *bus;
-+ 	struct ar8xxx_port_status port_sts[QCA8K_NUM_PORTS];
-diff --git a/target/linux/generic/backport-5.15/747-v5.16-15-dt-bindings-net-ipq8064-mdio-fix-warning-with-new-qc.patch b/target/linux/generic/backport-5.15/747-v5.16-15-dt-bindings-net-ipq8064-mdio-fix-warning-with-new-qc.patch
-new file mode 100644
-index 0000000000..f7cb514176
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/747-v5.16-15-dt-bindings-net-ipq8064-mdio-fix-warning-with-new-qc.patch
-@@ -0,0 +1,26 @@
-+From e52073a8e3086046a098b8a7cbeb282ff0cdb424 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Thu, 14 Oct 2021 00:39:20 +0200
-+Subject: dt-bindings: net: ipq8064-mdio: fix warning with new qca8k switch
-+
-+Fix warning now that we have qca8k switch Documentation using yaml.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ Documentation/devicetree/bindings/net/qcom,ipq8064-mdio.yaml | 5 ++++-
-+ 1 file changed, 4 insertions(+), 1 deletion(-)
-+
-+--- a/Documentation/devicetree/bindings/net/qcom,ipq8064-mdio.yaml
-++++ b/Documentation/devicetree/bindings/net/qcom,ipq8064-mdio.yaml
-+@@ -51,6 +51,9 @@ examples:
-+         switch@10 {
-+             compatible = "qca,qca8337";
-+             reg = <0x10>;
-+-            /* ... */
+++	if (!hw || !hw->wed_dev)
+++		return -ENODEV;
 ++
-++            ports {
-++              /* ... */
-++            };
-+         };
-+     };
-diff --git a/target/linux/generic/backport-5.15/747-v5.16-16-dt-bindings-net-dsa-qca8k-convert-to-YAML-schema.patch b/target/linux/generic/backport-5.15/747-v5.16-16-dt-bindings-net-dsa-qca8k-convert-to-YAML-schema.patch
-new file mode 100644
-index 0000000000..b9bce97dd3
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/747-v5.16-16-dt-bindings-net-dsa-qca8k-convert-to-YAML-schema.patch
-@@ -0,0 +1,631 @@
-+From d291fbb8245d5ba04979fed85575860a5cea7196 Mon Sep 17 00:00:00 2001
-+From: Matthew Hagan <mnhagan88@gmail.com>
-+Date: Thu, 14 Oct 2021 00:39:21 +0200
-+Subject: dt-bindings: net: dsa: qca8k: convert to YAML schema
-+
-+Convert the qca8k bindings to YAML format.
-+
-+Signed-off-by: Matthew Hagan <mnhagan88@gmail.com>
-+Co-developed-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ .../devicetree/bindings/net/dsa/qca8k.txt          | 245 --------------
-+ .../devicetree/bindings/net/dsa/qca8k.yaml         | 362 +++++++++++++++++++++
-+ 2 files changed, 362 insertions(+), 245 deletions(-)
-+ delete mode 100644 Documentation/devicetree/bindings/net/dsa/qca8k.txt
-+ create mode 100644 Documentation/devicetree/bindings/net/dsa/qca8k.yaml
-+
-+--- a/Documentation/devicetree/bindings/net/dsa/qca8k.txt
-++++ /dev/null
-+@@ -1,245 +0,0 @@
-+-* Qualcomm Atheros QCA8xxx switch family
-+-
-+-Required properties:
-+-
-+-- compatible: should be one of:
-+-    "qca,qca8328": referenced as AR8328(N)-AK1(A/B) QFN 176 pin package
-+-    "qca,qca8327": referenced as AR8327(N)-AL1A DR-QFN 148 pin package
-+-    "qca,qca8334": referenced as QCA8334-AL3C QFN 88 pin package
-+-    "qca,qca8337": referenced as QCA8337N-AL3(B/C) DR-QFN 148 pin package
-+-
-+-- #size-cells: must be 0
-+-- #address-cells: must be 1
-+-
-+-Optional properties:
-+-
-+-- reset-gpios: GPIO to be used to reset the whole device
-+-- qca,ignore-power-on-sel: Ignore power on pin strapping to configure led open
-+-                           drain or eeprom presence. This is needed for broken
-+-                           devices that have wrong configuration or when the oem
-+-                           decided to not use pin strapping and fallback to sw
-+-                           regs.
-+-- qca,led-open-drain: Set leds to open-drain mode. This requires the
-+-                      qca,ignore-power-on-sel to be set or the driver will fail
-+-                      to probe. This is needed if the oem doesn't use pin
-+-                      strapping to set this mode and prefers to set it using sw
-+-                      regs. The pin strapping related to led open drain mode is
-+-                      the pin B68 for QCA832x and B49 for QCA833x
-+-
-+-Subnodes:
-+-
-+-The integrated switch subnode should be specified according to the binding
-+-described in dsa/dsa.txt. If the QCA8K switch is connect to a SoC's external
-+-mdio-bus each subnode describing a port needs to have a valid phandle
-+-referencing the internal PHY it is connected to. This is because there's no
-+-N:N mapping of port and PHY id.
-+-To declare the internal mdio-bus configuration, declare a mdio node in the
-+-switch node and declare the phandle for the port referencing the internal
-+-PHY is connected to. In this config a internal mdio-bus is registered and
-+-the mdio MASTER is used as communication.
-+-
-+-Don't use mixed external and internal mdio-bus configurations, as this is
-+-not supported by the hardware.
-+-
-+-This switch support 2 CPU port. Normally and advised configuration is with
-+-CPU port set to port 0. It is also possible to set the CPU port to port 6
-+-if the device requires it. The driver will configure the switch to the defined
-+-port. With both CPU port declared the first CPU port is selected as primary
-+-and the secondary CPU ignored.
-+-
-+-A CPU port node has the following optional node:
-+-
-+-- fixed-link            : Fixed-link subnode describing a link to a non-MDIO
-+-                          managed entity. See
-+-                          Documentation/devicetree/bindings/net/fixed-link.txt
-+-                          for details.
-+-- qca,sgmii-rxclk-falling-edge: Set the receive clock phase to falling edge.
-+-                                Mostly used in qca8327 with CPU port 0 set to
-+-                                sgmii.
-+-- qca,sgmii-txclk-falling-edge: Set the transmit clock phase to falling edge.
-+-- qca,sgmii-enable-pll  : For SGMII CPU port, explicitly enable PLL, TX and RX
-+-                          chain along with Signal Detection.
-+-                          This should NOT be enabled for qca8327. If enabled with
-+-                          qca8327 the sgmii port won't correctly init and an err
-+-                          is printed.
-+-                          This can be required for qca8337 switch with revision 2.
-+-                          A warning is displayed when used with revision greater
-+-                          2.
-+-                          With CPU port set to sgmii and qca8337 it is advised
-+-                          to set this unless a communication problem is observed.
-+-
-+-For QCA8K the 'fixed-link' sub-node supports only the following properties:
-+-
-+-- 'speed' (integer, mandatory), to indicate the link speed. Accepted
-+-  values are 10, 100 and 1000
-+-- 'full-duplex' (boolean, optional), to indicate that full duplex is
-+-  used. When absent, half duplex is assumed.
-+-
-+-Examples:
-+-
-+-for the external mdio-bus configuration:
-+-
-+-	&mdio0 {
-+-		phy_port1: phy@0 {
-+-			reg = <0>;
-+-		};
-+-
-+-		phy_port2: phy@1 {
-+-			reg = <1>;
-+-		};
-+-
-+-		phy_port3: phy@2 {
-+-			reg = <2>;
-+-		};
-+-
-+-		phy_port4: phy@3 {
-+-			reg = <3>;
-+-		};
-+-
-+-		phy_port5: phy@4 {
-+-			reg = <4>;
-+-		};
-+-
-+-		switch@10 {
-+-			compatible = "qca,qca8337";
-+-			#address-cells = <1>;
-+-			#size-cells = <0>;
-+-
-+-			reset-gpios = <&gpio 42 GPIO_ACTIVE_LOW>;
-+-			reg = <0x10>;
-+-
-+-			ports {
-+-				#address-cells = <1>;
-+-				#size-cells = <0>;
-+-				port@0 {
-+-					reg = <0>;
-+-					label = "cpu";
-+-					ethernet = <&gmac1>;
-+-					phy-mode = "rgmii";
-+-					fixed-link {
-+-						speed = 1000;
-+-						full-duplex;
-+-					};
-+-				};
-+-
-+-				port@1 {
-+-					reg = <1>;
-+-					label = "lan1";
-+-					phy-handle = <&phy_port1>;
-+-				};
-+-
-+-				port@2 {
-+-					reg = <2>;
-+-					label = "lan2";
-+-					phy-handle = <&phy_port2>;
-+-				};
-+-
-+-				port@3 {
-+-					reg = <3>;
-+-					label = "lan3";
-+-					phy-handle = <&phy_port3>;
-+-				};
-+-
-+-				port@4 {
-+-					reg = <4>;
-+-					label = "lan4";
-+-					phy-handle = <&phy_port4>;
-+-				};
-+-
-+-				port@5 {
-+-					reg = <5>;
-+-					label = "wan";
-+-					phy-handle = <&phy_port5>;
-+-				};
-+-			};
-+-		};
-+-	};
-+-
-+-for the internal master mdio-bus configuration:
-+-
-+-	&mdio0 {
-+-		switch@10 {
-+-			compatible = "qca,qca8337";
-+-			#address-cells = <1>;
-+-			#size-cells = <0>;
-+-
-+-			reset-gpios = <&gpio 42 GPIO_ACTIVE_LOW>;
-+-			reg = <0x10>;
-+-
-+-			ports {
-+-				#address-cells = <1>;
-+-				#size-cells = <0>;
-+-
-+-				port@0 {
-+-					reg = <0>;
-+-					label = "cpu";
-+-					ethernet = <&gmac1>;
-+-					phy-mode = "rgmii";
-+-					fixed-link {
-+-						speed = 1000;
-+-						full-duplex;
-+-					};
-+-				};
-+-
-+-				port@1 {
-+-					reg = <1>;
-+-					label = "lan1";
-+-					phy-mode = "internal";
-+-					phy-handle = <&phy_port1>;
-+-				};
-+-
-+-				port@2 {
-+-					reg = <2>;
-+-					label = "lan2";
-+-					phy-mode = "internal";
-+-					phy-handle = <&phy_port2>;
-+-				};
-+-
-+-				port@3 {
-+-					reg = <3>;
-+-					label = "lan3";
-+-					phy-mode = "internal";
-+-					phy-handle = <&phy_port3>;
-+-				};
-+-
-+-				port@4 {
-+-					reg = <4>;
-+-					label = "lan4";
-+-					phy-mode = "internal";
-+-					phy-handle = <&phy_port4>;
-+-				};
-+-
-+-				port@5 {
-+-					reg = <5>;
-+-					label = "wan";
-+-					phy-mode = "internal";
-+-					phy-handle = <&phy_port5>;
-+-				};
-+-			};
-+-
-+-			mdio {
-+-				#address-cells = <1>;
-+-				#size-cells = <0>;
-+-
-+-				phy_port1: phy@0 {
-+-					reg = <0>;
-+-				};
-+-
-+-				phy_port2: phy@1 {
-+-					reg = <1>;
-+-				};
-+-
-+-				phy_port3: phy@2 {
-+-					reg = <2>;
-+-				};
-+-
-+-				phy_port4: phy@3 {
-+-					reg = <3>;
-+-				};
-+-
-+-				phy_port5: phy@4 {
-+-					reg = <4>;
-+-				};
-+-			};
-+-		};
-+-	};
-+--- /dev/null
-++++ b/Documentation/devicetree/bindings/net/dsa/qca8k.yaml
-+@@ -0,0 +1,362 @@
-++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
-++%YAML 1.2
-++---
-++$id: http://devicetree.org/schemas/net/dsa/qca8k.yaml#
-++$schema: http://devicetree.org/meta-schemas/core.yaml#
+++	if (hw->num_flows) {
+++		hw->num_flows++;
+++		return 0;
+++	}
 ++
-++title: Qualcomm Atheros QCA83xx switch family
+++	mutex_lock(&hw_lock);
+++	if (!hw->wed_dev) {
+++		ret = -ENODEV;
+++		goto out;
+++	}
 ++
-++maintainers:
-++  - John Crispin <john@phrozen.org>
+++	ret = hw->wed_dev->wlan.offload_enable(hw->wed_dev);
+++	if (!ret)
+++		hw->num_flows++;
+++	mtk_wed_set_ext_int(hw->wed_dev, true);
 ++
-++description:
-++  If the QCA8K switch is connect to an SoC's external mdio-bus, each subnode
-++  describing a port needs to have a valid phandle referencing the internal PHY
-++  it is connected to. This is because there is no N:N mapping of port and PHY
-++  ID. To declare the internal mdio-bus configuration, declare an MDIO node in
-++  the switch node and declare the phandle for the port, referencing the internal
-++  PHY it is connected to. In this config, an internal mdio-bus is registered and
-++  the MDIO master is used for communication. Mixed external and internal
-++  mdio-bus configurations are not supported by the hardware.
+++out:
+++	mutex_unlock(&hw_lock);
 ++
-++properties:
-++  compatible:
-++    oneOf:
-++      - enum:
-++          - qca,qca8327
-++          - qca,qca8328
-++          - qca,qca8334
-++          - qca,qca8337
-++    description: |
-++      qca,qca8328: referenced as AR8328(N)-AK1(A/B) QFN 176 pin package
-++      qca,qca8327: referenced as AR8327(N)-AL1A DR-QFN 148 pin package
-++      qca,qca8334: referenced as QCA8334-AL3C QFN 88 pin package
-++      qca,qca8337: referenced as QCA8337N-AL3(B/C) DR-QFN 148 pin package
+++	return ret;
+++}
 ++
-++  reg:
-++    maxItems: 1
+++void mtk_wed_flow_remove(int index)
+++{
+++	struct mtk_wed_hw *hw = hw_list[index];
 ++
-++  reset-gpios:
-++    description:
-++      GPIO to be used to reset the whole device
-++    maxItems: 1
+++	if (!hw)
+++		return;
 ++
-++  qca,ignore-power-on-sel:
-++    $ref: /schemas/types.yaml#/definitions/flag
-++    description:
-++      Ignore power-on pin strapping to configure LED open-drain or EEPROM
-++      presence. This is needed for devices with incorrect configuration or when
-++      the OEM has decided not to use pin strapping and falls back to SW regs.
+++	if (--hw->num_flows)
+++		return;
 ++
-++  qca,led-open-drain:
-++    $ref: /schemas/types.yaml#/definitions/flag
-++    description:
-++      Set LEDs to open-drain mode. This requires the qca,ignore-power-on-sel to
-++      be set, otherwise the driver will fail at probe. This is required if the
-++      OEM does not use pin strapping to set this mode and prefers to set it
-++      using SW regs. The pin strappings related to LED open-drain mode are
-++      B68 on the QCA832x and B49 on the QCA833x.
+++	mutex_lock(&hw_lock);
+++	if (!hw->wed_dev)
+++		goto out;
 ++
-++  mdio:
-++    type: object
-++    description: Qca8k switch have an internal mdio to access switch port.
-++                 If this is not present, the legacy mapping is used and the
-++                 internal mdio access is used.
-++                 With the legacy mapping the reg corresponding to the internal
-++                 mdio is the switch reg with an offset of -1.
+++	hw->wed_dev->wlan.offload_disable(hw->wed_dev);
+++	mtk_wed_set_ext_int(hw->wed_dev, true);
 ++
-++    properties:
-++      '#address-cells':
-++        const: 1
-++      '#size-cells':
-++        const: 0
+++out:
+++	mutex_unlock(&hw_lock);
+++}
 ++
-++    patternProperties:
-++      "^(ethernet-)?phy@[0-4]$":
-++        type: object
+++void mtk_wed_add_hw(struct device_node *np, struct mtk_eth *eth,
+++		    void __iomem *wdma, int index)
+++{
+++	static const struct mtk_wed_ops wed_ops = {
+++		.attach = mtk_wed_attach,
+++		.tx_ring_setup = mtk_wed_tx_ring_setup,
+++		.txfree_ring_setup = mtk_wed_txfree_ring_setup,
+++		.start = mtk_wed_start,
+++		.stop = mtk_wed_stop,
+++		.reset_dma = mtk_wed_reset_dma,
+++		.reg_read = wed_r32,
+++		.reg_write = wed_w32,
+++		.irq_get = mtk_wed_irq_get,
+++		.irq_set_mask = mtk_wed_irq_set_mask,
+++		.detach = mtk_wed_detach,
+++	};
+++	struct device_node *eth_np = eth->dev->of_node;
+++	struct platform_device *pdev;
+++	struct mtk_wed_hw *hw;
+++	struct regmap *regs;
+++	int irq;
 ++
-++        allOf:
-++          - $ref: "http://devicetree.org/schemas/net/mdio.yaml#"
+++	if (!np)
+++		return;
 ++
-++        properties:
-++          reg:
-++            maxItems: 1
+++	pdev = of_find_device_by_node(np);
+++	if (!pdev)
+++		return;
 ++
-++        required:
-++          - reg
+++	get_device(&pdev->dev);
+++	irq = platform_get_irq(pdev, 0);
+++	if (irq < 0)
+++		return;
 ++
-++patternProperties:
-++  "^(ethernet-)?ports$":
-++    type: object
-++    properties:
-++      '#address-cells':
-++        const: 1
-++      '#size-cells':
-++        const: 0
+++	regs = syscon_regmap_lookup_by_phandle(np, NULL);
+++	if (!regs)
+++		return;
 ++
-++    patternProperties:
-++      "^(ethernet-)?port@[0-6]$":
-++        type: object
-++        description: Ethernet switch ports
+++	rcu_assign_pointer(mtk_soc_wed_ops, &wed_ops);
 ++
-++        properties:
-++          reg:
-++            description: Port number
+++	mutex_lock(&hw_lock);
 ++
-++          label:
-++            description:
-++              Describes the label associated with this port, which will become
-++              the netdev name
-++            $ref: /schemas/types.yaml#/definitions/string
+++	if (WARN_ON(hw_list[index]))
+++		goto unlock;
 ++
-++          link:
-++            description:
-++              Should be a list of phandles to other switch's DSA port. This
-++              port is used as the outgoing port towards the phandle ports. The
-++              full routing information must be given, not just the one hop
-++              routes to neighbouring switches
-++            $ref: /schemas/types.yaml#/definitions/phandle-array
+++	hw = kzalloc(sizeof(*hw), GFP_KERNEL);
+++	hw->node = np;
+++	hw->regs = regs;
+++	hw->eth = eth;
+++	hw->dev = &pdev->dev;
+++	hw->wdma = wdma;
+++	hw->index = index;
+++	hw->irq = irq;
+++	hw->mirror = syscon_regmap_lookup_by_phandle(eth_np,
+++						     "mediatek,pcie-mirror");
+++	hw->hifsys = syscon_regmap_lookup_by_phandle(eth_np,
+++						     "mediatek,hifsys");
+++	if (IS_ERR(hw->mirror) || IS_ERR(hw->hifsys)) {
+++		kfree(hw);
+++		goto unlock;
+++	}
 ++
-++          ethernet:
-++            description:
-++              Should be a phandle to a valid Ethernet device node.  This host
-++              device is what the switch port is connected to
-++            $ref: /schemas/types.yaml#/definitions/phandle
+++	if (!index) {
+++		regmap_write(hw->mirror, 0, 0);
+++		regmap_write(hw->mirror, 4, 0);
+++	}
+++	mtk_wed_hw_add_debugfs(hw);
 ++
-++          phy-handle: true
+++	hw_list[index] = hw;
 ++
-++          phy-mode: true
+++unlock:
+++	mutex_unlock(&hw_lock);
+++}
 ++
-++          fixed-link: true
+++void mtk_wed_exit(void)
+++{
+++	int i;
 ++
-++          mac-address: true
+++	rcu_assign_pointer(mtk_soc_wed_ops, NULL);
 ++
-++          sfp: true
+++	synchronize_rcu();
 ++
-++          qca,sgmii-rxclk-falling-edge:
-++            $ref: /schemas/types.yaml#/definitions/flag
-++            description:
-++              Set the receive clock phase to falling edge. Mostly commonly used on
-++              the QCA8327 with CPU port 0 set to SGMII.
+++	for (i = 0; i < ARRAY_SIZE(hw_list); i++) {
+++		struct mtk_wed_hw *hw;
 ++
-++          qca,sgmii-txclk-falling-edge:
-++            $ref: /schemas/types.yaml#/definitions/flag
-++            description:
-++              Set the transmit clock phase to falling edge.
+++		hw = hw_list[i];
+++		if (!hw)
+++			continue;
 ++
-++          qca,sgmii-enable-pll:
-++            $ref: /schemas/types.yaml#/definitions/flag
-++            description:
-++              For SGMII CPU port, explicitly enable PLL, TX and RX chain along with
-++              Signal Detection. On the QCA8327 this should not be enabled, otherwise
-++              the SGMII port will not initialize. When used on the QCA8337, revision 3
-++              or greater, a warning will be displayed. When the CPU port is set to
-++              SGMII on the QCA8337, it is advised to set this unless a communication
-++              issue is observed.
+++		hw_list[i] = NULL;
+++		debugfs_remove(hw->debugfs_dir);
+++		put_device(hw->dev);
+++		kfree(hw);
+++	}
+++}
++--- /dev/null
+++++ b/drivers/net/ethernet/mediatek/mtk_wed.h
++@@ -0,0 +1,128 @@
+++// SPDX-License-Identifier: GPL-2.0-only
+++/* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name> */
 ++
-++        required:
-++          - reg
+++#ifndef __MTK_WED_PRIV_H
+++#define __MTK_WED_PRIV_H
 ++
-++        additionalProperties: false
+++#include <linux/soc/mediatek/mtk_wed.h>
+++#include <linux/debugfs.h>
+++#include <linux/regmap.h>
 ++
-++oneOf:
-++  - required:
-++      - ports
-++  - required:
-++      - ethernet-ports
+++struct mtk_eth;
 ++
-++required:
-++  - compatible
-++  - reg
+++struct mtk_wed_hw {
+++	struct device_node *node;
+++	struct mtk_eth *eth;
+++	struct regmap *regs;
+++	struct regmap *hifsys;
+++	struct device *dev;
+++	void __iomem *wdma;
+++	struct regmap *mirror;
+++	struct dentry *debugfs_dir;
+++	struct mtk_wed_device *wed_dev;
+++	u32 debugfs_reg;
+++	u32 num_flows;
+++	char dirname[5];
+++	int irq;
+++	int index;
+++};
 ++
-++additionalProperties: true
 ++
-++examples:
-++  - |
-++    #include <dt-bindings/gpio/gpio.h>
+++#ifdef CONFIG_NET_MEDIATEK_SOC_WED
+++static inline void
+++wed_w32(struct mtk_wed_device *dev, u32 reg, u32 val)
+++{
+++	regmap_write(dev->hw->regs, reg, val);
+++}
 ++
-++    mdio {
-++        #address-cells = <1>;
-++        #size-cells = <0>;
+++static inline u32
+++wed_r32(struct mtk_wed_device *dev, u32 reg)
+++{
+++	unsigned int val;
 ++
-++        external_phy_port1: ethernet-phy@0 {
-++            reg = <0>;
-++        };
+++	regmap_read(dev->hw->regs, reg, &val);
 ++
-++        external_phy_port2: ethernet-phy@1 {
-++            reg = <1>;
-++        };
+++	return val;
+++}
 ++
-++        external_phy_port3: ethernet-phy@2 {
-++            reg = <2>;
-++        };
+++static inline void
+++wdma_w32(struct mtk_wed_device *dev, u32 reg, u32 val)
+++{
+++	writel(val, dev->hw->wdma + reg);
+++}
 ++
-++        external_phy_port4: ethernet-phy@3 {
-++            reg = <3>;
-++        };
+++static inline u32
+++wdma_r32(struct mtk_wed_device *dev, u32 reg)
+++{
+++	return readl(dev->hw->wdma + reg);
+++}
 ++
-++        external_phy_port5: ethernet-phy@4 {
-++            reg = <4>;
-++        };
+++static inline u32
+++wpdma_tx_r32(struct mtk_wed_device *dev, int ring, u32 reg)
+++{
+++	if (!dev->tx_ring[ring].wpdma)
+++		return 0;
 ++
-++        switch@10 {
-++            compatible = "qca,qca8337";
-++            #address-cells = <1>;
-++            #size-cells = <0>;
-++            reset-gpios = <&gpio 42 GPIO_ACTIVE_LOW>;
-++            reg = <0x10>;
+++	return readl(dev->tx_ring[ring].wpdma + reg);
+++}
 ++
-++            ports {
-++                #address-cells = <1>;
-++                #size-cells = <0>;
+++static inline void
+++wpdma_tx_w32(struct mtk_wed_device *dev, int ring, u32 reg, u32 val)
+++{
+++	if (!dev->tx_ring[ring].wpdma)
+++		return;
 ++
-++                port@0 {
-++                    reg = <0>;
-++                    label = "cpu";
-++                    ethernet = <&gmac1>;
-++                    phy-mode = "rgmii";
+++	writel(val, dev->tx_ring[ring].wpdma + reg);
+++}
 ++
-++                    fixed-link {
-++                        speed = <1000>;
-++                        full-duplex;
-++                    };
-++                };
+++static inline u32
+++wpdma_txfree_r32(struct mtk_wed_device *dev, u32 reg)
+++{
+++	if (!dev->txfree_ring.wpdma)
+++		return 0;
 ++
-++                port@1 {
-++                    reg = <1>;
-++                    label = "lan1";
-++                    phy-handle = <&external_phy_port1>;
-++                };
+++	return readl(dev->txfree_ring.wpdma + reg);
+++}
 ++
-++                port@2 {
-++                    reg = <2>;
-++                    label = "lan2";
-++                    phy-handle = <&external_phy_port2>;
-++                };
+++static inline void
+++wpdma_txfree_w32(struct mtk_wed_device *dev, u32 reg, u32 val)
+++{
+++	if (!dev->txfree_ring.wpdma)
+++		return;
 ++
-++                port@3 {
-++                    reg = <3>;
-++                    label = "lan3";
-++                    phy-handle = <&external_phy_port3>;
-++                };
+++	writel(val, dev->txfree_ring.wpdma + reg);
+++}
 ++
-++                port@4 {
-++                    reg = <4>;
-++                    label = "lan4";
-++                    phy-handle = <&external_phy_port4>;
-++                };
+++void mtk_wed_add_hw(struct device_node *np, struct mtk_eth *eth,
+++		    void __iomem *wdma, int index);
+++void mtk_wed_exit(void);
+++int mtk_wed_flow_add(int index);
+++void mtk_wed_flow_remove(int index);
+++#else
+++static inline void
+++mtk_wed_add_hw(struct device_node *np, struct mtk_eth *eth,
+++	       void __iomem *wdma, int index)
+++{
+++}
+++static inline void
+++mtk_wed_exit(void)
+++{
+++}
+++static inline int mtk_wed_flow_add(int index)
+++{
+++	return -EINVAL;
+++}
+++static inline void mtk_wed_flow_remove(int index)
+++{
+++}
+++#endif
 ++
-++                port@5 {
-++                    reg = <5>;
-++                    label = "wan";
-++                    phy-handle = <&external_phy_port5>;
-++                };
-++            };
-++        };
-++    };
-++  - |
-++    #include <dt-bindings/gpio/gpio.h>
+++#ifdef CONFIG_DEBUG_FS
+++void mtk_wed_hw_add_debugfs(struct mtk_wed_hw *hw);
+++#else
+++static inline void mtk_wed_hw_add_debugfs(struct mtk_wed_hw *hw)
+++{
+++}
+++#endif
 ++
-++    mdio {
-++        #address-cells = <1>;
-++        #size-cells = <0>;
+++#endif
++--- /dev/null
+++++ b/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
++@@ -0,0 +1,175 @@
+++// SPDX-License-Identifier: GPL-2.0-only
+++/* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name> */
 ++
-++        switch@10 {
-++            compatible = "qca,qca8337";
-++            #address-cells = <1>;
-++            #size-cells = <0>;
-++            reset-gpios = <&gpio 42 GPIO_ACTIVE_LOW>;
-++            reg = <0x10>;
+++#include <linux/seq_file.h>
+++#include "mtk_wed.h"
+++#include "mtk_wed_regs.h"
 ++
-++            ports {
-++                #address-cells = <1>;
-++                #size-cells = <0>;
+++struct reg_dump {
+++	const char *name;
+++	u16 offset;
+++	u8 type;
+++	u8 base;
+++};
 ++
-++                port@0 {
-++                    reg = <0>;
-++                    label = "cpu";
-++                    ethernet = <&gmac1>;
-++                    phy-mode = "rgmii";
+++enum {
+++	DUMP_TYPE_STRING,
+++	DUMP_TYPE_WED,
+++	DUMP_TYPE_WDMA,
+++	DUMP_TYPE_WPDMA_TX,
+++	DUMP_TYPE_WPDMA_TXFREE,
+++};
 ++
-++                    fixed-link {
-++                        speed = <1000>;
-++                        full-duplex;
-++                    };
-++                };
+++#define DUMP_STR(_str) { _str, 0, DUMP_TYPE_STRING }
+++#define DUMP_REG(_reg, ...) { #_reg, MTK_##_reg, __VA_ARGS__ }
+++#define DUMP_RING(_prefix, _base, ...)				\
+++	{ _prefix " BASE", _base, __VA_ARGS__ },		\
+++	{ _prefix " CNT",  _base + 0x4, __VA_ARGS__ },	\
+++	{ _prefix " CIDX", _base + 0x8, __VA_ARGS__ },	\
+++	{ _prefix " DIDX", _base + 0xc, __VA_ARGS__ }
 ++
-++                port@1 {
-++                    reg = <1>;
-++                    label = "lan1";
-++                    phy-mode = "internal";
-++                    phy-handle = <&internal_phy_port1>;
-++                };
+++#define DUMP_WED(_reg) DUMP_REG(_reg, DUMP_TYPE_WED)
+++#define DUMP_WED_RING(_base) DUMP_RING(#_base, MTK_##_base, DUMP_TYPE_WED)
 ++
-++                port@2 {
-++                    reg = <2>;
-++                    label = "lan2";
-++                    phy-mode = "internal";
-++                    phy-handle = <&internal_phy_port2>;
-++                };
+++#define DUMP_WDMA(_reg) DUMP_REG(_reg, DUMP_TYPE_WDMA)
+++#define DUMP_WDMA_RING(_base) DUMP_RING(#_base, MTK_##_base, DUMP_TYPE_WDMA)
 ++
-++                port@3 {
-++                    reg = <3>;
-++                    label = "lan3";
-++                    phy-mode = "internal";
-++                    phy-handle = <&internal_phy_port3>;
-++                };
+++#define DUMP_WPDMA_TX_RING(_n) DUMP_RING("WPDMA_TX" #_n, 0, DUMP_TYPE_WPDMA_TX, _n)
+++#define DUMP_WPDMA_TXFREE_RING DUMP_RING("WPDMA_RX1", 0, DUMP_TYPE_WPDMA_TXFREE)
 ++
-++                port@4 {
-++                    reg = <4>;
-++                    label = "lan4";
-++                    phy-mode = "internal";
-++                    phy-handle = <&internal_phy_port4>;
-++                };
+++static void
+++print_reg_val(struct seq_file *s, const char *name, u32 val)
+++{
+++	seq_printf(s, "%-32s %08x\n", name, val);
+++}
 ++
-++                port@5 {
-++                    reg = <5>;
-++                    label = "wan";
-++                    phy-mode = "internal";
-++                    phy-handle = <&internal_phy_port5>;
-++                };
+++static void
+++dump_wed_regs(struct seq_file *s, struct mtk_wed_device *dev,
+++	      const struct reg_dump *regs, int n_regs)
+++{
+++	const struct reg_dump *cur;
+++	u32 val;
 ++
-++                port@6 {
-++                    reg = <0>;
-++                    label = "cpu";
-++                    ethernet = <&gmac1>;
-++                    phy-mode = "sgmii";
+++	for (cur = regs; cur < &regs[n_regs]; cur++) {
+++		switch (cur->type) {
+++		case DUMP_TYPE_STRING:
+++			seq_printf(s, "%s======== %s:\n",
+++				   cur > regs ? "\n" : "",
+++				   cur->name);
+++			continue;
+++		case DUMP_TYPE_WED:
+++			val = wed_r32(dev, cur->offset);
+++			break;
+++		case DUMP_TYPE_WDMA:
+++			val = wdma_r32(dev, cur->offset);
+++			break;
+++		case DUMP_TYPE_WPDMA_TX:
+++			val = wpdma_tx_r32(dev, cur->base, cur->offset);
+++			break;
+++		case DUMP_TYPE_WPDMA_TXFREE:
+++			val = wpdma_txfree_r32(dev, cur->offset);
+++			break;
+++		}
+++		print_reg_val(s, cur->name, val);
+++	}
+++}
 ++
-++                    qca,sgmii-rxclk-falling-edge;
 ++
-++                    fixed-link {
-++                        speed = <1000>;
-++                        full-duplex;
-++                    };
-++                };
-++            };
+++static int
+++wed_txinfo_show(struct seq_file *s, void *data)
+++{
+++	static const struct reg_dump regs[] = {
+++		DUMP_STR("WED TX"),
+++		DUMP_WED(WED_TX_MIB(0)),
+++		DUMP_WED_RING(WED_RING_TX(0)),
 ++
-++            mdio {
-++                #address-cells = <1>;
-++                #size-cells = <0>;
+++		DUMP_WED(WED_TX_MIB(1)),
+++		DUMP_WED_RING(WED_RING_TX(1)),
 ++
-++                internal_phy_port1: ethernet-phy@0 {
-++                    reg = <0>;
-++                };
+++		DUMP_STR("WPDMA TX"),
+++		DUMP_WED(WED_WPDMA_TX_MIB(0)),
+++		DUMP_WED_RING(WED_WPDMA_RING_TX(0)),
+++		DUMP_WED(WED_WPDMA_TX_COHERENT_MIB(0)),
 ++
-++                internal_phy_port2: ethernet-phy@1 {
-++                    reg = <1>;
-++                };
+++		DUMP_WED(WED_WPDMA_TX_MIB(1)),
+++		DUMP_WED_RING(WED_WPDMA_RING_TX(1)),
+++		DUMP_WED(WED_WPDMA_TX_COHERENT_MIB(1)),
 ++
-++                internal_phy_port3: ethernet-phy@2 {
-++                    reg = <2>;
-++                };
+++		DUMP_STR("WPDMA TX"),
+++		DUMP_WPDMA_TX_RING(0),
+++		DUMP_WPDMA_TX_RING(1),
 ++
-++                internal_phy_port4: ethernet-phy@3 {
-++                    reg = <3>;
-++                };
+++		DUMP_STR("WED WDMA RX"),
+++		DUMP_WED(WED_WDMA_RX_MIB(0)),
+++		DUMP_WED_RING(WED_WDMA_RING_RX(0)),
+++		DUMP_WED(WED_WDMA_RX_THRES(0)),
+++		DUMP_WED(WED_WDMA_RX_RECYCLE_MIB(0)),
+++		DUMP_WED(WED_WDMA_RX_PROCESSED_MIB(0)),
 ++
-++                internal_phy_port5: ethernet-phy@4 {
-++                    reg = <4>;
-++                };
-++            };
-++        };
-++    };
-diff --git a/target/linux/generic/backport-5.15/748-v5.16-net-dsa-qca8k-fix-delay-applied-to-wrong-cpu-in-parse-p.patch b/target/linux/generic/backport-5.15/748-v5.16-net-dsa-qca8k-fix-delay-applied-to-wrong-cpu-in-parse-p.patch
-new file mode 100644
-index 0000000000..a510cfdc18
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/748-v5.16-net-dsa-qca8k-fix-delay-applied-to-wrong-cpu-in-parse-p.patch
-@@ -0,0 +1,28 @@
-+From 06dd34a628ae5b6a839b757e746de165d6789ca8 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Sun, 17 Oct 2021 16:56:46 +0200
-+Subject: net: dsa: qca8k: fix delay applied to wrong cpu in parse_port_config
-+
-+Fix delay settings applied to wrong cpu in parse_port_config. The delay
-+values is set to the wrong index as the cpu_port_index is incremented
-+too early. Start the cpu_port_index to -1 so the correct value is
-+applied to address also the case with invalid phy mode and not available
-+port.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 2 +-
-+ 1 file changed, 1 insertion(+), 1 deletion(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -976,7 +976,7 @@ qca8k_setup_of_pws_reg(struct qca8k_priv
-+ static int
-+ qca8k_parse_port_config(struct qca8k_priv *priv)
-+ {
-+-	int port, cpu_port_index = 0, ret;
-++	int port, cpu_port_index = -1, ret;
-+ 	struct device_node *port_dn;
-+ 	phy_interface_t mode;
-+ 	struct dsa_port *dp;
-diff --git a/target/linux/generic/backport-5.15/749-v5.16-net-dsa-qca8k-tidy-for-loop-in-setup-and-add-cpu-port-c.patch b/target/linux/generic/backport-5.15/749-v5.16-net-dsa-qca8k-tidy-for-loop-in-setup-and-add-cpu-port-c.patch
-new file mode 100644
-index 0000000000..71fa3022d5
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/749-v5.16-net-dsa-qca8k-tidy-for-loop-in-setup-and-add-cpu-port-c.patch
-@@ -0,0 +1,151 @@
-+From 040e926f5813a5f4cc18dbff7c942d1e52f368f2 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Tue, 19 Oct 2021 02:08:50 +0200
-+Subject: net: dsa: qca8k: tidy for loop in setup and add cpu port check
-+
-+Tidy and organize qca8k setup function from multiple for loop.
-+Change for loop in bridge leave/join to scan all port and skip cpu port.
-+No functional change intended.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 74 +++++++++++++++++++++++++++++--------------------
-+ 1 file changed, 44 insertions(+), 30 deletions(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -1122,28 +1122,34 @@ qca8k_setup(struct dsa_switch *ds)
-+ 	if (ret)
-+ 		dev_warn(priv->dev, "mib init failed");
-+ 
-+-	/* Enable QCA header mode on the cpu port */
-+-	ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(cpu_port),
-+-			  QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_TX_S |
-+-			  QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_RX_S);
-+-	if (ret) {
-+-		dev_err(priv->dev, "failed enabling QCA header mode");
-+-		return ret;
-+-	}
-+-
-+-	/* Disable forwarding by default on all ports */
-++	/* Initial setup of all ports */
-+ 	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
-++		/* Disable forwarding by default on all ports */
-+ 		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
-+ 				QCA8K_PORT_LOOKUP_MEMBER, 0);
-+ 		if (ret)
-+ 			return ret;
-+-	}
-+ 
-+-	/* Disable MAC by default on all ports */
-+-	for (i = 1; i < QCA8K_NUM_PORTS; i++)
-+-		qca8k_port_set_status(priv, i, 0);
-++		/* Enable QCA header mode on all cpu ports */
-++		if (dsa_is_cpu_port(ds, i)) {
-++			ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(i),
-++					  QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_TX_S |
-++					  QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_RX_S);
-++			if (ret) {
-++				dev_err(priv->dev, "failed enabling QCA header mode");
-++				return ret;
-++			}
-++		}
+++		DUMP_WED(WED_WDMA_RX_MIB(1)),
+++		DUMP_WED_RING(WED_WDMA_RING_RX(1)),
+++		DUMP_WED(WED_WDMA_RX_THRES(1)),
+++		DUMP_WED(WED_WDMA_RX_RECYCLE_MIB(1)),
+++		DUMP_WED(WED_WDMA_RX_PROCESSED_MIB(1)),
 ++
-++		/* Disable MAC by default on all user ports */
-++		if (dsa_is_user_port(ds, i))
-++			qca8k_port_set_status(priv, i, 0);
-++	}
-+ 
-+-	/* Forward all unknown frames to CPU port for Linux processing */
-++	/* Forward all unknown frames to CPU port for Linux processing
-++	 * Notice that in multi-cpu config only one port should be set
-++	 * for igmp, unknown, multicast and broadcast packet
-++	 */
-+ 	ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1,
-+ 			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S |
-+ 			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_BC_DP_S |
-+@@ -1152,11 +1158,13 @@ qca8k_setup(struct dsa_switch *ds)
-+ 	if (ret)
-+ 		return ret;
-+ 
-+-	/* Setup connection between CPU port & user ports */
-++	/* Setup connection between CPU port & user ports
-++	 * Configure specific switch configuration for ports
-++	 */
-+ 	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
-+ 		/* CPU port gets connected to all user ports of the switch */
-+ 		if (dsa_is_cpu_port(ds, i)) {
-+-			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(cpu_port),
-++			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
-+ 					QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds));
-+ 			if (ret)
-+ 				return ret;
-+@@ -1193,16 +1201,14 @@ qca8k_setup(struct dsa_switch *ds)
-+ 			if (ret)
-+ 				return ret;
-+ 		}
-+-	}
-+ 
-+-	/* The port 5 of the qca8337 have some problem in flood condition. The
-+-	 * original legacy driver had some specific buffer and priority settings
-+-	 * for the different port suggested by the QCA switch team. Add this
-+-	 * missing settings to improve switch stability under load condition.
-+-	 * This problem is limited to qca8337 and other qca8k switch are not affected.
-+-	 */
-+-	if (priv->switch_id == QCA8K_ID_QCA8337) {
-+-		for (i = 0; i < QCA8K_NUM_PORTS; i++) {
-++		/* The port 5 of the qca8337 have some problem in flood condition. The
-++		 * original legacy driver had some specific buffer and priority settings
-++		 * for the different port suggested by the QCA switch team. Add this
-++		 * missing settings to improve switch stability under load condition.
-++		 * This problem is limited to qca8337 and other qca8k switch are not affected.
-++		 */
-++		if (priv->switch_id == QCA8K_ID_QCA8337) {
-+ 			switch (i) {
-+ 			/* The 2 CPU port and port 5 requires some different
-+ 			 * priority than any other ports.
-+@@ -1238,6 +1244,12 @@ qca8k_setup(struct dsa_switch *ds)
-+ 				  QCA8K_PORT_HOL_CTRL1_WRED_EN,
-+ 				  mask);
-+ 		}
+++		DUMP_STR("WDMA RX"),
+++		DUMP_WDMA(WDMA_GLO_CFG),
+++		DUMP_WDMA_RING(WDMA_RING_RX(0)),
+++		DUMP_WDMA_RING(WDMA_RING_RX(1)),
+++	};
+++	struct mtk_wed_hw *hw = s->private;
+++	struct mtk_wed_device *dev = hw->wed_dev;
 ++
-++		/* Set initial MTU for every port.
-++		 * We have only have a general MTU setting. So track
-++		 * every port and set the max across all port.
-++		 */
-++		priv->port_mtu[i] = ETH_FRAME_LEN + ETH_FCS_LEN;
-+ 	}
-+ 
-+ 	/* Special GLOBAL_FC_THRESH value are needed for ar8327 switch */
-+@@ -1251,8 +1263,6 @@ qca8k_setup(struct dsa_switch *ds)
-+ 	}
-+ 
-+ 	/* Setup our port MTUs to match power on defaults */
-+-	for (i = 0; i < QCA8K_NUM_PORTS; i++)
-+-		priv->port_mtu[i] = ETH_FRAME_LEN + ETH_FCS_LEN;
-+ 	ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, ETH_FRAME_LEN + ETH_FCS_LEN);
-+ 	if (ret)
-+ 		dev_warn(priv->dev, "failed setting MTU settings");
-+@@ -1728,7 +1738,9 @@ qca8k_port_bridge_join(struct dsa_switch
-+ 	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
-+ 	port_mask = BIT(cpu_port);
-+ 
-+-	for (i = 1; i < QCA8K_NUM_PORTS; i++) {
-++	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
-++		if (dsa_is_cpu_port(ds, i))
-++			continue;
-+ 		if (dsa_to_port(ds, i)->bridge_dev != br)
-+ 			continue;
-+ 		/* Add this port to the portvlan mask of the other ports
-+@@ -1758,7 +1770,9 @@ qca8k_port_bridge_leave(struct dsa_switc
-+ 
-+ 	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
-+ 
-+-	for (i = 1; i < QCA8K_NUM_PORTS; i++) {
-++	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
-++		if (dsa_is_cpu_port(ds, i))
-++			continue;
-+ 		if (dsa_to_port(ds, i)->bridge_dev != br)
-+ 			continue;
-+ 		/* Remove this port to the portvlan mask of the other ports
-diff --git a/target/linux/generic/backport-5.15/750-v5.16-net-dsa-qca8k-make-sure-pad0-mac06-exchange-is-disabled.patch b/target/linux/generic/backport-5.15/750-v5.16-net-dsa-qca8k-make-sure-pad0-mac06-exchange-is-disabled.patch
-new file mode 100644
-index 0000000000..4a61703c52
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/750-v5.16-net-dsa-qca8k-make-sure-pad0-mac06-exchange-is-disabled.patch
-@@ -0,0 +1,47 @@
-+From 5f15d392dcb4aa250a63d6f2c5adfc26c0aedc78 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Tue, 2 Nov 2021 19:30:41 +0100
-+Subject: net: dsa: qca8k: make sure PAD0 MAC06 exchange is disabled
-+
-+Some device set MAC06 exchange in the bootloader. This cause some
-+problem as we don't support this strange mode and we just set the port6
-+as the primary CPU port. With MAC06 exchange, PAD0 reg configure port6
-+instead of port0. Add an extra check and explicitly disable MAC06 exchange
-+to correctly configure the port PAD config.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Fixes: 3fcf734aa482 ("net: dsa: qca8k: add support for cpu port 6")
-+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 8 ++++++++
-+ drivers/net/dsa/qca8k.h | 1 +
-+ 2 files changed, 9 insertions(+)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -1109,6 +1109,14 @@ qca8k_setup(struct dsa_switch *ds)
-+ 	if (ret)
-+ 		return ret;
-+ 
-++	/* Make sure MAC06 is disabled */
-++	ret = qca8k_reg_clear(priv, QCA8K_REG_PORT0_PAD_CTRL,
-++			      QCA8K_PORT0_PAD_MAC06_EXCHANGE_EN);
-++	if (ret) {
-++		dev_err(priv->dev, "failed disabling MAC06 exchange");
+++	if (!dev)
+++		return 0;
+++
+++	dump_wed_regs(s, dev, regs, ARRAY_SIZE(regs));
+++
+++	return 0;
+++}
+++DEFINE_SHOW_ATTRIBUTE(wed_txinfo);
+++
+++
+++static int
+++mtk_wed_reg_set(void *data, u64 val)
+++{
+++	struct mtk_wed_hw *hw = data;
+++
+++	regmap_write(hw->regs, hw->debugfs_reg, val);
+++
+++	return 0;
+++}
+++
+++static int
+++mtk_wed_reg_get(void *data, u64 *val)
+++{
+++	struct mtk_wed_hw *hw = data;
+++	unsigned int regval;
+++	int ret;
+++
+++	ret = regmap_read(hw->regs, hw->debugfs_reg, &regval);
+++	if (ret)
 ++		return ret;
-++	}
 ++
-+ 	/* Enable CPU Port */
-+ 	ret = qca8k_reg_set(priv, QCA8K_REG_GLOBAL_FW_CTRL0,
-+ 			    QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -34,6 +34,7 @@
-+ #define   QCA8K_MASK_CTRL_DEVICE_ID_MASK		GENMASK(15, 8)
-+ #define   QCA8K_MASK_CTRL_DEVICE_ID(x)			((x) >> 8)
-+ #define QCA8K_REG_PORT0_PAD_CTRL			0x004
-++#define   QCA8K_PORT0_PAD_MAC06_EXCHANGE_EN		BIT(31)
-+ #define   QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE	BIT(19)
-+ #define   QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE	BIT(18)
-+ #define QCA8K_REG_PORT5_PAD_CTRL			0x008
-diff --git a/target/linux/generic/backport-5.15/751-v5.16-net-dsa-qca8k-fix-internal-delay-applied-to-the-wrong-PAD.patch b/target/linux/generic/backport-5.15/751-v5.16-net-dsa-qca8k-fix-internal-delay-applied-to-the-wrong-PAD.patch
-new file mode 100644
-index 0000000000..df9518d86c
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/751-v5.16-net-dsa-qca8k-fix-internal-delay-applied-to-the-wrong-PAD.patch
-@@ -0,0 +1,48 @@
-+From 3b00a07c2443745d62babfe08dbb2ad8e649526e Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Fri, 19 Nov 2021 03:03:49 +0100
-+Subject: [PATCH] net: dsa: qca8k: fix internal delay applied to the wrong PAD
-+ config
-+
-+With SGMII phy the internal delay is always applied to the PAD0 config.
-+This is caused by the falling edge configuration that hardcode the reg
-+to PAD0 (as the falling edge bits are present only in PAD0 reg)
-+Move the delay configuration before the reg overwrite to correctly apply
-+the delay.
-+
-+Fixes: cef08115846e ("net: dsa: qca8k: set internal delay also for sgmii")
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 12 ++++++------
-+ 1 file changed, 6 insertions(+), 6 deletions(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -1433,6 +1433,12 @@ qca8k_phylink_mac_config(struct dsa_swit
-+ 
-+ 		qca8k_write(priv, QCA8K_REG_SGMII_CTRL, val);
-+ 
-++		/* From original code is reported port instability as SGMII also
-++		 * require delay set. Apply advised values here or take them from DT.
-++		 */
-++		if (state->interface == PHY_INTERFACE_MODE_SGMII)
-++			qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg);
+++	*val = regval;
 ++
-+ 		/* For qca8327/qca8328/qca8334/qca8338 sgmii is unique and
-+ 		 * falling edge is set writing in the PORT0 PAD reg
-+ 		 */
-+@@ -1455,12 +1461,6 @@ qca8k_phylink_mac_config(struct dsa_swit
-+ 					QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE,
-+ 					val);
-+ 
-+-		/* From original code is reported port instability as SGMII also
-+-		 * require delay set. Apply advised values here or take them from DT.
-+-		 */
-+-		if (state->interface == PHY_INTERFACE_MODE_SGMII)
-+-			qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg);
-+-
-+ 		break;
-+ 	default:
-+ 		dev_err(ds->dev, "xMII mode %s not supported for port %d\n",
-diff --git a/target/linux/generic/backport-5.15/752-v5.16-net-dsa-qca8k-fix-MTU-calculation.patch b/target/linux/generic/backport-5.15/752-v5.16-net-dsa-qca8k-fix-MTU-calculation.patch
-new file mode 100644
-index 0000000000..7348d93ec4
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/752-v5.16-net-dsa-qca8k-fix-MTU-calculation.patch
-@@ -0,0 +1,46 @@
-+From 65258b9d8cde45689bdc86ca39b50f01f983733b Mon Sep 17 00:00:00 2001
-+From: Robert Marko <robert.marko@sartura.hr>
-+Date: Fri, 19 Nov 2021 03:03:50 +0100
-+Subject: [PATCH] net: dsa: qca8k: fix MTU calculation
-+
-+qca8k has a global MTU, so its tracking the MTU per port to make sure
-+that the largest MTU gets applied.
-+Since it uses the frame size instead of MTU the driver MTU change function
-+will then add the size of Ethernet header and checksum on top of MTU.
-+
-+The driver currently populates the per port MTU size as Ethernet frame
-+length + checksum which equals 1518.
-+
-+The issue is that then MTU change function will go through all of the
-+ports, find the largest MTU and apply the Ethernet header + checksum on
-+top of it again, so for a desired MTU of 1500 you will end up with 1536.
-+
-+This is obviously incorrect, so to correct it populate the per port struct
-+MTU with just the MTU and not include the Ethernet header + checksum size
-+as those will be added by the MTU change function.
-+
-+Fixes: f58d2598cf70 ("net: dsa: qca8k: implement the port MTU callbacks")
-+Signed-off-by: Robert Marko <robert.marko@sartura.hr>
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 6 +++++-
-+ 1 file changed, 5 insertions(+), 1 deletion(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -1256,8 +1256,12 @@ qca8k_setup(struct dsa_switch *ds)
-+ 		/* Set initial MTU for every port.
-+ 		 * We have only have a general MTU setting. So track
-+ 		 * every port and set the max across all port.
-++		 * Set per port MTU to 1500 as the MTU change function
-++		 * will add the overhead and if its set to 1518 then it
-++		 * will apply the overhead again and we will end up with
-++		 * MTU of 1536 instead of 1518
-+ 		 */
-+-		priv->port_mtu[i] = ETH_FRAME_LEN + ETH_FCS_LEN;
-++		priv->port_mtu[i] = ETH_DATA_LEN;
-+ 	}
-+ 
-+ 	/* Special GLOBAL_FC_THRESH value are needed for ar8327 switch */
-diff --git a/target/linux/generic/backport-5.15/753-net-next-net-dsa-qca8k-remove-redundant-check-in-parse_port_config.patch b/target/linux/generic/backport-5.15/753-net-next-net-dsa-qca8k-remove-redundant-check-in-parse_port_config.patch
-new file mode 100644
-index 0000000000..f477b1b929
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/753-net-next-net-dsa-qca8k-remove-redundant-check-in-parse_port_config.patch
-@@ -0,0 +1,29 @@
-+From b9133f3ef5a2659730cf47a74bd0a9259f1cf8ff Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Mon, 22 Nov 2021 16:23:40 +0100
-+Subject: net: dsa: qca8k: remove redundant check in parse_port_config
-+
-+The very next check for port 0 and 6 already makes sure we don't go out
-+of bounds with the ports_config delay table.
-+Remove the redundant check.
-+
-+Reported-by: kernel test robot <lkp@intel.com>
-+Reported-by: Dan Carpenter <dan.carpenter@oracle.com>
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 2 +-
-+ 1 file changed, 1 insertion(+), 1 deletion(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -983,7 +983,7 @@ qca8k_parse_port_config(struct qca8k_pri
-+ 	u32 delay;
-+ 
-+ 	/* We have 2 CPU port. Check them */
-+-	for (port = 0; port < QCA8K_NUM_PORTS && cpu_port_index < QCA8K_NUM_CPU_PORTS; port++) {
-++	for (port = 0; port < QCA8K_NUM_PORTS; port++) {
-+ 		/* Skip every other port */
-+ 		if (port != 0 && port != 6)
-+ 			continue;
-diff --git a/target/linux/generic/backport-5.15/754-net-next-net-dsa-qca8k-convert-to-GENMASK_FIELD_PREP_FIELD_GET.patch b/target/linux/generic/backport-5.15/754-net-next-net-dsa-qca8k-convert-to-GENMASK_FIELD_PREP_FIELD_GET.patch
-new file mode 100644
-index 0000000000..408a59df85
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/754-net-next-net-dsa-qca8k-convert-to-GENMASK_FIELD_PREP_FIELD_GET.patch
-@@ -0,0 +1,507 @@
-+From 90ae68bfc2ffcb54a4ba4f64edbeb84a80cbb57c Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Mon, 22 Nov 2021 16:23:41 +0100
-+Subject: net: dsa: qca8k: convert to GENMASK/FIELD_PREP/FIELD_GET
-+
-+Convert and try to standardize bit fields using
-+GENMASK/FIELD_PREP/FIELD_GET macros. Rework some logic to support the
-+standard macro and tidy things up. No functional change intended.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c |  98 +++++++++++++++----------------
-+ drivers/net/dsa/qca8k.h | 153 ++++++++++++++++++++++++++----------------------
-+ 2 files changed, 130 insertions(+), 121 deletions(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -9,6 +9,7 @@
-+ #include <linux/module.h>
-+ #include <linux/phy.h>
-+ #include <linux/netdevice.h>
-++#include <linux/bitfield.h>
-+ #include <net/dsa.h>
-+ #include <linux/of_net.h>
-+ #include <linux/of_mdio.h>
-+@@ -319,18 +320,18 @@ qca8k_fdb_read(struct qca8k_priv *priv,
-+ 	}
-+ 
-+ 	/* vid - 83:72 */
-+-	fdb->vid = (reg[2] >> QCA8K_ATU_VID_S) & QCA8K_ATU_VID_M;
-++	fdb->vid = FIELD_GET(QCA8K_ATU_VID_MASK, reg[2]);
-+ 	/* aging - 67:64 */
-+-	fdb->aging = reg[2] & QCA8K_ATU_STATUS_M;
-++	fdb->aging = FIELD_GET(QCA8K_ATU_STATUS_MASK, reg[2]);
-+ 	/* portmask - 54:48 */
-+-	fdb->port_mask = (reg[1] >> QCA8K_ATU_PORT_S) & QCA8K_ATU_PORT_M;
-++	fdb->port_mask = FIELD_GET(QCA8K_ATU_PORT_MASK, reg[1]);
-+ 	/* mac - 47:0 */
-+-	fdb->mac[0] = (reg[1] >> QCA8K_ATU_ADDR0_S) & 0xff;
-+-	fdb->mac[1] = reg[1] & 0xff;
-+-	fdb->mac[2] = (reg[0] >> QCA8K_ATU_ADDR2_S) & 0xff;
-+-	fdb->mac[3] = (reg[0] >> QCA8K_ATU_ADDR3_S) & 0xff;
-+-	fdb->mac[4] = (reg[0] >> QCA8K_ATU_ADDR4_S) & 0xff;
-+-	fdb->mac[5] = reg[0] & 0xff;
-++	fdb->mac[0] = FIELD_GET(QCA8K_ATU_ADDR0_MASK, reg[1]);
-++	fdb->mac[1] = FIELD_GET(QCA8K_ATU_ADDR1_MASK, reg[1]);
-++	fdb->mac[2] = FIELD_GET(QCA8K_ATU_ADDR2_MASK, reg[0]);
-++	fdb->mac[3] = FIELD_GET(QCA8K_ATU_ADDR3_MASK, reg[0]);
-++	fdb->mac[4] = FIELD_GET(QCA8K_ATU_ADDR4_MASK, reg[0]);
-++	fdb->mac[5] = FIELD_GET(QCA8K_ATU_ADDR5_MASK, reg[0]);
-+ 
-+ 	return 0;
-+ }
-+@@ -343,18 +344,18 @@ qca8k_fdb_write(struct qca8k_priv *priv,
-+ 	int i;
-+ 
-+ 	/* vid - 83:72 */
-+-	reg[2] = (vid & QCA8K_ATU_VID_M) << QCA8K_ATU_VID_S;
-++	reg[2] = FIELD_PREP(QCA8K_ATU_VID_MASK, vid);
-+ 	/* aging - 67:64 */
-+-	reg[2] |= aging & QCA8K_ATU_STATUS_M;
-++	reg[2] |= FIELD_PREP(QCA8K_ATU_STATUS_MASK, aging);
-+ 	/* portmask - 54:48 */
-+-	reg[1] = (port_mask & QCA8K_ATU_PORT_M) << QCA8K_ATU_PORT_S;
-++	reg[1] = FIELD_PREP(QCA8K_ATU_PORT_MASK, port_mask);
-+ 	/* mac - 47:0 */
-+-	reg[1] |= mac[0] << QCA8K_ATU_ADDR0_S;
-+-	reg[1] |= mac[1];
-+-	reg[0] |= mac[2] << QCA8K_ATU_ADDR2_S;
-+-	reg[0] |= mac[3] << QCA8K_ATU_ADDR3_S;
-+-	reg[0] |= mac[4] << QCA8K_ATU_ADDR4_S;
-+-	reg[0] |= mac[5];
-++	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR0_MASK, mac[0]);
-++	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR1_MASK, mac[1]);
-++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR2_MASK, mac[2]);
-++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR3_MASK, mac[3]);
-++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR4_MASK, mac[4]);
-++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR5_MASK, mac[5]);
-+ 
-+ 	/* load the array into the ARL table */
-+ 	for (i = 0; i < 3; i++)
-+@@ -372,7 +373,7 @@ qca8k_fdb_access(struct qca8k_priv *priv
-+ 	reg |= cmd;
-+ 	if (port >= 0) {
-+ 		reg |= QCA8K_ATU_FUNC_PORT_EN;
-+-		reg |= (port & QCA8K_ATU_FUNC_PORT_M) << QCA8K_ATU_FUNC_PORT_S;
-++		reg |= FIELD_PREP(QCA8K_ATU_FUNC_PORT_MASK, port);
-+ 	}
-+ 
-+ 	/* Write the function register triggering the table access */
-+@@ -454,7 +455,7 @@ qca8k_vlan_access(struct qca8k_priv *pri
-+ 	/* Set the command and VLAN index */
-+ 	reg = QCA8K_VTU_FUNC1_BUSY;
-+ 	reg |= cmd;
-+-	reg |= vid << QCA8K_VTU_FUNC1_VID_S;
-++	reg |= FIELD_PREP(QCA8K_VTU_FUNC1_VID_MASK, vid);
-+ 
-+ 	/* Write the function register triggering the table access */
-+ 	ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC1, reg);
-+@@ -500,13 +501,11 @@ qca8k_vlan_add(struct qca8k_priv *priv,
-+ 	if (ret < 0)
-+ 		goto out;
-+ 	reg |= QCA8K_VTU_FUNC0_VALID | QCA8K_VTU_FUNC0_IVL_EN;
-+-	reg &= ~(QCA8K_VTU_FUNC0_EG_MODE_MASK << QCA8K_VTU_FUNC0_EG_MODE_S(port));
-++	reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port);
-+ 	if (untagged)
-+-		reg |= QCA8K_VTU_FUNC0_EG_MODE_UNTAG <<
-+-				QCA8K_VTU_FUNC0_EG_MODE_S(port);
-++		reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_UNTAG(port);
-+ 	else
-+-		reg |= QCA8K_VTU_FUNC0_EG_MODE_TAG <<
-+-				QCA8K_VTU_FUNC0_EG_MODE_S(port);
-++		reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_TAG(port);
-+ 
-+ 	ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
-+ 	if (ret)
-+@@ -534,15 +533,13 @@ qca8k_vlan_del(struct qca8k_priv *priv,
-+ 	ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, &reg);
-+ 	if (ret < 0)
-+ 		goto out;
-+-	reg &= ~(3 << QCA8K_VTU_FUNC0_EG_MODE_S(port));
-+-	reg |= QCA8K_VTU_FUNC0_EG_MODE_NOT <<
-+-			QCA8K_VTU_FUNC0_EG_MODE_S(port);
-++	reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port);
-++	reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(port);
-+ 
-+ 	/* Check if we're the last member to be removed */
-+ 	del = true;
-+ 	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
-+-		mask = QCA8K_VTU_FUNC0_EG_MODE_NOT;
-+-		mask <<= QCA8K_VTU_FUNC0_EG_MODE_S(i);
-++		mask = QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(i);
-+ 
-+ 		if ((reg & mask) != mask) {
-+ 			del = false;
-+@@ -1014,7 +1011,7 @@ qca8k_parse_port_config(struct qca8k_pri
-+ 				 mode == PHY_INTERFACE_MODE_RGMII_TXID)
-+ 				delay = 1;
-+ 
-+-			if (delay > QCA8K_MAX_DELAY) {
-++			if (!FIELD_FIT(QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK, delay)) {
-+ 				dev_err(priv->dev, "rgmii tx delay is limited to a max value of 3ns, setting to the max value");
-+ 				delay = 3;
-+ 			}
-+@@ -1030,7 +1027,7 @@ qca8k_parse_port_config(struct qca8k_pri
-+ 				 mode == PHY_INTERFACE_MODE_RGMII_RXID)
-+ 				delay = 2;
-+ 
-+-			if (delay > QCA8K_MAX_DELAY) {
-++			if (!FIELD_FIT(QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK, delay)) {
-+ 				dev_err(priv->dev, "rgmii rx delay is limited to a max value of 3ns, setting to the max value");
-+ 				delay = 3;
-+ 			}
-+@@ -1141,8 +1138,8 @@ qca8k_setup(struct dsa_switch *ds)
-+ 		/* Enable QCA header mode on all cpu ports */
-+ 		if (dsa_is_cpu_port(ds, i)) {
-+ 			ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(i),
-+-					  QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_TX_S |
-+-					  QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_RX_S);
-++					  FIELD_PREP(QCA8K_PORT_HDR_CTRL_TX_MASK, QCA8K_PORT_HDR_CTRL_ALL) |
-++					  FIELD_PREP(QCA8K_PORT_HDR_CTRL_RX_MASK, QCA8K_PORT_HDR_CTRL_ALL));
-+ 			if (ret) {
-+ 				dev_err(priv->dev, "failed enabling QCA header mode");
-+ 				return ret;
-+@@ -1159,10 +1156,10 @@ qca8k_setup(struct dsa_switch *ds)
-+ 	 * for igmp, unknown, multicast and broadcast packet
-+ 	 */
-+ 	ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1,
-+-			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S |
-+-			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_BC_DP_S |
-+-			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_MC_DP_S |
-+-			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_UC_DP_S);
-++			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK, BIT(cpu_port)) |
-++			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK, BIT(cpu_port)) |
-++			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_MC_DP_MASK, BIT(cpu_port)) |
-++			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_UC_DP_MASK, BIT(cpu_port)));
-+ 	if (ret)
-+ 		return ret;
-+ 
-+@@ -1180,8 +1177,6 @@ qca8k_setup(struct dsa_switch *ds)
-+ 
-+ 		/* Individual user ports get connected to CPU port only */
-+ 		if (dsa_is_user_port(ds, i)) {
-+-			int shift = 16 * (i % 2);
-+-
-+ 			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
-+ 					QCA8K_PORT_LOOKUP_MEMBER,
-+ 					BIT(cpu_port));
-+@@ -1198,8 +1193,8 @@ qca8k_setup(struct dsa_switch *ds)
-+ 			 * default egress vid
-+ 			 */
-+ 			ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(i),
-+-					0xfff << shift,
-+-					QCA8K_PORT_VID_DEF << shift);
-++					QCA8K_EGREES_VLAN_PORT_MASK(i),
-++					QCA8K_EGREES_VLAN_PORT(i, QCA8K_PORT_VID_DEF));
-+ 			if (ret)
-+ 				return ret;
-+ 
-+@@ -1246,7 +1241,7 @@ qca8k_setup(struct dsa_switch *ds)
-+ 			QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
-+ 			QCA8K_PORT_HOL_CTRL1_WRED_EN;
-+ 			qca8k_rmw(priv, QCA8K_REG_PORT_HOL_CTRL1(i),
-+-				  QCA8K_PORT_HOL_CTRL1_ING_BUF |
-++				  QCA8K_PORT_HOL_CTRL1_ING_BUF_MASK |
-+ 				  QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
-+ 				  QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
-+ 				  QCA8K_PORT_HOL_CTRL1_WRED_EN,
-+@@ -1269,8 +1264,8 @@ qca8k_setup(struct dsa_switch *ds)
-+ 		mask = QCA8K_GLOBAL_FC_GOL_XON_THRES(288) |
-+ 		       QCA8K_GLOBAL_FC_GOL_XOFF_THRES(496);
-+ 		qca8k_rmw(priv, QCA8K_REG_GLOBAL_FC_THRESH,
-+-			  QCA8K_GLOBAL_FC_GOL_XON_THRES_S |
-+-			  QCA8K_GLOBAL_FC_GOL_XOFF_THRES_S,
-++			  QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK |
-++			  QCA8K_GLOBAL_FC_GOL_XOFF_THRES_MASK,
-+ 			  mask);
-+ 	}
-+ 
-+@@ -1916,11 +1911,11 @@ qca8k_port_vlan_filtering(struct dsa_swi
-+ 
-+ 	if (vlan_filtering) {
-+ 		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
-+-				QCA8K_PORT_LOOKUP_VLAN_MODE,
-++				QCA8K_PORT_LOOKUP_VLAN_MODE_MASK,
-+ 				QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE);
-+ 	} else {
-+ 		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
-+-				QCA8K_PORT_LOOKUP_VLAN_MODE,
-++				QCA8K_PORT_LOOKUP_VLAN_MODE_MASK,
-+ 				QCA8K_PORT_LOOKUP_VLAN_MODE_NONE);
-+ 	}
-+ 
-+@@ -1944,10 +1939,9 @@ qca8k_port_vlan_add(struct dsa_switch *d
-+ 	}
-+ 
-+ 	if (pvid) {
-+-		int shift = 16 * (port % 2);
-+-
-+ 		ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port),
-+-				0xfff << shift, vlan->vid << shift);
-++				QCA8K_EGREES_VLAN_PORT_MASK(port),
-++				QCA8K_EGREES_VLAN_PORT(port, vlan->vid));
-+ 		if (ret)
-+ 			return ret;
+++	return 0;
+++}
+++
+++DEFINE_DEBUGFS_ATTRIBUTE(fops_regval, mtk_wed_reg_get, mtk_wed_reg_set,
+++             "0x%08llx\n");
+++
+++void mtk_wed_hw_add_debugfs(struct mtk_wed_hw *hw)
+++{
+++	struct dentry *dir;
+++
+++	snprintf(hw->dirname, sizeof(hw->dirname), "wed%d", hw->index);
+++	dir = debugfs_create_dir(hw->dirname, NULL);
+++	if (!dir)
+++		return;
+++
+++	hw->debugfs_dir = dir;
+++	debugfs_create_u32("regidx", 0600, dir, &hw->debugfs_reg);
+++	debugfs_create_file_unsafe("regval", 0600, dir, hw, &fops_regval);
+++	debugfs_create_file_unsafe("txinfo", 0400, dir, hw, &wed_txinfo_fops);
+++}
++--- /dev/null
+++++ b/drivers/net/ethernet/mediatek/mtk_wed_ops.c
++@@ -0,0 +1,8 @@
+++// SPDX-License-Identifier: GPL-2.0-only
+++/* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name> */
+++
+++#include <linux/kernel.h>
+++#include <linux/soc/mediatek/mtk_wed.h>
+++
+++const struct mtk_wed_ops __rcu *mtk_soc_wed_ops;
+++EXPORT_SYMBOL_GPL(mtk_soc_wed_ops);
++--- /dev/null
+++++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
++@@ -0,0 +1,251 @@
+++// SPDX-License-Identifier: GPL-2.0-only
+++/* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name> */
+++
+++#ifndef __MTK_WED_REGS_H
+++#define __MTK_WED_REGS_H
+++
+++#define MTK_WDMA_DESC_CTRL_LEN1			GENMASK(14, 0)
+++#define MTK_WDMA_DESC_CTRL_LAST_SEG1		BIT(15)
+++#define MTK_WDMA_DESC_CTRL_BURST		BIT(16)
+++#define MTK_WDMA_DESC_CTRL_LEN0			GENMASK(29, 16)
+++#define MTK_WDMA_DESC_CTRL_LAST_SEG0		BIT(30)
+++#define MTK_WDMA_DESC_CTRL_DMA_DONE		BIT(31)
+++
+++struct mtk_wdma_desc {
+++	__le32 buf0;
+++	__le32 ctrl;
+++	__le32 buf1;
+++	__le32 info;
+++} __packed __aligned(4);
+++
+++#define MTK_WED_RESET					0x008
+++#define MTK_WED_RESET_TX_BM				BIT(0)
+++#define MTK_WED_RESET_TX_FREE_AGENT			BIT(4)
+++#define MTK_WED_RESET_WPDMA_TX_DRV			BIT(8)
+++#define MTK_WED_RESET_WPDMA_RX_DRV			BIT(9)
+++#define MTK_WED_RESET_WPDMA_INT_AGENT			BIT(11)
+++#define MTK_WED_RESET_WED_TX_DMA			BIT(12)
+++#define MTK_WED_RESET_WDMA_RX_DRV			BIT(17)
+++#define MTK_WED_RESET_WDMA_INT_AGENT			BIT(19)
+++#define MTK_WED_RESET_WED				BIT(31)
+++
+++#define MTK_WED_CTRL					0x00c
+++#define MTK_WED_CTRL_WPDMA_INT_AGENT_EN			BIT(0)
+++#define MTK_WED_CTRL_WPDMA_INT_AGENT_BUSY		BIT(1)
+++#define MTK_WED_CTRL_WDMA_INT_AGENT_EN			BIT(2)
+++#define MTK_WED_CTRL_WDMA_INT_AGENT_BUSY		BIT(3)
+++#define MTK_WED_CTRL_WED_TX_BM_EN			BIT(8)
+++#define MTK_WED_CTRL_WED_TX_BM_BUSY			BIT(9)
+++#define MTK_WED_CTRL_WED_TX_FREE_AGENT_EN		BIT(10)
+++#define MTK_WED_CTRL_WED_TX_FREE_AGENT_BUSY		BIT(11)
+++#define MTK_WED_CTRL_RESERVE_EN				BIT(12)
+++#define MTK_WED_CTRL_RESERVE_BUSY			BIT(13)
+++#define MTK_WED_CTRL_FINAL_DIDX_READ			BIT(24)
+++#define MTK_WED_CTRL_MIB_READ_CLEAR			BIT(28)
+++
+++#define MTK_WED_EXT_INT_STATUS				0x020
+++#define MTK_WED_EXT_INT_STATUS_TF_LEN_ERR		BIT(0)
+++#define MTK_WED_EXT_INT_STATUS_TKID_WO_PYLD		BIT(1)
+++#define MTK_WED_EXT_INT_STATUS_TKID_TITO_INVALID	BIT(4)
+++#define MTK_WED_EXT_INT_STATUS_TX_FBUF_LO_TH		BIT(8)
+++#define MTK_WED_EXT_INT_STATUS_TX_FBUF_HI_TH		BIT(9)
+++#define MTK_WED_EXT_INT_STATUS_RX_FBUF_LO_TH		BIT(12)
+++#define MTK_WED_EXT_INT_STATUS_RX_FBUF_HI_TH		BIT(13)
+++#define MTK_WED_EXT_INT_STATUS_RX_DRV_R_RESP_ERR	BIT(16)
+++#define MTK_WED_EXT_INT_STATUS_RX_DRV_W_RESP_ERR	BIT(17)
+++#define MTK_WED_EXT_INT_STATUS_RX_DRV_COHERENT		BIT(18)
+++#define MTK_WED_EXT_INT_STATUS_RX_DRV_INIT_WDMA_EN	BIT(19)
+++#define MTK_WED_EXT_INT_STATUS_RX_DRV_BM_DMAD_COHERENT	BIT(20)
+++#define MTK_WED_EXT_INT_STATUS_TX_DRV_R_RESP_ERR	BIT(21)
+++#define MTK_WED_EXT_INT_STATUS_TX_DRV_W_RESP_ERR	BIT(22)
+++#define MTK_WED_EXT_INT_STATUS_RX_DRV_DMA_RECYCLE	BIT(24)
+++#define MTK_WED_EXT_INT_STATUS_ERROR_MASK		(MTK_WED_EXT_INT_STATUS_TF_LEN_ERR | \
+++							 MTK_WED_EXT_INT_STATUS_TKID_WO_PYLD | \
+++							 MTK_WED_EXT_INT_STATUS_TKID_TITO_INVALID | \
+++							 MTK_WED_EXT_INT_STATUS_RX_DRV_R_RESP_ERR | \
+++							 MTK_WED_EXT_INT_STATUS_RX_DRV_W_RESP_ERR | \
+++							 MTK_WED_EXT_INT_STATUS_RX_DRV_INIT_WDMA_EN | \
+++							 MTK_WED_EXT_INT_STATUS_TX_DRV_R_RESP_ERR | \
+++							 MTK_WED_EXT_INT_STATUS_TX_DRV_W_RESP_ERR)
+++
+++#define MTK_WED_EXT_INT_MASK				0x028
+++
+++#define MTK_WED_STATUS					0x060
+++#define MTK_WED_STATUS_TX				GENMASK(15, 8)
+++
+++#define MTK_WED_TX_BM_CTRL				0x080
+++#define MTK_WED_TX_BM_CTRL_VLD_GRP_NUM			GENMASK(6, 0)
+++#define MTK_WED_TX_BM_CTRL_RSV_GRP_NUM			GENMASK(22, 16)
+++#define MTK_WED_TX_BM_CTRL_PAUSE			BIT(28)
+++
+++#define MTK_WED_TX_BM_BASE				0x084
+++
+++#define MTK_WED_TX_BM_TKID				0x088
+++#define MTK_WED_TX_BM_TKID_START			GENMASK(15, 0)
+++#define MTK_WED_TX_BM_TKID_END				GENMASK(31, 16)
+++
+++#define MTK_WED_TX_BM_BUF_LEN				0x08c
+++
+++#define MTK_WED_TX_BM_INTF				0x09c
+++#define MTK_WED_TX_BM_INTF_TKID				GENMASK(15, 0)
+++#define MTK_WED_TX_BM_INTF_TKFIFO_FDEP			GENMASK(23, 16)
+++#define MTK_WED_TX_BM_INTF_TKID_VALID			BIT(28)
+++#define MTK_WED_TX_BM_INTF_TKID_READ			BIT(29)
+++
+++#define MTK_WED_TX_BM_DYN_THR				0x0a0
+++#define MTK_WED_TX_BM_DYN_THR_LO			GENMASK(6, 0)
+++#define MTK_WED_TX_BM_DYN_THR_HI			GENMASK(22, 16)
+++
+++#define MTK_WED_INT_STATUS				0x200
+++#define MTK_WED_INT_MASK				0x204
+++
+++#define MTK_WED_GLO_CFG					0x208
+++#define MTK_WED_GLO_CFG_TX_DMA_EN			BIT(0)
+++#define MTK_WED_GLO_CFG_TX_DMA_BUSY			BIT(1)
+++#define MTK_WED_GLO_CFG_RX_DMA_EN			BIT(2)
+++#define MTK_WED_GLO_CFG_RX_DMA_BUSY			BIT(3)
+++#define MTK_WED_GLO_CFG_RX_BT_SIZE			GENMASK(5, 4)
+++#define MTK_WED_GLO_CFG_TX_WB_DDONE			BIT(6)
+++#define MTK_WED_GLO_CFG_BIG_ENDIAN			BIT(7)
+++#define MTK_WED_GLO_CFG_DIS_BT_SIZE_ALIGN		BIT(8)
+++#define MTK_WED_GLO_CFG_TX_BT_SIZE_LO			BIT(9)
+++#define MTK_WED_GLO_CFG_MULTI_DMA_EN			GENMASK(11, 10)
+++#define MTK_WED_GLO_CFG_FIFO_LITTLE_ENDIAN		BIT(12)
+++#define MTK_WED_GLO_CFG_MI_DEPTH_RD			GENMASK(21, 13)
+++#define MTK_WED_GLO_CFG_TX_BT_SIZE_HI			GENMASK(23, 22)
+++#define MTK_WED_GLO_CFG_SW_RESET			BIT(24)
+++#define MTK_WED_GLO_CFG_FIRST_TOKEN_ONLY		BIT(26)
+++#define MTK_WED_GLO_CFG_OMIT_RX_INFO			BIT(27)
+++#define MTK_WED_GLO_CFG_OMIT_TX_INFO			BIT(28)
+++#define MTK_WED_GLO_CFG_BYTE_SWAP			BIT(29)
+++#define MTK_WED_GLO_CFG_RX_2B_OFFSET			BIT(31)
+++
+++#define MTK_WED_RESET_IDX				0x20c
+++#define MTK_WED_RESET_IDX_TX				GENMASK(3, 0)
+++#define MTK_WED_RESET_IDX_RX				GENMASK(17, 16)
+++
+++#define MTK_WED_TX_MIB(_n)				(0x2a0 + (_n) * 4)
+++
+++#define MTK_WED_RING_TX(_n)				(0x300 + (_n) * 0x10)
+++
+++#define MTK_WED_RING_RX(_n)				(0x400 + (_n) * 0x10)
+++
+++#define MTK_WED_WPDMA_INT_TRIGGER			0x504
+++#define MTK_WED_WPDMA_INT_TRIGGER_RX_DONE		BIT(1)
+++#define MTK_WED_WPDMA_INT_TRIGGER_TX_DONE		GENMASK(5, 4)
+++
+++#define MTK_WED_WPDMA_GLO_CFG				0x508
+++#define MTK_WED_WPDMA_GLO_CFG_TX_DRV_EN			BIT(0)
+++#define MTK_WED_WPDMA_GLO_CFG_TX_DRV_BUSY		BIT(1)
+++#define MTK_WED_WPDMA_GLO_CFG_RX_DRV_EN			BIT(2)
+++#define MTK_WED_WPDMA_GLO_CFG_RX_DRV_BUSY		BIT(3)
+++#define MTK_WED_WPDMA_GLO_CFG_RX_BT_SIZE		GENMASK(5, 4)
+++#define MTK_WED_WPDMA_GLO_CFG_TX_WB_DDONE		BIT(6)
+++#define MTK_WED_WPDMA_GLO_CFG_BIG_ENDIAN		BIT(7)
+++#define MTK_WED_WPDMA_GLO_CFG_DIS_BT_SIZE_ALIGN		BIT(8)
+++#define MTK_WED_WPDMA_GLO_CFG_TX_BT_SIZE_LO		BIT(9)
+++#define MTK_WED_WPDMA_GLO_CFG_MULTI_DMA_EN		GENMASK(11, 10)
+++#define MTK_WED_WPDMA_GLO_CFG_FIFO_LITTLE_ENDIAN	BIT(12)
+++#define MTK_WED_WPDMA_GLO_CFG_MI_DEPTH_RD		GENMASK(21, 13)
+++#define MTK_WED_WPDMA_GLO_CFG_TX_BT_SIZE_HI		GENMASK(23, 22)
+++#define MTK_WED_WPDMA_GLO_CFG_SW_RESET			BIT(24)
+++#define MTK_WED_WPDMA_GLO_CFG_FIRST_TOKEN_ONLY		BIT(26)
+++#define MTK_WED_WPDMA_GLO_CFG_OMIT_RX_INFO		BIT(27)
+++#define MTK_WED_WPDMA_GLO_CFG_OMIT_TX_INFO		BIT(28)
+++#define MTK_WED_WPDMA_GLO_CFG_BYTE_SWAP			BIT(29)
+++#define MTK_WED_WPDMA_GLO_CFG_RX_2B_OFFSET		BIT(31)
+++
+++#define MTK_WED_WPDMA_RESET_IDX				0x50c
+++#define MTK_WED_WPDMA_RESET_IDX_TX			GENMASK(3, 0)
+++#define MTK_WED_WPDMA_RESET_IDX_RX			GENMASK(17, 16)
+++
+++#define MTK_WED_WPDMA_INT_CTRL				0x520
+++#define MTK_WED_WPDMA_INT_CTRL_SUBRT_ADV		BIT(21)
+++
+++#define MTK_WED_WPDMA_INT_MASK				0x524
+++
+++#define MTK_WED_PCIE_CFG_BASE				0x560
+++
+++#define MTK_WED_PCIE_INT_TRIGGER			0x570
+++#define MTK_WED_PCIE_INT_TRIGGER_STATUS			BIT(16)
+++
+++#define MTK_WED_WPDMA_CFG_BASE				0x580
+++
+++#define MTK_WED_WPDMA_TX_MIB(_n)			(0x5a0 + (_n) * 4)
+++#define MTK_WED_WPDMA_TX_COHERENT_MIB(_n)		(0x5d0 + (_n) * 4)
+++
+++#define MTK_WED_WPDMA_RING_TX(_n)			(0x600 + (_n) * 0x10)
+++#define MTK_WED_WPDMA_RING_RX(_n)			(0x700 + (_n) * 0x10)
+++#define MTK_WED_WDMA_RING_RX(_n)			(0x900 + (_n) * 0x10)
+++#define MTK_WED_WDMA_RX_THRES(_n)			(0x940 + (_n) * 0x4)
+++
+++#define MTK_WED_WDMA_GLO_CFG				0xa04
+++#define MTK_WED_WDMA_GLO_CFG_TX_DRV_EN			BIT(0)
+++#define MTK_WED_WDMA_GLO_CFG_RX_DRV_EN			BIT(2)
+++#define MTK_WED_WDMA_GLO_CFG_RX_DRV_BUSY		BIT(3)
+++#define MTK_WED_WDMA_GLO_CFG_BT_SIZE			GENMASK(5, 4)
+++#define MTK_WED_WDMA_GLO_CFG_TX_WB_DDONE		BIT(6)
+++#define MTK_WED_WDMA_GLO_CFG_RX_DIS_FSM_AUTO_IDLE	BIT(13)
+++#define MTK_WED_WDMA_GLO_CFG_WCOMPLETE_SEL		BIT(16)
+++#define MTK_WED_WDMA_GLO_CFG_INIT_PHASE_RXDMA_BYPASS	BIT(17)
+++#define MTK_WED_WDMA_GLO_CFG_INIT_PHASE_BYPASS		BIT(18)
+++#define MTK_WED_WDMA_GLO_CFG_FSM_RETURN_IDLE		BIT(19)
+++#define MTK_WED_WDMA_GLO_CFG_WAIT_COHERENT		BIT(20)
+++#define MTK_WED_WDMA_GLO_CFG_AXI_W_AFTER_AW		BIT(21)
+++#define MTK_WED_WDMA_GLO_CFG_IDLE_DMAD_SUPPLY_SINGLE_W	BIT(22)
+++#define MTK_WED_WDMA_GLO_CFG_IDLE_DMAD_SUPPLY		BIT(23)
+++#define MTK_WED_WDMA_GLO_CFG_DYNAMIC_SKIP_DMAD_PREP	BIT(24)
+++#define MTK_WED_WDMA_GLO_CFG_DYNAMIC_DMAD_RECYCLE	BIT(25)
+++#define MTK_WED_WDMA_GLO_CFG_RST_INIT_COMPLETE		BIT(26)
+++#define MTK_WED_WDMA_GLO_CFG_RXDRV_CLKGATE_BYPASS	BIT(30)
+++
+++#define MTK_WED_WDMA_RESET_IDX				0xa08
+++#define MTK_WED_WDMA_RESET_IDX_RX			GENMASK(17, 16)
+++#define MTK_WED_WDMA_RESET_IDX_DRV			GENMASK(25, 24)
+++
+++#define MTK_WED_WDMA_INT_TRIGGER			0xa28
+++#define MTK_WED_WDMA_INT_TRIGGER_RX_DONE		GENMASK(17, 16)
+++
+++#define MTK_WED_WDMA_INT_CTRL				0xa2c
+++#define MTK_WED_WDMA_INT_CTRL_POLL_SRC_SEL		GENMASK(17, 16)
+++
+++#define MTK_WED_WDMA_OFFSET0				0xaa4
+++#define MTK_WED_WDMA_OFFSET1				0xaa8
+++
+++#define MTK_WED_WDMA_RX_MIB(_n)				(0xae0 + (_n) * 4)
+++#define MTK_WED_WDMA_RX_RECYCLE_MIB(_n)			(0xae8 + (_n) * 4)
+++#define MTK_WED_WDMA_RX_PROCESSED_MIB(_n)		(0xaf0 + (_n) * 4)
+++
+++#define MTK_WED_RING_OFS_BASE				0x00
+++#define MTK_WED_RING_OFS_COUNT				0x04
+++#define MTK_WED_RING_OFS_CPU_IDX			0x08
+++#define MTK_WED_RING_OFS_DMA_IDX			0x0c
+++
+++#define MTK_WDMA_RING_RX(_n)				(0x100 + (_n) * 0x10)
+++
+++#define MTK_WDMA_GLO_CFG				0x204
+++#define MTK_WDMA_GLO_CFG_RX_INFO_PRERES			GENMASK(28, 26)
+++
+++#define MTK_WDMA_RESET_IDX				0x208
+++#define MTK_WDMA_RESET_IDX_TX				GENMASK(3, 0)
+++#define MTK_WDMA_RESET_IDX_RX				GENMASK(17, 16)
+++
+++#define MTK_WDMA_INT_MASK				0x228
+++#define MTK_WDMA_INT_MASK_TX_DONE			GENMASK(3, 0)
+++#define MTK_WDMA_INT_MASK_RX_DONE			GENMASK(17, 16)
+++#define MTK_WDMA_INT_MASK_TX_DELAY			BIT(28)
+++#define MTK_WDMA_INT_MASK_TX_COHERENT			BIT(29)
+++#define MTK_WDMA_INT_MASK_RX_DELAY			BIT(30)
+++#define MTK_WDMA_INT_MASK_RX_COHERENT			BIT(31)
+++
+++#define MTK_WDMA_INT_GRP1				0x250
+++#define MTK_WDMA_INT_GRP2				0x254
+++
+++#define MTK_PCIE_MIRROR_MAP(n)				((n) ? 0x4 : 0x0)
+++#define MTK_PCIE_MIRROR_MAP_EN				BIT(0)
+++#define MTK_PCIE_MIRROR_MAP_WED_ID			BIT(1)
+++
+++/* DMA channel mapping */
+++#define HIFSYS_DMA_AG_MAP				0x008
+++
+++#endif
++--- /dev/null
+++++ b/include/linux/soc/mediatek/mtk_wed.h
++@@ -0,0 +1,131 @@
+++#ifndef __MTK_WED_H
+++#define __MTK_WED_H
+++
+++#include <linux/kernel.h>
+++#include <linux/rcupdate.h>
+++#include <linux/regmap.h>
+++#include <linux/pci.h>
+++
+++#define MTK_WED_TX_QUEUES		2
+++
+++struct mtk_wed_hw;
+++struct mtk_wdma_desc;
+++
+++struct mtk_wed_ring {
+++	struct mtk_wdma_desc *desc;
+++	dma_addr_t desc_phys;
+++	int size;
+++
+++	u32 reg_base;
+++	void __iomem *wpdma;
+++};
+++
+++struct mtk_wed_device {
+++#ifdef CONFIG_NET_MEDIATEK_SOC_WED
+++	const struct mtk_wed_ops *ops;
+++	struct device *dev;
+++	struct mtk_wed_hw *hw;
+++	bool init_done, running;
+++	int wdma_idx;
+++	int irq;
+++
+++	struct mtk_wed_ring tx_ring[MTK_WED_TX_QUEUES];
+++	struct mtk_wed_ring txfree_ring;
+++	struct mtk_wed_ring tx_wdma[MTK_WED_TX_QUEUES];
+++
+++	struct {
+++		int size;
+++		void **pages;
+++		struct mtk_wdma_desc *desc;
+++		dma_addr_t desc_phys;
+++	} buf_ring;
+++
+++	/* filled by driver: */
+++	struct {
+++		struct pci_dev *pci_dev;
+++
+++		u32 wpdma_phys;
+++
+++		u16 token_start;
+++		unsigned int nbuf;
+++
+++		u32 (*init_buf)(void *ptr, dma_addr_t phys, int token_id);
+++		int (*offload_enable)(struct mtk_wed_device *wed);
+++		void (*offload_disable)(struct mtk_wed_device *wed);
+++	} wlan;
+++#endif
+++};
+++
+++struct mtk_wed_ops {
+++	int (*attach)(struct mtk_wed_device *dev);
+++	int (*tx_ring_setup)(struct mtk_wed_device *dev, int ring,
+++			     void __iomem *regs);
+++	int (*txfree_ring_setup)(struct mtk_wed_device *dev,
+++				 void __iomem *regs);
+++	void (*detach)(struct mtk_wed_device *dev);
+++
+++	void (*stop)(struct mtk_wed_device *dev);
+++	void (*start)(struct mtk_wed_device *dev, u32 irq_mask);
+++	void (*reset_dma)(struct mtk_wed_device *dev);
+++
+++	u32 (*reg_read)(struct mtk_wed_device *dev, u32 reg);
+++	void (*reg_write)(struct mtk_wed_device *dev, u32 reg, u32 val);
+++
+++	u32 (*irq_get)(struct mtk_wed_device *dev, u32 mask);
+++	void (*irq_set_mask)(struct mtk_wed_device *dev, u32 mask);
+++};
+++
+++extern const struct mtk_wed_ops __rcu *mtk_soc_wed_ops;
+++
+++static inline int
+++mtk_wed_device_attach(struct mtk_wed_device *dev)
+++{
+++	int ret = -ENODEV;
+++
+++#ifdef CONFIG_NET_MEDIATEK_SOC_WED
+++	rcu_read_lock();
+++	dev->ops = rcu_dereference(mtk_soc_wed_ops);
+++	if (dev->ops)
+++		ret = dev->ops->attach(dev);
+++	else
+++		rcu_read_unlock();
+++
+++	if (ret)
+++		dev->ops = NULL;
+++#endif
+++
+++	return ret;
+++}
+++
+++#ifdef CONFIG_NET_MEDIATEK_SOC_WED
+++#define mtk_wed_device_active(_dev) !!(_dev)->ops
+++#define mtk_wed_device_detach(_dev) (_dev)->ops->detach(_dev)
+++#define mtk_wed_device_start(_dev, _mask) (_dev)->ops->start(_dev, _mask)
+++#define mtk_wed_device_tx_ring_setup(_dev, _ring, _regs) \
+++	(_dev)->ops->tx_ring_setup(_dev, _ring, _regs)
+++#define mtk_wed_device_txfree_ring_setup(_dev, _regs) \
+++	(_dev)->ops->txfree_ring_setup(_dev, _regs)
+++#define mtk_wed_device_reg_read(_dev, _reg) \
+++	(_dev)->ops->reg_read(_dev, _reg)
+++#define mtk_wed_device_reg_write(_dev, _reg, _val) \
+++	(_dev)->ops->reg_write(_dev, _reg, _val)
+++#define mtk_wed_device_irq_get(_dev, _mask) \
+++	(_dev)->ops->irq_get(_dev, _mask)
+++#define mtk_wed_device_irq_set_mask(_dev, _mask) \
+++	(_dev)->ops->irq_set_mask(_dev, _mask)
+++#else
+++static inline bool mtk_wed_device_active(struct mtk_wed_device *dev)
+++{
+++	return false;
+++}
+++#define mtk_wed_device_detach(_dev) do {} while (0)
+++#define mtk_wed_device_start(_dev, _mask) do {} while (0)
+++#define mtk_wed_device_tx_ring_setup(_dev, _ring, _regs) -ENODEV
+++#define mtk_wed_device_txfree_ring_setup(_dev, _ring, _regs) -ENODEV
+++#define mtk_wed_device_reg_read(_dev, _reg) 0
+++#define mtk_wed_device_reg_write(_dev, _reg, _val) do {} while (0)
+++#define mtk_wed_device_irq_get(_dev, _mask) 0
+++#define mtk_wed_device_irq_set_mask(_dev, _mask) do {} while (0)
+++#endif
+++
+++#endif
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-03-net-ethernet-mtk_eth_soc-implement-flow-offloading-t.patch b/target/linux/generic/backport-5.15/702-v5.19-03-net-ethernet-mtk_eth_soc-implement-flow-offloading-t.patch
+new file mode 100644
+index 0000000000..2f93518e15
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-03-net-ethernet-mtk_eth_soc-implement-flow-offloading-t.patch
+@@ -0,0 +1,269 @@
++From: Felix Fietkau <nbd@nbd.name>
++Date: Sat, 5 Feb 2022 18:29:22 +0100
++Subject: [PATCH] net: ethernet: mtk_eth_soc: implement flow offloading
++ to WED devices
++
++This allows hardware flow offloading from Ethernet to WLAN on MT7622 SoC
++
++Co-developed-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: Felix Fietkau <nbd@nbd.name>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
++@@ -329,6 +329,24 @@ int mtk_foe_entry_set_pppoe(struct mtk_f
++ 	return 0;
++ }
 + 
-+@@ -2041,7 +2035,7 @@ static int qca8k_read_switch_id(struct q
-+ 	if (ret < 0)
-+ 		return -ENODEV;
+++int mtk_foe_entry_set_wdma(struct mtk_foe_entry *entry, int wdma_idx, int txq,
+++			   int bss, int wcid)
+++{
+++	struct mtk_foe_mac_info *l2 = mtk_foe_entry_l2(entry);
+++	u32 *ib2 = mtk_foe_entry_ib2(entry);
+++
+++	*ib2 &= ~MTK_FOE_IB2_PORT_MG;
+++	*ib2 |= MTK_FOE_IB2_WDMA_WINFO;
+++	if (wdma_idx)
+++		*ib2 |= MTK_FOE_IB2_WDMA_DEVIDX;
+++
+++	l2->vlan2 = FIELD_PREP(MTK_FOE_VLAN2_WINFO_BSS, bss) |
+++		    FIELD_PREP(MTK_FOE_VLAN2_WINFO_WCID, wcid) |
+++		    FIELD_PREP(MTK_FOE_VLAN2_WINFO_RING, txq);
+++
+++	return 0;
+++}
+++
++ static inline bool mtk_foe_entry_usable(struct mtk_foe_entry *entry)
++ {
++ 	return !(entry->ib1 & MTK_FOE_IB1_STATIC) &&
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
++@@ -48,9 +48,9 @@ enum {
++ #define MTK_FOE_IB2_DEST_PORT		GENMASK(7, 5)
++ #define MTK_FOE_IB2_MULTICAST		BIT(8)
 + 
-+-	id = QCA8K_MASK_CTRL_DEVICE_ID(val & QCA8K_MASK_CTRL_DEVICE_ID_MASK);
-++	id = QCA8K_MASK_CTRL_DEVICE_ID(val);
-+ 	if (id != data->id) {
-+ 		dev_err(priv->dev, "Switch id detected %x but expected %x", id, data->id);
-+ 		return -ENODEV;
-+@@ -2050,7 +2044,7 @@ static int qca8k_read_switch_id(struct q
-+ 	priv->switch_id = id;
++-#define MTK_FOE_IB2_WHNAT_QID2		GENMASK(13, 12)
++-#define MTK_FOE_IB2_WHNAT_DEVIDX	BIT(16)
++-#define MTK_FOE_IB2_WHNAT_NAT		BIT(17)
+++#define MTK_FOE_IB2_WDMA_QID2		GENMASK(13, 12)
+++#define MTK_FOE_IB2_WDMA_DEVIDX		BIT(16)
+++#define MTK_FOE_IB2_WDMA_WINFO		BIT(17)
 + 
-+ 	/* Save revision to communicate to the internal PHY driver */
-+-	priv->switch_revision = (val & QCA8K_MASK_CTRL_REV_ID_MASK);
-++	priv->switch_revision = QCA8K_MASK_CTRL_REV_ID(val);
++ #define MTK_FOE_IB2_PORT_MG		GENMASK(17, 12)
 + 
-+ 	return 0;
-+ }
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -30,9 +30,9 @@
-+ /* Global control registers */
-+ #define QCA8K_REG_MASK_CTRL				0x000
-+ #define   QCA8K_MASK_CTRL_REV_ID_MASK			GENMASK(7, 0)
-+-#define   QCA8K_MASK_CTRL_REV_ID(x)			((x) >> 0)
-++#define   QCA8K_MASK_CTRL_REV_ID(x)			FIELD_GET(QCA8K_MASK_CTRL_REV_ID_MASK, x)
-+ #define   QCA8K_MASK_CTRL_DEVICE_ID_MASK		GENMASK(15, 8)
-+-#define   QCA8K_MASK_CTRL_DEVICE_ID(x)			((x) >> 8)
-++#define   QCA8K_MASK_CTRL_DEVICE_ID(x)			FIELD_GET(QCA8K_MASK_CTRL_DEVICE_ID_MASK, x)
-+ #define QCA8K_REG_PORT0_PAD_CTRL			0x004
-+ #define   QCA8K_PORT0_PAD_MAC06_EXCHANGE_EN		BIT(31)
-+ #define   QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE	BIT(19)
-+@@ -41,12 +41,11 @@
-+ #define QCA8K_REG_PORT6_PAD_CTRL			0x00c
-+ #define   QCA8K_PORT_PAD_RGMII_EN			BIT(26)
-+ #define   QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK		GENMASK(23, 22)
-+-#define   QCA8K_PORT_PAD_RGMII_TX_DELAY(x)		((x) << 22)
-++#define   QCA8K_PORT_PAD_RGMII_TX_DELAY(x)		FIELD_PREP(QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK, x)
-+ #define   QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK		GENMASK(21, 20)
-+-#define   QCA8K_PORT_PAD_RGMII_RX_DELAY(x)		((x) << 20)
-++#define   QCA8K_PORT_PAD_RGMII_RX_DELAY(x)		FIELD_PREP(QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK, x)
-+ #define	  QCA8K_PORT_PAD_RGMII_TX_DELAY_EN		BIT(25)
-+ #define   QCA8K_PORT_PAD_RGMII_RX_DELAY_EN		BIT(24)
-+-#define   QCA8K_MAX_DELAY				3
-+ #define   QCA8K_PORT_PAD_SGMII_EN			BIT(7)
-+ #define QCA8K_REG_PWS					0x010
-+ #define   QCA8K_PWS_POWER_ON_SEL			BIT(31)
-+@@ -68,10 +67,12 @@
-+ #define   QCA8K_MDIO_MASTER_READ			BIT(27)
-+ #define   QCA8K_MDIO_MASTER_WRITE			0
-+ #define   QCA8K_MDIO_MASTER_SUP_PRE			BIT(26)
-+-#define   QCA8K_MDIO_MASTER_PHY_ADDR(x)			((x) << 21)
-+-#define   QCA8K_MDIO_MASTER_REG_ADDR(x)			((x) << 16)
-+-#define   QCA8K_MDIO_MASTER_DATA(x)			(x)
-++#define   QCA8K_MDIO_MASTER_PHY_ADDR_MASK		GENMASK(25, 21)
-++#define   QCA8K_MDIO_MASTER_PHY_ADDR(x)			FIELD_PREP(QCA8K_MDIO_MASTER_PHY_ADDR_MASK, x)
-++#define   QCA8K_MDIO_MASTER_REG_ADDR_MASK		GENMASK(20, 16)
-++#define   QCA8K_MDIO_MASTER_REG_ADDR(x)			FIELD_PREP(QCA8K_MDIO_MASTER_REG_ADDR_MASK, x)
-+ #define   QCA8K_MDIO_MASTER_DATA_MASK			GENMASK(15, 0)
-++#define   QCA8K_MDIO_MASTER_DATA(x)			FIELD_PREP(QCA8K_MDIO_MASTER_DATA_MASK, x)
-+ #define   QCA8K_MDIO_MASTER_MAX_PORTS			5
-+ #define   QCA8K_MDIO_MASTER_MAX_REG			32
-+ #define QCA8K_GOL_MAC_ADDR0				0x60
-+@@ -93,9 +94,7 @@
-+ #define   QCA8K_PORT_STATUS_FLOW_AUTO			BIT(12)
-+ #define QCA8K_REG_PORT_HDR_CTRL(_i)			(0x9c + (_i * 4))
-+ #define   QCA8K_PORT_HDR_CTRL_RX_MASK			GENMASK(3, 2)
-+-#define   QCA8K_PORT_HDR_CTRL_RX_S			2
-+ #define   QCA8K_PORT_HDR_CTRL_TX_MASK			GENMASK(1, 0)
-+-#define   QCA8K_PORT_HDR_CTRL_TX_S			0
-+ #define   QCA8K_PORT_HDR_CTRL_ALL			2
-+ #define   QCA8K_PORT_HDR_CTRL_MGMT			1
-+ #define   QCA8K_PORT_HDR_CTRL_NONE			0
-+@@ -105,10 +104,11 @@
-+ #define   QCA8K_SGMII_EN_TX				BIT(3)
-+ #define   QCA8K_SGMII_EN_SD				BIT(4)
-+ #define   QCA8K_SGMII_CLK125M_DELAY			BIT(7)
-+-#define   QCA8K_SGMII_MODE_CTRL_MASK			(BIT(22) | BIT(23))
-+-#define   QCA8K_SGMII_MODE_CTRL_BASEX			(0 << 22)
-+-#define   QCA8K_SGMII_MODE_CTRL_PHY			(1 << 22)
-+-#define   QCA8K_SGMII_MODE_CTRL_MAC			(2 << 22)
-++#define   QCA8K_SGMII_MODE_CTRL_MASK			GENMASK(23, 22)
-++#define   QCA8K_SGMII_MODE_CTRL(x)			FIELD_PREP(QCA8K_SGMII_MODE_CTRL_MASK, x)
-++#define   QCA8K_SGMII_MODE_CTRL_BASEX			QCA8K_SGMII_MODE_CTRL(0x0)
-++#define   QCA8K_SGMII_MODE_CTRL_PHY			QCA8K_SGMII_MODE_CTRL(0x1)
-++#define   QCA8K_SGMII_MODE_CTRL_MAC			QCA8K_SGMII_MODE_CTRL(0x2)
++@@ -58,9 +58,9 @@ enum {
 + 
-+ /* MAC_PWR_SEL registers */
-+ #define QCA8K_REG_MAC_PWR_SEL				0x0e4
-+@@ -121,100 +121,115 @@
++ #define MTK_FOE_IB2_DSCP		GENMASK(31, 24)
 + 
-+ /* ACL registers */
-+ #define QCA8K_REG_PORT_VLAN_CTRL0(_i)			(0x420 + (_i * 8))
-+-#define   QCA8K_PORT_VLAN_CVID(x)			(x << 16)
-+-#define   QCA8K_PORT_VLAN_SVID(x)			x
-++#define   QCA8K_PORT_VLAN_CVID_MASK			GENMASK(27, 16)
-++#define   QCA8K_PORT_VLAN_CVID(x)			FIELD_PREP(QCA8K_PORT_VLAN_CVID_MASK, x)
-++#define   QCA8K_PORT_VLAN_SVID_MASK			GENMASK(11, 0)
-++#define   QCA8K_PORT_VLAN_SVID(x)			FIELD_PREP(QCA8K_PORT_VLAN_SVID_MASK, x)
-+ #define QCA8K_REG_PORT_VLAN_CTRL1(_i)			(0x424 + (_i * 8))
-+ #define QCA8K_REG_IPV4_PRI_BASE_ADDR			0x470
-+ #define QCA8K_REG_IPV4_PRI_ADDR_MASK			0x474
++-#define MTK_FOE_VLAN2_WHNAT_BSS		GEMMASK(5, 0)
++-#define MTK_FOE_VLAN2_WHNAT_WCID	GENMASK(13, 6)
++-#define MTK_FOE_VLAN2_WHNAT_RING	GENMASK(15, 14)
+++#define MTK_FOE_VLAN2_WINFO_BSS		GENMASK(5, 0)
+++#define MTK_FOE_VLAN2_WINFO_WCID	GENMASK(13, 6)
+++#define MTK_FOE_VLAN2_WINFO_RING	GENMASK(15, 14)
 + 
-+ /* Lookup registers */
-+ #define QCA8K_REG_ATU_DATA0				0x600
-+-#define   QCA8K_ATU_ADDR2_S				24
-+-#define   QCA8K_ATU_ADDR3_S				16
-+-#define   QCA8K_ATU_ADDR4_S				8
-++#define   QCA8K_ATU_ADDR2_MASK				GENMASK(31, 24)
-++#define   QCA8K_ATU_ADDR3_MASK				GENMASK(23, 16)
-++#define   QCA8K_ATU_ADDR4_MASK				GENMASK(15, 8)
-++#define   QCA8K_ATU_ADDR5_MASK				GENMASK(7, 0)
-+ #define QCA8K_REG_ATU_DATA1				0x604
-+-#define   QCA8K_ATU_PORT_M				0x7f
-+-#define   QCA8K_ATU_PORT_S				16
-+-#define   QCA8K_ATU_ADDR0_S				8
-++#define   QCA8K_ATU_PORT_MASK				GENMASK(22, 16)
-++#define   QCA8K_ATU_ADDR0_MASK				GENMASK(15, 8)
-++#define   QCA8K_ATU_ADDR1_MASK				GENMASK(7, 0)
-+ #define QCA8K_REG_ATU_DATA2				0x608
-+-#define   QCA8K_ATU_VID_M				0xfff
-+-#define   QCA8K_ATU_VID_S				8
-+-#define   QCA8K_ATU_STATUS_M				0xf
-++#define   QCA8K_ATU_VID_MASK				GENMASK(19, 8)
-++#define   QCA8K_ATU_STATUS_MASK				GENMASK(3, 0)
-+ #define   QCA8K_ATU_STATUS_STATIC			0xf
-+ #define QCA8K_REG_ATU_FUNC				0x60c
-+ #define   QCA8K_ATU_FUNC_BUSY				BIT(31)
-+ #define   QCA8K_ATU_FUNC_PORT_EN			BIT(14)
-+ #define   QCA8K_ATU_FUNC_MULTI_EN			BIT(13)
-+ #define   QCA8K_ATU_FUNC_FULL				BIT(12)
-+-#define   QCA8K_ATU_FUNC_PORT_M				0xf
-+-#define   QCA8K_ATU_FUNC_PORT_S				8
-++#define   QCA8K_ATU_FUNC_PORT_MASK			GENMASK(11, 8)
-+ #define QCA8K_REG_VTU_FUNC0				0x610
-+ #define   QCA8K_VTU_FUNC0_VALID				BIT(20)
-+ #define   QCA8K_VTU_FUNC0_IVL_EN			BIT(19)
-+-#define   QCA8K_VTU_FUNC0_EG_MODE_S(_i)			(4 + (_i) * 2)
-+-#define   QCA8K_VTU_FUNC0_EG_MODE_MASK			3
-+-#define   QCA8K_VTU_FUNC0_EG_MODE_UNMOD			0
-+-#define   QCA8K_VTU_FUNC0_EG_MODE_UNTAG			1
-+-#define   QCA8K_VTU_FUNC0_EG_MODE_TAG			2
-+-#define   QCA8K_VTU_FUNC0_EG_MODE_NOT			3
-++/*        QCA8K_VTU_FUNC0_EG_MODE_MASK			GENMASK(17, 4)
-++ *          It does contain VLAN_MODE for each port [5:4] for port0,
-++ *          [7:6] for port1 ... [17:16] for port6. Use virtual port
-++ *          define to handle this.
-++ */
-++#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i)	(4 + (_i) * 2)
-++#define   QCA8K_VTU_FUNC0_EG_MODE_MASK			GENMASK(1, 0)
-++#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(_i)		(GENMASK(1, 0) << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
-++#define   QCA8K_VTU_FUNC0_EG_MODE_UNMOD			FIELD_PREP(QCA8K_VTU_FUNC0_EG_MODE_MASK, 0x0)
-++#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_UNMOD(_i)	(QCA8K_VTU_FUNC0_EG_MODE_UNMOD << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
-++#define   QCA8K_VTU_FUNC0_EG_MODE_UNTAG			FIELD_PREP(QCA8K_VTU_FUNC0_EG_MODE_MASK, 0x1)
-++#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_UNTAG(_i)	(QCA8K_VTU_FUNC0_EG_MODE_UNTAG << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
-++#define   QCA8K_VTU_FUNC0_EG_MODE_TAG			FIELD_PREP(QCA8K_VTU_FUNC0_EG_MODE_MASK, 0x2)
-++#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_TAG(_i)		(QCA8K_VTU_FUNC0_EG_MODE_TAG << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
-++#define   QCA8K_VTU_FUNC0_EG_MODE_NOT			FIELD_PREP(QCA8K_VTU_FUNC0_EG_MODE_MASK, 0x3)
-++#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(_i)		(QCA8K_VTU_FUNC0_EG_MODE_NOT << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
-+ #define QCA8K_REG_VTU_FUNC1				0x614
-+ #define   QCA8K_VTU_FUNC1_BUSY				BIT(31)
-+-#define   QCA8K_VTU_FUNC1_VID_S				16
-++#define   QCA8K_VTU_FUNC1_VID_MASK			GENMASK(27, 16)
-+ #define   QCA8K_VTU_FUNC1_FULL				BIT(4)
-+ #define QCA8K_REG_GLOBAL_FW_CTRL0			0x620
-+ #define   QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN		BIT(10)
-+ #define QCA8K_REG_GLOBAL_FW_CTRL1			0x624
-+-#define   QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S		24
-+-#define   QCA8K_GLOBAL_FW_CTRL1_BC_DP_S			16
-+-#define   QCA8K_GLOBAL_FW_CTRL1_MC_DP_S			8
-+-#define   QCA8K_GLOBAL_FW_CTRL1_UC_DP_S			0
-++#define   QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK		GENMASK(30, 24)
-++#define   QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK		GENMASK(22, 16)
-++#define   QCA8K_GLOBAL_FW_CTRL1_MC_DP_MASK		GENMASK(14, 8)
-++#define   QCA8K_GLOBAL_FW_CTRL1_UC_DP_MASK		GENMASK(6, 0)
-+ #define QCA8K_PORT_LOOKUP_CTRL(_i)			(0x660 + (_i) * 0xc)
-+ #define   QCA8K_PORT_LOOKUP_MEMBER			GENMASK(6, 0)
-+-#define   QCA8K_PORT_LOOKUP_VLAN_MODE			GENMASK(9, 8)
-+-#define   QCA8K_PORT_LOOKUP_VLAN_MODE_NONE		(0 << 8)
-+-#define   QCA8K_PORT_LOOKUP_VLAN_MODE_FALLBACK		(1 << 8)
-+-#define   QCA8K_PORT_LOOKUP_VLAN_MODE_CHECK		(2 << 8)
-+-#define   QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE		(3 << 8)
-++#define   QCA8K_PORT_LOOKUP_VLAN_MODE_MASK		GENMASK(9, 8)
-++#define   QCA8K_PORT_LOOKUP_VLAN_MODE(x)		FIELD_PREP(QCA8K_PORT_LOOKUP_VLAN_MODE_MASK, x)
-++#define   QCA8K_PORT_LOOKUP_VLAN_MODE_NONE		QCA8K_PORT_LOOKUP_VLAN_MODE(0x0)
-++#define   QCA8K_PORT_LOOKUP_VLAN_MODE_FALLBACK		QCA8K_PORT_LOOKUP_VLAN_MODE(0x1)
-++#define   QCA8K_PORT_LOOKUP_VLAN_MODE_CHECK		QCA8K_PORT_LOOKUP_VLAN_MODE(0x2)
-++#define   QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE		QCA8K_PORT_LOOKUP_VLAN_MODE(0x3)
-+ #define   QCA8K_PORT_LOOKUP_STATE_MASK			GENMASK(18, 16)
-+-#define   QCA8K_PORT_LOOKUP_STATE_DISABLED		(0 << 16)
-+-#define   QCA8K_PORT_LOOKUP_STATE_BLOCKING		(1 << 16)
-+-#define   QCA8K_PORT_LOOKUP_STATE_LISTENING		(2 << 16)
-+-#define   QCA8K_PORT_LOOKUP_STATE_LEARNING		(3 << 16)
-+-#define   QCA8K_PORT_LOOKUP_STATE_FORWARD		(4 << 16)
-+-#define   QCA8K_PORT_LOOKUP_STATE			GENMASK(18, 16)
-++#define   QCA8K_PORT_LOOKUP_STATE(x)			FIELD_PREP(QCA8K_PORT_LOOKUP_STATE_MASK, x)
-++#define   QCA8K_PORT_LOOKUP_STATE_DISABLED		QCA8K_PORT_LOOKUP_STATE(0x0)
-++#define   QCA8K_PORT_LOOKUP_STATE_BLOCKING		QCA8K_PORT_LOOKUP_STATE(0x1)
-++#define   QCA8K_PORT_LOOKUP_STATE_LISTENING		QCA8K_PORT_LOOKUP_STATE(0x2)
-++#define   QCA8K_PORT_LOOKUP_STATE_LEARNING		QCA8K_PORT_LOOKUP_STATE(0x3)
-++#define   QCA8K_PORT_LOOKUP_STATE_FORWARD		QCA8K_PORT_LOOKUP_STATE(0x4)
-+ #define   QCA8K_PORT_LOOKUP_LEARN			BIT(20)
++ enum {
++ 	MTK_FOE_STATE_INVALID,
++@@ -281,6 +281,8 @@ int mtk_foe_entry_set_ipv6_tuple(struct
++ int mtk_foe_entry_set_dsa(struct mtk_foe_entry *entry, int port);
++ int mtk_foe_entry_set_vlan(struct mtk_foe_entry *entry, int vid);
++ int mtk_foe_entry_set_pppoe(struct mtk_foe_entry *entry, int sid);
+++int mtk_foe_entry_set_wdma(struct mtk_foe_entry *entry, int wdma_idx, int txq,
+++			   int bss, int wcid);
++ int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_foe_entry *entry,
++ 			 u16 timestamp);
++ int mtk_ppe_debugfs_init(struct mtk_ppe *ppe);
++--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++@@ -10,6 +10,7 @@
++ #include <net/pkt_cls.h>
++ #include <net/dsa.h>
++ #include "mtk_eth_soc.h"
+++#include "mtk_wed.h"
 + 
-+ #define QCA8K_REG_GLOBAL_FC_THRESH			0x800
-+-#define   QCA8K_GLOBAL_FC_GOL_XON_THRES(x)		((x) << 16)
-+-#define   QCA8K_GLOBAL_FC_GOL_XON_THRES_S		GENMASK(24, 16)
-+-#define   QCA8K_GLOBAL_FC_GOL_XOFF_THRES(x)		((x) << 0)
-+-#define   QCA8K_GLOBAL_FC_GOL_XOFF_THRES_S		GENMASK(8, 0)
-++#define   QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK		GENMASK(24, 16)
-++#define   QCA8K_GLOBAL_FC_GOL_XON_THRES(x)		FIELD_PREP(QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK, x)
-++#define   QCA8K_GLOBAL_FC_GOL_XOFF_THRES_MASK		GENMASK(8, 0)
-++#define   QCA8K_GLOBAL_FC_GOL_XOFF_THRES(x)		FIELD_PREP(QCA8K_GLOBAL_FC_GOL_XOFF_THRES_MASK, x)
++ struct mtk_flow_data {
++ 	struct ethhdr eth;
++@@ -39,6 +40,7 @@ struct mtk_flow_entry {
++ 	struct rhash_head node;
++ 	unsigned long cookie;
++ 	u16 hash;
+++	s8 wed_index;
++ };
 + 
-+ #define QCA8K_REG_PORT_HOL_CTRL0(_i)			(0x970 + (_i) * 0x8)
-+-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI0_BUF		GENMASK(3, 0)
-+-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI0(x)		((x) << 0)
-+-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI1_BUF		GENMASK(7, 4)
-+-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI1(x)		((x) << 4)
-+-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI2_BUF		GENMASK(11, 8)
-+-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI2(x)		((x) << 8)
-+-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI3_BUF		GENMASK(15, 12)
-+-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI3(x)		((x) << 12)
-+-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI4_BUF		GENMASK(19, 16)
-+-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI4(x)		((x) << 16)
-+-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI5_BUF		GENMASK(23, 20)
-+-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI5(x)		((x) << 20)
-+-#define   QCA8K_PORT_HOL_CTRL0_EG_PORT_BUF		GENMASK(29, 24)
-+-#define   QCA8K_PORT_HOL_CTRL0_EG_PORT(x)		((x) << 24)
-++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI0_BUF_MASK		GENMASK(3, 0)
-++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI0(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI0_BUF_MASK, x)
-++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI1_BUF_MASK		GENMASK(7, 4)
-++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI1(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI1_BUF_MASK, x)
-++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI2_BUF_MASK		GENMASK(11, 8)
-++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI2(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI2_BUF_MASK, x)
-++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI3_BUF_MASK		GENMASK(15, 12)
-++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI3(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI3_BUF_MASK, x)
-++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI4_BUF_MASK		GENMASK(19, 16)
-++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI4(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI4_BUF_MASK, x)
-++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI5_BUF_MASK		GENMASK(23, 20)
-++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI5(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI5_BUF_MASK, x)
-++#define   QCA8K_PORT_HOL_CTRL0_EG_PORT_BUF_MASK		GENMASK(29, 24)
-++#define   QCA8K_PORT_HOL_CTRL0_EG_PORT(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PORT_BUF_MASK, x)
++ static const struct rhashtable_params mtk_flow_ht_params = {
++@@ -80,6 +82,35 @@ mtk_flow_offload_mangle_eth(const struct
++ 	memcpy(dest, src, act->mangle.mask ? 2 : 4);
++ }
 + 
-+ #define QCA8K_REG_PORT_HOL_CTRL1(_i)			(0x974 + (_i) * 0x8)
-+-#define   QCA8K_PORT_HOL_CTRL1_ING_BUF			GENMASK(3, 0)
-+-#define   QCA8K_PORT_HOL_CTRL1_ING(x)			((x) << 0)
-++#define   QCA8K_PORT_HOL_CTRL1_ING_BUF_MASK		GENMASK(3, 0)
-++#define   QCA8K_PORT_HOL_CTRL1_ING(x)			FIELD_PREP(QCA8K_PORT_HOL_CTRL1_ING_BUF_MASK, x)
-+ #define   QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN		BIT(6)
-+ #define   QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN		BIT(7)
-+ #define   QCA8K_PORT_HOL_CTRL1_WRED_EN			BIT(8)
-+ #define   QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN		BIT(16)
+++static int
+++mtk_flow_get_wdma_info(struct net_device *dev, const u8 *addr, struct mtk_wdma_info *info)
+++{
+++	struct net_device_path_ctx ctx = {
+++		.dev = dev,
+++		.daddr = addr,
+++	};
+++	struct net_device_path path = {};
+++
+++	if (!IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED))
+++		return -1;
+++
+++	if (!dev->netdev_ops->ndo_fill_forward_path)
+++		return -1;
+++
+++	if (dev->netdev_ops->ndo_fill_forward_path(&ctx, &path))
+++		return -1;
+++
+++	if (path.type != DEV_PATH_MTK_WDMA)
+++		return -1;
+++
+++	info->wdma_idx = path.mtk_wdma.wdma_idx;
+++	info->queue = path.mtk_wdma.queue;
+++	info->bss = path.mtk_wdma.bss;
+++	info->wcid = path.mtk_wdma.wcid;
+++
+++	return 0;
+++}
+++
 + 
-+ /* Pkt edit registers */
-++#define QCA8K_EGREES_VLAN_PORT_SHIFT(_i)		(16 * ((_i) % 2))
-++#define QCA8K_EGREES_VLAN_PORT_MASK(_i)			(GENMASK(11, 0) << QCA8K_EGREES_VLAN_PORT_SHIFT(_i))
-++#define QCA8K_EGREES_VLAN_PORT(_i, x)			((x) << QCA8K_EGREES_VLAN_PORT_SHIFT(_i))
-+ #define QCA8K_EGRESS_VLAN(x)				(0x0c70 + (4 * (x / 2)))
++ static int
++ mtk_flow_mangle_ports(const struct flow_action_entry *act,
++@@ -149,10 +180,20 @@ mtk_flow_get_dsa_port(struct net_device
 + 
-+ /* L3 registers */
-diff --git a/target/linux/generic/backport-5.15/755-net-next-net-dsa-qca8k-remove-extra-mutex_init-in-qca8k_setup.patch b/target/linux/generic/backport-5.15/755-net-next-net-dsa-qca8k-remove-extra-mutex_init-in-qca8k_setup.patch
-new file mode 100644
-index 0000000000..8c39b8ea29
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/755-net-next-net-dsa-qca8k-remove-extra-mutex_init-in-qca8k_setup.patch
-@@ -0,0 +1,25 @@
-+From 994c28b6f971fa5db8ae977daea37eee87d93d51 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Mon, 22 Nov 2021 16:23:42 +0100
-+Subject: net: dsa: qca8k: remove extra mutex_init in qca8k_setup
-+
-+Mutex is already init in sw_probe. Remove the extra init in qca8k_setup.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 2 --
-+ 1 file changed, 2 deletions(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -1086,8 +1086,6 @@ qca8k_setup(struct dsa_switch *ds)
-+ 	if (ret)
-+ 		return ret;
++ static int
++ mtk_flow_set_output_device(struct mtk_eth *eth, struct mtk_foe_entry *foe,
++-			   struct net_device *dev)
+++			   struct net_device *dev, const u8 *dest_mac,
+++			   int *wed_index)
++ {
+++	struct mtk_wdma_info info = {};
++ 	int pse_port, dsa_port;
 + 
-+-	mutex_init(&priv->reg_mutex);
-+-
-+ 	/* Start by setting up the register mapping */
-+ 	priv->regmap = devm_regmap_init(ds->dev, NULL, priv,
-+ 					&qca8k_regmap_config);
-diff --git a/target/linux/generic/backport-5.15/756-net-next-net-dsa-qca8k-move-regmap-init-in-probe-and-set-it.patch b/target/linux/generic/backport-5.15/756-net-next-net-dsa-qca8k-move-regmap-init-in-probe-and-set-it.patch
+++	if (mtk_flow_get_wdma_info(dev, dest_mac, &info) == 0) {
+++		mtk_foe_entry_set_wdma(foe, info.wdma_idx, info.queue, info.bss,
+++				       info.wcid);
+++		pse_port = 3;
+++		*wed_index = info.wdma_idx;
+++		goto out;
+++	}
+++
++ 	dsa_port = mtk_flow_get_dsa_port(&dev);
++ 	if (dsa_port >= 0)
++ 		mtk_foe_entry_set_dsa(foe, dsa_port);
++@@ -164,6 +205,7 @@ mtk_flow_set_output_device(struct mtk_et
++ 	else
++ 		return -EOPNOTSUPP;
++ 
+++out:
++ 	mtk_foe_entry_set_pse_port(foe, pse_port);
++ 
++ 	return 0;
++@@ -179,6 +221,7 @@ mtk_flow_offload_replace(struct mtk_eth
++ 	struct net_device *odev = NULL;
++ 	struct mtk_flow_entry *entry;
++ 	int offload_type = 0;
+++	int wed_index = -1;
++ 	u16 addr_type = 0;
++ 	u32 timestamp;
++ 	u8 l4proto = 0;
++@@ -326,10 +369,14 @@ mtk_flow_offload_replace(struct mtk_eth
++ 	if (data.pppoe.num == 1)
++ 		mtk_foe_entry_set_pppoe(&foe, data.pppoe.sid);
++ 
++-	err = mtk_flow_set_output_device(eth, &foe, odev);
+++	err = mtk_flow_set_output_device(eth, &foe, odev, data.eth.h_dest,
+++					 &wed_index);
++ 	if (err)
++ 		return err;
++ 
+++	if (wed_index >= 0 && (err = mtk_wed_flow_add(wed_index)) < 0)
+++		return err;
+++
++ 	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
++ 	if (!entry)
++ 		return -ENOMEM;
++@@ -343,6 +390,7 @@ mtk_flow_offload_replace(struct mtk_eth
++ 	}
++ 
++ 	entry->hash = hash;
+++	entry->wed_index = wed_index;
++ 	err = rhashtable_insert_fast(&eth->flow_table, &entry->node,
++ 				     mtk_flow_ht_params);
++ 	if (err < 0)
++@@ -353,6 +401,8 @@ clear_flow:
++ 	mtk_foe_entry_clear(&eth->ppe, hash);
++ free:
++ 	kfree(entry);
+++	if (wed_index >= 0)
+++	    mtk_wed_flow_remove(wed_index);
++ 	return err;
++ }
++ 
++@@ -369,6 +419,8 @@ mtk_flow_offload_destroy(struct mtk_eth
++ 	mtk_foe_entry_clear(&eth->ppe, entry->hash);
++ 	rhashtable_remove_fast(&eth->flow_table, &entry->node,
++ 			       mtk_flow_ht_params);
+++	if (entry->wed_index >= 0)
+++		mtk_wed_flow_remove(entry->wed_index);
++ 	kfree(entry);
++ 
++ 	return 0;
++--- a/drivers/net/ethernet/mediatek/mtk_wed.h
+++++ b/drivers/net/ethernet/mediatek/mtk_wed.h
++@@ -7,6 +7,7 @@
++ #include <linux/soc/mediatek/mtk_wed.h>
++ #include <linux/debugfs.h>
++ #include <linux/regmap.h>
+++#include <linux/netdevice.h>
++ 
++ struct mtk_eth;
++ 
++@@ -27,6 +28,12 @@ struct mtk_wed_hw {
++ 	int index;
++ };
++ 
+++struct mtk_wdma_info {
+++	u8 wdma_idx;
+++	u8 queue;
+++	u16 wcid;
+++	u8 bss;
+++};
++ 
++ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
++ static inline void
++--- a/include/linux/netdevice.h
+++++ b/include/linux/netdevice.h
++@@ -863,6 +863,7 @@ enum net_device_path_type {
++ 	DEV_PATH_BRIDGE,
++ 	DEV_PATH_PPPOE,
++ 	DEV_PATH_DSA,
+++	DEV_PATH_MTK_WDMA,
++ };
++ 
++ struct net_device_path {
++@@ -888,6 +889,12 @@ struct net_device_path {
++ 			int port;
++ 			u16 proto;
++ 		} dsa;
+++		struct {
+++			u8 wdma_idx;
+++			u8 queue;
+++			u16 wcid;
+++			u8 bss;
+++		} mtk_wdma;
++ 	};
++ };
++ 
++--- a/net/core/dev.c
+++++ b/net/core/dev.c
++@@ -763,6 +763,10 @@ int dev_fill_forward_path(const struct n
++ 		if (WARN_ON_ONCE(last_dev == ctx.dev))
++ 			return -1;
++ 	}
+++
+++	if (!ctx.dev)
+++		return ret;
+++
++ 	path = dev_fwd_path(stack);
++ 	if (!path)
++ 		return -1;
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-04-arm64-dts-mediatek-mt7622-introduce-nodes-for-Wirele.patch b/target/linux/generic/backport-5.15/702-v5.19-04-arm64-dts-mediatek-mt7622-introduce-nodes-for-Wirele.patch
 new file mode 100644
-index 0000000000..f873b70d0f
+index 0000000000..f59a364a73
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/756-net-next-net-dsa-qca8k-move-regmap-init-in-probe-and-set-it.patch
-@@ -0,0 +1,46 @@
-+From 36b8af12f424e7a7f60a935c60a0fd4aa0822378 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Mon, 22 Nov 2021 16:23:43 +0100
-+Subject: net: dsa: qca8k: move regmap init in probe and set it mandatory
++++ b/target/linux/generic/backport-5.15/702-v5.19-04-arm64-dts-mediatek-mt7622-introduce-nodes-for-Wirele.patch
+@@ -0,0 +1,62 @@
++From: Felix Fietkau <nbd@nbd.name>
++Date: Sat, 5 Feb 2022 18:36:36 +0100
++Subject: [PATCH] arm64: dts: mediatek: mt7622: introduce nodes for
++ Wireless Ethernet Dispatch
 +
-+In preparation for regmap conversion, move regmap init in the probe
-+function and make it mandatory as any read/write/rmw operation will be
-+converted to regmap API.
++Introduce wed0 and wed1 nodes in order to enable offloading forwarding
++between ethernet and wireless devices on the mt7622 chipset.
 +
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Felix Fietkau <nbd@nbd.name>
 +---
-+ drivers/net/dsa/qca8k.c | 14 ++++++++------
-+ 1 file changed, 8 insertions(+), 6 deletions(-)
 +
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -1086,12 +1086,6 @@ qca8k_setup(struct dsa_switch *ds)
-+ 	if (ret)
-+ 		return ret;
++--- a/arch/arm64/boot/dts/mediatek/mt7622.dtsi
+++++ b/arch/arm64/boot/dts/mediatek/mt7622.dtsi
++@@ -893,6 +893,11 @@
++ 		};
++ 	};
 + 
-+-	/* Start by setting up the register mapping */
-+-	priv->regmap = devm_regmap_init(ds->dev, NULL, priv,
-+-					&qca8k_regmap_config);
-+-	if (IS_ERR(priv->regmap))
-+-		dev_warn(priv->dev, "regmap initialization failed");
-+-
-+ 	ret = qca8k_setup_mdio_bus(priv);
-+ 	if (ret)
-+ 		return ret;
-+@@ -2077,6 +2071,14 @@ qca8k_sw_probe(struct mdio_device *mdiod
-+ 		gpiod_set_value_cansleep(priv->reset_gpio, 0);
-+ 	}
+++	hifsys: syscon@1af00000 {
+++		compatible = "mediatek,mt7622-hifsys", "syscon";
+++		reg = <0 0x1af00000 0 0x70>;
+++	};
+++
++ 	ethsys: syscon@1b000000 {
++ 		compatible = "mediatek,mt7622-ethsys",
++ 			     "syscon";
++@@ -911,6 +916,26 @@
++ 		#dma-cells = <1>;
++ 	};
 + 
+++	pcie_mirror: pcie-mirror@10000400 {
+++		compatible = "mediatek,mt7622-pcie-mirror",
+++			     "syscon";
+++		reg = <0 0x10000400 0 0x10>;
+++	};
+++
+++	wed0: wed@1020a000 {
+++		compatible = "mediatek,mt7622-wed",
+++			     "syscon";
+++		reg = <0 0x1020a000 0 0x1000>;
+++		interrupts = <GIC_SPI 214 IRQ_TYPE_LEVEL_LOW>;
+++	};
+++
+++	wed1: wed@1020b000 {
+++		compatible = "mediatek,mt7622-wed",
+++			     "syscon";
+++		reg = <0 0x1020b000 0 0x1000>;
+++		interrupts = <GIC_SPI 215 IRQ_TYPE_LEVEL_LOW>;
+++	};
+++
++ 	eth: ethernet@1b100000 {
++ 		compatible = "mediatek,mt7622-eth",
++ 			     "mediatek,mt2701-eth",
++@@ -938,6 +963,9 @@
++ 		mediatek,ethsys = <&ethsys>;
++ 		mediatek,sgmiisys = <&sgmiisys>;
++ 		mediatek,cci-control = <&cci_control2>;
+++		mediatek,wed = <&wed0>, <&wed1>;
+++		mediatek,pcie-mirror = <&pcie_mirror>;
+++		mediatek,hifsys = <&hifsys>;
++ 		dma-coherent;
++ 		#address-cells = <1>;
++ 		#size-cells = <0>;
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-05-net-ethernet-mtk_eth_soc-add-ipv6-flow-offload-suppo.patch b/target/linux/generic/backport-5.15/702-v5.19-05-net-ethernet-mtk_eth_soc-add-ipv6-flow-offload-suppo.patch
+new file mode 100644
+index 0000000000..9adb067015
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-05-net-ethernet-mtk_eth_soc-add-ipv6-flow-offload-suppo.patch
+@@ -0,0 +1,79 @@
++From: David Bentham <db260179@gmail.com>
++Date: Mon, 21 Feb 2022 15:36:16 +0100
++Subject: [PATCH] net: ethernet: mtk_eth_soc: add ipv6 flow offload
++ support
++
++Add the missing IPv6 flow offloading support for routing only.
++Hardware flow offloading is done by the packet processing engine (PPE)
++of the Ethernet MAC and as it doesn't support mangling of IPv6 packets,
++IPv6 NAT cannot be supported.
++
++Signed-off-by: David Bentham <db260179@gmail.com>
++Signed-off-by: Felix Fietkau <nbd@nbd.name>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++@@ -6,6 +6,7 @@
++ #include <linux/if_ether.h>
++ #include <linux/rhashtable.h>
++ #include <linux/ip.h>
+++#include <linux/ipv6.h>
++ #include <net/flow_offload.h>
++ #include <net/pkt_cls.h>
++ #include <net/dsa.h>
++@@ -20,6 +21,11 @@ struct mtk_flow_data {
++ 			__be32 src_addr;
++ 			__be32 dst_addr;
++ 		} v4;
+++
+++		struct {
+++			struct in6_addr src_addr;
+++			struct in6_addr dst_addr;
+++		} v6;
++ 	};
++ 
++ 	__be16 src_port;
++@@ -65,6 +71,14 @@ mtk_flow_set_ipv4_addr(struct mtk_foe_en
++ 					    data->v4.dst_addr, data->dst_port);
++ }
++ 
+++static int
+++mtk_flow_set_ipv6_addr(struct mtk_foe_entry *foe, struct mtk_flow_data *data)
+++{
+++	return mtk_foe_entry_set_ipv6_tuple(foe,
+++					    data->v6.src_addr.s6_addr32, data->src_port,
+++					    data->v6.dst_addr.s6_addr32, data->dst_port);
+++}
+++
++ static void
++ mtk_flow_offload_mangle_eth(const struct flow_action_entry *act, void *eth)
++ {
++@@ -296,6 +310,9 @@ mtk_flow_offload_replace(struct mtk_eth
++ 	case FLOW_DISSECTOR_KEY_IPV4_ADDRS:
++ 		offload_type = MTK_PPE_PKT_TYPE_IPV4_HNAPT;
++ 		break;
+++	case FLOW_DISSECTOR_KEY_IPV6_ADDRS:
+++		offload_type = MTK_PPE_PKT_TYPE_IPV6_ROUTE_5T;
+++		break;
++ 	default:
++ 		return -EOPNOTSUPP;
++ 	}
++@@ -331,6 +348,17 @@ mtk_flow_offload_replace(struct mtk_eth
++ 		mtk_flow_set_ipv4_addr(&foe, &data, false);
++ 	}
++ 
+++	if (addr_type == FLOW_DISSECTOR_KEY_IPV6_ADDRS) {
+++		struct flow_match_ipv6_addrs addrs;
+++
+++		flow_rule_match_ipv6_addrs(rule, &addrs);
+++
+++		data.v6.src_addr = addrs.key->src;
+++		data.v6.dst_addr = addrs.key->dst;
+++
+++		mtk_flow_set_ipv6_addr(&foe, &data);
+++	}
+++
++ 	flow_action_for_each(i, act, &rule->action) {
++ 		if (act->id != FLOW_ACTION_MANGLE)
++ 			continue;
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-06-net-ethernet-mtk_eth_soc-support-TC_SETUP_BLOCK-for-.patch b/target/linux/generic/backport-5.15/702-v5.19-06-net-ethernet-mtk_eth_soc-support-TC_SETUP_BLOCK-for-.patch
+new file mode 100644
+index 0000000000..72c6d28172
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-06-net-ethernet-mtk_eth_soc-support-TC_SETUP_BLOCK-for-.patch
+@@ -0,0 +1,29 @@
++From: Felix Fietkau <nbd@nbd.name>
++Date: Mon, 21 Feb 2022 15:37:21 +0100
++Subject: [PATCH] net: ethernet: mtk_eth_soc: support TC_SETUP_BLOCK for
++ PPE offload
++
++This allows offload entries to be created from user space
++
++Signed-off-by: Felix Fietkau <nbd@nbd.name>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++@@ -563,10 +563,13 @@ mtk_eth_setup_tc_block(struct net_device
++ int mtk_eth_setup_tc(struct net_device *dev, enum tc_setup_type type,
++ 		     void *type_data)
++ {
++-	if (type == TC_SETUP_FT)
+++	switch (type) {
+++	case TC_SETUP_BLOCK:
+++	case TC_SETUP_FT:
++ 		return mtk_eth_setup_tc_block(dev, type_data);
++-
++-	return -EOPNOTSUPP;
+++	default:
+++		return -EOPNOTSUPP;
+++	}
++ }
++ 
++ int mtk_eth_offload_init(struct mtk_eth *eth)
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-07-net-ethernet-mtk_eth_soc-allocate-struct-mtk_ppe-sep.patch b/target/linux/generic/backport-5.15/702-v5.19-07-net-ethernet-mtk_eth_soc-allocate-struct-mtk_ppe-sep.patch
+new file mode 100644
+index 0000000000..a17d49cac1
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-07-net-ethernet-mtk_eth_soc-allocate-struct-mtk_ppe-sep.patch
+@@ -0,0 +1,159 @@
++From: Felix Fietkau <nbd@nbd.name>
++Date: Mon, 21 Feb 2022 15:38:20 +0100
++Subject: [PATCH] net: ethernet: mtk_eth_soc: allocate struct mtk_ppe
++ separately
++
++Preparation for adding more data to it, which will increase its size.
++
++Signed-off-by: Felix Fietkau <nbd@nbd.name>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -2332,7 +2332,7 @@ static int mtk_open(struct net_device *d
++ 		if (err)
++ 			return err;
++ 
++-		if (eth->soc->offload_version && mtk_ppe_start(&eth->ppe) == 0)
+++		if (eth->soc->offload_version && mtk_ppe_start(eth->ppe) == 0)
++ 			gdm_config = MTK_GDMA_TO_PPE;
++ 
++ 		mtk_gdm_config(eth, gdm_config);
++@@ -2406,7 +2406,7 @@ static int mtk_stop(struct net_device *d
++ 	mtk_dma_free(eth);
++ 
++ 	if (eth->soc->offload_version)
++-		mtk_ppe_stop(&eth->ppe);
+++		mtk_ppe_stop(eth->ppe);
++ 
++ 	return 0;
++ }
++@@ -3298,10 +3298,11 @@ static int mtk_probe(struct platform_dev
++ 	}
++ 
++ 	if (eth->soc->offload_version) {
++-		err = mtk_ppe_init(&eth->ppe, eth->dev,
++-				   eth->base + MTK_ETH_PPE_BASE, 2);
++-		if (err)
+++		eth->ppe = mtk_ppe_init(eth->dev, eth->base + MTK_ETH_PPE_BASE, 2);
+++		if (!eth->ppe) {
+++			err = -ENOMEM;
++ 			goto err_free_dev;
+++		}
++ 
++ 		err = mtk_eth_offload_init(eth);
++ 		if (err)
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -982,7 +982,7 @@ struct mtk_eth {
++ 	u32				rx_dma_l4_valid;
++ 	int				ip_align;
++ 
++-	struct mtk_ppe			ppe;
+++	struct mtk_ppe			*ppe;
++ 	struct rhashtable		flow_table;
++ };
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
++@@ -384,10 +384,15 @@ int mtk_foe_entry_commit(struct mtk_ppe
++ 	return hash;
++ }
++ 
++-int mtk_ppe_init(struct mtk_ppe *ppe, struct device *dev, void __iomem *base,
+++struct mtk_ppe *mtk_ppe_init(struct device *dev, void __iomem *base,
++ 		 int version)
++ {
++ 	struct mtk_foe_entry *foe;
+++	struct mtk_ppe *ppe;
+++
+++	ppe = devm_kzalloc(dev, sizeof(*ppe), GFP_KERNEL);
+++	if (!ppe)
+++		return NULL;
++ 
++ 	/* need to allocate a separate device, since it PPE DMA access is
++ 	 * not coherent.
++@@ -399,13 +404,13 @@ int mtk_ppe_init(struct mtk_ppe *ppe, st
++ 	foe = dmam_alloc_coherent(ppe->dev, MTK_PPE_ENTRIES * sizeof(*foe),
++ 				  &ppe->foe_phys, GFP_KERNEL);
++ 	if (!foe)
++-		return -ENOMEM;
+++		return NULL;
++ 
++ 	ppe->foe_table = foe;
++ 
++ 	mtk_ppe_debugfs_init(ppe);
++ 
++-	return 0;
+++	return ppe;
++ }
++ 
++ static void mtk_ppe_init_foe_table(struct mtk_ppe *ppe)
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
++@@ -246,8 +246,7 @@ struct mtk_ppe {
++ 	void *acct_table;
++ };
++ 
++-int mtk_ppe_init(struct mtk_ppe *ppe, struct device *dev, void __iomem *base,
++-		 int version);
+++struct mtk_ppe *mtk_ppe_init(struct device *dev, void __iomem *base, int version);
++ int mtk_ppe_start(struct mtk_ppe *ppe);
++ int mtk_ppe_stop(struct mtk_ppe *ppe);
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++@@ -411,7 +411,7 @@ mtk_flow_offload_replace(struct mtk_eth
++ 
++ 	entry->cookie = f->cookie;
++ 	timestamp = mtk_eth_timestamp(eth);
++-	hash = mtk_foe_entry_commit(&eth->ppe, &foe, timestamp);
+++	hash = mtk_foe_entry_commit(eth->ppe, &foe, timestamp);
++ 	if (hash < 0) {
++ 		err = hash;
++ 		goto free;
++@@ -426,7 +426,7 @@ mtk_flow_offload_replace(struct mtk_eth
++ 
++ 	return 0;
++ clear_flow:
++-	mtk_foe_entry_clear(&eth->ppe, hash);
+++	mtk_foe_entry_clear(eth->ppe, hash);
++ free:
++ 	kfree(entry);
++ 	if (wed_index >= 0)
++@@ -444,7 +444,7 @@ mtk_flow_offload_destroy(struct mtk_eth
++ 	if (!entry)
++ 		return -ENOENT;
++ 
++-	mtk_foe_entry_clear(&eth->ppe, entry->hash);
+++	mtk_foe_entry_clear(eth->ppe, entry->hash);
++ 	rhashtable_remove_fast(&eth->flow_table, &entry->node,
++ 			       mtk_flow_ht_params);
++ 	if (entry->wed_index >= 0)
++@@ -466,7 +466,7 @@ mtk_flow_offload_stats(struct mtk_eth *e
++ 	if (!entry)
++ 		return -ENOENT;
++ 
++-	timestamp = mtk_foe_entry_timestamp(&eth->ppe, entry->hash);
+++	timestamp = mtk_foe_entry_timestamp(eth->ppe, entry->hash);
++ 	if (timestamp < 0)
++ 		return -ETIMEDOUT;
++ 
++@@ -522,7 +522,7 @@ mtk_eth_setup_tc_block(struct net_device
++ 	struct flow_block_cb *block_cb;
++ 	flow_setup_cb_t *cb;
++ 
++-	if (!eth->ppe.foe_table)
+++	if (!eth->ppe || !eth->ppe->foe_table)
++ 		return -EOPNOTSUPP;
++ 
++ 	if (f->binder_type != FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS)
++@@ -574,7 +574,7 @@ int mtk_eth_setup_tc(struct net_device *
++ 
++ int mtk_eth_offload_init(struct mtk_eth *eth)
++ {
++-	if (!eth->ppe.foe_table)
+++	if (!eth->ppe || !eth->ppe->foe_table)
++ 		return 0;
++ 
++ 	return rhashtable_init(&eth->flow_table, &mtk_flow_ht_params);
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-08-net-ethernet-mtk_eth_soc-rework-hardware-flow-table-.patch b/target/linux/generic/backport-5.15/702-v5.19-08-net-ethernet-mtk_eth_soc-rework-hardware-flow-table-.patch
+new file mode 100644
+index 0000000000..76e8eb11fd
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-08-net-ethernet-mtk_eth_soc-rework-hardware-flow-table-.patch
+@@ -0,0 +1,424 @@
++From: Felix Fietkau <nbd@nbd.name>
++Date: Mon, 21 Feb 2022 15:39:18 +0100
++Subject: [PATCH] net: ethernet: mtk_eth_soc: rework hardware flow table
++ management
++
++The hardware was designed to handle flow detection and creation of flow entries
++by itself, relying on the software primarily for filling in egress routing
++information.
++When there is a hash collision between multiple flows, this allows the hardware
++to maintain the entry for the most active flow.
++Additionally, the hardware only keeps offloading active for entries with at
++least 30 packets per second.
++
++With this rework, the code no longer creates a hardware entries directly.
++Instead, the hardware entry is only created when the PPE reports a matching
++unbound flow with the minimum target rate.
++In order to reduce CPU overhead, looking for flows belonging to a hash entry
++is rate limited to once every 100ms.
++
++This rework is also used as preparation for emulating bridge offload by
++managing L4 offload entries on demand.
++
++Signed-off-by: Felix Fietkau <nbd@nbd.name>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -21,6 +21,7 @@
++ #include <linux/pinctrl/devinfo.h>
++ #include <linux/phylink.h>
++ #include <linux/jhash.h>
+++#include <linux/bitfield.h>
++ #include <net/dsa.h>
++ 
++ #include "mtk_eth_soc.h"
++@@ -1292,7 +1293,7 @@ static int mtk_poll_rx(struct napi_struc
++ 		struct net_device *netdev;
++ 		unsigned int pktlen;
++ 		dma_addr_t dma_addr;
++-		u32 hash;
+++		u32 hash, reason;
++ 		int mac;
++ 
++ 		ring = mtk_get_rx_ring(eth);
++@@ -1371,6 +1372,11 @@ static int mtk_poll_rx(struct napi_struc
++ 			skb_set_hash(skb, hash, PKT_HASH_TYPE_L4);
++ 		}
++ 
+++		reason = FIELD_GET(MTK_RXD4_PPE_CPU_REASON, trxd.rxd4);
+++		if (reason == MTK_PPE_CPU_REASON_HIT_UNBIND_RATE_REACHED)
+++			mtk_ppe_check_skb(eth->ppe, skb,
+++					  trxd.rxd4 & MTK_RXD4_FOE_ENTRY);
+++
++ 		if (netdev->features & NETIF_F_HW_VLAN_CTAG_RX &&
++ 		    (trxd.rxd2 & RX_DMA_VTAG))
++ 			__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q),
++@@ -3298,7 +3304,7 @@ static int mtk_probe(struct platform_dev
++ 	}
++ 
++ 	if (eth->soc->offload_version) {
++-		eth->ppe = mtk_ppe_init(eth->dev, eth->base + MTK_ETH_PPE_BASE, 2);
+++		eth->ppe = mtk_ppe_init(eth, eth->base + MTK_ETH_PPE_BASE, 2);
++ 		if (!eth->ppe) {
++ 			err = -ENOMEM;
++ 			goto err_free_dev;
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
++@@ -6,9 +6,12 @@
++ #include <linux/iopoll.h>
++ #include <linux/etherdevice.h>
++ #include <linux/platform_device.h>
+++#include "mtk_eth_soc.h"
++ #include "mtk_ppe.h"
++ #include "mtk_ppe_regs.h"
++ 
+++static DEFINE_SPINLOCK(ppe_lock);
+++
++ static void ppe_w32(struct mtk_ppe *ppe, u32 reg, u32 val)
++ {
++ 	writel(val, ppe->base + reg);
++@@ -41,6 +44,11 @@ static u32 ppe_clear(struct mtk_ppe *ppe
++ 	return ppe_m32(ppe, reg, val, 0);
++ }
++ 
+++static u32 mtk_eth_timestamp(struct mtk_eth *eth)
+++{
+++	return mtk_r32(eth, 0x0010) & MTK_FOE_IB1_BIND_TIMESTAMP;
+++}
+++
++ static int mtk_ppe_wait_busy(struct mtk_ppe *ppe)
++ {
++ 	int ret;
++@@ -353,26 +361,59 @@ static inline bool mtk_foe_entry_usable(
++ 	       FIELD_GET(MTK_FOE_IB1_STATE, entry->ib1) != MTK_FOE_STATE_BIND;
++ }
++ 
++-int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_foe_entry *entry,
++-			 u16 timestamp)
+++static bool
+++mtk_flow_entry_match(struct mtk_flow_entry *entry, struct mtk_foe_entry *data)
+++{
+++	int type, len;
+++
+++	if ((data->ib1 ^ entry->data.ib1) & MTK_FOE_IB1_UDP)
+++		return false;
+++
+++	type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->data.ib1);
+++	if (type > MTK_PPE_PKT_TYPE_IPV4_DSLITE)
+++		len = offsetof(struct mtk_foe_entry, ipv6._rsv);
+++	else
+++		len = offsetof(struct mtk_foe_entry, ipv4.ib2);
+++
+++	return !memcmp(&entry->data.data, &data->data, len - 4);
+++}
+++
+++static void
+++mtk_flow_entry_update(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
++ {
++ 	struct mtk_foe_entry *hwe;
++-	u32 hash;
+++	struct mtk_foe_entry foe;
++ 
+++	spin_lock_bh(&ppe_lock);
+++	if (entry->hash == 0xffff)
+++		goto out;
+++
+++	hwe = &ppe->foe_table[entry->hash];
+++	memcpy(&foe, hwe, sizeof(foe));
+++	if (!mtk_flow_entry_match(entry, &foe)) {
+++		entry->hash = 0xffff;
+++		goto out;
+++	}
+++
+++	entry->data.ib1 = foe.ib1;
+++
+++out:
+++	spin_unlock_bh(&ppe_lock);
+++}
+++
+++static void
+++__mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_foe_entry *entry,
+++		       u16 hash)
+++{
+++	struct mtk_foe_entry *hwe;
+++	u16 timestamp;
+++
+++	timestamp = mtk_eth_timestamp(ppe->eth);
++ 	timestamp &= MTK_FOE_IB1_BIND_TIMESTAMP;
++ 	entry->ib1 &= ~MTK_FOE_IB1_BIND_TIMESTAMP;
++ 	entry->ib1 |= FIELD_PREP(MTK_FOE_IB1_BIND_TIMESTAMP, timestamp);
++ 
++-	hash = mtk_ppe_hash_entry(entry);
++ 	hwe = &ppe->foe_table[hash];
++-	if (!mtk_foe_entry_usable(hwe)) {
++-		hwe++;
++-		hash++;
++-
++-		if (!mtk_foe_entry_usable(hwe))
++-			return -ENOSPC;
++-	}
++-
++ 	memcpy(&hwe->data, &entry->data, sizeof(hwe->data));
++ 	wmb();
++ 	hwe->ib1 = entry->ib1;
++@@ -380,13 +421,77 @@ int mtk_foe_entry_commit(struct mtk_ppe
++ 	dma_wmb();
++ 
++ 	mtk_ppe_cache_clear(ppe);
+++}
++ 
++-	return hash;
+++void mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
+++{
+++	spin_lock_bh(&ppe_lock);
+++	hlist_del_init(&entry->list);
+++	if (entry->hash != 0xffff) {
+++		ppe->foe_table[entry->hash].ib1 &= ~MTK_FOE_IB1_STATE;
+++		ppe->foe_table[entry->hash].ib1 |= FIELD_PREP(MTK_FOE_IB1_STATE,
+++							      MTK_FOE_STATE_BIND);
+++		dma_wmb();
+++	}
+++	entry->hash = 0xffff;
+++	spin_unlock_bh(&ppe_lock);
+++}
+++
+++int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
+++{
+++	u32 hash = mtk_ppe_hash_entry(&entry->data);
+++
+++	entry->hash = 0xffff;
+++	spin_lock_bh(&ppe_lock);
+++	hlist_add_head(&entry->list, &ppe->foe_flow[hash / 2]);
+++	spin_unlock_bh(&ppe_lock);
+++
+++	return 0;
+++}
+++
+++void __mtk_ppe_check_skb(struct mtk_ppe *ppe, struct sk_buff *skb, u16 hash)
+++{
+++	struct hlist_head *head = &ppe->foe_flow[hash / 2];
+++	struct mtk_flow_entry *entry;
+++	struct mtk_foe_entry *hwe = &ppe->foe_table[hash];
+++	bool found = false;
+++
+++	if (hlist_empty(head))
+++		return;
+++
+++	spin_lock_bh(&ppe_lock);
+++	hlist_for_each_entry(entry, head, list) {
+++		if (found || !mtk_flow_entry_match(entry, hwe)) {
+++			if (entry->hash != 0xffff)
+++				entry->hash = 0xffff;
+++			continue;
+++		}
+++
+++		entry->hash = hash;
+++		__mtk_foe_entry_commit(ppe, &entry->data, hash);
+++		found = true;
+++	}
+++	spin_unlock_bh(&ppe_lock);
+++}
+++
+++int mtk_foe_entry_idle_time(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
+++{
+++	u16 now = mtk_eth_timestamp(ppe->eth) & MTK_FOE_IB1_BIND_TIMESTAMP;
+++	u16 timestamp;
+++
+++	mtk_flow_entry_update(ppe, entry);
+++	timestamp = entry->data.ib1 & MTK_FOE_IB1_BIND_TIMESTAMP;
+++
+++	if (timestamp > now)
+++		return MTK_FOE_IB1_BIND_TIMESTAMP + 1 - timestamp + now;
+++	else
+++		return now - timestamp;
++ }
++ 
++-struct mtk_ppe *mtk_ppe_init(struct device *dev, void __iomem *base,
+++struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base,
++ 		 int version)
++ {
+++	struct device *dev = eth->dev;
++ 	struct mtk_foe_entry *foe;
++ 	struct mtk_ppe *ppe;
++ 
++@@ -398,6 +503,7 @@ struct mtk_ppe *mtk_ppe_init(struct devi
++ 	 * not coherent.
++ 	 */
++ 	ppe->base = base;
+++	ppe->eth = eth;
++ 	ppe->dev = dev;
++ 	ppe->version = version;
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
++@@ -235,7 +235,17 @@ enum {
++ 	MTK_PPE_CPU_REASON_INVALID			= 0x1f,
++ };
++ 
+++struct mtk_flow_entry {
+++	struct rhash_head node;
+++	struct hlist_node list;
+++	unsigned long cookie;
+++	struct mtk_foe_entry data;
+++	u16 hash;
+++	s8 wed_index;
+++};
+++
++ struct mtk_ppe {
+++	struct mtk_eth *eth;
++ 	struct device *dev;
++ 	void __iomem *base;
++ 	int version;
++@@ -243,18 +253,33 @@ struct mtk_ppe {
++ 	struct mtk_foe_entry *foe_table;
++ 	dma_addr_t foe_phys;
++ 
+++	u16 foe_check_time[MTK_PPE_ENTRIES];
+++	struct hlist_head foe_flow[MTK_PPE_ENTRIES / 2];
+++
++ 	void *acct_table;
++ };
++ 
++-struct mtk_ppe *mtk_ppe_init(struct device *dev, void __iomem *base, int version);
+++struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base, int version);
++ int mtk_ppe_start(struct mtk_ppe *ppe);
++ int mtk_ppe_stop(struct mtk_ppe *ppe);
++ 
+++void __mtk_ppe_check_skb(struct mtk_ppe *ppe, struct sk_buff *skb, u16 hash);
+++
++ static inline void
++-mtk_foe_entry_clear(struct mtk_ppe *ppe, u16 hash)
+++mtk_ppe_check_skb(struct mtk_ppe *ppe, struct sk_buff *skb, u16 hash)
++ {
++-	ppe->foe_table[hash].ib1 = 0;
++-	dma_wmb();
+++	u16 now, diff;
+++
+++	if (!ppe)
+++		return;
+++
+++	now = (u16)jiffies;
+++	diff = now - ppe->foe_check_time[hash];
+++	if (diff < HZ / 10)
+++		return;
+++
+++	ppe->foe_check_time[hash] = now;
+++	__mtk_ppe_check_skb(ppe, skb, hash);
++ }
++ 
++ static inline int
++@@ -282,8 +307,9 @@ int mtk_foe_entry_set_vlan(struct mtk_fo
++ int mtk_foe_entry_set_pppoe(struct mtk_foe_entry *entry, int sid);
++ int mtk_foe_entry_set_wdma(struct mtk_foe_entry *entry, int wdma_idx, int txq,
++ 			   int bss, int wcid);
++-int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_foe_entry *entry,
++-			 u16 timestamp);
+++int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
+++void mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
+++int mtk_foe_entry_idle_time(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
++ int mtk_ppe_debugfs_init(struct mtk_ppe *ppe);
++ 
++ #endif
++--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++@@ -42,13 +42,6 @@ struct mtk_flow_data {
++ 	} pppoe;
++ };
++ 
++-struct mtk_flow_entry {
++-	struct rhash_head node;
++-	unsigned long cookie;
++-	u16 hash;
++-	s8 wed_index;
++-};
++-
++ static const struct rhashtable_params mtk_flow_ht_params = {
++ 	.head_offset = offsetof(struct mtk_flow_entry, node),
++ 	.key_offset = offsetof(struct mtk_flow_entry, cookie),
++@@ -56,12 +49,6 @@ static const struct rhashtable_params mt
++ 	.automatic_shrinking = true,
++ };
++ 
++-static u32
++-mtk_eth_timestamp(struct mtk_eth *eth)
++-{
++-	return mtk_r32(eth, 0x0010) & MTK_FOE_IB1_BIND_TIMESTAMP;
++-}
++-
++ static int
++ mtk_flow_set_ipv4_addr(struct mtk_foe_entry *foe, struct mtk_flow_data *data,
++ 		       bool egress)
++@@ -237,10 +224,8 @@ mtk_flow_offload_replace(struct mtk_eth
++ 	int offload_type = 0;
++ 	int wed_index = -1;
++ 	u16 addr_type = 0;
++-	u32 timestamp;
++ 	u8 l4proto = 0;
++ 	int err = 0;
++-	int hash;
++ 	int i;
++ 
++ 	if (rhashtable_lookup(&eth->flow_table, &f->cookie, mtk_flow_ht_params))
++@@ -410,23 +395,21 @@ mtk_flow_offload_replace(struct mtk_eth
++ 		return -ENOMEM;
++ 
++ 	entry->cookie = f->cookie;
++-	timestamp = mtk_eth_timestamp(eth);
++-	hash = mtk_foe_entry_commit(eth->ppe, &foe, timestamp);
++-	if (hash < 0) {
++-		err = hash;
+++	memcpy(&entry->data, &foe, sizeof(entry->data));
+++	entry->wed_index = wed_index;
+++
+++	if (mtk_foe_entry_commit(eth->ppe, entry) < 0)
++ 		goto free;
++-	}
++ 
++-	entry->hash = hash;
++-	entry->wed_index = wed_index;
++ 	err = rhashtable_insert_fast(&eth->flow_table, &entry->node,
++ 				     mtk_flow_ht_params);
++ 	if (err < 0)
++-		goto clear_flow;
+++		goto clear;
++ 
++ 	return 0;
++-clear_flow:
++-	mtk_foe_entry_clear(eth->ppe, hash);
+++
+++clear:
+++	mtk_foe_entry_clear(eth->ppe, entry);
++ free:
++ 	kfree(entry);
++ 	if (wed_index >= 0)
++@@ -444,7 +427,7 @@ mtk_flow_offload_destroy(struct mtk_eth
++ 	if (!entry)
++ 		return -ENOENT;
++ 
++-	mtk_foe_entry_clear(eth->ppe, entry->hash);
+++	mtk_foe_entry_clear(eth->ppe, entry);
++ 	rhashtable_remove_fast(&eth->flow_table, &entry->node,
++ 			       mtk_flow_ht_params);
++ 	if (entry->wed_index >= 0)
++@@ -458,7 +441,6 @@ static int
++ mtk_flow_offload_stats(struct mtk_eth *eth, struct flow_cls_offload *f)
++ {
++ 	struct mtk_flow_entry *entry;
++-	int timestamp;
++ 	u32 idle;
++ 
++ 	entry = rhashtable_lookup(&eth->flow_table, &f->cookie,
++@@ -466,11 +448,7 @@ mtk_flow_offload_stats(struct mtk_eth *e
++ 	if (!entry)
++ 		return -ENOENT;
++ 
++-	timestamp = mtk_foe_entry_timestamp(eth->ppe, entry->hash);
++-	if (timestamp < 0)
++-		return -ETIMEDOUT;
++-
++-	idle = mtk_eth_timestamp(eth) - timestamp;
+++	idle = mtk_foe_entry_idle_time(eth->ppe, entry);
++ 	f->stats.lastused = jiffies - idle * HZ;
++ 
++ 	return 0;
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-09-net-ethernet-mtk_eth_soc-remove-bridge-flow-offload-.patch b/target/linux/generic/backport-5.15/702-v5.19-09-net-ethernet-mtk_eth_soc-remove-bridge-flow-offload-.patch
+new file mode 100644
+index 0000000000..2ff0b341f9
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-09-net-ethernet-mtk_eth_soc-remove-bridge-flow-offload-.patch
+@@ -0,0 +1,44 @@
++From: Felix Fietkau <nbd@nbd.name>
++Date: Mon, 21 Feb 2022 15:55:19 +0100
++Subject: [PATCH] net: ethernet: mtk_eth_soc: remove bridge flow offload
++ type entry support
++
++According to MediaTek, this feature is not supported in current hardware
++
++Signed-off-by: Felix Fietkau <nbd@nbd.name>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
++@@ -84,13 +84,6 @@ static u32 mtk_ppe_hash_entry(struct mtk
++ 	u32 hash;
++ 
++ 	switch (FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, e->ib1)) {
++-		case MTK_PPE_PKT_TYPE_BRIDGE:
++-			hv1 = e->bridge.src_mac_lo;
++-			hv1 ^= ((e->bridge.src_mac_hi & 0xffff) << 16);
++-			hv2 = e->bridge.src_mac_hi >> 16;
++-			hv2 ^= e->bridge.dest_mac_lo;
++-			hv3 = e->bridge.dest_mac_hi;
++-			break;
++ 		case MTK_PPE_PKT_TYPE_IPV4_ROUTE:
++ 		case MTK_PPE_PKT_TYPE_IPV4_HNAPT:
++ 			hv1 = e->ipv4.orig.ports;
++@@ -572,7 +565,6 @@ int mtk_ppe_start(struct mtk_ppe *ppe)
++ 	      MTK_PPE_FLOW_CFG_IP4_NAT |
++ 	      MTK_PPE_FLOW_CFG_IP4_NAPT |
++ 	      MTK_PPE_FLOW_CFG_IP4_DSLITE |
++-	      MTK_PPE_FLOW_CFG_L2_BRIDGE |
++ 	      MTK_PPE_FLOW_CFG_IP4_NAT_FRAG;
++ 	ppe_w32(ppe, MTK_PPE_FLOW_CFG, val);
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c
++@@ -32,7 +32,6 @@ static const char *mtk_foe_pkt_type_str(
++ 	static const char * const type_str[] = {
++ 		[MTK_PPE_PKT_TYPE_IPV4_HNAPT] = "IPv4 5T",
++ 		[MTK_PPE_PKT_TYPE_IPV4_ROUTE] = "IPv4 3T",
++-		[MTK_PPE_PKT_TYPE_BRIDGE] = "L2",
++ 		[MTK_PPE_PKT_TYPE_IPV4_DSLITE] = "DS-LITE",
++ 		[MTK_PPE_PKT_TYPE_IPV6_ROUTE_3T] = "IPv6 3T",
++ 		[MTK_PPE_PKT_TYPE_IPV6_ROUTE_5T] = "IPv6 5T",
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-10-net-ethernet-mtk_eth_soc-support-creating-mac-addres.patch b/target/linux/generic/backport-5.15/702-v5.19-10-net-ethernet-mtk_eth_soc-support-creating-mac-addres.patch
+new file mode 100644
+index 0000000000..209c65e66a
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-10-net-ethernet-mtk_eth_soc-support-creating-mac-addres.patch
+@@ -0,0 +1,553 @@
++From: Felix Fietkau <nbd@nbd.name>
++Date: Wed, 23 Feb 2022 10:56:34 +0100
++Subject: [PATCH] net: ethernet: mtk_eth_soc: support creating mac
++ address based offload entries
++
++This will be used to implement a limited form of bridge offloading.
++Since the hardware does not support flow table entries with just source
++and destination MAC address, the driver has to emulate it.
++
++The hardware automatically creates entries entries for incoming flows, even
++when they are bridged instead of routed, and reports when packets for these
++flows have reached the minimum PPS rate for offloading.
++
++After this happens, we look up the L2 flow offload entry based on the MAC
++header and fill in the output routing information in the flow table.
++The dynamically created per-flow entries are automatically removed when
++either the hardware flowtable entry expires, is replaced, or if the offload
++rule they belong to is removed
++
++Signed-off-by: Felix Fietkau <nbd@nbd.name>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
++@@ -6,12 +6,22 @@
++ #include <linux/iopoll.h>
++ #include <linux/etherdevice.h>
++ #include <linux/platform_device.h>
+++#include <linux/if_ether.h>
+++#include <linux/if_vlan.h>
+++#include <net/dsa.h>
++ #include "mtk_eth_soc.h"
++ #include "mtk_ppe.h"
++ #include "mtk_ppe_regs.h"
++ 
++ static DEFINE_SPINLOCK(ppe_lock);
++ 
+++static const struct rhashtable_params mtk_flow_l2_ht_params = {
+++	.head_offset = offsetof(struct mtk_flow_entry, l2_node),
+++	.key_offset = offsetof(struct mtk_flow_entry, data.bridge),
+++	.key_len = offsetof(struct mtk_foe_bridge, key_end),
+++	.automatic_shrinking = true,
+++};
+++
++ static void ppe_w32(struct mtk_ppe *ppe, u32 reg, u32 val)
++ {
++ 	writel(val, ppe->base + reg);
++@@ -123,6 +133,9 @@ mtk_foe_entry_l2(struct mtk_foe_entry *e
++ {
++ 	int type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->ib1);
++ 
+++	if (type == MTK_PPE_PKT_TYPE_BRIDGE)
+++		return &entry->bridge.l2;
+++
++ 	if (type >= MTK_PPE_PKT_TYPE_IPV4_DSLITE)
++ 		return &entry->ipv6.l2;
++ 
++@@ -134,6 +147,9 @@ mtk_foe_entry_ib2(struct mtk_foe_entry *
++ {
++ 	int type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->ib1);
++ 
+++	if (type == MTK_PPE_PKT_TYPE_BRIDGE)
+++		return &entry->bridge.ib2;
+++
++ 	if (type >= MTK_PPE_PKT_TYPE_IPV4_DSLITE)
++ 		return &entry->ipv6.ib2;
++ 
++@@ -168,7 +184,12 @@ int mtk_foe_entry_prepare(struct mtk_foe
++ 	if (type == MTK_PPE_PKT_TYPE_IPV6_ROUTE_3T)
++ 		entry->ipv6.ports = ports_pad;
++ 
++-	if (type >= MTK_PPE_PKT_TYPE_IPV4_DSLITE) {
+++	if (type == MTK_PPE_PKT_TYPE_BRIDGE) {
+++		ether_addr_copy(entry->bridge.src_mac, src_mac);
+++		ether_addr_copy(entry->bridge.dest_mac, dest_mac);
+++		entry->bridge.ib2 = val;
+++		l2 = &entry->bridge.l2;
+++	} else if (type >= MTK_PPE_PKT_TYPE_IPV4_DSLITE) {
++ 		entry->ipv6.ib2 = val;
++ 		l2 = &entry->ipv6.l2;
++ 	} else {
++@@ -372,12 +393,96 @@ mtk_flow_entry_match(struct mtk_flow_ent
++ }
++ 
++ static void
+++__mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
+++{
+++	struct hlist_head *head;
+++	struct hlist_node *tmp;
+++
+++	if (entry->type == MTK_FLOW_TYPE_L2) {
+++		rhashtable_remove_fast(&ppe->l2_flows, &entry->l2_node,
+++				       mtk_flow_l2_ht_params);
+++
+++		head = &entry->l2_flows;
+++		hlist_for_each_entry_safe(entry, tmp, head, l2_data.list)
+++			__mtk_foe_entry_clear(ppe, entry);
+++		return;
+++	}
+++
+++	hlist_del_init(&entry->list);
+++	if (entry->hash != 0xffff) {
+++		ppe->foe_table[entry->hash].ib1 &= ~MTK_FOE_IB1_STATE;
+++		ppe->foe_table[entry->hash].ib1 |= FIELD_PREP(MTK_FOE_IB1_STATE,
+++							      MTK_FOE_STATE_BIND);
+++		dma_wmb();
+++	}
+++	entry->hash = 0xffff;
+++
+++	if (entry->type != MTK_FLOW_TYPE_L2_SUBFLOW)
+++		return;
+++
+++	hlist_del_init(&entry->l2_data.list);
+++	kfree(entry);
+++}
+++
+++static int __mtk_foe_entry_idle_time(struct mtk_ppe *ppe, u32 ib1)
+++{
+++	u16 timestamp;
+++	u16 now;
+++
+++	now = mtk_eth_timestamp(ppe->eth) & MTK_FOE_IB1_BIND_TIMESTAMP;
+++	timestamp = ib1 & MTK_FOE_IB1_BIND_TIMESTAMP;
+++
+++	if (timestamp > now)
+++		return MTK_FOE_IB1_BIND_TIMESTAMP + 1 - timestamp + now;
+++	else
+++		return now - timestamp;
+++}
+++
+++static void
+++mtk_flow_entry_update_l2(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
+++{
+++	struct mtk_flow_entry *cur;
+++	struct mtk_foe_entry *hwe;
+++	struct hlist_node *tmp;
+++	int idle;
+++
+++	idle = __mtk_foe_entry_idle_time(ppe, entry->data.ib1);
+++	hlist_for_each_entry_safe(cur, tmp, &entry->l2_flows, l2_data.list) {
+++		int cur_idle;
+++		u32 ib1;
+++
+++		hwe = &ppe->foe_table[cur->hash];
+++		ib1 = READ_ONCE(hwe->ib1);
+++
+++		if (FIELD_GET(MTK_FOE_IB1_STATE, ib1) != MTK_FOE_STATE_BIND) {
+++			cur->hash = 0xffff;
+++			__mtk_foe_entry_clear(ppe, cur);
+++			continue;
+++		}
+++
+++		cur_idle = __mtk_foe_entry_idle_time(ppe, ib1);
+++		if (cur_idle >= idle)
+++			continue;
+++
+++		idle = cur_idle;
+++		entry->data.ib1 &= ~MTK_FOE_IB1_BIND_TIMESTAMP;
+++		entry->data.ib1 |= hwe->ib1 & MTK_FOE_IB1_BIND_TIMESTAMP;
+++	}
+++}
+++
+++static void
++ mtk_flow_entry_update(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
++ {
++ 	struct mtk_foe_entry *hwe;
++ 	struct mtk_foe_entry foe;
++ 
++ 	spin_lock_bh(&ppe_lock);
+++
+++	if (entry->type == MTK_FLOW_TYPE_L2) {
+++		mtk_flow_entry_update_l2(ppe, entry);
+++		goto out;
+++	}
+++
++ 	if (entry->hash == 0xffff)
++ 		goto out;
++ 
++@@ -419,21 +524,28 @@ __mtk_foe_entry_commit(struct mtk_ppe *p
++ void mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
++ {
++ 	spin_lock_bh(&ppe_lock);
++-	hlist_del_init(&entry->list);
++-	if (entry->hash != 0xffff) {
++-		ppe->foe_table[entry->hash].ib1 &= ~MTK_FOE_IB1_STATE;
++-		ppe->foe_table[entry->hash].ib1 |= FIELD_PREP(MTK_FOE_IB1_STATE,
++-							      MTK_FOE_STATE_BIND);
++-		dma_wmb();
++-	}
++-	entry->hash = 0xffff;
+++	__mtk_foe_entry_clear(ppe, entry);
++ 	spin_unlock_bh(&ppe_lock);
++ }
++ 
+++static int
+++mtk_foe_entry_commit_l2(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
+++{
+++	entry->type = MTK_FLOW_TYPE_L2;
+++
+++	return rhashtable_insert_fast(&ppe->l2_flows, &entry->l2_node,
+++				      mtk_flow_l2_ht_params);
+++}
+++
++ int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
++ {
++-	u32 hash = mtk_ppe_hash_entry(&entry->data);
+++	int type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->data.ib1);
+++	u32 hash;
+++
+++	if (type == MTK_PPE_PKT_TYPE_BRIDGE)
+++		return mtk_foe_entry_commit_l2(ppe, entry);
++ 
+++	hash = mtk_ppe_hash_entry(&entry->data);
++ 	entry->hash = 0xffff;
++ 	spin_lock_bh(&ppe_lock);
++ 	hlist_add_head(&entry->list, &ppe->foe_flow[hash / 2]);
++@@ -442,18 +554,72 @@ int mtk_foe_entry_commit(struct mtk_ppe
++ 	return 0;
++ }
++ 
+++static void
+++mtk_foe_entry_commit_subflow(struct mtk_ppe *ppe, struct mtk_flow_entry *entry,
+++			     u16 hash)
+++{
+++	struct mtk_flow_entry *flow_info;
+++	struct mtk_foe_entry foe, *hwe;
+++	struct mtk_foe_mac_info *l2;
+++	u32 ib1_mask = MTK_FOE_IB1_PACKET_TYPE | MTK_FOE_IB1_UDP;
+++	int type;
+++
+++	flow_info = kzalloc(offsetof(struct mtk_flow_entry, l2_data.end),
+++			    GFP_ATOMIC);
+++	if (!flow_info)
+++		return;
+++
+++	flow_info->l2_data.base_flow = entry;
+++	flow_info->type = MTK_FLOW_TYPE_L2_SUBFLOW;
+++	flow_info->hash = hash;
+++	hlist_add_head(&flow_info->list, &ppe->foe_flow[hash / 2]);
+++	hlist_add_head(&flow_info->l2_data.list, &entry->l2_flows);
+++
+++	hwe = &ppe->foe_table[hash];
+++	memcpy(&foe, hwe, sizeof(foe));
+++	foe.ib1 &= ib1_mask;
+++	foe.ib1 |= entry->data.ib1 & ~ib1_mask;
+++
+++	l2 = mtk_foe_entry_l2(&foe);
+++	memcpy(l2, &entry->data.bridge.l2, sizeof(*l2));
+++
+++	type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, foe.ib1);
+++	if (type == MTK_PPE_PKT_TYPE_IPV4_HNAPT)
+++		memcpy(&foe.ipv4.new, &foe.ipv4.orig, sizeof(foe.ipv4.new));
+++	else if (type >= MTK_PPE_PKT_TYPE_IPV6_ROUTE_3T && l2->etype == ETH_P_IP)
+++		l2->etype = ETH_P_IPV6;
+++
+++	*mtk_foe_entry_ib2(&foe) = entry->data.bridge.ib2;
+++
+++	__mtk_foe_entry_commit(ppe, &foe, hash);
+++}
+++
++ void __mtk_ppe_check_skb(struct mtk_ppe *ppe, struct sk_buff *skb, u16 hash)
++ {
++ 	struct hlist_head *head = &ppe->foe_flow[hash / 2];
++-	struct mtk_flow_entry *entry;
++ 	struct mtk_foe_entry *hwe = &ppe->foe_table[hash];
+++	struct mtk_flow_entry *entry;
+++	struct mtk_foe_bridge key = {};
+++	struct ethhdr *eh;
++ 	bool found = false;
++-
++-	if (hlist_empty(head))
++-		return;
+++	u8 *tag;
++ 
++ 	spin_lock_bh(&ppe_lock);
+++
+++	if (FIELD_GET(MTK_FOE_IB1_STATE, hwe->ib1) == MTK_FOE_STATE_BIND)
+++		goto out;
+++
++ 	hlist_for_each_entry(entry, head, list) {
+++		if (entry->type == MTK_FLOW_TYPE_L2_SUBFLOW) {
+++			if (unlikely(FIELD_GET(MTK_FOE_IB1_STATE, hwe->ib1) ==
+++				     MTK_FOE_STATE_BIND))
+++				continue;
+++
+++			entry->hash = 0xffff;
+++			__mtk_foe_entry_clear(ppe, entry);
+++			continue;
+++		}
+++
++ 		if (found || !mtk_flow_entry_match(entry, hwe)) {
++ 			if (entry->hash != 0xffff)
++ 				entry->hash = 0xffff;
++@@ -464,21 +630,50 @@ void __mtk_ppe_check_skb(struct mtk_ppe
++ 		__mtk_foe_entry_commit(ppe, &entry->data, hash);
++ 		found = true;
++ 	}
+++
+++	if (found)
+++		goto out;
+++
+++	eh = eth_hdr(skb);
+++	ether_addr_copy(key.dest_mac, eh->h_dest);
+++	ether_addr_copy(key.src_mac, eh->h_source);
+++	tag = skb->data - 2;
+++	key.vlan = 0;
+++	switch (skb->protocol) {
+++#if IS_ENABLED(CONFIG_NET_DSA)
+++	case htons(ETH_P_XDSA):
+++		if (!netdev_uses_dsa(skb->dev) ||
+++		    skb->dev->dsa_ptr->tag_ops->proto != DSA_TAG_PROTO_MTK)
+++			goto out;
+++
+++		tag += 4;
+++		if (get_unaligned_be16(tag) != ETH_P_8021Q)
+++			break;
+++
+++		fallthrough;
+++#endif
+++	case htons(ETH_P_8021Q):
+++		key.vlan = get_unaligned_be16(tag + 2) & VLAN_VID_MASK;
+++		break;
+++	default:
+++		break;
+++	}
+++
+++	entry = rhashtable_lookup_fast(&ppe->l2_flows, &key, mtk_flow_l2_ht_params);
+++	if (!entry)
+++		goto out;
+++
+++	mtk_foe_entry_commit_subflow(ppe, entry, hash);
+++
+++out:
++ 	spin_unlock_bh(&ppe_lock);
++ }
++ 
++ int mtk_foe_entry_idle_time(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
++ {
++-	u16 now = mtk_eth_timestamp(ppe->eth) & MTK_FOE_IB1_BIND_TIMESTAMP;
++-	u16 timestamp;
++-
++ 	mtk_flow_entry_update(ppe, entry);
++-	timestamp = entry->data.ib1 & MTK_FOE_IB1_BIND_TIMESTAMP;
++ 
++-	if (timestamp > now)
++-		return MTK_FOE_IB1_BIND_TIMESTAMP + 1 - timestamp + now;
++-	else
++-		return now - timestamp;
+++	return __mtk_foe_entry_idle_time(ppe, entry->data.ib1);
++ }
++ 
++ struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base,
++@@ -492,6 +687,8 @@ struct mtk_ppe *mtk_ppe_init(struct mtk_
++ 	if (!ppe)
++ 		return NULL;
++ 
+++	rhashtable_init(&ppe->l2_flows, &mtk_flow_l2_ht_params);
+++
++ 	/* need to allocate a separate device, since it PPE DMA access is
++ 	 * not coherent.
++ 	 */
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
++@@ -6,6 +6,7 @@
++ 
++ #include <linux/kernel.h>
++ #include <linux/bitfield.h>
+++#include <linux/rhashtable.h>
++ 
++ #define MTK_ETH_PPE_BASE		0xc00
++ 
++@@ -84,19 +85,16 @@ struct mtk_foe_mac_info {
++ 	u16 src_mac_lo;
++ };
++ 
+++/* software-only entry type */
++ struct mtk_foe_bridge {
++-	u32 dest_mac_hi;
++-
++-	u16 src_mac_lo;
++-	u16 dest_mac_lo;
+++	u8 dest_mac[ETH_ALEN];
+++	u8 src_mac[ETH_ALEN];
+++	u16 vlan;
++ 
++-	u32 src_mac_hi;
+++	struct {} key_end;
++ 
++ 	u32 ib2;
++ 
++-	u32 _rsv[5];
++-
++-	u32 udf_tsid;
++ 	struct mtk_foe_mac_info l2;
++ };
++ 
++@@ -235,13 +233,33 @@ enum {
++ 	MTK_PPE_CPU_REASON_INVALID			= 0x1f,
++ };
++ 
+++enum {
+++	MTK_FLOW_TYPE_L4,
+++	MTK_FLOW_TYPE_L2,
+++	MTK_FLOW_TYPE_L2_SUBFLOW,
+++};
+++
++ struct mtk_flow_entry {
+++	union {
+++		struct hlist_node list;
+++		struct {
+++			struct rhash_head l2_node;
+++			struct hlist_head l2_flows;
+++		};
+++	};
+++	u8 type;
+++	s8 wed_index;
+++	u16 hash;
+++	union {
+++		struct mtk_foe_entry data;
+++		struct {
+++			struct mtk_flow_entry *base_flow;
+++			struct hlist_node list;
+++			struct {} end;
+++		} l2_data;
+++	};
++ 	struct rhash_head node;
++-	struct hlist_node list;
++ 	unsigned long cookie;
++-	struct mtk_foe_entry data;
++-	u16 hash;
++-	s8 wed_index;
++ };
++ 
++ struct mtk_ppe {
++@@ -256,6 +274,8 @@ struct mtk_ppe {
++ 	u16 foe_check_time[MTK_PPE_ENTRIES];
++ 	struct hlist_head foe_flow[MTK_PPE_ENTRIES / 2];
++ 
+++	struct rhashtable l2_flows;
+++
++ 	void *acct_table;
++ };
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++@@ -31,6 +31,8 @@ struct mtk_flow_data {
++ 	__be16 src_port;
++ 	__be16 dst_port;
++ 
+++	u16 vlan_in;
+++
++ 	struct {
++ 		u16 id;
++ 		__be16 proto;
++@@ -257,9 +259,45 @@ mtk_flow_offload_replace(struct mtk_eth
++ 		return -EOPNOTSUPP;
++ 	}
++ 
+++	switch (addr_type) {
+++	case 0:
+++		offload_type = MTK_PPE_PKT_TYPE_BRIDGE;
+++		if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
+++			struct flow_match_eth_addrs match;
+++
+++			flow_rule_match_eth_addrs(rule, &match);
+++			memcpy(data.eth.h_dest, match.key->dst, ETH_ALEN);
+++			memcpy(data.eth.h_source, match.key->src, ETH_ALEN);
+++		} else {
+++			return -EOPNOTSUPP;
+++		}
+++
+++		if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_VLAN)) {
+++			struct flow_match_vlan match;
+++
+++			flow_rule_match_vlan(rule, &match);
+++
+++			if (match.key->vlan_tpid != cpu_to_be16(ETH_P_8021Q))
+++				return -EOPNOTSUPP;
+++
+++			data.vlan_in = match.key->vlan_id;
+++		}
+++		break;
+++	case FLOW_DISSECTOR_KEY_IPV4_ADDRS:
+++		offload_type = MTK_PPE_PKT_TYPE_IPV4_HNAPT;
+++		break;
+++	case FLOW_DISSECTOR_KEY_IPV6_ADDRS:
+++		offload_type = MTK_PPE_PKT_TYPE_IPV6_ROUTE_5T;
+++		break;
+++	default:
+++		return -EOPNOTSUPP;
+++	}
+++
++ 	flow_action_for_each(i, act, &rule->action) {
++ 		switch (act->id) {
++ 		case FLOW_ACTION_MANGLE:
+++			if (offload_type == MTK_PPE_PKT_TYPE_BRIDGE)
+++				return -EOPNOTSUPP;
++ 			if (act->mangle.htype == FLOW_ACT_MANGLE_HDR_TYPE_ETH)
++ 				mtk_flow_offload_mangle_eth(act, &data.eth);
++ 			break;
++@@ -291,17 +329,6 @@ mtk_flow_offload_replace(struct mtk_eth
++ 		}
++ 	}
++ 
++-	switch (addr_type) {
++-	case FLOW_DISSECTOR_KEY_IPV4_ADDRS:
++-		offload_type = MTK_PPE_PKT_TYPE_IPV4_HNAPT;
++-		break;
++-	case FLOW_DISSECTOR_KEY_IPV6_ADDRS:
++-		offload_type = MTK_PPE_PKT_TYPE_IPV6_ROUTE_5T;
++-		break;
++-	default:
++-		return -EOPNOTSUPP;
++-	}
++-
++ 	if (!is_valid_ether_addr(data.eth.h_source) ||
++ 	    !is_valid_ether_addr(data.eth.h_dest))
++ 		return -EINVAL;
++@@ -315,10 +342,13 @@ mtk_flow_offload_replace(struct mtk_eth
++ 	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) {
++ 		struct flow_match_ports ports;
++ 
+++		if (offload_type == MTK_PPE_PKT_TYPE_BRIDGE)
+++			return -EOPNOTSUPP;
+++
++ 		flow_rule_match_ports(rule, &ports);
++ 		data.src_port = ports.key->src;
++ 		data.dst_port = ports.key->dst;
++-	} else {
+++	} else if (offload_type != MTK_PPE_PKT_TYPE_BRIDGE) {
++ 		return -EOPNOTSUPP;
++ 	}
++ 
++@@ -348,6 +378,9 @@ mtk_flow_offload_replace(struct mtk_eth
++ 		if (act->id != FLOW_ACTION_MANGLE)
++ 			continue;
++ 
+++		if (offload_type == MTK_PPE_PKT_TYPE_BRIDGE)
+++			return -EOPNOTSUPP;
+++
++ 		switch (act->mangle.htype) {
++ 		case FLOW_ACT_MANGLE_HDR_TYPE_TCP:
++ 		case FLOW_ACT_MANGLE_HDR_TYPE_UDP:
++@@ -373,6 +406,9 @@ mtk_flow_offload_replace(struct mtk_eth
++ 			return err;
++ 	}
++ 
+++	if (offload_type == MTK_PPE_PKT_TYPE_BRIDGE)
+++		foe.bridge.vlan = data.vlan_in;
+++
++ 	if (data.vlan.num == 1) {
++ 		if (data.vlan.proto != htons(ETH_P_8021Q))
++ 			return -EOPNOTSUPP;
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-11-net-ethernet-mtk_eth_soc-wed-fix-sparse-endian-warni.patch b/target/linux/generic/backport-5.15/702-v5.19-11-net-ethernet-mtk_eth_soc-wed-fix-sparse-endian-warni.patch
+new file mode 100644
+index 0000000000..8f3dfe8239
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-11-net-ethernet-mtk_eth_soc-wed-fix-sparse-endian-warni.patch
+@@ -0,0 +1,56 @@
++From: Felix Fietkau <nbd@nbd.name>
++Date: Fri, 8 Apr 2022 10:59:45 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc/wed: fix sparse endian warnings
++
++Descriptor fields are little-endian
++
++Fixes: 804775dfc288 ("net: ethernet: mtk_eth_soc: add support for Wireless Ethernet Dispatch (WED)")
++Reported-by: kernel test robot <lkp@intel.com>
++Signed-off-by: Felix Fietkau <nbd@nbd.name>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_wed.c
+++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
++@@ -144,16 +144,17 @@ mtk_wed_buffer_alloc(struct mtk_wed_devi
++ 
++ 		for (s = 0; s < MTK_WED_BUF_PER_PAGE; s++) {
++ 			u32 txd_size;
+++			u32 ctrl;
++ 
++ 			txd_size = dev->wlan.init_buf(buf, buf_phys, token++);
++ 
++-			desc->buf0 = buf_phys;
++-			desc->buf1 = buf_phys + txd_size;
++-			desc->ctrl = FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN0,
++-						txd_size) |
++-				     FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN1,
++-						MTK_WED_BUF_SIZE - txd_size) |
++-				     MTK_WDMA_DESC_CTRL_LAST_SEG1;
+++			desc->buf0 = cpu_to_le32(buf_phys);
+++			desc->buf1 = cpu_to_le32(buf_phys + txd_size);
+++			ctrl = FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN0, txd_size) |
+++			       FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN1,
+++					  MTK_WED_BUF_SIZE - txd_size) |
+++			       MTK_WDMA_DESC_CTRL_LAST_SEG1;
+++			desc->ctrl = cpu_to_le32(ctrl);
++ 			desc->info = 0;
++ 			desc++;
++ 
++@@ -184,12 +185,14 @@ mtk_wed_free_buffer(struct mtk_wed_devic
++ 
++ 	for (i = 0, page_idx = 0; i < dev->buf_ring.size; i += MTK_WED_BUF_PER_PAGE) {
++ 		void *page = page_list[page_idx++];
+++		dma_addr_t buf_addr;
++ 
++ 		if (!page)
++ 			break;
++ 
++-		dma_unmap_page(dev->hw->dev, desc[i].buf0,
++-			       PAGE_SIZE, DMA_BIDIRECTIONAL);
+++		buf_addr = le32_to_cpu(desc[i].buf0);
+++		dma_unmap_page(dev->hw->dev, buf_addr, PAGE_SIZE,
+++			       DMA_BIDIRECTIONAL);
++ 		__free_page(page);
++ 	}
++ 
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-12-net-ethernet-mtk_eth_soc-fix-return-value-check-in-m.patch b/target/linux/generic/backport-5.15/702-v5.19-12-net-ethernet-mtk_eth_soc-fix-return-value-check-in-m.patch
+new file mode 100644
+index 0000000000..4ec8fe74bc
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-12-net-ethernet-mtk_eth_soc-fix-return-value-check-in-m.patch
+@@ -0,0 +1,25 @@
++From: Yang Yingliang <yangyingliang@huawei.com>
++Date: Fri, 8 Apr 2022 11:22:46 +0800
++Subject: [PATCH] net: ethernet: mtk_eth_soc: fix return value check in
++ mtk_wed_add_hw()
++
++If syscon_regmap_lookup_by_phandle() fails, it never return NULL pointer,
++change the check to IS_ERR().
++
++Fixes: 804775dfc288 ("net: ethernet: mtk_eth_soc: add support for Wireless Ethernet Dispatch (WED)")
++Reported-by: Hulk Robot <hulkci@huawei.com>
++Signed-off-by: Yang Yingliang <yangyingliang@huawei.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_wed.c
+++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
++@@ -816,7 +816,7 @@ void mtk_wed_add_hw(struct device_node *
++ 		return;
++ 
++ 	regs = syscon_regmap_lookup_by_phandle(np, NULL);
++-	if (!regs)
+++	if (IS_ERR(regs))
++ 		return;
++ 
++ 	rcu_assign_pointer(mtk_soc_wed_ops, &wed_ops);
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-13-net-ethernet-mtk_eth_soc-use-standard-property-for-c.patch b/target/linux/generic/backport-5.15/702-v5.19-13-net-ethernet-mtk_eth_soc-use-standard-property-for-c.patch
+new file mode 100644
+index 0000000000..d87022fce4
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-13-net-ethernet-mtk_eth_soc-use-standard-property-for-c.patch
+@@ -0,0 +1,35 @@
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Mon, 11 Apr 2022 12:13:25 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: use standard property for
++ cci-control-port
++
++Rely on standard cci-control-port property to identify CCI port
++reference.
++Update mt7622 dts binding.
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/arch/arm64/boot/dts/mediatek/mt7622.dtsi
+++++ b/arch/arm64/boot/dts/mediatek/mt7622.dtsi
++@@ -962,7 +962,7 @@
++ 		power-domains = <&scpsys MT7622_POWER_DOMAIN_ETHSYS>;
++ 		mediatek,ethsys = <&ethsys>;
++ 		mediatek,sgmiisys = <&sgmiisys>;
++-		mediatek,cci-control = <&cci_control2>;
+++		cci-control-port = <&cci_control2>;
++ 		mediatek,wed = <&wed0>, <&wed1>;
++ 		mediatek,pcie-mirror = <&pcie_mirror>;
++ 		mediatek,hifsys = <&hifsys>;
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -3185,7 +3185,7 @@ static int mtk_probe(struct platform_dev
++ 		struct regmap *cci;
++ 
++ 		cci = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
++-						      "mediatek,cci-control");
+++						      "cci-control-port");
++ 		/* enable CPU/bus coherency */
++ 		if (!IS_ERR(cci))
++ 			regmap_write(cci, 0, 3);
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-14-net-ethernet-mtk_eth_soc-use-after-free-in-__mtk_ppe.patch b/target/linux/generic/backport-5.15/702-v5.19-14-net-ethernet-mtk_eth_soc-use-after-free-in-__mtk_ppe.patch
+new file mode 100644
+index 0000000000..656b3a159e
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-14-net-ethernet-mtk_eth_soc-use-after-free-in-__mtk_ppe.patch
+@@ -0,0 +1,33 @@
++From: Dan Carpenter <dan.carpenter@oracle.com>
++Date: Tue, 12 Apr 2022 12:24:19 +0300
++Subject: [PATCH] net: ethernet: mtk_eth_soc: use after free in
++ __mtk_ppe_check_skb()
++
++The __mtk_foe_entry_clear() function frees "entry" so we have to use
++the _safe() version of hlist_for_each_entry() to prevent a use after
++free.
++
++Fixes: 33fc42de3327 ("net: ethernet: mtk_eth_soc: support creating mac address based offload entries")
++Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
++@@ -600,6 +600,7 @@ void __mtk_ppe_check_skb(struct mtk_ppe
++ 	struct mtk_foe_entry *hwe = &ppe->foe_table[hash];
++ 	struct mtk_flow_entry *entry;
++ 	struct mtk_foe_bridge key = {};
+++	struct hlist_node *n;
++ 	struct ethhdr *eh;
++ 	bool found = false;
++ 	u8 *tag;
++@@ -609,7 +610,7 @@ void __mtk_ppe_check_skb(struct mtk_ppe
++ 	if (FIELD_GET(MTK_FOE_IB1_STATE, hwe->ib1) == MTK_FOE_STATE_BIND)
++ 		goto out;
++ 
++-	hlist_for_each_entry(entry, head, list) {
+++	hlist_for_each_entry_safe(entry, n, head, list) {
++ 		if (entry->type == MTK_FLOW_TYPE_L2_SUBFLOW) {
++ 			if (unlikely(FIELD_GET(MTK_FOE_IB1_STATE, hwe->ib1) ==
++ 				     MTK_FOE_STATE_BIND))
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-15-net-ethernet-mtk_eth_soc-add-check-for-allocation-fa.patch b/target/linux/generic/backport-5.15/702-v5.19-15-net-ethernet-mtk_eth_soc-add-check-for-allocation-fa.patch
+new file mode 100644
+index 0000000000..714163c86b
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-15-net-ethernet-mtk_eth_soc-add-check-for-allocation-fa.patch
+@@ -0,0 +1,22 @@
++From: Dan Carpenter <dan.carpenter@oracle.com>
++Date: Thu, 21 Apr 2022 18:49:02 +0300
++Subject: [PATCH] net: ethernet: mtk_eth_soc: add check for allocation failure
++
++Check if the kzalloc() failed.
++
++Fixes: 804775dfc288 ("net: ethernet: mtk_eth_soc: add support for Wireless Ethernet Dispatch (WED)")
++Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_wed.c
+++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
++@@ -827,6 +827,8 @@ void mtk_wed_add_hw(struct device_node *
++ 		goto unlock;
++ 
++ 	hw = kzalloc(sizeof(*hw), GFP_KERNEL);
+++	if (!hw)
+++		goto unlock;
++ 	hw->node = np;
++ 	hw->regs = regs;
++ 	hw->eth = eth;
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-16-eth-mtk_eth_soc-silence-the-GCC-12-array-bounds-warn.patch b/target/linux/generic/backport-5.15/702-v5.19-16-eth-mtk_eth_soc-silence-the-GCC-12-array-bounds-warn.patch
+new file mode 100644
+index 0000000000..aa98745ac6
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-16-eth-mtk_eth_soc-silence-the-GCC-12-array-bounds-warn.patch
+@@ -0,0 +1,26 @@
++From: Jakub Kicinski <kuba@kernel.org>
++Date: Fri, 20 May 2022 12:56:03 -0700
++Subject: [PATCH] eth: mtk_eth_soc: silence the GCC 12 array-bounds warning
++
++GCC 12 gets upset because in mtk_foe_entry_commit_subflow()
++this driver allocates a partial structure. The writes are
++within bounds.
++
++Silence these warnings for now, our build bot runs GCC 12
++so we won't allow any new instances.
++
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/Makefile
+++++ b/drivers/net/ethernet/mediatek/Makefile
++@@ -11,3 +11,8 @@ mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) +
++ endif
++ obj-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed_ops.o
++ obj-$(CONFIG_NET_MEDIATEK_STAR_EMAC) += mtk_star_emac.o
+++
+++# FIXME: temporarily silence -Warray-bounds on non W=1+ builds
+++ifndef KBUILD_EXTRA_WARN
+++CFLAGS_mtk_ppe.o += -Wno-array-bounds
+++endif
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-17-net-ethernet-mtk_eth_soc-rely-on-GFP_KERNEL-for-dma_.patch b/target/linux/generic/backport-5.15/702-v5.19-17-net-ethernet-mtk_eth_soc-rely-on-GFP_KERNEL-for-dma_.patch
+new file mode 100644
+index 0000000000..97677670cc
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-17-net-ethernet-mtk_eth_soc-rely-on-GFP_KERNEL-for-dma_.patch
+@@ -0,0 +1,52 @@
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 20 May 2022 20:11:26 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: rely on GFP_KERNEL for
++ dma_alloc_coherent whenever possible
++
++Rely on GFP_KERNEL for dma descriptors mappings in mtk_tx_alloc(),
++mtk_rx_alloc() and mtk_init_fq_dma() since they are run in non-irq
++context.
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -845,7 +845,7 @@ static int mtk_init_fq_dma(struct mtk_et
++ 	eth->scratch_ring = dma_alloc_coherent(eth->dma_dev,
++ 					       cnt * sizeof(struct mtk_tx_dma),
++ 					       &eth->phy_scratch_ring,
++-					       GFP_ATOMIC);
+++					       GFP_KERNEL);
++ 	if (unlikely(!eth->scratch_ring))
++ 		return -ENOMEM;
++ 
++@@ -1623,7 +1623,7 @@ static int mtk_tx_alloc(struct mtk_eth *
++ 		goto no_tx_mem;
++ 
++ 	ring->dma = dma_alloc_coherent(eth->dma_dev, MTK_DMA_SIZE * sz,
++-				       &ring->phys, GFP_ATOMIC);
+++				       &ring->phys, GFP_KERNEL);
++ 	if (!ring->dma)
++ 		goto no_tx_mem;
++ 
++@@ -1641,8 +1641,7 @@ static int mtk_tx_alloc(struct mtk_eth *
++ 	 */
++ 	if (!MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
++ 		ring->dma_pdma = dma_alloc_coherent(eth->dma_dev, MTK_DMA_SIZE * sz,
++-						    &ring->phys_pdma,
++-						    GFP_ATOMIC);
+++						    &ring->phys_pdma, GFP_KERNEL);
++ 		if (!ring->dma_pdma)
++ 			goto no_tx_mem;
++ 
++@@ -1757,7 +1756,7 @@ static int mtk_rx_alloc(struct mtk_eth *
++ 
++ 	ring->dma = dma_alloc_coherent(eth->dma_dev,
++ 				       rx_dma_size * sizeof(*ring->dma),
++-				       &ring->phys, GFP_ATOMIC);
+++				       &ring->phys, GFP_KERNEL);
++ 	if (!ring->dma)
++ 		return -ENOMEM;
++ 
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-18-net-ethernet-mtk_eth_soc-move-tx-dma-desc-configurat.patch b/target/linux/generic/backport-5.15/702-v5.19-18-net-ethernet-mtk_eth_soc-move-tx-dma-desc-configurat.patch
+new file mode 100644
+index 0000000000..95f122f730
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-18-net-ethernet-mtk_eth_soc-move-tx-dma-desc-configurat.patch
+@@ -0,0 +1,206 @@
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 20 May 2022 20:11:27 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: move tx dma desc configuration in
++ mtk_tx_set_dma_desc
++
++Move tx dma descriptor configuration in mtk_tx_set_dma_desc routine.
++This is a preliminary patch to introduce mt7986 ethernet support since
++it relies on a different tx dma descriptor layout.
++
++Tested-by: Sam Shih <sam.shih@mediatek.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -971,18 +971,51 @@ static void setup_tx_buf(struct mtk_eth
++ 	}
++ }
++ 
+++static void mtk_tx_set_dma_desc(struct net_device *dev, struct mtk_tx_dma *desc,
+++				struct mtk_tx_dma_desc_info *info)
+++{
+++	struct mtk_mac *mac = netdev_priv(dev);
+++	u32 data;
+++
+++	WRITE_ONCE(desc->txd1, info->addr);
+++
+++	data = TX_DMA_SWC | TX_DMA_PLEN0(info->size);
+++	if (info->last)
+++		data |= TX_DMA_LS0;
+++	WRITE_ONCE(desc->txd3, data);
+++
+++	data = (mac->id + 1) << TX_DMA_FPORT_SHIFT; /* forward port */
+++	if (info->first) {
+++		if (info->gso)
+++			data |= TX_DMA_TSO;
+++		/* tx checksum offload */
+++		if (info->csum)
+++			data |= TX_DMA_CHKSUM;
+++		/* vlan header offload */
+++		if (info->vlan)
+++			data |= TX_DMA_INS_VLAN | info->vlan_tci;
+++	}
+++	WRITE_ONCE(desc->txd4, data);
+++}
+++
++ static int mtk_tx_map(struct sk_buff *skb, struct net_device *dev,
++ 		      int tx_num, struct mtk_tx_ring *ring, bool gso)
++ {
+++	struct mtk_tx_dma_desc_info txd_info = {
+++		.size = skb_headlen(skb),
+++		.gso = gso,
+++		.csum = skb->ip_summed == CHECKSUM_PARTIAL,
+++		.vlan = skb_vlan_tag_present(skb),
+++		.vlan_tci = skb_vlan_tag_get(skb),
+++		.first = true,
+++		.last = !skb_is_nonlinear(skb),
+++	};
++ 	struct mtk_mac *mac = netdev_priv(dev);
++ 	struct mtk_eth *eth = mac->hw;
++ 	struct mtk_tx_dma *itxd, *txd;
++ 	struct mtk_tx_dma *itxd_pdma, *txd_pdma;
++ 	struct mtk_tx_buf *itx_buf, *tx_buf;
++-	dma_addr_t mapped_addr;
++-	unsigned int nr_frags;
++ 	int i, n_desc = 1;
++-	u32 txd4 = 0, fport;
++ 	int k = 0;
++ 
++ 	itxd = ring->next_free;
++@@ -990,49 +1023,32 @@ static int mtk_tx_map(struct sk_buff *sk
++ 	if (itxd == ring->last_free)
++ 		return -ENOMEM;
++ 
++-	/* set the forward port */
++-	fport = (mac->id + 1) << TX_DMA_FPORT_SHIFT;
++-	txd4 |= fport;
++-
++ 	itx_buf = mtk_desc_to_tx_buf(ring, itxd);
++ 	memset(itx_buf, 0, sizeof(*itx_buf));
++ 
++-	if (gso)
++-		txd4 |= TX_DMA_TSO;
++-
++-	/* TX Checksum offload */
++-	if (skb->ip_summed == CHECKSUM_PARTIAL)
++-		txd4 |= TX_DMA_CHKSUM;
++-
++-	/* VLAN header offload */
++-	if (skb_vlan_tag_present(skb))
++-		txd4 |= TX_DMA_INS_VLAN | skb_vlan_tag_get(skb);
++-
++-	mapped_addr = dma_map_single(eth->dma_dev, skb->data,
++-				     skb_headlen(skb), DMA_TO_DEVICE);
++-	if (unlikely(dma_mapping_error(eth->dma_dev, mapped_addr)))
+++	txd_info.addr = dma_map_single(eth->dma_dev, skb->data, txd_info.size,
+++				       DMA_TO_DEVICE);
+++	if (unlikely(dma_mapping_error(eth->dma_dev, txd_info.addr)))
++ 		return -ENOMEM;
++ 
++-	WRITE_ONCE(itxd->txd1, mapped_addr);
+++	mtk_tx_set_dma_desc(dev, itxd, &txd_info);
+++
++ 	itx_buf->flags |= MTK_TX_FLAGS_SINGLE0;
++ 	itx_buf->flags |= (!mac->id) ? MTK_TX_FLAGS_FPORT0 :
++ 			  MTK_TX_FLAGS_FPORT1;
++-	setup_tx_buf(eth, itx_buf, itxd_pdma, mapped_addr, skb_headlen(skb),
+++	setup_tx_buf(eth, itx_buf, itxd_pdma, txd_info.addr, txd_info.size,
++ 		     k++);
++ 
++ 	/* TX SG offload */
++ 	txd = itxd;
++ 	txd_pdma = qdma_to_pdma(ring, txd);
++-	nr_frags = skb_shinfo(skb)->nr_frags;
++ 
++-	for (i = 0; i < nr_frags; i++) {
+++	for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
++ 		skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
++ 		unsigned int offset = 0;
++ 		int frag_size = skb_frag_size(frag);
++ 
++ 		while (frag_size) {
++-			bool last_frag = false;
++-			unsigned int frag_map_size;
++ 			bool new_desc = true;
++ 
++ 			if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA) ||
++@@ -1047,23 +1063,17 @@ static int mtk_tx_map(struct sk_buff *sk
++ 				new_desc = false;
++ 			}
++ 
++-
++-			frag_map_size = min(frag_size, MTK_TX_DMA_BUF_LEN);
++-			mapped_addr = skb_frag_dma_map(eth->dma_dev, frag, offset,
++-						       frag_map_size,
++-						       DMA_TO_DEVICE);
++-			if (unlikely(dma_mapping_error(eth->dma_dev, mapped_addr)))
+++			memset(&txd_info, 0, sizeof(struct mtk_tx_dma_desc_info));
+++			txd_info.size = min(frag_size, MTK_TX_DMA_BUF_LEN);
+++			txd_info.last = i == skb_shinfo(skb)->nr_frags - 1 &&
+++					!(frag_size - txd_info.size);
+++			txd_info.addr = skb_frag_dma_map(eth->dma_dev, frag,
+++							 offset, txd_info.size,
+++							 DMA_TO_DEVICE);
+++			if (unlikely(dma_mapping_error(eth->dma_dev, txd_info.addr)))
++ 				goto err_dma;
++ 
++-			if (i == nr_frags - 1 &&
++-			    (frag_size - frag_map_size) == 0)
++-				last_frag = true;
++-
++-			WRITE_ONCE(txd->txd1, mapped_addr);
++-			WRITE_ONCE(txd->txd3, (TX_DMA_SWC |
++-					       TX_DMA_PLEN0(frag_map_size) |
++-					       last_frag * TX_DMA_LS0));
++-			WRITE_ONCE(txd->txd4, fport);
+++			mtk_tx_set_dma_desc(dev, txd, &txd_info);
++ 
++ 			tx_buf = mtk_desc_to_tx_buf(ring, txd);
++ 			if (new_desc)
++@@ -1073,20 +1083,17 @@ static int mtk_tx_map(struct sk_buff *sk
++ 			tx_buf->flags |= (!mac->id) ? MTK_TX_FLAGS_FPORT0 :
++ 					 MTK_TX_FLAGS_FPORT1;
++ 
++-			setup_tx_buf(eth, tx_buf, txd_pdma, mapped_addr,
++-				     frag_map_size, k++);
+++			setup_tx_buf(eth, tx_buf, txd_pdma, txd_info.addr,
+++				     txd_info.size, k++);
++ 
++-			frag_size -= frag_map_size;
++-			offset += frag_map_size;
+++			frag_size -= txd_info.size;
+++			offset += txd_info.size;
++ 		}
++ 	}
++ 
++ 	/* store skb to cleanup */
++ 	itx_buf->skb = skb;
++ 
++-	WRITE_ONCE(itxd->txd4, txd4);
++-	WRITE_ONCE(itxd->txd3, (TX_DMA_SWC | TX_DMA_PLEN0(skb_headlen(skb)) |
++-				(!nr_frags * TX_DMA_LS0)));
++ 	if (!MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
++ 		if (k & 0x1)
++ 			txd_pdma->txd2 |= TX_DMA_LS0;
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -842,6 +842,17 @@ enum mkt_eth_capabilities {
++ 		      MTK_MUX_U3_GMAC2_TO_QPHY | \
++ 		      MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA)
++ 
+++struct mtk_tx_dma_desc_info {
+++	dma_addr_t	addr;
+++	u32		size;
+++	u16		vlan_tci;
+++	u8		gso:1;
+++	u8		csum:1;
+++	u8		vlan:1;
+++	u8		first:1;
+++	u8		last:1;
+++};
+++
++ /* struct mtk_eth_data -	This is the structure holding all differences
++  *				among various plaforms
++  * @ana_rgc3:                   The offset for register ANA_RGC3 related to
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-19-net-ethernet-mtk_eth_soc-add-txd_size-to-mtk_soc_dat.patch b/target/linux/generic/backport-5.15/702-v5.19-19-net-ethernet-mtk_eth_soc-add-txd_size-to-mtk_soc_dat.patch
+new file mode 100644
+index 0000000000..7aeeda0ba0
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-19-net-ethernet-mtk_eth_soc-add-txd_size-to-mtk_soc_dat.patch
+@@ -0,0 +1,167 @@
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 20 May 2022 20:11:28 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: add txd_size to mtk_soc_data
++
++In order to remove mtk_tx_dma size dependency, introduce txd_size in
++mtk_soc_data data structure. Rely on txd_size in mtk_init_fq_dma() and
++mtk_dma_free() routines.
++This is a preliminary patch to add mt7986 ethernet support.
++
++Tested-by: Sam Shih <sam.shih@mediatek.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -837,20 +837,20 @@ static void *mtk_max_lro_buf_alloc(gfp_t
++ /* the qdma core needs scratch memory to be setup */
++ static int mtk_init_fq_dma(struct mtk_eth *eth)
++ {
+++	const struct mtk_soc_data *soc = eth->soc;
++ 	dma_addr_t phy_ring_tail;
++ 	int cnt = MTK_DMA_SIZE;
++ 	dma_addr_t dma_addr;
++ 	int i;
++ 
++ 	eth->scratch_ring = dma_alloc_coherent(eth->dma_dev,
++-					       cnt * sizeof(struct mtk_tx_dma),
+++					       cnt * soc->txrx.txd_size,
++ 					       &eth->phy_scratch_ring,
++ 					       GFP_KERNEL);
++ 	if (unlikely(!eth->scratch_ring))
++ 		return -ENOMEM;
++ 
++-	eth->scratch_head = kcalloc(cnt, MTK_QDMA_PAGE_SIZE,
++-				    GFP_KERNEL);
+++	eth->scratch_head = kcalloc(cnt, MTK_QDMA_PAGE_SIZE, GFP_KERNEL);
++ 	if (unlikely(!eth->scratch_head))
++ 		return -ENOMEM;
++ 
++@@ -860,16 +860,19 @@ static int mtk_init_fq_dma(struct mtk_et
++ 	if (unlikely(dma_mapping_error(eth->dma_dev, dma_addr)))
++ 		return -ENOMEM;
++ 
++-	phy_ring_tail = eth->phy_scratch_ring +
++-			(sizeof(struct mtk_tx_dma) * (cnt - 1));
+++	phy_ring_tail = eth->phy_scratch_ring + soc->txrx.txd_size * (cnt - 1);
++ 
++ 	for (i = 0; i < cnt; i++) {
++-		eth->scratch_ring[i].txd1 =
++-					(dma_addr + (i * MTK_QDMA_PAGE_SIZE));
+++		struct mtk_tx_dma *txd;
+++
+++		txd = (void *)eth->scratch_ring + i * soc->txrx.txd_size;
+++		txd->txd1 = dma_addr + i * MTK_QDMA_PAGE_SIZE;
++ 		if (i < cnt - 1)
++-			eth->scratch_ring[i].txd2 = (eth->phy_scratch_ring +
++-				((i + 1) * sizeof(struct mtk_tx_dma)));
++-		eth->scratch_ring[i].txd3 = TX_DMA_SDL(MTK_QDMA_PAGE_SIZE);
+++			txd->txd2 = eth->phy_scratch_ring +
+++				    (i + 1) * soc->txrx.txd_size;
+++
+++		txd->txd3 = TX_DMA_PLEN0(MTK_QDMA_PAGE_SIZE);
+++		txd->txd4 = 0;
++ 	}
++ 
++ 	mtk_w32(eth, eth->phy_scratch_ring, MTK_QDMA_FQ_HEAD);
++@@ -2169,6 +2172,7 @@ static int mtk_dma_init(struct mtk_eth *
++ 
++ static void mtk_dma_free(struct mtk_eth *eth)
++ {
+++	const struct mtk_soc_data *soc = eth->soc;
++ 	int i;
++ 
++ 	for (i = 0; i < MTK_MAC_COUNT; i++)
++@@ -2176,9 +2180,8 @@ static void mtk_dma_free(struct mtk_eth
++ 			netdev_reset_queue(eth->netdev[i]);
++ 	if (eth->scratch_ring) {
++ 		dma_free_coherent(eth->dma_dev,
++-				  MTK_DMA_SIZE * sizeof(struct mtk_tx_dma),
++-				  eth->scratch_ring,
++-				  eth->phy_scratch_ring);
+++				  MTK_DMA_SIZE * soc->txrx.txd_size,
+++				  eth->scratch_ring, eth->phy_scratch_ring);
++ 		eth->scratch_ring = NULL;
++ 		eth->phy_scratch_ring = 0;
++ 	}
++@@ -3388,6 +3391,9 @@ static const struct mtk_soc_data mt2701_
++ 	.hw_features = MTK_HW_FEATURES,
++ 	.required_clks = MT7623_CLKS_BITMAP,
++ 	.required_pctl = true,
+++	.txrx = {
+++		.txd_size = sizeof(struct mtk_tx_dma),
+++	},
++ };
++ 
++ static const struct mtk_soc_data mt7621_data = {
++@@ -3396,6 +3402,9 @@ static const struct mtk_soc_data mt7621_
++ 	.required_clks = MT7621_CLKS_BITMAP,
++ 	.required_pctl = false,
++ 	.offload_version = 2,
+++	.txrx = {
+++		.txd_size = sizeof(struct mtk_tx_dma),
+++	},
++ };
++ 
++ static const struct mtk_soc_data mt7622_data = {
++@@ -3405,6 +3414,9 @@ static const struct mtk_soc_data mt7622_
++ 	.required_clks = MT7622_CLKS_BITMAP,
++ 	.required_pctl = false,
++ 	.offload_version = 2,
+++	.txrx = {
+++		.txd_size = sizeof(struct mtk_tx_dma),
+++	},
++ };
++ 
++ static const struct mtk_soc_data mt7623_data = {
++@@ -3413,6 +3425,9 @@ static const struct mtk_soc_data mt7623_
++ 	.required_clks = MT7623_CLKS_BITMAP,
++ 	.required_pctl = true,
++ 	.offload_version = 2,
+++	.txrx = {
+++		.txd_size = sizeof(struct mtk_tx_dma),
+++	},
++ };
++ 
++ static const struct mtk_soc_data mt7629_data = {
++@@ -3421,6 +3436,9 @@ static const struct mtk_soc_data mt7629_
++ 	.hw_features = MTK_HW_FEATURES,
++ 	.required_clks = MT7629_CLKS_BITMAP,
++ 	.required_pctl = false,
+++	.txrx = {
+++		.txd_size = sizeof(struct mtk_tx_dma),
+++	},
++ };
++ 
++ static const struct mtk_soc_data rt5350_data = {
++@@ -3428,6 +3446,9 @@ static const struct mtk_soc_data rt5350_
++ 	.hw_features = MTK_HW_FEATURES_MT7628,
++ 	.required_clks = MT7628_CLKS_BITMAP,
++ 	.required_pctl = false,
+++	.txrx = {
+++		.txd_size = sizeof(struct mtk_tx_dma),
+++	},
++ };
++ 
++ const struct of_device_id of_mtk_match[] = {
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -863,6 +863,7 @@ struct mtk_tx_dma_desc_info {
++  *				the target SoC
++  * @required_pctl		A bool value to show whether the SoC requires
++  *				the extra setup for those pins used by GMAC.
+++ * @txd_size			Tx DMA descriptor size.
++  */
++ struct mtk_soc_data {
++ 	u32             ana_rgc3;
++@@ -871,6 +872,9 @@ struct mtk_soc_data {
++ 	bool		required_pctl;
++ 	u8		offload_version;
++ 	netdev_features_t hw_features;
+++	struct {
+++		u32	txd_size;
+++	} txrx;
++ };
++ 
++ /* currently no SoC has more than 2 macs */
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-20-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-mtk_tx_.patch b/target/linux/generic/backport-5.15/702-v5.19-20-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-mtk_tx_.patch
+new file mode 100644
+index 0000000000..01dbca0753
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-20-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-mtk_tx_.patch
+@@ -0,0 +1,78 @@
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 20 May 2022 20:11:29 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: rely on txd_size in
++ mtk_tx_alloc/mtk_tx_clean
++
++This is a preliminary patch to add mt7986 ethernet support.
++
++Tested-by: Sam Shih <sam.shih@mediatek.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -1624,8 +1624,10 @@ static int mtk_napi_rx(struct napi_struc
++ 
++ static int mtk_tx_alloc(struct mtk_eth *eth)
++ {
+++	const struct mtk_soc_data *soc = eth->soc;
++ 	struct mtk_tx_ring *ring = &eth->tx_ring;
++-	int i, sz = sizeof(*ring->dma);
+++	int i, sz = soc->txrx.txd_size;
+++	struct mtk_tx_dma *txd;
++ 
++ 	ring->buf = kcalloc(MTK_DMA_SIZE, sizeof(*ring->buf),
++ 			       GFP_KERNEL);
++@@ -1641,8 +1643,10 @@ static int mtk_tx_alloc(struct mtk_eth *
++ 		int next = (i + 1) % MTK_DMA_SIZE;
++ 		u32 next_ptr = ring->phys + next * sz;
++ 
++-		ring->dma[i].txd2 = next_ptr;
++-		ring->dma[i].txd3 = TX_DMA_LS0 | TX_DMA_OWNER_CPU;
+++		txd = (void *)ring->dma + i * sz;
+++		txd->txd2 = next_ptr;
+++		txd->txd3 = TX_DMA_LS0 | TX_DMA_OWNER_CPU;
+++		txd->txd4 = 0;
++ 	}
++ 
++ 	/* On MT7688 (PDMA only) this driver uses the ring->dma structs
++@@ -1664,7 +1668,7 @@ static int mtk_tx_alloc(struct mtk_eth *
++ 	ring->dma_size = MTK_DMA_SIZE;
++ 	atomic_set(&ring->free_count, MTK_DMA_SIZE - 2);
++ 	ring->next_free = &ring->dma[0];
++-	ring->last_free = &ring->dma[MTK_DMA_SIZE - 1];
+++	ring->last_free = (void *)txd;
++ 	ring->last_free_ptr = (u32)(ring->phys + ((MTK_DMA_SIZE - 1) * sz));
++ 	ring->thresh = MAX_SKB_FRAGS;
++ 
++@@ -1697,6 +1701,7 @@ no_tx_mem:
++ 
++ static void mtk_tx_clean(struct mtk_eth *eth)
++ {
+++	const struct mtk_soc_data *soc = eth->soc;
++ 	struct mtk_tx_ring *ring = &eth->tx_ring;
++ 	int i;
++ 
++@@ -1709,17 +1714,15 @@ static void mtk_tx_clean(struct mtk_eth
++ 
++ 	if (ring->dma) {
++ 		dma_free_coherent(eth->dma_dev,
++-				  MTK_DMA_SIZE * sizeof(*ring->dma),
++-				  ring->dma,
++-				  ring->phys);
+++				  MTK_DMA_SIZE * soc->txrx.txd_size,
+++				  ring->dma, ring->phys);
++ 		ring->dma = NULL;
++ 	}
++ 
++ 	if (ring->dma_pdma) {
++ 		dma_free_coherent(eth->dma_dev,
++-				  MTK_DMA_SIZE * sizeof(*ring->dma_pdma),
++-				  ring->dma_pdma,
++-				  ring->phys_pdma);
+++				  MTK_DMA_SIZE * soc->txrx.txd_size,
+++				  ring->dma_pdma, ring->phys_pdma);
++ 		ring->dma_pdma = NULL;
++ 	}
++ }
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-21-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-mtk_des.patch b/target/linux/generic/backport-5.15/702-v5.19-21-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-mtk_des.patch
+new file mode 100644
+index 0000000000..1d23a178b4
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-21-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-mtk_des.patch
+@@ -0,0 +1,109 @@
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 20 May 2022 20:11:30 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: rely on txd_size in
++ mtk_desc_to_tx_buf
++
++This is a preliminary patch to add mt7986 ethernet support.
++
++Tested-by: Sam Shih <sam.shih@mediatek.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -890,10 +890,11 @@ static inline void *mtk_qdma_phys_to_vir
++ 	return ret + (desc - ring->phys);
++ }
++ 
++-static inline struct mtk_tx_buf *mtk_desc_to_tx_buf(struct mtk_tx_ring *ring,
++-						    struct mtk_tx_dma *txd)
+++static struct mtk_tx_buf *mtk_desc_to_tx_buf(struct mtk_tx_ring *ring,
+++					     struct mtk_tx_dma *txd,
+++					     u32 txd_size)
++ {
++-	int idx = txd - ring->dma;
+++	int idx = ((void *)txd - (void *)ring->dma) / txd_size;
++ 
++ 	return &ring->buf[idx];
++ }
++@@ -1015,6 +1016,7 @@ static int mtk_tx_map(struct sk_buff *sk
++ 	};
++ 	struct mtk_mac *mac = netdev_priv(dev);
++ 	struct mtk_eth *eth = mac->hw;
+++	const struct mtk_soc_data *soc = eth->soc;
++ 	struct mtk_tx_dma *itxd, *txd;
++ 	struct mtk_tx_dma *itxd_pdma, *txd_pdma;
++ 	struct mtk_tx_buf *itx_buf, *tx_buf;
++@@ -1026,7 +1028,7 @@ static int mtk_tx_map(struct sk_buff *sk
++ 	if (itxd == ring->last_free)
++ 		return -ENOMEM;
++ 
++-	itx_buf = mtk_desc_to_tx_buf(ring, itxd);
+++	itx_buf = mtk_desc_to_tx_buf(ring, itxd, soc->txrx.txd_size);
++ 	memset(itx_buf, 0, sizeof(*itx_buf));
++ 
++ 	txd_info.addr = dma_map_single(eth->dma_dev, skb->data, txd_info.size,
++@@ -1054,7 +1056,7 @@ static int mtk_tx_map(struct sk_buff *sk
++ 		while (frag_size) {
++ 			bool new_desc = true;
++ 
++-			if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA) ||
+++			if (MTK_HAS_CAPS(soc->caps, MTK_QDMA) ||
++ 			    (i & 0x1)) {
++ 				txd = mtk_qdma_phys_to_virt(ring, txd->txd2);
++ 				txd_pdma = qdma_to_pdma(ring, txd);
++@@ -1078,7 +1080,8 @@ static int mtk_tx_map(struct sk_buff *sk
++ 
++ 			mtk_tx_set_dma_desc(dev, txd, &txd_info);
++ 
++-			tx_buf = mtk_desc_to_tx_buf(ring, txd);
+++			tx_buf = mtk_desc_to_tx_buf(ring, txd,
+++						    soc->txrx.txd_size);
++ 			if (new_desc)
++ 				memset(tx_buf, 0, sizeof(*tx_buf));
++ 			tx_buf->skb = (struct sk_buff *)MTK_DMA_DUMMY_DESC;
++@@ -1097,7 +1100,7 @@ static int mtk_tx_map(struct sk_buff *sk
++ 	/* store skb to cleanup */
++ 	itx_buf->skb = skb;
++ 
++-	if (!MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
+++	if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
++ 		if (k & 0x1)
++ 			txd_pdma->txd2 |= TX_DMA_LS0;
++ 		else
++@@ -1115,7 +1118,7 @@ static int mtk_tx_map(struct sk_buff *sk
++ 	 */
++ 	wmb();
++ 
++-	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
+++	if (MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
++ 		if (netif_xmit_stopped(netdev_get_tx_queue(dev, 0)) ||
++ 		    !netdev_xmit_more())
++ 			mtk_w32(eth, txd->txd2, MTK_QTX_CTX_PTR);
++@@ -1129,13 +1132,13 @@ static int mtk_tx_map(struct sk_buff *sk
++ 
++ err_dma:
++ 	do {
++-		tx_buf = mtk_desc_to_tx_buf(ring, itxd);
+++		tx_buf = mtk_desc_to_tx_buf(ring, itxd, soc->txrx.txd_size);
++ 
++ 		/* unmap dma */
++ 		mtk_tx_unmap(eth, tx_buf, false);
++ 
++ 		itxd->txd3 = TX_DMA_LS0 | TX_DMA_OWNER_CPU;
++-		if (!MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA))
+++		if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA))
++ 			itxd_pdma->txd2 = TX_DMA_DESP2_DEF;
++ 
++ 		itxd = mtk_qdma_phys_to_virt(ring, itxd->txd2);
++@@ -1449,7 +1452,8 @@ static int mtk_poll_tx_qdma(struct mtk_e
++ 		if ((desc->txd3 & TX_DMA_OWNER_CPU) == 0)
++ 			break;
++ 
++-		tx_buf = mtk_desc_to_tx_buf(ring, desc);
+++		tx_buf = mtk_desc_to_tx_buf(ring, desc,
+++					    eth->soc->txrx.txd_size);
++ 		if (tx_buf->flags & MTK_TX_FLAGS_FPORT1)
++ 			mac = 1;
++ 
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-22-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-txd_to_.patch b/target/linux/generic/backport-5.15/702-v5.19-22-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-txd_to_.patch
+new file mode 100644
+index 0000000000..2989d190d8
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-22-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-txd_to_.patch
+@@ -0,0 +1,39 @@
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 20 May 2022 20:11:31 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: rely on txd_size in txd_to_idx
++
++This is a preliminary patch to add mt7986 ethernet support.
++
++Tested-by: Sam Shih <sam.shih@mediatek.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -905,9 +905,10 @@ static struct mtk_tx_dma *qdma_to_pdma(s
++ 	return ring->dma_pdma - ring->dma + dma;
++ }
++ 
++-static int txd_to_idx(struct mtk_tx_ring *ring, struct mtk_tx_dma *dma)
+++static int txd_to_idx(struct mtk_tx_ring *ring, struct mtk_tx_dma *dma,
+++		      u32 txd_size)
++ {
++-	return ((void *)dma - (void *)ring->dma) / sizeof(*dma);
+++	return ((void *)dma - (void *)ring->dma) / txd_size;
++ }
++ 
++ static void mtk_tx_unmap(struct mtk_eth *eth, struct mtk_tx_buf *tx_buf,
++@@ -1123,8 +1124,10 @@ static int mtk_tx_map(struct sk_buff *sk
++ 		    !netdev_xmit_more())
++ 			mtk_w32(eth, txd->txd2, MTK_QTX_CTX_PTR);
++ 	} else {
++-		int next_idx = NEXT_DESP_IDX(txd_to_idx(ring, txd),
++-					     ring->dma_size);
+++		int next_idx;
+++
+++		next_idx = NEXT_DESP_IDX(txd_to_idx(ring, txd, soc->txrx.txd_size),
+++					 ring->dma_size);
++ 		mtk_w32(eth, next_idx, MT7628_TX_CTX_IDX0);
++ 	}
++ 
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-23-net-ethernet-mtk_eth_soc-add-rxd_size-to-mtk_soc_dat.patch b/target/linux/generic/backport-5.15/702-v5.19-23-net-ethernet-mtk_eth_soc-add-rxd_size-to-mtk_soc_dat.patch
+new file mode 100644
+index 0000000000..be258da75a
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-23-net-ethernet-mtk_eth_soc-add-rxd_size-to-mtk_soc_dat.patch
+@@ -0,0 +1,102 @@
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 20 May 2022 20:11:32 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: add rxd_size to mtk_soc_data
++
++Similar to tx counterpart, introduce rxd_size in mtk_soc_data data
++structure.
++This is a preliminary patch to add mt7986 ethernet support.
++
++Tested-by: Sam Shih <sam.shih@mediatek.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -1775,7 +1775,7 @@ static int mtk_rx_alloc(struct mtk_eth *
++ 	}
++ 
++ 	ring->dma = dma_alloc_coherent(eth->dma_dev,
++-				       rx_dma_size * sizeof(*ring->dma),
+++				       rx_dma_size * eth->soc->txrx.rxd_size,
++ 				       &ring->phys, GFP_KERNEL);
++ 	if (!ring->dma)
++ 		return -ENOMEM;
++@@ -1833,9 +1833,8 @@ static void mtk_rx_clean(struct mtk_eth
++ 
++ 	if (ring->dma) {
++ 		dma_free_coherent(eth->dma_dev,
++-				  ring->dma_size * sizeof(*ring->dma),
++-				  ring->dma,
++-				  ring->phys);
+++				  ring->dma_size * eth->soc->txrx.rxd_size,
+++				  ring->dma, ring->phys);
++ 		ring->dma = NULL;
++ 	}
++ }
++@@ -3403,6 +3402,7 @@ static const struct mtk_soc_data mt2701_
++ 	.required_pctl = true,
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
+++		.rxd_size = sizeof(struct mtk_rx_dma),
++ 	},
++ };
++ 
++@@ -3414,6 +3414,7 @@ static const struct mtk_soc_data mt7621_
++ 	.offload_version = 2,
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
+++		.rxd_size = sizeof(struct mtk_rx_dma),
++ 	},
++ };
++ 
++@@ -3426,6 +3427,7 @@ static const struct mtk_soc_data mt7622_
++ 	.offload_version = 2,
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
+++		.rxd_size = sizeof(struct mtk_rx_dma),
++ 	},
++ };
++ 
++@@ -3437,6 +3439,7 @@ static const struct mtk_soc_data mt7623_
++ 	.offload_version = 2,
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
+++		.rxd_size = sizeof(struct mtk_rx_dma),
++ 	},
++ };
++ 
++@@ -3448,6 +3451,7 @@ static const struct mtk_soc_data mt7629_
++ 	.required_pctl = false,
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
+++		.rxd_size = sizeof(struct mtk_rx_dma),
++ 	},
++ };
++ 
++@@ -3458,6 +3462,7 @@ static const struct mtk_soc_data rt5350_
++ 	.required_pctl = false,
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
+++		.rxd_size = sizeof(struct mtk_rx_dma),
++ 	},
++ };
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -864,6 +864,7 @@ struct mtk_tx_dma_desc_info {
++  * @required_pctl		A bool value to show whether the SoC requires
++  *				the extra setup for those pins used by GMAC.
++  * @txd_size			Tx DMA descriptor size.
+++ * @rxd_size			Rx DMA descriptor size.
++  */
++ struct mtk_soc_data {
++ 	u32             ana_rgc3;
++@@ -874,6 +875,7 @@ struct mtk_soc_data {
++ 	netdev_features_t hw_features;
++ 	struct {
++ 		u32	txd_size;
+++		u32	rxd_size;
++ 	} txrx;
++ };
++ 
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-24-net-ethernet-mtk_eth_soc-rely-on-txd_size-field-in-m.patch b/target/linux/generic/backport-5.15/702-v5.19-24-net-ethernet-mtk_eth_soc-rely-on-txd_size-field-in-m.patch
+new file mode 100644
+index 0000000000..53af586b6c
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-24-net-ethernet-mtk_eth_soc-rely-on-txd_size-field-in-m.patch
+@@ -0,0 +1,46 @@
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 20 May 2022 20:11:33 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: rely on txd_size field in
++ mtk_poll_tx/mtk_poll_rx
++
++This is a preliminary to ad mt7986 ethernet support.
++
++Tested-by: Sam Shih <sam.shih@mediatek.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -1264,9 +1264,12 @@ static struct mtk_rx_ring *mtk_get_rx_ri
++ 		return &eth->rx_ring[0];
++ 
++ 	for (i = 0; i < MTK_MAX_RX_RING_NUM; i++) {
+++		struct mtk_rx_dma *rxd;
+++
++ 		ring = &eth->rx_ring[i];
++ 		idx = NEXT_DESP_IDX(ring->calc_idx, ring->dma_size);
++-		if (ring->dma[idx].rxd2 & RX_DMA_DONE) {
+++		rxd = (void *)ring->dma + idx * eth->soc->txrx.rxd_size;
+++		if (rxd->rxd2 & RX_DMA_DONE) {
++ 			ring->calc_idx_update = true;
++ 			return ring;
++ 		}
++@@ -1317,7 +1320,7 @@ static int mtk_poll_rx(struct napi_struc
++ 			goto rx_done;
++ 
++ 		idx = NEXT_DESP_IDX(ring->calc_idx, ring->dma_size);
++-		rxd = &ring->dma[idx];
+++		rxd = (void *)ring->dma + idx * eth->soc->txrx.rxd_size;
++ 		data = ring->data[idx];
++ 
++ 		if (!mtk_rx_get_desc(&trxd, rxd))
++@@ -1509,7 +1512,7 @@ static int mtk_poll_tx_pdma(struct mtk_e
++ 
++ 		mtk_tx_unmap(eth, tx_buf, true);
++ 
++-		desc = &ring->dma[cpu];
+++		desc = (void *)ring->dma + cpu * eth->soc->txrx.txd_size;
++ 		ring->last_free = desc;
++ 		atomic_inc(&ring->free_count);
++ 
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-25-net-ethernet-mtk_eth_soc-rely-on-rxd_size-field-in-m.patch b/target/linux/generic/backport-5.15/702-v5.19-25-net-ethernet-mtk_eth_soc-rely-on-rxd_size-field-in-m.patch
+new file mode 100644
+index 0000000000..1f4fa1dfb5
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-25-net-ethernet-mtk_eth_soc-rely-on-rxd_size-field-in-m.patch
+@@ -0,0 +1,68 @@
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 20 May 2022 20:11:34 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: rely on rxd_size field in
++ mtk_rx_alloc/mtk_rx_clean
++
++Remove mtk_rx_dma structure layout dependency in mtk_rx_alloc/mtk_rx_clean.
++Initialize to 0 rxd3 and rxd4 in mtk_rx_alloc.
++This is a preliminary patch to add mt7986 ethernet support.
++
++Tested-by: Sam Shih <sam.shih@mediatek.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -1784,18 +1784,25 @@ static int mtk_rx_alloc(struct mtk_eth *
++ 		return -ENOMEM;
++ 
++ 	for (i = 0; i < rx_dma_size; i++) {
+++		struct mtk_rx_dma *rxd;
+++
++ 		dma_addr_t dma_addr = dma_map_single(eth->dma_dev,
++ 				ring->data[i] + NET_SKB_PAD + eth->ip_align,
++ 				ring->buf_size,
++ 				DMA_FROM_DEVICE);
++ 		if (unlikely(dma_mapping_error(eth->dma_dev, dma_addr)))
++ 			return -ENOMEM;
++-		ring->dma[i].rxd1 = (unsigned int)dma_addr;
+++
+++		rxd = (void *)ring->dma + i * eth->soc->txrx.rxd_size;
+++		rxd->rxd1 = (unsigned int)dma_addr;
++ 
++ 		if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628))
++-			ring->dma[i].rxd2 = RX_DMA_LSO;
+++			rxd->rxd2 = RX_DMA_LSO;
++ 		else
++-			ring->dma[i].rxd2 = RX_DMA_PLEN0(ring->buf_size);
+++			rxd->rxd2 = RX_DMA_PLEN0(ring->buf_size);
+++
+++		rxd->rxd3 = 0;
+++		rxd->rxd4 = 0;
++ 	}
++ 	ring->dma_size = rx_dma_size;
++ 	ring->calc_idx_update = false;
++@@ -1820,14 +1827,17 @@ static void mtk_rx_clean(struct mtk_eth
++ 
++ 	if (ring->data && ring->dma) {
++ 		for (i = 0; i < ring->dma_size; i++) {
+++			struct mtk_rx_dma *rxd;
+++
++ 			if (!ring->data[i])
++ 				continue;
++-			if (!ring->dma[i].rxd1)
+++
+++			rxd = (void *)ring->dma + i * eth->soc->txrx.rxd_size;
+++			if (!rxd->rxd1)
++ 				continue;
++-			dma_unmap_single(eth->dma_dev,
++-					 ring->dma[i].rxd1,
++-					 ring->buf_size,
++-					 DMA_FROM_DEVICE);
+++
+++			dma_unmap_single(eth->dma_dev, rxd->rxd1,
+++					 ring->buf_size, DMA_FROM_DEVICE);
++ 			skb_free_frag(ring->data[i]);
++ 		}
++ 		kfree(ring->data);
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-26-net-ethernet-mtk_eth_soc-introduce-device-register-m.patch b/target/linux/generic/backport-5.15/702-v5.19-26-net-ethernet-mtk_eth_soc-introduce-device-register-m.patch
+new file mode 100644
+index 0000000000..99f482c418
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-26-net-ethernet-mtk_eth_soc-introduce-device-register-m.patch
+@@ -0,0 +1,814 @@
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 20 May 2022 20:11:35 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: introduce device register map
++
++Introduce reg_map structure to add the capability to support different
++register definitions. Move register definitions in mtk_regmap structure.
++This is a preliminary patch to introduce mt7986 ethernet support.
++
++Tested-by: Sam Shih <sam.shih@mediatek.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -34,6 +34,59 @@ MODULE_PARM_DESC(msg_level, "Message lev
++ #define MTK_ETHTOOL_STAT(x) { #x, \
++ 			      offsetof(struct mtk_hw_stats, x) / sizeof(u64) }
++ 
+++static const struct mtk_reg_map mtk_reg_map = {
+++	.tx_irq_mask		= 0x1a1c,
+++	.tx_irq_status		= 0x1a18,
+++	.pdma = {
+++		.rx_ptr		= 0x0900,
+++		.rx_cnt_cfg	= 0x0904,
+++		.pcrx_ptr	= 0x0908,
+++		.glo_cfg	= 0x0a04,
+++		.rst_idx	= 0x0a08,
+++		.delay_irq	= 0x0a0c,
+++		.irq_status	= 0x0a20,
+++		.irq_mask	= 0x0a28,
+++		.int_grp	= 0x0a50,
+++	},
+++	.qdma = {
+++		.qtx_cfg	= 0x1800,
+++		.rx_ptr		= 0x1900,
+++		.rx_cnt_cfg	= 0x1904,
+++		.qcrx_ptr	= 0x1908,
+++		.glo_cfg	= 0x1a04,
+++		.rst_idx	= 0x1a08,
+++		.delay_irq	= 0x1a0c,
+++		.fc_th		= 0x1a10,
+++		.int_grp	= 0x1a20,
+++		.hred		= 0x1a44,
+++		.ctx_ptr	= 0x1b00,
+++		.dtx_ptr	= 0x1b04,
+++		.crx_ptr	= 0x1b10,
+++		.drx_ptr	= 0x1b14,
+++		.fq_head	= 0x1b20,
+++		.fq_tail	= 0x1b24,
+++		.fq_count	= 0x1b28,
+++		.fq_blen	= 0x1b2c,
+++	},
+++	.gdm1_cnt		= 0x2400,
+++};
+++
+++static const struct mtk_reg_map mt7628_reg_map = {
+++	.tx_irq_mask		= 0x0a28,
+++	.tx_irq_status		= 0x0a20,
+++	.pdma = {
+++		.rx_ptr		= 0x0900,
+++		.rx_cnt_cfg	= 0x0904,
+++		.pcrx_ptr	= 0x0908,
+++		.glo_cfg	= 0x0a04,
+++		.rst_idx	= 0x0a08,
+++		.delay_irq	= 0x0a0c,
+++		.irq_status	= 0x0a20,
+++		.irq_mask	= 0x0a28,
+++		.int_grp	= 0x0a50,
+++	},
+++};
+++
++ /* strings used by ethtool */
++ static const struct mtk_ethtool_stats {
++ 	char str[ETH_GSTRING_LEN];
++@@ -618,8 +671,8 @@ static inline void mtk_tx_irq_disable(st
++ 	u32 val;
++ 
++ 	spin_lock_irqsave(&eth->tx_irq_lock, flags);
++-	val = mtk_r32(eth, eth->tx_int_mask_reg);
++-	mtk_w32(eth, val & ~mask, eth->tx_int_mask_reg);
+++	val = mtk_r32(eth, eth->soc->reg_map->tx_irq_mask);
+++	mtk_w32(eth, val & ~mask, eth->soc->reg_map->tx_irq_mask);
++ 	spin_unlock_irqrestore(&eth->tx_irq_lock, flags);
++ }
++ 
++@@ -629,8 +682,8 @@ static inline void mtk_tx_irq_enable(str
++ 	u32 val;
++ 
++ 	spin_lock_irqsave(&eth->tx_irq_lock, flags);
++-	val = mtk_r32(eth, eth->tx_int_mask_reg);
++-	mtk_w32(eth, val | mask, eth->tx_int_mask_reg);
+++	val = mtk_r32(eth, eth->soc->reg_map->tx_irq_mask);
+++	mtk_w32(eth, val | mask, eth->soc->reg_map->tx_irq_mask);
++ 	spin_unlock_irqrestore(&eth->tx_irq_lock, flags);
++ }
++ 
++@@ -640,8 +693,8 @@ static inline void mtk_rx_irq_disable(st
++ 	u32 val;
++ 
++ 	spin_lock_irqsave(&eth->rx_irq_lock, flags);
++-	val = mtk_r32(eth, MTK_PDMA_INT_MASK);
++-	mtk_w32(eth, val & ~mask, MTK_PDMA_INT_MASK);
+++	val = mtk_r32(eth, eth->soc->reg_map->pdma.irq_mask);
+++	mtk_w32(eth, val & ~mask, eth->soc->reg_map->pdma.irq_mask);
++ 	spin_unlock_irqrestore(&eth->rx_irq_lock, flags);
++ }
++ 
++@@ -651,8 +704,8 @@ static inline void mtk_rx_irq_enable(str
++ 	u32 val;
++ 
++ 	spin_lock_irqsave(&eth->rx_irq_lock, flags);
++-	val = mtk_r32(eth, MTK_PDMA_INT_MASK);
++-	mtk_w32(eth, val | mask, MTK_PDMA_INT_MASK);
+++	val = mtk_r32(eth, eth->soc->reg_map->pdma.irq_mask);
+++	mtk_w32(eth, val | mask, eth->soc->reg_map->pdma.irq_mask);
++ 	spin_unlock_irqrestore(&eth->rx_irq_lock, flags);
++ }
++ 
++@@ -703,39 +756,39 @@ void mtk_stats_update_mac(struct mtk_mac
++ 		hw_stats->rx_checksum_errors +=
++ 			mtk_r32(mac->hw, MT7628_SDM_CS_ERR);
++ 	} else {
+++		const struct mtk_reg_map *reg_map = eth->soc->reg_map;
++ 		unsigned int offs = hw_stats->reg_offset;
++ 		u64 stats;
++ 
++-		hw_stats->rx_bytes += mtk_r32(mac->hw,
++-					      MTK_GDM1_RX_GBCNT_L + offs);
++-		stats = mtk_r32(mac->hw, MTK_GDM1_RX_GBCNT_H + offs);
+++		hw_stats->rx_bytes += mtk_r32(mac->hw, reg_map->gdm1_cnt + offs);
+++		stats = mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x4 + offs);
++ 		if (stats)
++ 			hw_stats->rx_bytes += (stats << 32);
++ 		hw_stats->rx_packets +=
++-			mtk_r32(mac->hw, MTK_GDM1_RX_GPCNT + offs);
+++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x8 + offs);
++ 		hw_stats->rx_overflow +=
++-			mtk_r32(mac->hw, MTK_GDM1_RX_OERCNT + offs);
+++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x10 + offs);
++ 		hw_stats->rx_fcs_errors +=
++-			mtk_r32(mac->hw, MTK_GDM1_RX_FERCNT + offs);
+++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x14 + offs);
++ 		hw_stats->rx_short_errors +=
++-			mtk_r32(mac->hw, MTK_GDM1_RX_SERCNT + offs);
+++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x18 + offs);
++ 		hw_stats->rx_long_errors +=
++-			mtk_r32(mac->hw, MTK_GDM1_RX_LENCNT + offs);
+++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x1c + offs);
++ 		hw_stats->rx_checksum_errors +=
++-			mtk_r32(mac->hw, MTK_GDM1_RX_CERCNT + offs);
+++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x20 + offs);
++ 		hw_stats->rx_flow_control_packets +=
++-			mtk_r32(mac->hw, MTK_GDM1_RX_FCCNT + offs);
+++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x24 + offs);
++ 		hw_stats->tx_skip +=
++-			mtk_r32(mac->hw, MTK_GDM1_TX_SKIPCNT + offs);
+++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x28 + offs);
++ 		hw_stats->tx_collisions +=
++-			mtk_r32(mac->hw, MTK_GDM1_TX_COLCNT + offs);
+++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x2c + offs);
++ 		hw_stats->tx_bytes +=
++-			mtk_r32(mac->hw, MTK_GDM1_TX_GBCNT_L + offs);
++-		stats =  mtk_r32(mac->hw, MTK_GDM1_TX_GBCNT_H + offs);
+++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x30 + offs);
+++		stats =  mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x34 + offs);
++ 		if (stats)
++ 			hw_stats->tx_bytes += (stats << 32);
++ 		hw_stats->tx_packets +=
++-			mtk_r32(mac->hw, MTK_GDM1_TX_GPCNT + offs);
+++			mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x38 + offs);
++ 	}
++ 
++ 	u64_stats_update_end(&hw_stats->syncp);
++@@ -875,10 +928,10 @@ static int mtk_init_fq_dma(struct mtk_et
++ 		txd->txd4 = 0;
++ 	}
++ 
++-	mtk_w32(eth, eth->phy_scratch_ring, MTK_QDMA_FQ_HEAD);
++-	mtk_w32(eth, phy_ring_tail, MTK_QDMA_FQ_TAIL);
++-	mtk_w32(eth, (cnt << 16) | cnt, MTK_QDMA_FQ_CNT);
++-	mtk_w32(eth, MTK_QDMA_PAGE_SIZE << 16, MTK_QDMA_FQ_BLEN);
+++	mtk_w32(eth, eth->phy_scratch_ring, soc->reg_map->qdma.fq_head);
+++	mtk_w32(eth, phy_ring_tail, soc->reg_map->qdma.fq_tail);
+++	mtk_w32(eth, (cnt << 16) | cnt, soc->reg_map->qdma.fq_count);
+++	mtk_w32(eth, MTK_QDMA_PAGE_SIZE << 16, soc->reg_map->qdma.fq_blen);
++ 
++ 	return 0;
++ }
++@@ -1122,7 +1175,7 @@ static int mtk_tx_map(struct sk_buff *sk
++ 	if (MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
++ 		if (netif_xmit_stopped(netdev_get_tx_queue(dev, 0)) ||
++ 		    !netdev_xmit_more())
++-			mtk_w32(eth, txd->txd2, MTK_QTX_CTX_PTR);
+++			mtk_w32(eth, txd->txd2, soc->reg_map->qdma.ctx_ptr);
++ 	} else {
++ 		int next_idx;
++ 
++@@ -1439,6 +1492,7 @@ rx_done:
++ static int mtk_poll_tx_qdma(struct mtk_eth *eth, int budget,
++ 			    unsigned int *done, unsigned int *bytes)
++ {
+++	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
++ 	struct mtk_tx_ring *ring = &eth->tx_ring;
++ 	struct mtk_tx_dma *desc;
++ 	struct sk_buff *skb;
++@@ -1446,7 +1500,7 @@ static int mtk_poll_tx_qdma(struct mtk_e
++ 	u32 cpu, dma;
++ 
++ 	cpu = ring->last_free_ptr;
++-	dma = mtk_r32(eth, MTK_QTX_DRX_PTR);
+++	dma = mtk_r32(eth, reg_map->qdma.drx_ptr);
++ 
++ 	desc = mtk_qdma_phys_to_virt(ring, cpu);
++ 
++@@ -1481,7 +1535,7 @@ static int mtk_poll_tx_qdma(struct mtk_e
++ 	}
++ 
++ 	ring->last_free_ptr = cpu;
++-	mtk_w32(eth, cpu, MTK_QTX_CRX_PTR);
+++	mtk_w32(eth, cpu, reg_map->qdma.crx_ptr);
++ 
++ 	return budget;
++ }
++@@ -1574,24 +1628,25 @@ static void mtk_handle_status_irq(struct
++ static int mtk_napi_tx(struct napi_struct *napi, int budget)
++ {
++ 	struct mtk_eth *eth = container_of(napi, struct mtk_eth, tx_napi);
+++	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
++ 	int tx_done = 0;
++ 
++ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA))
++ 		mtk_handle_status_irq(eth);
++-	mtk_w32(eth, MTK_TX_DONE_INT, eth->tx_int_status_reg);
+++	mtk_w32(eth, MTK_TX_DONE_INT, reg_map->tx_irq_status);
++ 	tx_done = mtk_poll_tx(eth, budget);
++ 
++ 	if (unlikely(netif_msg_intr(eth))) {
++ 		dev_info(eth->dev,
++ 			 "done tx %d, intr 0x%08x/0x%x\n", tx_done,
++-			 mtk_r32(eth, eth->tx_int_status_reg),
++-			 mtk_r32(eth, eth->tx_int_mask_reg));
+++			 mtk_r32(eth, reg_map->tx_irq_status),
+++			 mtk_r32(eth, reg_map->tx_irq_mask));
++ 	}
++ 
++ 	if (tx_done == budget)
++ 		return budget;
++ 
++-	if (mtk_r32(eth, eth->tx_int_status_reg) & MTK_TX_DONE_INT)
+++	if (mtk_r32(eth, reg_map->tx_irq_status) & MTK_TX_DONE_INT)
++ 		return budget;
++ 
++ 	if (napi_complete_done(napi, tx_done))
++@@ -1603,6 +1658,7 @@ static int mtk_napi_tx(struct napi_struc
++ static int mtk_napi_rx(struct napi_struct *napi, int budget)
++ {
++ 	struct mtk_eth *eth = container_of(napi, struct mtk_eth, rx_napi);
+++	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
++ 	int rx_done_total = 0;
++ 
++ 	mtk_handle_status_irq(eth);
++@@ -1610,21 +1666,21 @@ static int mtk_napi_rx(struct napi_struc
++ 	do {
++ 		int rx_done;
++ 
++-		mtk_w32(eth, MTK_RX_DONE_INT, MTK_PDMA_INT_STATUS);
+++		mtk_w32(eth, MTK_RX_DONE_INT, reg_map->pdma.irq_status);
++ 		rx_done = mtk_poll_rx(napi, budget - rx_done_total, eth);
++ 		rx_done_total += rx_done;
++ 
++ 		if (unlikely(netif_msg_intr(eth))) {
++ 			dev_info(eth->dev,
++ 				 "done rx %d, intr 0x%08x/0x%x\n", rx_done,
++-				 mtk_r32(eth, MTK_PDMA_INT_STATUS),
++-				 mtk_r32(eth, MTK_PDMA_INT_MASK));
+++				 mtk_r32(eth, reg_map->pdma.irq_status),
+++				 mtk_r32(eth, reg_map->pdma.irq_mask));
++ 		}
++ 
++ 		if (rx_done_total == budget)
++ 			return budget;
++ 
++-	} while (mtk_r32(eth, MTK_PDMA_INT_STATUS) & MTK_RX_DONE_INT);
+++	} while (mtk_r32(eth, reg_map->pdma.irq_status) & MTK_RX_DONE_INT);
++ 
++ 	if (napi_complete_done(napi, rx_done_total))
++ 		mtk_rx_irq_enable(eth, MTK_RX_DONE_INT);
++@@ -1687,20 +1743,20 @@ static int mtk_tx_alloc(struct mtk_eth *
++ 	 */
++ 	wmb();
++ 
++-	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
++-		mtk_w32(eth, ring->phys, MTK_QTX_CTX_PTR);
++-		mtk_w32(eth, ring->phys, MTK_QTX_DTX_PTR);
+++	if (MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
+++		mtk_w32(eth, ring->phys, soc->reg_map->qdma.ctx_ptr);
+++		mtk_w32(eth, ring->phys, soc->reg_map->qdma.dtx_ptr);
++ 		mtk_w32(eth,
++ 			ring->phys + ((MTK_DMA_SIZE - 1) * sz),
++-			MTK_QTX_CRX_PTR);
++-		mtk_w32(eth, ring->last_free_ptr, MTK_QTX_DRX_PTR);
+++			soc->reg_map->qdma.crx_ptr);
+++		mtk_w32(eth, ring->last_free_ptr, soc->reg_map->qdma.drx_ptr);
++ 		mtk_w32(eth, (QDMA_RES_THRES << 8) | QDMA_RES_THRES,
++-			MTK_QTX_CFG(0));
+++			soc->reg_map->qdma.qtx_cfg);
++ 	} else {
++ 		mtk_w32(eth, ring->phys_pdma, MT7628_TX_BASE_PTR0);
++ 		mtk_w32(eth, MTK_DMA_SIZE, MT7628_TX_MAX_CNT0);
++ 		mtk_w32(eth, 0, MT7628_TX_CTX_IDX0);
++-		mtk_w32(eth, MT7628_PST_DTX_IDX0, MTK_PDMA_RST_IDX);
+++		mtk_w32(eth, MT7628_PST_DTX_IDX0, soc->reg_map->pdma.rst_idx);
++ 	}
++ 
++ 	return 0;
++@@ -1739,6 +1795,7 @@ static void mtk_tx_clean(struct mtk_eth
++ 
++ static int mtk_rx_alloc(struct mtk_eth *eth, int ring_no, int rx_flag)
++ {
+++	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
++ 	struct mtk_rx_ring *ring;
++ 	int rx_data_len, rx_dma_size;
++ 	int i;
++@@ -1807,16 +1864,18 @@ static int mtk_rx_alloc(struct mtk_eth *
++ 	ring->dma_size = rx_dma_size;
++ 	ring->calc_idx_update = false;
++ 	ring->calc_idx = rx_dma_size - 1;
++-	ring->crx_idx_reg = MTK_PRX_CRX_IDX_CFG(ring_no);
+++	ring->crx_idx_reg = reg_map->pdma.pcrx_ptr + ring_no * MTK_QRX_OFFSET;
++ 	/* make sure that all changes to the dma ring are flushed before we
++ 	 * continue
++ 	 */
++ 	wmb();
++ 
++-	mtk_w32(eth, ring->phys, MTK_PRX_BASE_PTR_CFG(ring_no) + offset);
++-	mtk_w32(eth, rx_dma_size, MTK_PRX_MAX_CNT_CFG(ring_no) + offset);
+++	mtk_w32(eth, ring->phys,
+++		reg_map->pdma.rx_ptr + ring_no * MTK_QRX_OFFSET + offset);
+++	mtk_w32(eth, rx_dma_size,
+++		reg_map->pdma.rx_cnt_cfg + ring_no * MTK_QRX_OFFSET + offset);
++ 	mtk_w32(eth, ring->calc_idx, ring->crx_idx_reg + offset);
++-	mtk_w32(eth, MTK_PST_DRX_IDX_CFG(ring_no), MTK_PDMA_RST_IDX + offset);
+++	mtk_w32(eth, MTK_PST_DRX_IDX_CFG(ring_no), reg_map->pdma.rst_idx + offset);
++ 
++ 	return 0;
++ }
++@@ -2125,9 +2184,9 @@ static int mtk_dma_busy_wait(struct mtk_
++ 	u32 val;
++ 
++ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA))
++-		reg = MTK_QDMA_GLO_CFG;
+++		reg = eth->soc->reg_map->qdma.glo_cfg;
++ 	else
++-		reg = MTK_PDMA_GLO_CFG;
+++		reg = eth->soc->reg_map->pdma.glo_cfg;
++ 
++ 	ret = readx_poll_timeout_atomic(__raw_readl, eth->base + reg, val,
++ 					!(val & (MTK_RX_DMA_BUSY | MTK_TX_DMA_BUSY)),
++@@ -2185,8 +2244,8 @@ static int mtk_dma_init(struct mtk_eth *
++ 		 * automatically
++ 		 */
++ 		mtk_w32(eth, FC_THRES_DROP_MODE | FC_THRES_DROP_EN |
++-			FC_THRES_MIN, MTK_QDMA_FC_THRES);
++-		mtk_w32(eth, 0x0, MTK_QDMA_HRED2);
+++			FC_THRES_MIN, eth->soc->reg_map->qdma.fc_th);
+++		mtk_w32(eth, 0x0, eth->soc->reg_map->qdma.hred);
++ 	}
++ 
++ 	return 0;
++@@ -2260,13 +2319,14 @@ static irqreturn_t mtk_handle_irq_tx(int
++ static irqreturn_t mtk_handle_irq(int irq, void *_eth)
++ {
++ 	struct mtk_eth *eth = _eth;
+++	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
++ 
++-	if (mtk_r32(eth, MTK_PDMA_INT_MASK) & MTK_RX_DONE_INT) {
++-		if (mtk_r32(eth, MTK_PDMA_INT_STATUS) & MTK_RX_DONE_INT)
+++	if (mtk_r32(eth, reg_map->pdma.irq_mask) & MTK_RX_DONE_INT) {
+++		if (mtk_r32(eth, reg_map->pdma.irq_status) & MTK_RX_DONE_INT)
++ 			mtk_handle_irq_rx(irq, _eth);
++ 	}
++-	if (mtk_r32(eth, eth->tx_int_mask_reg) & MTK_TX_DONE_INT) {
++-		if (mtk_r32(eth, eth->tx_int_status_reg) & MTK_TX_DONE_INT)
+++	if (mtk_r32(eth, reg_map->tx_irq_mask) & MTK_TX_DONE_INT) {
+++		if (mtk_r32(eth, reg_map->tx_irq_status) & MTK_TX_DONE_INT)
++ 			mtk_handle_irq_tx(irq, _eth);
++ 	}
++ 
++@@ -2290,6 +2350,7 @@ static void mtk_poll_controller(struct n
++ static int mtk_start_dma(struct mtk_eth *eth)
++ {
++ 	u32 rx_2b_offset = (NET_IP_ALIGN == 2) ? MTK_RX_2B_OFFSET : 0;
+++	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
++ 	int err;
++ 
++ 	err = mtk_dma_init(eth);
++@@ -2304,16 +2365,15 @@ static int mtk_start_dma(struct mtk_eth
++ 			MTK_TX_BT_32DWORDS | MTK_NDP_CO_PRO |
++ 			MTK_RX_DMA_EN | MTK_RX_2B_OFFSET |
++ 			MTK_RX_BT_32DWORDS,
++-			MTK_QDMA_GLO_CFG);
++-
+++			reg_map->qdma.glo_cfg);
++ 		mtk_w32(eth,
++ 			MTK_RX_DMA_EN | rx_2b_offset |
++ 			MTK_RX_BT_32DWORDS | MTK_MULTI_EN,
++-			MTK_PDMA_GLO_CFG);
+++			reg_map->pdma.glo_cfg);
++ 	} else {
++ 		mtk_w32(eth, MTK_TX_WB_DDONE | MTK_TX_DMA_EN | MTK_RX_DMA_EN |
++ 			MTK_MULTI_EN | MTK_PDMA_SIZE_8DWORDS,
++-			MTK_PDMA_GLO_CFG);
+++			reg_map->pdma.glo_cfg);
++ 	}
++ 
++ 	return 0;
++@@ -2437,8 +2497,8 @@ static int mtk_stop(struct net_device *d
++ 	cancel_work_sync(&eth->tx_dim.work);
++ 
++ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA))
++-		mtk_stop_dma(eth, MTK_QDMA_GLO_CFG);
++-	mtk_stop_dma(eth, MTK_PDMA_GLO_CFG);
+++		mtk_stop_dma(eth, eth->soc->reg_map->qdma.glo_cfg);
+++	mtk_stop_dma(eth, eth->soc->reg_map->pdma.glo_cfg);
++ 
++ 	mtk_dma_free(eth);
++ 
++@@ -2492,6 +2552,7 @@ static void mtk_dim_rx(struct work_struc
++ {
++ 	struct dim *dim = container_of(work, struct dim, work);
++ 	struct mtk_eth *eth = container_of(dim, struct mtk_eth, rx_dim);
+++	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
++ 	struct dim_cq_moder cur_profile;
++ 	u32 val, cur;
++ 
++@@ -2499,7 +2560,7 @@ static void mtk_dim_rx(struct work_struc
++ 						dim->profile_ix);
++ 	spin_lock_bh(&eth->dim_lock);
++ 
++-	val = mtk_r32(eth, MTK_PDMA_DELAY_INT);
+++	val = mtk_r32(eth, reg_map->pdma.delay_irq);
++ 	val &= MTK_PDMA_DELAY_TX_MASK;
++ 	val |= MTK_PDMA_DELAY_RX_EN;
++ 
++@@ -2509,9 +2570,9 @@ static void mtk_dim_rx(struct work_struc
++ 	cur = min_t(u32, cur_profile.pkts, MTK_PDMA_DELAY_PINT_MASK);
++ 	val |= cur << MTK_PDMA_DELAY_RX_PINT_SHIFT;
++ 
++-	mtk_w32(eth, val, MTK_PDMA_DELAY_INT);
+++	mtk_w32(eth, val, reg_map->pdma.delay_irq);
++ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA))
++-		mtk_w32(eth, val, MTK_QDMA_DELAY_INT);
+++		mtk_w32(eth, val, reg_map->qdma.delay_irq);
++ 
++ 	spin_unlock_bh(&eth->dim_lock);
++ 
++@@ -2522,6 +2583,7 @@ static void mtk_dim_tx(struct work_struc
++ {
++ 	struct dim *dim = container_of(work, struct dim, work);
++ 	struct mtk_eth *eth = container_of(dim, struct mtk_eth, tx_dim);
+++	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
++ 	struct dim_cq_moder cur_profile;
++ 	u32 val, cur;
++ 
++@@ -2529,7 +2591,7 @@ static void mtk_dim_tx(struct work_struc
++ 						dim->profile_ix);
++ 	spin_lock_bh(&eth->dim_lock);
++ 
++-	val = mtk_r32(eth, MTK_PDMA_DELAY_INT);
+++	val = mtk_r32(eth, reg_map->pdma.delay_irq);
++ 	val &= MTK_PDMA_DELAY_RX_MASK;
++ 	val |= MTK_PDMA_DELAY_TX_EN;
++ 
++@@ -2539,9 +2601,9 @@ static void mtk_dim_tx(struct work_struc
++ 	cur = min_t(u32, cur_profile.pkts, MTK_PDMA_DELAY_PINT_MASK);
++ 	val |= cur << MTK_PDMA_DELAY_TX_PINT_SHIFT;
++ 
++-	mtk_w32(eth, val, MTK_PDMA_DELAY_INT);
+++	mtk_w32(eth, val, reg_map->pdma.delay_irq);
++ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA))
++-		mtk_w32(eth, val, MTK_QDMA_DELAY_INT);
+++		mtk_w32(eth, val, reg_map->qdma.delay_irq);
++ 
++ 	spin_unlock_bh(&eth->dim_lock);
++ 
++@@ -2552,6 +2614,7 @@ static int mtk_hw_init(struct mtk_eth *e
++ {
++ 	u32 dma_mask = ETHSYS_DMA_AG_MAP_PDMA | ETHSYS_DMA_AG_MAP_QDMA |
++ 		       ETHSYS_DMA_AG_MAP_PPE;
+++	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
++ 	int i, val, ret;
++ 
++ 	if (test_and_set_bit(MTK_HW_INIT, &eth->state))
++@@ -2626,10 +2689,10 @@ static int mtk_hw_init(struct mtk_eth *e
++ 	mtk_rx_irq_disable(eth, ~0);
++ 
++ 	/* FE int grouping */
++-	mtk_w32(eth, MTK_TX_DONE_INT, MTK_PDMA_INT_GRP1);
++-	mtk_w32(eth, MTK_RX_DONE_INT, MTK_PDMA_INT_GRP2);
++-	mtk_w32(eth, MTK_TX_DONE_INT, MTK_QDMA_INT_GRP1);
++-	mtk_w32(eth, MTK_RX_DONE_INT, MTK_QDMA_INT_GRP2);
+++	mtk_w32(eth, MTK_TX_DONE_INT, reg_map->pdma.int_grp);
+++	mtk_w32(eth, MTK_RX_DONE_INT, reg_map->pdma.int_grp + 4);
+++	mtk_w32(eth, MTK_TX_DONE_INT, reg_map->qdma.int_grp);
+++	mtk_w32(eth, MTK_RX_DONE_INT, reg_map->qdma.int_grp + 4);
++ 	mtk_w32(eth, 0x21021000, MTK_FE_INT_GRP);
++ 
++ 	return 0;
++@@ -3168,14 +3231,6 @@ static int mtk_probe(struct platform_dev
++ 	if (IS_ERR(eth->base))
++ 		return PTR_ERR(eth->base);
++ 
++-	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
++-		eth->tx_int_mask_reg = MTK_QDMA_INT_MASK;
++-		eth->tx_int_status_reg = MTK_QDMA_INT_STATUS;
++-	} else {
++-		eth->tx_int_mask_reg = MTK_PDMA_INT_MASK;
++-		eth->tx_int_status_reg = MTK_PDMA_INT_STATUS;
++-	}
++-
++ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628)) {
++ 		eth->rx_dma_l4_valid = RX_DMA_L4_VALID_PDMA;
++ 		eth->ip_align = NET_IP_ALIGN;
++@@ -3409,6 +3464,7 @@ static int mtk_remove(struct platform_de
++ }
++ 
++ static const struct mtk_soc_data mt2701_data = {
+++	.reg_map = &mtk_reg_map,
++ 	.caps = MT7623_CAPS | MTK_HWLRO,
++ 	.hw_features = MTK_HW_FEATURES,
++ 	.required_clks = MT7623_CLKS_BITMAP,
++@@ -3420,6 +3476,7 @@ static const struct mtk_soc_data mt2701_
++ };
++ 
++ static const struct mtk_soc_data mt7621_data = {
+++	.reg_map = &mtk_reg_map,
++ 	.caps = MT7621_CAPS,
++ 	.hw_features = MTK_HW_FEATURES,
++ 	.required_clks = MT7621_CLKS_BITMAP,
++@@ -3432,6 +3489,7 @@ static const struct mtk_soc_data mt7621_
++ };
++ 
++ static const struct mtk_soc_data mt7622_data = {
+++	.reg_map = &mtk_reg_map,
++ 	.ana_rgc3 = 0x2028,
++ 	.caps = MT7622_CAPS | MTK_HWLRO,
++ 	.hw_features = MTK_HW_FEATURES,
++@@ -3445,6 +3503,7 @@ static const struct mtk_soc_data mt7622_
++ };
++ 
++ static const struct mtk_soc_data mt7623_data = {
+++	.reg_map = &mtk_reg_map,
++ 	.caps = MT7623_CAPS | MTK_HWLRO,
++ 	.hw_features = MTK_HW_FEATURES,
++ 	.required_clks = MT7623_CLKS_BITMAP,
++@@ -3457,6 +3516,7 @@ static const struct mtk_soc_data mt7623_
++ };
++ 
++ static const struct mtk_soc_data mt7629_data = {
+++	.reg_map = &mtk_reg_map,
++ 	.ana_rgc3 = 0x128,
++ 	.caps = MT7629_CAPS | MTK_HWLRO,
++ 	.hw_features = MTK_HW_FEATURES,
++@@ -3469,6 +3529,7 @@ static const struct mtk_soc_data mt7629_
++ };
++ 
++ static const struct mtk_soc_data rt5350_data = {
+++	.reg_map = &mt7628_reg_map,
++ 	.caps = MT7628_CAPS,
++ 	.hw_features = MTK_HW_FEATURES_MT7628,
++ 	.required_clks = MT7628_CLKS_BITMAP,
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -48,6 +48,8 @@
++ #define MTK_HW_FEATURES_MT7628	(NETIF_F_SG | NETIF_F_RXCSUM)
++ #define NEXT_DESP_IDX(X, Y)	(((X) + 1) & ((Y) - 1))
++ 
+++#define MTK_QRX_OFFSET		0x10
+++
++ #define MTK_MAX_RX_RING_NUM	4
++ #define MTK_HW_LRO_DMA_SIZE	8
++ 
++@@ -100,18 +102,6 @@
++ /* Unicast Filter MAC Address Register - High */
++ #define MTK_GDMA_MAC_ADRH(x)	(0x50C + (x * 0x1000))
++ 
++-/* PDMA RX Base Pointer Register */
++-#define MTK_PRX_BASE_PTR0	0x900
++-#define MTK_PRX_BASE_PTR_CFG(x)	(MTK_PRX_BASE_PTR0 + (x * 0x10))
++-
++-/* PDMA RX Maximum Count Register */
++-#define MTK_PRX_MAX_CNT0	0x904
++-#define MTK_PRX_MAX_CNT_CFG(x)	(MTK_PRX_MAX_CNT0 + (x * 0x10))
++-
++-/* PDMA RX CPU Pointer Register */
++-#define MTK_PRX_CRX_IDX0	0x908
++-#define MTK_PRX_CRX_IDX_CFG(x)	(MTK_PRX_CRX_IDX0 + (x * 0x10))
++-
++ /* PDMA HW LRO Control Registers */
++ #define MTK_PDMA_LRO_CTRL_DW0	0x980
++ #define MTK_LRO_EN			BIT(0)
++@@ -126,18 +116,19 @@
++ #define MTK_ADMA_MODE		BIT(15)
++ #define MTK_LRO_MIN_RXD_SDL	(MTK_HW_LRO_SDL_REMAIN_ROOM << 16)
++ 
++-/* PDMA Global Configuration Register */
++-#define MTK_PDMA_GLO_CFG	0xa04
+++#define MTK_RX_DMA_LRO_EN	BIT(8)
++ #define MTK_MULTI_EN		BIT(10)
++ #define MTK_PDMA_SIZE_8DWORDS	(1 << 4)
++ 
+++/* PDMA Global Configuration Register */
+++#define MTK_PDMA_LRO_SDL	0x3000
+++#define MTK_RX_CFG_SDL_OFFSET	16
+++
++ /* PDMA Reset Index Register */
++-#define MTK_PDMA_RST_IDX	0xa08
++ #define MTK_PST_DRX_IDX0	BIT(16)
++ #define MTK_PST_DRX_IDX_CFG(x)	(MTK_PST_DRX_IDX0 << (x))
++ 
++ /* PDMA Delay Interrupt Register */
++-#define MTK_PDMA_DELAY_INT		0xa0c
++ #define MTK_PDMA_DELAY_RX_MASK		GENMASK(15, 0)
++ #define MTK_PDMA_DELAY_RX_EN		BIT(15)
++ #define MTK_PDMA_DELAY_RX_PINT_SHIFT	8
++@@ -151,19 +142,9 @@
++ #define MTK_PDMA_DELAY_PINT_MASK	0x7f
++ #define MTK_PDMA_DELAY_PTIME_MASK	0xff
++ 
++-/* PDMA Interrupt Status Register */
++-#define MTK_PDMA_INT_STATUS	0xa20
++-
++-/* PDMA Interrupt Mask Register */
++-#define MTK_PDMA_INT_MASK	0xa28
++-
++ /* PDMA HW LRO Alter Flow Delta Register */
++ #define MTK_PDMA_LRO_ALT_SCORE_DELTA	0xa4c
++ 
++-/* PDMA Interrupt grouping registers */
++-#define MTK_PDMA_INT_GRP1	0xa50
++-#define MTK_PDMA_INT_GRP2	0xa54
++-
++ /* PDMA HW LRO IP Setting Registers */
++ #define MTK_LRO_RX_RING0_DIP_DW0	0xb04
++ #define MTK_LRO_DIP_DW0_CFG(x)		(MTK_LRO_RX_RING0_DIP_DW0 + (x * 0x40))
++@@ -185,26 +166,9 @@
++ #define MTK_RING_MAX_AGG_CNT_H		((MTK_HW_LRO_MAX_AGG_CNT >> 6) & 0x3)
++ 
++ /* QDMA TX Queue Configuration Registers */
++-#define MTK_QTX_CFG(x)		(0x1800 + (x * 0x10))
++ #define QDMA_RES_THRES		4
++ 
++-/* QDMA TX Queue Scheduler Registers */
++-#define MTK_QTX_SCH(x)		(0x1804 + (x * 0x10))
++-
++-/* QDMA RX Base Pointer Register */
++-#define MTK_QRX_BASE_PTR0	0x1900
++-
++-/* QDMA RX Maximum Count Register */
++-#define MTK_QRX_MAX_CNT0	0x1904
++-
++-/* QDMA RX CPU Pointer Register */
++-#define MTK_QRX_CRX_IDX0	0x1908
++-
++-/* QDMA RX DMA Pointer Register */
++-#define MTK_QRX_DRX_IDX0	0x190C
++-
++ /* QDMA Global Configuration Register */
++-#define MTK_QDMA_GLO_CFG	0x1A04
++ #define MTK_RX_2B_OFFSET	BIT(31)
++ #define MTK_RX_BT_32DWORDS	(3 << 11)
++ #define MTK_NDP_CO_PRO		BIT(10)
++@@ -216,20 +180,12 @@
++ #define MTK_TX_DMA_EN		BIT(0)
++ #define MTK_DMA_BUSY_TIMEOUT_US	1000000
++ 
++-/* QDMA Reset Index Register */
++-#define MTK_QDMA_RST_IDX	0x1A08
++-
++-/* QDMA Delay Interrupt Register */
++-#define MTK_QDMA_DELAY_INT	0x1A0C
++-
++ /* QDMA Flow Control Register */
++-#define MTK_QDMA_FC_THRES	0x1A10
++ #define FC_THRES_DROP_MODE	BIT(20)
++ #define FC_THRES_DROP_EN	(7 << 16)
++ #define FC_THRES_MIN		0x4444
++ 
++ /* QDMA Interrupt Status Register */
++-#define MTK_QDMA_INT_STATUS	0x1A18
++ #define MTK_RX_DONE_DLY		BIT(30)
++ #define MTK_TX_DONE_DLY		BIT(28)
++ #define MTK_RX_DONE_INT3	BIT(19)
++@@ -244,55 +200,8 @@
++ #define MTK_TX_DONE_INT		MTK_TX_DONE_DLY
++ 
++ /* QDMA Interrupt grouping registers */
++-#define MTK_QDMA_INT_GRP1	0x1a20
++-#define MTK_QDMA_INT_GRP2	0x1a24
++ #define MTK_RLS_DONE_INT	BIT(0)
++ 
++-/* QDMA Interrupt Status Register */
++-#define MTK_QDMA_INT_MASK	0x1A1C
++-
++-/* QDMA Interrupt Mask Register */
++-#define MTK_QDMA_HRED2		0x1A44
++-
++-/* QDMA TX Forward CPU Pointer Register */
++-#define MTK_QTX_CTX_PTR		0x1B00
++-
++-/* QDMA TX Forward DMA Pointer Register */
++-#define MTK_QTX_DTX_PTR		0x1B04
++-
++-/* QDMA TX Release CPU Pointer Register */
++-#define MTK_QTX_CRX_PTR		0x1B10
++-
++-/* QDMA TX Release DMA Pointer Register */
++-#define MTK_QTX_DRX_PTR		0x1B14
++-
++-/* QDMA FQ Head Pointer Register */
++-#define MTK_QDMA_FQ_HEAD	0x1B20
++-
++-/* QDMA FQ Head Pointer Register */
++-#define MTK_QDMA_FQ_TAIL	0x1B24
++-
++-/* QDMA FQ Free Page Counter Register */
++-#define MTK_QDMA_FQ_CNT		0x1B28
++-
++-/* QDMA FQ Free Page Buffer Length Register */
++-#define MTK_QDMA_FQ_BLEN	0x1B2C
++-
++-/* GMA1 counter / statics register */
++-#define MTK_GDM1_RX_GBCNT_L	0x2400
++-#define MTK_GDM1_RX_GBCNT_H	0x2404
++-#define MTK_GDM1_RX_GPCNT	0x2408
++-#define MTK_GDM1_RX_OERCNT	0x2410
++-#define MTK_GDM1_RX_FERCNT	0x2414
++-#define MTK_GDM1_RX_SERCNT	0x2418
++-#define MTK_GDM1_RX_LENCNT	0x241c
++-#define MTK_GDM1_RX_CERCNT	0x2420
++-#define MTK_GDM1_RX_FCCNT	0x2424
++-#define MTK_GDM1_TX_SKIPCNT	0x2428
++-#define MTK_GDM1_TX_COLCNT	0x242c
++-#define MTK_GDM1_TX_GBCNT_L	0x2430
++-#define MTK_GDM1_TX_GBCNT_H	0x2434
++-#define MTK_GDM1_TX_GPCNT	0x2438
++ #define MTK_STAT_OFFSET		0x40
++ 
++ #define MTK_WDMA0_BASE		0x2800
++@@ -853,8 +762,46 @@ struct mtk_tx_dma_desc_info {
++ 	u8		last:1;
++ };
++ 
+++struct mtk_reg_map {
+++	u32	tx_irq_mask;
+++	u32	tx_irq_status;
+++	struct {
+++		u32	rx_ptr;		/* rx base pointer */
+++		u32	rx_cnt_cfg;	/* rx max count configuration */
+++		u32	pcrx_ptr;	/* rx cpu pointer */
+++		u32	glo_cfg;	/* global configuration */
+++		u32	rst_idx;	/* reset index */
+++		u32	delay_irq;	/* delay interrupt */
+++		u32	irq_status;	/* interrupt status */
+++		u32	irq_mask;	/* interrupt mask */
+++		u32	int_grp;
+++	} pdma;
+++	struct {
+++		u32	qtx_cfg;	/* tx queue configuration */
+++		u32	rx_ptr;		/* rx base pointer */
+++		u32	rx_cnt_cfg;	/* rx max count configuration */
+++		u32	qcrx_ptr;	/* rx cpu pointer */
+++		u32	glo_cfg;	/* global configuration */
+++		u32	rst_idx;	/* reset index */
+++		u32	delay_irq;	/* delay interrupt */
+++		u32	fc_th;		/* flow control */
+++		u32	int_grp;
+++		u32	hred;		/* interrupt mask */
+++		u32	ctx_ptr;	/* tx acquire cpu pointer */
+++		u32	dtx_ptr;	/* tx acquire dma pointer */
+++		u32	crx_ptr;	/* tx release cpu pointer */
+++		u32	drx_ptr;	/* tx release dma pointer */
+++		u32	fq_head;	/* fq head pointer */
+++		u32	fq_tail;	/* fq tail pointer */
+++		u32	fq_count;	/* fq free page count */
+++		u32	fq_blen;	/* fq free page buffer length */
+++	} qdma;
+++	u32	gdm1_cnt;
+++};
+++
++ /* struct mtk_eth_data -	This is the structure holding all differences
++  *				among various plaforms
+++ * @reg_map			Soc register map.
++  * @ana_rgc3:                   The offset for register ANA_RGC3 related to
++  *				sgmiisys syscon
++  * @caps			Flags shown the extra capability for the SoC
++@@ -867,6 +814,7 @@ struct mtk_tx_dma_desc_info {
++  * @rxd_size			Rx DMA descriptor size.
++  */
++ struct mtk_soc_data {
+++	const struct mtk_reg_map *reg_map;
++ 	u32             ana_rgc3;
++ 	u32		caps;
++ 	u32		required_clks;
++@@ -994,8 +942,6 @@ struct mtk_eth {
++ 	u32				tx_bytes;
++ 	struct dim			tx_dim;
++ 
++-	u32				tx_int_mask_reg;
++-	u32				tx_int_status_reg;
++ 	u32				rx_dma_l4_valid;
++ 	int				ip_align;
++ 
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-27-net-ethernet-mtk_eth_soc-introduce-MTK_NETSYS_V2-sup.patch b/target/linux/generic/backport-5.15/702-v5.19-27-net-ethernet-mtk_eth_soc-introduce-MTK_NETSYS_V2-sup.patch
+new file mode 100644
+index 0000000000..e15854ecad
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-27-net-ethernet-mtk_eth_soc-introduce-MTK_NETSYS_V2-sup.patch
+@@ -0,0 +1,917 @@
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 20 May 2022 20:11:36 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: introduce MTK_NETSYS_V2 support
++
++Introduce MTK_NETSYS_V2 support. MTK_NETSYS_V2 defines 32B TX/RX DMA
++descriptors.
++This is a preliminary patch to add mt7986 ethernet support.
++
++Tested-by: Sam Shih <sam.shih@mediatek.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -862,8 +862,8 @@ static inline int mtk_max_buf_size(int f
++ 	return buf_size;
++ }
++ 
++-static inline bool mtk_rx_get_desc(struct mtk_rx_dma *rxd,
++-				   struct mtk_rx_dma *dma_rxd)
+++static bool mtk_rx_get_desc(struct mtk_eth *eth, struct mtk_rx_dma_v2 *rxd,
+++			    struct mtk_rx_dma_v2 *dma_rxd)
++ {
++ 	rxd->rxd2 = READ_ONCE(dma_rxd->rxd2);
++ 	if (!(rxd->rxd2 & RX_DMA_DONE))
++@@ -872,6 +872,10 @@ static inline bool mtk_rx_get_desc(struc
++ 	rxd->rxd1 = READ_ONCE(dma_rxd->rxd1);
++ 	rxd->rxd3 = READ_ONCE(dma_rxd->rxd3);
++ 	rxd->rxd4 = READ_ONCE(dma_rxd->rxd4);
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+++		rxd->rxd5 = READ_ONCE(dma_rxd->rxd5);
+++		rxd->rxd6 = READ_ONCE(dma_rxd->rxd6);
+++	}
++ 
++ 	return true;
++ }
++@@ -916,7 +920,7 @@ static int mtk_init_fq_dma(struct mtk_et
++ 	phy_ring_tail = eth->phy_scratch_ring + soc->txrx.txd_size * (cnt - 1);
++ 
++ 	for (i = 0; i < cnt; i++) {
++-		struct mtk_tx_dma *txd;
+++		struct mtk_tx_dma_v2 *txd;
++ 
++ 		txd = (void *)eth->scratch_ring + i * soc->txrx.txd_size;
++ 		txd->txd1 = dma_addr + i * MTK_QDMA_PAGE_SIZE;
++@@ -926,6 +930,12 @@ static int mtk_init_fq_dma(struct mtk_et
++ 
++ 		txd->txd3 = TX_DMA_PLEN0(MTK_QDMA_PAGE_SIZE);
++ 		txd->txd4 = 0;
+++		if (MTK_HAS_CAPS(soc->caps, MTK_NETSYS_V2)) {
+++			txd->txd5 = 0;
+++			txd->txd6 = 0;
+++			txd->txd7 = 0;
+++			txd->txd8 = 0;
+++		}
++ 	}
++ 
++ 	mtk_w32(eth, eth->phy_scratch_ring, soc->reg_map->qdma.fq_head);
++@@ -1029,10 +1039,12 @@ static void setup_tx_buf(struct mtk_eth
++ 	}
++ }
++ 
++-static void mtk_tx_set_dma_desc(struct net_device *dev, struct mtk_tx_dma *desc,
++-				struct mtk_tx_dma_desc_info *info)
+++static void mtk_tx_set_dma_desc_v1(struct net_device *dev, void *txd,
+++				   struct mtk_tx_dma_desc_info *info)
++ {
++ 	struct mtk_mac *mac = netdev_priv(dev);
+++	struct mtk_eth *eth = mac->hw;
+++	struct mtk_tx_dma *desc = txd;
++ 	u32 data;
++ 
++ 	WRITE_ONCE(desc->txd1, info->addr);
++@@ -1056,6 +1068,59 @@ static void mtk_tx_set_dma_desc(struct n
++ 	WRITE_ONCE(desc->txd4, data);
++ }
++ 
+++static void mtk_tx_set_dma_desc_v2(struct net_device *dev, void *txd,
+++				   struct mtk_tx_dma_desc_info *info)
+++{
+++	struct mtk_mac *mac = netdev_priv(dev);
+++	struct mtk_tx_dma_v2 *desc = txd;
+++	struct mtk_eth *eth = mac->hw;
+++	u32 data;
+++
+++	WRITE_ONCE(desc->txd1, info->addr);
+++
+++	data = TX_DMA_PLEN0(info->size);
+++	if (info->last)
+++		data |= TX_DMA_LS0;
+++	WRITE_ONCE(desc->txd3, data);
+++
+++	if (!info->qid && mac->id)
+++		info->qid = MTK_QDMA_GMAC2_QID;
+++
+++	data = (mac->id + 1) << TX_DMA_FPORT_SHIFT_V2; /* forward port */
+++	data |= TX_DMA_SWC_V2 | QID_BITS_V2(info->qid);
+++	WRITE_ONCE(desc->txd4, data);
+++
+++	data = 0;
+++	if (info->first) {
+++		if (info->gso)
+++			data |= TX_DMA_TSO_V2;
+++		/* tx checksum offload */
+++		if (info->csum)
+++			data |= TX_DMA_CHKSUM_V2;
+++	}
+++	WRITE_ONCE(desc->txd5, data);
+++
+++	data = 0;
+++	if (info->first && info->vlan)
+++		data |= TX_DMA_INS_VLAN_V2 | info->vlan_tci;
+++	WRITE_ONCE(desc->txd6, data);
+++
+++	WRITE_ONCE(desc->txd7, 0);
+++	WRITE_ONCE(desc->txd8, 0);
+++}
+++
+++static void mtk_tx_set_dma_desc(struct net_device *dev, void *txd,
+++				struct mtk_tx_dma_desc_info *info)
+++{
+++	struct mtk_mac *mac = netdev_priv(dev);
+++	struct mtk_eth *eth = mac->hw;
+++
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
+++		mtk_tx_set_dma_desc_v2(dev, txd, info);
+++	else
+++		mtk_tx_set_dma_desc_v1(dev, txd, info);
+++}
+++
++ static int mtk_tx_map(struct sk_buff *skb, struct net_device *dev,
++ 		      int tx_num, struct mtk_tx_ring *ring, bool gso)
++ {
++@@ -1064,6 +1129,7 @@ static int mtk_tx_map(struct sk_buff *sk
++ 		.gso = gso,
++ 		.csum = skb->ip_summed == CHECKSUM_PARTIAL,
++ 		.vlan = skb_vlan_tag_present(skb),
+++		.qid = skb->mark & MTK_QDMA_TX_MASK,
++ 		.vlan_tci = skb_vlan_tag_get(skb),
++ 		.first = true,
++ 		.last = !skb_is_nonlinear(skb),
++@@ -1123,7 +1189,9 @@ static int mtk_tx_map(struct sk_buff *sk
++ 			}
++ 
++ 			memset(&txd_info, 0, sizeof(struct mtk_tx_dma_desc_info));
++-			txd_info.size = min(frag_size, MTK_TX_DMA_BUF_LEN);
+++			txd_info.size = min_t(unsigned int, frag_size,
+++					      soc->txrx.dma_max_len);
+++			txd_info.qid = skb->mark & MTK_QDMA_TX_MASK;
++ 			txd_info.last = i == skb_shinfo(skb)->nr_frags - 1 &&
++ 					!(frag_size - txd_info.size);
++ 			txd_info.addr = skb_frag_dma_map(eth->dma_dev, frag,
++@@ -1204,17 +1272,16 @@ err_dma:
++ 	return -ENOMEM;
++ }
++ 
++-static inline int mtk_cal_txd_req(struct sk_buff *skb)
+++static int mtk_cal_txd_req(struct mtk_eth *eth, struct sk_buff *skb)
++ {
++-	int i, nfrags;
+++	int i, nfrags = 1;
++ 	skb_frag_t *frag;
++ 
++-	nfrags = 1;
++ 	if (skb_is_gso(skb)) {
++ 		for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
++ 			frag = &skb_shinfo(skb)->frags[i];
++ 			nfrags += DIV_ROUND_UP(skb_frag_size(frag),
++-						MTK_TX_DMA_BUF_LEN);
+++					       eth->soc->txrx.dma_max_len);
++ 		}
++ 	} else {
++ 		nfrags += skb_shinfo(skb)->nr_frags;
++@@ -1266,7 +1333,7 @@ static netdev_tx_t mtk_start_xmit(struct
++ 	if (unlikely(test_bit(MTK_RESETTING, &eth->state)))
++ 		goto drop;
++ 
++-	tx_num = mtk_cal_txd_req(skb);
+++	tx_num = mtk_cal_txd_req(eth, skb);
++ 	if (unlikely(atomic_read(&ring->free_count) <= tx_num)) {
++ 		netif_stop_queue(dev);
++ 		netif_err(eth, tx_queued, dev,
++@@ -1358,7 +1425,7 @@ static int mtk_poll_rx(struct napi_struc
++ 	int idx;
++ 	struct sk_buff *skb;
++ 	u8 *data, *new_data;
++-	struct mtk_rx_dma *rxd, trxd;
+++	struct mtk_rx_dma_v2 *rxd, trxd;
++ 	int done = 0, bytes = 0;
++ 
++ 	while (done < budget) {
++@@ -1366,7 +1433,7 @@ static int mtk_poll_rx(struct napi_struc
++ 		unsigned int pktlen;
++ 		dma_addr_t dma_addr;
++ 		u32 hash, reason;
++-		int mac;
+++		int mac = 0;
++ 
++ 		ring = mtk_get_rx_ring(eth);
++ 		if (unlikely(!ring))
++@@ -1376,16 +1443,15 @@ static int mtk_poll_rx(struct napi_struc
++ 		rxd = (void *)ring->dma + idx * eth->soc->txrx.rxd_size;
++ 		data = ring->data[idx];
++ 
++-		if (!mtk_rx_get_desc(&trxd, rxd))
+++		if (!mtk_rx_get_desc(eth, &trxd, rxd))
++ 			break;
++ 
++ 		/* find out which mac the packet come from. values start at 1 */
++-		if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628) ||
++-		    (trxd.rxd4 & RX_DMA_SPECIAL_TAG))
++-			mac = 0;
++-		else
++-			mac = ((trxd.rxd4 >> RX_DMA_FPORT_SHIFT) &
++-			       RX_DMA_FPORT_MASK) - 1;
+++		if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
+++			mac = RX_DMA_GET_SPORT_V2(trxd.rxd5) - 1;
+++		else if (!MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628) &&
+++			 !(trxd.rxd4 & RX_DMA_SPECIAL_TAG))
+++			mac = RX_DMA_GET_SPORT(trxd.rxd4) - 1;
++ 
++ 		if (unlikely(mac < 0 || mac >= MTK_MAC_COUNT ||
++ 			     !eth->netdev[mac]))
++@@ -1431,7 +1497,7 @@ static int mtk_poll_rx(struct napi_struc
++ 		pktlen = RX_DMA_GET_PLEN0(trxd.rxd2);
++ 		skb->dev = netdev;
++ 		skb_put(skb, pktlen);
++-		if (trxd.rxd4 & eth->rx_dma_l4_valid)
+++		if (trxd.rxd4 & eth->soc->txrx.rx_dma_l4_valid)
++ 			skb->ip_summed = CHECKSUM_UNNECESSARY;
++ 		else
++ 			skb_checksum_none_assert(skb);
++@@ -1449,10 +1515,25 @@ static int mtk_poll_rx(struct napi_struc
++ 			mtk_ppe_check_skb(eth->ppe, skb,
++ 					  trxd.rxd4 & MTK_RXD4_FOE_ENTRY);
++ 
++-		if (netdev->features & NETIF_F_HW_VLAN_CTAG_RX &&
++-		    (trxd.rxd2 & RX_DMA_VTAG))
++-			__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q),
++-					       RX_DMA_VID(trxd.rxd3));
+++		if (netdev->features & NETIF_F_HW_VLAN_CTAG_RX) {
+++			if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+++				if (trxd.rxd3 & RX_DMA_VTAG_V2)
+++					__vlan_hwaccel_put_tag(skb,
+++						htons(RX_DMA_VPID(trxd.rxd4)),
+++						RX_DMA_VID(trxd.rxd4));
+++			} else if (trxd.rxd2 & RX_DMA_VTAG) {
+++				__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q),
+++						       RX_DMA_VID(trxd.rxd3));
+++			}
+++
+++			/* If the device is attached to a dsa switch, the special
+++			 * tag inserted in VLAN field by hw switch can * be offloaded
+++			 * by RX HW VLAN offload. Clear vlan info.
+++			 */
+++			if (netdev_uses_dsa(netdev))
+++				__vlan_hwaccel_clear_tag(skb);
+++		}
+++
++ 		skb_record_rx_queue(skb, 0);
++ 		napi_gro_receive(napi, skb);
++ 
++@@ -1464,7 +1545,7 @@ release_desc:
++ 		if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628))
++ 			rxd->rxd2 = RX_DMA_LSO;
++ 		else
++-			rxd->rxd2 = RX_DMA_PLEN0(ring->buf_size);
+++			rxd->rxd2 = RX_DMA_PREP_PLEN0(ring->buf_size);
++ 
++ 		ring->calc_idx = idx;
++ 
++@@ -1666,7 +1747,8 @@ static int mtk_napi_rx(struct napi_struc
++ 	do {
++ 		int rx_done;
++ 
++-		mtk_w32(eth, MTK_RX_DONE_INT, reg_map->pdma.irq_status);
+++		mtk_w32(eth, eth->soc->txrx.rx_irq_done_mask,
+++			reg_map->pdma.irq_status);
++ 		rx_done = mtk_poll_rx(napi, budget - rx_done_total, eth);
++ 		rx_done_total += rx_done;
++ 
++@@ -1680,10 +1762,11 @@ static int mtk_napi_rx(struct napi_struc
++ 		if (rx_done_total == budget)
++ 			return budget;
++ 
++-	} while (mtk_r32(eth, reg_map->pdma.irq_status) & MTK_RX_DONE_INT);
+++	} while (mtk_r32(eth, reg_map->pdma.irq_status) &
+++		 eth->soc->txrx.rx_irq_done_mask);
++ 
++ 	if (napi_complete_done(napi, rx_done_total))
++-		mtk_rx_irq_enable(eth, MTK_RX_DONE_INT);
+++		mtk_rx_irq_enable(eth, eth->soc->txrx.rx_irq_done_mask);
++ 
++ 	return rx_done_total;
++ }
++@@ -1693,7 +1776,7 @@ static int mtk_tx_alloc(struct mtk_eth *
++ 	const struct mtk_soc_data *soc = eth->soc;
++ 	struct mtk_tx_ring *ring = &eth->tx_ring;
++ 	int i, sz = soc->txrx.txd_size;
++-	struct mtk_tx_dma *txd;
+++	struct mtk_tx_dma_v2 *txd;
++ 
++ 	ring->buf = kcalloc(MTK_DMA_SIZE, sizeof(*ring->buf),
++ 			       GFP_KERNEL);
++@@ -1713,13 +1796,19 @@ static int mtk_tx_alloc(struct mtk_eth *
++ 		txd->txd2 = next_ptr;
++ 		txd->txd3 = TX_DMA_LS0 | TX_DMA_OWNER_CPU;
++ 		txd->txd4 = 0;
+++		if (MTK_HAS_CAPS(soc->caps, MTK_NETSYS_V2)) {
+++			txd->txd5 = 0;
+++			txd->txd6 = 0;
+++			txd->txd7 = 0;
+++			txd->txd8 = 0;
+++		}
++ 	}
++ 
++ 	/* On MT7688 (PDMA only) this driver uses the ring->dma structs
++ 	 * only as the framework. The real HW descriptors are the PDMA
++ 	 * descriptors in ring->dma_pdma.
++ 	 */
++-	if (!MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
+++	if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
++ 		ring->dma_pdma = dma_alloc_coherent(eth->dma_dev, MTK_DMA_SIZE * sz,
++ 						    &ring->phys_pdma, GFP_KERNEL);
++ 		if (!ring->dma_pdma)
++@@ -1799,13 +1888,11 @@ static int mtk_rx_alloc(struct mtk_eth *
++ 	struct mtk_rx_ring *ring;
++ 	int rx_data_len, rx_dma_size;
++ 	int i;
++-	u32 offset = 0;
++ 
++ 	if (rx_flag == MTK_RX_FLAGS_QDMA) {
++ 		if (ring_no)
++ 			return -EINVAL;
++ 		ring = &eth->rx_ring_qdma;
++-		offset = 0x1000;
++ 	} else {
++ 		ring = &eth->rx_ring[ring_no];
++ 	}
++@@ -1841,7 +1928,7 @@ static int mtk_rx_alloc(struct mtk_eth *
++ 		return -ENOMEM;
++ 
++ 	for (i = 0; i < rx_dma_size; i++) {
++-		struct mtk_rx_dma *rxd;
+++		struct mtk_rx_dma_v2 *rxd;
++ 
++ 		dma_addr_t dma_addr = dma_map_single(eth->dma_dev,
++ 				ring->data[i] + NET_SKB_PAD + eth->ip_align,
++@@ -1856,26 +1943,47 @@ static int mtk_rx_alloc(struct mtk_eth *
++ 		if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628))
++ 			rxd->rxd2 = RX_DMA_LSO;
++ 		else
++-			rxd->rxd2 = RX_DMA_PLEN0(ring->buf_size);
+++			rxd->rxd2 = RX_DMA_PREP_PLEN0(ring->buf_size);
++ 
++ 		rxd->rxd3 = 0;
++ 		rxd->rxd4 = 0;
+++		if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+++			rxd->rxd5 = 0;
+++			rxd->rxd6 = 0;
+++			rxd->rxd7 = 0;
+++			rxd->rxd8 = 0;
+++		}
++ 	}
++ 	ring->dma_size = rx_dma_size;
++ 	ring->calc_idx_update = false;
++ 	ring->calc_idx = rx_dma_size - 1;
++-	ring->crx_idx_reg = reg_map->pdma.pcrx_ptr + ring_no * MTK_QRX_OFFSET;
+++	if (rx_flag == MTK_RX_FLAGS_QDMA)
+++		ring->crx_idx_reg = reg_map->qdma.qcrx_ptr +
+++				    ring_no * MTK_QRX_OFFSET;
+++	else
+++		ring->crx_idx_reg = reg_map->pdma.pcrx_ptr +
+++				    ring_no * MTK_QRX_OFFSET;
++ 	/* make sure that all changes to the dma ring are flushed before we
++ 	 * continue
++ 	 */
++ 	wmb();
++ 
++-	mtk_w32(eth, ring->phys,
++-		reg_map->pdma.rx_ptr + ring_no * MTK_QRX_OFFSET + offset);
++-	mtk_w32(eth, rx_dma_size,
++-		reg_map->pdma.rx_cnt_cfg + ring_no * MTK_QRX_OFFSET + offset);
++-	mtk_w32(eth, ring->calc_idx, ring->crx_idx_reg + offset);
++-	mtk_w32(eth, MTK_PST_DRX_IDX_CFG(ring_no), reg_map->pdma.rst_idx + offset);
+++	if (rx_flag == MTK_RX_FLAGS_QDMA) {
+++		mtk_w32(eth, ring->phys,
+++			reg_map->qdma.rx_ptr + ring_no * MTK_QRX_OFFSET);
+++		mtk_w32(eth, rx_dma_size,
+++			reg_map->qdma.rx_cnt_cfg + ring_no * MTK_QRX_OFFSET);
+++		mtk_w32(eth, MTK_PST_DRX_IDX_CFG(ring_no),
+++			reg_map->qdma.rst_idx);
+++	} else {
+++		mtk_w32(eth, ring->phys,
+++			reg_map->pdma.rx_ptr + ring_no * MTK_QRX_OFFSET);
+++		mtk_w32(eth, rx_dma_size,
+++			reg_map->pdma.rx_cnt_cfg + ring_no * MTK_QRX_OFFSET);
+++		mtk_w32(eth, MTK_PST_DRX_IDX_CFG(ring_no),
+++			reg_map->pdma.rst_idx);
+++	}
+++	mtk_w32(eth, ring->calc_idx, ring->crx_idx_reg);
++ 
++ 	return 0;
++ }
++@@ -2297,7 +2405,7 @@ static irqreturn_t mtk_handle_irq_rx(int
++ 	eth->rx_events++;
++ 	if (likely(napi_schedule_prep(&eth->rx_napi))) {
++ 		__napi_schedule(&eth->rx_napi);
++-		mtk_rx_irq_disable(eth, MTK_RX_DONE_INT);
+++		mtk_rx_irq_disable(eth, eth->soc->txrx.rx_irq_done_mask);
++ 	}
++ 
++ 	return IRQ_HANDLED;
++@@ -2321,8 +2429,10 @@ static irqreturn_t mtk_handle_irq(int ir
++ 	struct mtk_eth *eth = _eth;
++ 	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
++ 
++-	if (mtk_r32(eth, reg_map->pdma.irq_mask) & MTK_RX_DONE_INT) {
++-		if (mtk_r32(eth, reg_map->pdma.irq_status) & MTK_RX_DONE_INT)
+++	if (mtk_r32(eth, reg_map->pdma.irq_mask) &
+++	    eth->soc->txrx.rx_irq_done_mask) {
+++		if (mtk_r32(eth, reg_map->pdma.irq_status) &
+++		    eth->soc->txrx.rx_irq_done_mask)
++ 			mtk_handle_irq_rx(irq, _eth);
++ 	}
++ 	if (mtk_r32(eth, reg_map->tx_irq_mask) & MTK_TX_DONE_INT) {
++@@ -2340,16 +2450,16 @@ static void mtk_poll_controller(struct n
++ 	struct mtk_eth *eth = mac->hw;
++ 
++ 	mtk_tx_irq_disable(eth, MTK_TX_DONE_INT);
++-	mtk_rx_irq_disable(eth, MTK_RX_DONE_INT);
+++	mtk_rx_irq_disable(eth, eth->soc->txrx.rx_irq_done_mask);
++ 	mtk_handle_irq_rx(eth->irq[2], dev);
++ 	mtk_tx_irq_enable(eth, MTK_TX_DONE_INT);
++-	mtk_rx_irq_enable(eth, MTK_RX_DONE_INT);
+++	mtk_rx_irq_enable(eth, eth->soc->txrx.rx_irq_done_mask);
++ }
++ #endif
++ 
++ static int mtk_start_dma(struct mtk_eth *eth)
++ {
++-	u32 rx_2b_offset = (NET_IP_ALIGN == 2) ? MTK_RX_2B_OFFSET : 0;
+++	u32 val, rx_2b_offset = (NET_IP_ALIGN == 2) ? MTK_RX_2B_OFFSET : 0;
++ 	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
++ 	int err;
++ 
++@@ -2360,12 +2470,19 @@ static int mtk_start_dma(struct mtk_eth
++ 	}
++ 
++ 	if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
++-		mtk_w32(eth,
++-			MTK_TX_WB_DDONE | MTK_TX_DMA_EN |
++-			MTK_TX_BT_32DWORDS | MTK_NDP_CO_PRO |
++-			MTK_RX_DMA_EN | MTK_RX_2B_OFFSET |
++-			MTK_RX_BT_32DWORDS,
++-			reg_map->qdma.glo_cfg);
+++		val = mtk_r32(eth, reg_map->qdma.glo_cfg);
+++		val |= MTK_TX_DMA_EN | MTK_RX_DMA_EN |
+++		       MTK_TX_BT_32DWORDS | MTK_NDP_CO_PRO |
+++		       MTK_RX_2B_OFFSET | MTK_TX_WB_DDONE;
+++
+++		if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
+++			val |= MTK_MUTLI_CNT | MTK_RESV_BUF |
+++			       MTK_WCOMP_EN | MTK_DMAD_WR_WDONE |
+++			       MTK_CHK_DDONE_EN;
+++		else
+++			val |= MTK_RX_BT_32DWORDS;
+++		mtk_w32(eth, val, reg_map->qdma.glo_cfg);
+++
++ 		mtk_w32(eth,
++ 			MTK_RX_DMA_EN | rx_2b_offset |
++ 			MTK_RX_BT_32DWORDS | MTK_MULTI_EN,
++@@ -2437,7 +2554,7 @@ static int mtk_open(struct net_device *d
++ 		napi_enable(&eth->tx_napi);
++ 		napi_enable(&eth->rx_napi);
++ 		mtk_tx_irq_enable(eth, MTK_TX_DONE_INT);
++-		mtk_rx_irq_enable(eth, MTK_RX_DONE_INT);
+++		mtk_rx_irq_enable(eth, eth->soc->txrx.rx_irq_done_mask);
++ 		refcount_set(&eth->dma_refcnt, 1);
++ 	}
++ 	else
++@@ -2489,7 +2606,7 @@ static int mtk_stop(struct net_device *d
++ 	mtk_gdm_config(eth, MTK_GDMA_DROP_ALL);
++ 
++ 	mtk_tx_irq_disable(eth, MTK_TX_DONE_INT);
++-	mtk_rx_irq_disable(eth, MTK_RX_DONE_INT);
+++	mtk_rx_irq_disable(eth, eth->soc->txrx.rx_irq_done_mask);
++ 	napi_disable(&eth->tx_napi);
++ 	napi_disable(&eth->rx_napi);
++ 
++@@ -2649,9 +2766,25 @@ static int mtk_hw_init(struct mtk_eth *e
++ 		return 0;
++ 	}
++ 
++-	/* Non-MT7628 handling... */
++-	ethsys_reset(eth, RSTCTRL_FE);
++-	ethsys_reset(eth, RSTCTRL_PPE);
+++	val = RSTCTRL_FE | RSTCTRL_PPE;
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+++		regmap_write(eth->ethsys, ETHSYS_FE_RST_CHK_IDLE_EN, 0);
+++
+++		val |= RSTCTRL_ETH;
+++		if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
+++			val |= RSTCTRL_PPE1;
+++	}
+++
+++	ethsys_reset(eth, val);
+++
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+++		regmap_write(eth->ethsys, ETHSYS_FE_RST_CHK_IDLE_EN,
+++			     0x3ffffff);
+++
+++		/* Set FE to PDMAv2 if necessary */
+++		val = mtk_r32(eth, MTK_FE_GLO_MISC);
+++		mtk_w32(eth,  val | BIT(4), MTK_FE_GLO_MISC);
+++	}
++ 
++ 	if (eth->pctl) {
++ 		/* Set GE2 driving and slew rate */
++@@ -2690,11 +2823,47 @@ static int mtk_hw_init(struct mtk_eth *e
++ 
++ 	/* FE int grouping */
++ 	mtk_w32(eth, MTK_TX_DONE_INT, reg_map->pdma.int_grp);
++-	mtk_w32(eth, MTK_RX_DONE_INT, reg_map->pdma.int_grp + 4);
+++	mtk_w32(eth, eth->soc->txrx.rx_irq_done_mask, reg_map->pdma.int_grp + 4);
++ 	mtk_w32(eth, MTK_TX_DONE_INT, reg_map->qdma.int_grp);
++-	mtk_w32(eth, MTK_RX_DONE_INT, reg_map->qdma.int_grp + 4);
+++	mtk_w32(eth, eth->soc->txrx.rx_irq_done_mask, reg_map->qdma.int_grp + 4);
++ 	mtk_w32(eth, 0x21021000, MTK_FE_INT_GRP);
++ 
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+++		/* PSE should not drop port8 and port9 packets */
+++		mtk_w32(eth, 0x00000300, PSE_DROP_CFG);
+++
+++		/* PSE Free Queue Flow Control  */
+++		mtk_w32(eth, 0x01fa01f4, PSE_FQFC_CFG2);
+++
+++		/* PSE config input queue threshold */
+++		mtk_w32(eth, 0x001a000e, PSE_IQ_REV(1));
+++		mtk_w32(eth, 0x01ff001a, PSE_IQ_REV(2));
+++		mtk_w32(eth, 0x000e01ff, PSE_IQ_REV(3));
+++		mtk_w32(eth, 0x000e000e, PSE_IQ_REV(4));
+++		mtk_w32(eth, 0x000e000e, PSE_IQ_REV(5));
+++		mtk_w32(eth, 0x000e000e, PSE_IQ_REV(6));
+++		mtk_w32(eth, 0x000e000e, PSE_IQ_REV(7));
+++		mtk_w32(eth, 0x000e000e, PSE_IQ_REV(8));
+++
+++		/* PSE config output queue threshold */
+++		mtk_w32(eth, 0x000f000a, PSE_OQ_TH(1));
+++		mtk_w32(eth, 0x001a000f, PSE_OQ_TH(2));
+++		mtk_w32(eth, 0x000f001a, PSE_OQ_TH(3));
+++		mtk_w32(eth, 0x01ff000f, PSE_OQ_TH(4));
+++		mtk_w32(eth, 0x000f000f, PSE_OQ_TH(5));
+++		mtk_w32(eth, 0x0006000f, PSE_OQ_TH(6));
+++		mtk_w32(eth, 0x00060006, PSE_OQ_TH(7));
+++		mtk_w32(eth, 0x00060006, PSE_OQ_TH(8));
+++
+++		/* GDM and CDM Threshold */
+++		mtk_w32(eth, 0x00000004, MTK_GDM2_THRES);
+++		mtk_w32(eth, 0x00000004, MTK_CDMW0_THRES);
+++		mtk_w32(eth, 0x00000004, MTK_CDMW1_THRES);
+++		mtk_w32(eth, 0x00000004, MTK_CDME0_THRES);
+++		mtk_w32(eth, 0x00000004, MTK_CDME1_THRES);
+++		mtk_w32(eth, 0x00000004, MTK_CDMM_THRES);
+++	}
+++
++ 	return 0;
++ 
++ err_disable_pm:
++@@ -3231,12 +3400,8 @@ static int mtk_probe(struct platform_dev
++ 	if (IS_ERR(eth->base))
++ 		return PTR_ERR(eth->base);
++ 
++-	if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628)) {
++-		eth->rx_dma_l4_valid = RX_DMA_L4_VALID_PDMA;
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628))
++ 		eth->ip_align = NET_IP_ALIGN;
++-	} else {
++-		eth->rx_dma_l4_valid = RX_DMA_L4_VALID;
++-	}
++ 
++ 	spin_lock_init(&eth->page_lock);
++ 	spin_lock_init(&eth->tx_irq_lock);
++@@ -3472,6 +3637,10 @@ static const struct mtk_soc_data mt2701_
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
++ 		.rxd_size = sizeof(struct mtk_rx_dma),
+++		.rx_irq_done_mask = MTK_RX_DONE_INT,
+++		.rx_dma_l4_valid = RX_DMA_L4_VALID,
+++		.dma_max_len = MTK_TX_DMA_BUF_LEN,
+++		.dma_len_offset = 16,
++ 	},
++ };
++ 
++@@ -3485,6 +3654,10 @@ static const struct mtk_soc_data mt7621_
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
++ 		.rxd_size = sizeof(struct mtk_rx_dma),
+++		.rx_irq_done_mask = MTK_RX_DONE_INT,
+++		.rx_dma_l4_valid = RX_DMA_L4_VALID,
+++		.dma_max_len = MTK_TX_DMA_BUF_LEN,
+++		.dma_len_offset = 16,
++ 	},
++ };
++ 
++@@ -3499,6 +3672,10 @@ static const struct mtk_soc_data mt7622_
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
++ 		.rxd_size = sizeof(struct mtk_rx_dma),
+++		.rx_irq_done_mask = MTK_RX_DONE_INT,
+++		.rx_dma_l4_valid = RX_DMA_L4_VALID,
+++		.dma_max_len = MTK_TX_DMA_BUF_LEN,
+++		.dma_len_offset = 16,
++ 	},
++ };
++ 
++@@ -3512,6 +3689,10 @@ static const struct mtk_soc_data mt7623_
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
++ 		.rxd_size = sizeof(struct mtk_rx_dma),
+++		.rx_irq_done_mask = MTK_RX_DONE_INT,
+++		.rx_dma_l4_valid = RX_DMA_L4_VALID,
+++		.dma_max_len = MTK_TX_DMA_BUF_LEN,
+++		.dma_len_offset = 16,
++ 	},
++ };
++ 
++@@ -3525,6 +3706,10 @@ static const struct mtk_soc_data mt7629_
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
++ 		.rxd_size = sizeof(struct mtk_rx_dma),
+++		.rx_irq_done_mask = MTK_RX_DONE_INT,
+++		.rx_dma_l4_valid = RX_DMA_L4_VALID,
+++		.dma_max_len = MTK_TX_DMA_BUF_LEN,
+++		.dma_len_offset = 16,
++ 	},
++ };
++ 
++@@ -3537,6 +3722,10 @@ static const struct mtk_soc_data rt5350_
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
++ 		.rxd_size = sizeof(struct mtk_rx_dma),
+++		.rx_irq_done_mask = MTK_RX_DONE_INT,
+++		.rx_dma_l4_valid = RX_DMA_L4_VALID_PDMA,
+++		.dma_max_len = MTK_TX_DMA_BUF_LEN,
+++		.dma_len_offset = 16,
++ 	},
++ };
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -23,6 +23,7 @@
++ #define MTK_MAX_RX_LENGTH	1536
++ #define MTK_MAX_RX_LENGTH_2K	2048
++ #define MTK_TX_DMA_BUF_LEN	0x3fff
+++#define MTK_TX_DMA_BUF_LEN_V2	0xffff
++ #define MTK_DMA_SIZE		512
++ #define MTK_NAPI_WEIGHT		64
++ #define MTK_MAC_COUNT		2
++@@ -83,6 +84,10 @@
++ #define MTK_CDMQ_IG_CTRL	0x1400
++ #define MTK_CDMQ_STAG_EN	BIT(0)
++ 
+++/* CDMP Ingress Control Register */
+++#define MTK_CDMP_IG_CTRL	0x400
+++#define MTK_CDMP_STAG_EN	BIT(0)
+++
++ /* CDMP Exgress Control Register */
++ #define MTK_CDMP_EG_CTRL	0x404
++ 
++@@ -102,13 +107,38 @@
++ /* Unicast Filter MAC Address Register - High */
++ #define MTK_GDMA_MAC_ADRH(x)	(0x50C + (x * 0x1000))
++ 
+++/* FE global misc reg*/
+++#define MTK_FE_GLO_MISC         0x124
+++
+++/* PSE Free Queue Flow Control  */
+++#define PSE_FQFC_CFG1		0x100
+++#define PSE_FQFC_CFG2		0x104
+++#define PSE_DROP_CFG		0x108
+++
+++/* PSE Input Queue Reservation Register*/
+++#define PSE_IQ_REV(x)		(0x140 + (((x) - 1) << 2))
+++
+++/* PSE Output Queue Threshold Register*/
+++#define PSE_OQ_TH(x)		(0x160 + (((x) - 1) << 2))
+++
+++/* GDM and CDM Threshold */
+++#define MTK_GDM2_THRES		0x1530
+++#define MTK_CDMW0_THRES		0x164c
+++#define MTK_CDMW1_THRES		0x1650
+++#define MTK_CDME0_THRES		0x1654
+++#define MTK_CDME1_THRES		0x1658
+++#define MTK_CDMM_THRES		0x165c
+++
++ /* PDMA HW LRO Control Registers */
++ #define MTK_PDMA_LRO_CTRL_DW0	0x980
++ #define MTK_LRO_EN			BIT(0)
++ #define MTK_L3_CKS_UPD_EN		BIT(7)
+++#define MTK_L3_CKS_UPD_EN_V2		BIT(19)
++ #define MTK_LRO_ALT_PKT_CNT_MODE	BIT(21)
++ #define MTK_LRO_RING_RELINQUISH_REQ	(0x7 << 26)
+++#define MTK_LRO_RING_RELINQUISH_REQ_V2	(0xf << 24)
++ #define MTK_LRO_RING_RELINQUISH_DONE	(0x7 << 29)
+++#define MTK_LRO_RING_RELINQUISH_DONE_V2	(0xf << 28)
++ 
++ #define MTK_PDMA_LRO_CTRL_DW1	0x984
++ #define MTK_PDMA_LRO_CTRL_DW2	0x988
++@@ -180,6 +210,13 @@
++ #define MTK_TX_DMA_EN		BIT(0)
++ #define MTK_DMA_BUSY_TIMEOUT_US	1000000
++ 
+++/* QDMA V2 Global Configuration Register */
+++#define MTK_CHK_DDONE_EN	BIT(28)
+++#define MTK_DMAD_WR_WDONE	BIT(26)
+++#define MTK_WCOMP_EN		BIT(24)
+++#define MTK_RESV_BUF		(0x40 << 16)
+++#define MTK_MUTLI_CNT		(0x4 << 12)
+++
++ /* QDMA Flow Control Register */
++ #define FC_THRES_DROP_MODE	BIT(20)
++ #define FC_THRES_DROP_EN	(7 << 16)
++@@ -199,11 +236,32 @@
++ #define MTK_RX_DONE_INT		MTK_RX_DONE_DLY
++ #define MTK_TX_DONE_INT		MTK_TX_DONE_DLY
++ 
+++#define MTK_RX_DONE_INT_V2	BIT(14)
+++
++ /* QDMA Interrupt grouping registers */
++ #define MTK_RLS_DONE_INT	BIT(0)
++ 
++ #define MTK_STAT_OFFSET		0x40
++ 
+++/* QDMA TX NUM */
+++#define MTK_QDMA_TX_NUM		16
+++#define MTK_QDMA_TX_MASK	(MTK_QDMA_TX_NUM - 1)
+++#define QID_BITS_V2(x)		(((x) & 0x3f) << 16)
+++#define MTK_QDMA_GMAC2_QID	8
+++
+++#define MTK_TX_DMA_BUF_SHIFT	8
+++
+++/* QDMA V2 descriptor txd6 */
+++#define TX_DMA_INS_VLAN_V2	BIT(16)
+++/* QDMA V2 descriptor txd5 */
+++#define TX_DMA_CHKSUM_V2	(0x7 << 28)
+++#define TX_DMA_TSO_V2		BIT(31)
+++
+++/* QDMA V2 descriptor txd4 */
+++#define TX_DMA_FPORT_SHIFT_V2	8
+++#define TX_DMA_FPORT_MASK_V2	0xf
+++#define TX_DMA_SWC_V2		BIT(30)
+++
++ #define MTK_WDMA0_BASE		0x2800
++ #define MTK_WDMA1_BASE		0x2c00
++ 
++@@ -217,10 +275,9 @@
++ /* QDMA descriptor txd3 */
++ #define TX_DMA_OWNER_CPU	BIT(31)
++ #define TX_DMA_LS0		BIT(30)
++-#define TX_DMA_PLEN0(_x)	(((_x) & MTK_TX_DMA_BUF_LEN) << 16)
++-#define TX_DMA_PLEN1(_x)	((_x) & MTK_TX_DMA_BUF_LEN)
+++#define TX_DMA_PLEN0(x)		(((x) & eth->soc->txrx.dma_max_len) << eth->soc->txrx.dma_len_offset)
+++#define TX_DMA_PLEN1(x)		((x) & eth->soc->txrx.dma_max_len)
++ #define TX_DMA_SWC		BIT(14)
++-#define TX_DMA_SDL(_x)		(((_x) & 0x3fff) << 16)
++ 
++ /* PDMA on MT7628 */
++ #define TX_DMA_DONE		BIT(31)
++@@ -230,12 +287,14 @@
++ /* QDMA descriptor rxd2 */
++ #define RX_DMA_DONE		BIT(31)
++ #define RX_DMA_LSO		BIT(30)
++-#define RX_DMA_PLEN0(_x)	(((_x) & 0x3fff) << 16)
++-#define RX_DMA_GET_PLEN0(_x)	(((_x) >> 16) & 0x3fff)
+++#define RX_DMA_PREP_PLEN0(x)	(((x) & eth->soc->txrx.dma_max_len) << eth->soc->txrx.dma_len_offset)
+++#define RX_DMA_GET_PLEN0(x)	(((x) >> eth->soc->txrx.dma_len_offset) & eth->soc->txrx.dma_max_len)
++ #define RX_DMA_VTAG		BIT(15)
++ 
++ /* QDMA descriptor rxd3 */
++-#define RX_DMA_VID(_x)		((_x) & 0xfff)
+++#define RX_DMA_VID(x)		((x) & VLAN_VID_MASK)
+++#define RX_DMA_TCI(x)		((x) & (VLAN_PRIO_MASK | VLAN_VID_MASK))
+++#define RX_DMA_VPID(x)		(((x) >> 16) & 0xffff)
++ 
++ /* QDMA descriptor rxd4 */
++ #define MTK_RXD4_FOE_ENTRY	GENMASK(13, 0)
++@@ -246,10 +305,15 @@
++ /* QDMA descriptor rxd4 */
++ #define RX_DMA_L4_VALID		BIT(24)
++ #define RX_DMA_L4_VALID_PDMA	BIT(30)		/* when PDMA is used */
++-#define RX_DMA_FPORT_SHIFT	19
++-#define RX_DMA_FPORT_MASK	0x7
++ #define RX_DMA_SPECIAL_TAG	BIT(22)
++ 
+++#define RX_DMA_GET_SPORT(x)	(((x) >> 19) & 0xf)
+++#define RX_DMA_GET_SPORT_V2(x)	(((x) >> 26) & 0x7)
+++
+++/* PDMA V2 descriptor rxd3 */
+++#define RX_DMA_VTAG_V2		BIT(0)
+++#define RX_DMA_L4_VALID_V2	BIT(2)
+++
++ /* PHY Indirect Access Control registers */
++ #define MTK_PHY_IAC		0x10004
++ #define PHY_IAC_ACCESS		BIT(31)
++@@ -370,6 +434,16 @@
++ #define ETHSYS_TRGMII_MT7621_DDR_PLL	BIT(5)
++ 
++ /* ethernet reset control register */
+++#define ETHSYS_RSTCTRL			0x34
+++#define RSTCTRL_FE			BIT(6)
+++#define RSTCTRL_PPE			BIT(31)
+++#define RSTCTRL_PPE1			BIT(30)
+++#define RSTCTRL_ETH			BIT(23)
+++
+++/* ethernet reset check idle register */
+++#define ETHSYS_FE_RST_CHK_IDLE_EN	0x28
+++
+++/* ethernet reset control register */
++ #define ETHSYS_RSTCTRL		0x34
++ #define RSTCTRL_FE		BIT(6)
++ #define RSTCTRL_PPE		BIT(31)
++@@ -453,6 +527,17 @@ struct mtk_rx_dma {
++ 	unsigned int rxd4;
++ } __packed __aligned(4);
++ 
+++struct mtk_rx_dma_v2 {
+++	unsigned int rxd1;
+++	unsigned int rxd2;
+++	unsigned int rxd3;
+++	unsigned int rxd4;
+++	unsigned int rxd5;
+++	unsigned int rxd6;
+++	unsigned int rxd7;
+++	unsigned int rxd8;
+++} __packed __aligned(4);
+++
++ struct mtk_tx_dma {
++ 	unsigned int txd1;
++ 	unsigned int txd2;
++@@ -460,6 +545,17 @@ struct mtk_tx_dma {
++ 	unsigned int txd4;
++ } __packed __aligned(4);
++ 
+++struct mtk_tx_dma_v2 {
+++	unsigned int txd1;
+++	unsigned int txd2;
+++	unsigned int txd3;
+++	unsigned int txd4;
+++	unsigned int txd5;
+++	unsigned int txd6;
+++	unsigned int txd7;
+++	unsigned int txd8;
+++} __packed __aligned(4);
+++
++ struct mtk_eth;
++ struct mtk_mac;
++ 
++@@ -646,7 +742,9 @@ enum mkt_eth_capabilities {
++ 	MTK_SHARED_INT_BIT,
++ 	MTK_TRGMII_MT7621_CLK_BIT,
++ 	MTK_QDMA_BIT,
+++	MTK_NETSYS_V2_BIT,
++ 	MTK_SOC_MT7628_BIT,
+++	MTK_RSTCTRL_PPE1_BIT,
++ 
++ 	/* MUX BITS*/
++ 	MTK_ETH_MUX_GDM1_TO_GMAC1_ESW_BIT,
++@@ -678,7 +776,9 @@ enum mkt_eth_capabilities {
++ #define MTK_SHARED_INT		BIT(MTK_SHARED_INT_BIT)
++ #define MTK_TRGMII_MT7621_CLK	BIT(MTK_TRGMII_MT7621_CLK_BIT)
++ #define MTK_QDMA		BIT(MTK_QDMA_BIT)
+++#define MTK_NETSYS_V2		BIT(MTK_NETSYS_V2_BIT)
++ #define MTK_SOC_MT7628		BIT(MTK_SOC_MT7628_BIT)
+++#define MTK_RSTCTRL_PPE1	BIT(MTK_RSTCTRL_PPE1_BIT)
++ 
++ #define MTK_ETH_MUX_GDM1_TO_GMAC1_ESW		\
++ 	BIT(MTK_ETH_MUX_GDM1_TO_GMAC1_ESW_BIT)
++@@ -755,6 +855,7 @@ struct mtk_tx_dma_desc_info {
++ 	dma_addr_t	addr;
++ 	u32		size;
++ 	u16		vlan_tci;
+++	u16		qid;
++ 	u8		gso:1;
++ 	u8		csum:1;
++ 	u8		vlan:1;
++@@ -812,6 +913,10 @@ struct mtk_reg_map {
++  *				the extra setup for those pins used by GMAC.
++  * @txd_size			Tx DMA descriptor size.
++  * @rxd_size			Rx DMA descriptor size.
+++ * @rx_irq_done_mask		Rx irq done register mask.
+++ * @rx_dma_l4_valid		Rx DMA valid register mask.
+++ * @dma_max_len			Max DMA tx/rx buffer length.
+++ * @dma_len_offset		Tx/Rx DMA length field offset.
++  */
++ struct mtk_soc_data {
++ 	const struct mtk_reg_map *reg_map;
++@@ -824,6 +929,10 @@ struct mtk_soc_data {
++ 	struct {
++ 		u32	txd_size;
++ 		u32	rxd_size;
+++		u32	rx_irq_done_mask;
+++		u32	rx_dma_l4_valid;
+++		u32	dma_max_len;
+++		u32	dma_len_offset;
++ 	} txrx;
++ };
++ 
++@@ -942,7 +1051,6 @@ struct mtk_eth {
++ 	u32				tx_bytes;
++ 	struct dim			tx_dim;
++ 
++-	u32				rx_dma_l4_valid;
++ 	int				ip_align;
++ 
++ 	struct mtk_ppe			*ppe;
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-28-net-ethernet-mtk_eth_soc-convert-ring-dma-pointer-to.patch b/target/linux/generic/backport-5.15/702-v5.19-28-net-ethernet-mtk_eth_soc-convert-ring-dma-pointer-to.patch
+new file mode 100644
+index 0000000000..a956639239
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-28-net-ethernet-mtk_eth_soc-convert-ring-dma-pointer-to.patch
+@@ -0,0 +1,135 @@
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 20 May 2022 20:11:37 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: convert ring dma pointer to void
++
++Simplify the code converting {tx,rx} ring dma pointer to void
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -946,18 +946,15 @@ static int mtk_init_fq_dma(struct mtk_et
++ 	return 0;
++ }
++ 
++-static inline void *mtk_qdma_phys_to_virt(struct mtk_tx_ring *ring, u32 desc)
+++static void *mtk_qdma_phys_to_virt(struct mtk_tx_ring *ring, u32 desc)
++ {
++-	void *ret = ring->dma;
++-
++-	return ret + (desc - ring->phys);
+++	return ring->dma + (desc - ring->phys);
++ }
++ 
++ static struct mtk_tx_buf *mtk_desc_to_tx_buf(struct mtk_tx_ring *ring,
++-					     struct mtk_tx_dma *txd,
++-					     u32 txd_size)
+++					     void *txd, u32 txd_size)
++ {
++-	int idx = ((void *)txd - (void *)ring->dma) / txd_size;
+++	int idx = (txd - ring->dma) / txd_size;
++ 
++ 	return &ring->buf[idx];
++ }
++@@ -965,13 +962,12 @@ static struct mtk_tx_buf *mtk_desc_to_tx
++ static struct mtk_tx_dma *qdma_to_pdma(struct mtk_tx_ring *ring,
++ 				       struct mtk_tx_dma *dma)
++ {
++-	return ring->dma_pdma - ring->dma + dma;
+++	return ring->dma_pdma - (struct mtk_tx_dma *)ring->dma + dma;
++ }
++ 
++-static int txd_to_idx(struct mtk_tx_ring *ring, struct mtk_tx_dma *dma,
++-		      u32 txd_size)
+++static int txd_to_idx(struct mtk_tx_ring *ring, void *dma, u32 txd_size)
++ {
++-	return ((void *)dma - (void *)ring->dma) / txd_size;
+++	return (dma - ring->dma) / txd_size;
++ }
++ 
++ static void mtk_tx_unmap(struct mtk_eth *eth, struct mtk_tx_buf *tx_buf,
++@@ -1388,7 +1384,7 @@ static struct mtk_rx_ring *mtk_get_rx_ri
++ 
++ 		ring = &eth->rx_ring[i];
++ 		idx = NEXT_DESP_IDX(ring->calc_idx, ring->dma_size);
++-		rxd = (void *)ring->dma + idx * eth->soc->txrx.rxd_size;
+++		rxd = ring->dma + idx * eth->soc->txrx.rxd_size;
++ 		if (rxd->rxd2 & RX_DMA_DONE) {
++ 			ring->calc_idx_update = true;
++ 			return ring;
++@@ -1440,7 +1436,7 @@ static int mtk_poll_rx(struct napi_struc
++ 			goto rx_done;
++ 
++ 		idx = NEXT_DESP_IDX(ring->calc_idx, ring->dma_size);
++-		rxd = (void *)ring->dma + idx * eth->soc->txrx.rxd_size;
+++		rxd = ring->dma + idx * eth->soc->txrx.rxd_size;
++ 		data = ring->data[idx];
++ 
++ 		if (!mtk_rx_get_desc(eth, &trxd, rxd))
++@@ -1647,7 +1643,7 @@ static int mtk_poll_tx_pdma(struct mtk_e
++ 
++ 		mtk_tx_unmap(eth, tx_buf, true);
++ 
++-		desc = (void *)ring->dma + cpu * eth->soc->txrx.txd_size;
+++		desc = ring->dma + cpu * eth->soc->txrx.txd_size;
++ 		ring->last_free = desc;
++ 		atomic_inc(&ring->free_count);
++ 
++@@ -1792,7 +1788,7 @@ static int mtk_tx_alloc(struct mtk_eth *
++ 		int next = (i + 1) % MTK_DMA_SIZE;
++ 		u32 next_ptr = ring->phys + next * sz;
++ 
++-		txd = (void *)ring->dma + i * sz;
+++		txd = ring->dma + i * sz;
++ 		txd->txd2 = next_ptr;
++ 		txd->txd3 = TX_DMA_LS0 | TX_DMA_OWNER_CPU;
++ 		txd->txd4 = 0;
++@@ -1822,7 +1818,7 @@ static int mtk_tx_alloc(struct mtk_eth *
++ 
++ 	ring->dma_size = MTK_DMA_SIZE;
++ 	atomic_set(&ring->free_count, MTK_DMA_SIZE - 2);
++-	ring->next_free = &ring->dma[0];
+++	ring->next_free = ring->dma;
++ 	ring->last_free = (void *)txd;
++ 	ring->last_free_ptr = (u32)(ring->phys + ((MTK_DMA_SIZE - 1) * sz));
++ 	ring->thresh = MAX_SKB_FRAGS;
++@@ -1937,7 +1933,7 @@ static int mtk_rx_alloc(struct mtk_eth *
++ 		if (unlikely(dma_mapping_error(eth->dma_dev, dma_addr)))
++ 			return -ENOMEM;
++ 
++-		rxd = (void *)ring->dma + i * eth->soc->txrx.rxd_size;
+++		rxd = ring->dma + i * eth->soc->txrx.rxd_size;
++ 		rxd->rxd1 = (unsigned int)dma_addr;
++ 
++ 		if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628))
++@@ -1999,7 +1995,7 @@ static void mtk_rx_clean(struct mtk_eth
++ 			if (!ring->data[i])
++ 				continue;
++ 
++-			rxd = (void *)ring->dma + i * eth->soc->txrx.rxd_size;
+++			rxd = ring->dma + i * eth->soc->txrx.rxd_size;
++ 			if (!rxd->rxd1)
++ 				continue;
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -688,7 +688,7 @@ struct mtk_tx_buf {
++  *			are present
++  */
++ struct mtk_tx_ring {
++-	struct mtk_tx_dma *dma;
+++	void *dma;
++ 	struct mtk_tx_buf *buf;
++ 	dma_addr_t phys;
++ 	struct mtk_tx_dma *next_free;
++@@ -718,7 +718,7 @@ enum mtk_rx_flags {
++  * @calc_idx:		The current head of ring
++  */
++ struct mtk_rx_ring {
++-	struct mtk_rx_dma *dma;
+++	void *dma;
++ 	u8 **data;
++ 	dma_addr_t phys;
++ 	u16 frag_size;
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-29-net-ethernet-mtk_eth_soc-convert-scratch_ring-pointe.patch b/target/linux/generic/backport-5.15/702-v5.19-29-net-ethernet-mtk_eth_soc-convert-scratch_ring-pointe.patch
+new file mode 100644
+index 0000000000..459ffd5406
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-29-net-ethernet-mtk_eth_soc-convert-scratch_ring-pointe.patch
+@@ -0,0 +1,33 @@
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 20 May 2022 20:11:38 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: convert scratch_ring pointer to
++ void
++
++Simplify the code converting scratch_ring pointer to void
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -922,7 +922,7 @@ static int mtk_init_fq_dma(struct mtk_et
++ 	for (i = 0; i < cnt; i++) {
++ 		struct mtk_tx_dma_v2 *txd;
++ 
++-		txd = (void *)eth->scratch_ring + i * soc->txrx.txd_size;
+++		txd = eth->scratch_ring + i * soc->txrx.txd_size;
++ 		txd->txd1 = dma_addr + i * MTK_QDMA_PAGE_SIZE;
++ 		if (i < cnt - 1)
++ 			txd->txd2 = eth->phy_scratch_ring +
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -1028,7 +1028,7 @@ struct mtk_eth {
++ 	struct mtk_rx_ring		rx_ring_qdma;
++ 	struct napi_struct		tx_napi;
++ 	struct napi_struct		rx_napi;
++-	struct mtk_tx_dma		*scratch_ring;
+++	void				*scratch_ring;
++ 	dma_addr_t			phy_scratch_ring;
++ 	void				*scratch_head;
++ 	struct clk			*clks[MTK_CLK_MAX];
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-30-net-ethernet-mtk_eth_soc-introduce-support-for-mt798.patch b/target/linux/generic/backport-5.15/702-v5.19-30-net-ethernet-mtk_eth_soc-introduce-support-for-mt798.patch
+new file mode 100644
+index 0000000000..4baeb2244f
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-30-net-ethernet-mtk_eth_soc-introduce-support-for-mt798.patch
+@@ -0,0 +1,138 @@
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 20 May 2022 20:11:39 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: introduce support for mt7986
++ chipset
++
++Add support for mt7986-eth driver available on mt7986 soc.
++
++Tested-by: Sam Shih <sam.shih@mediatek.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -87,6 +87,43 @@ static const struct mtk_reg_map mt7628_r
++ 	},
++ };
++ 
+++static const struct mtk_reg_map mt7986_reg_map = {
+++	.tx_irq_mask		= 0x461c,
+++	.tx_irq_status		= 0x4618,
+++	.pdma = {
+++		.rx_ptr		= 0x6100,
+++		.rx_cnt_cfg	= 0x6104,
+++		.pcrx_ptr	= 0x6108,
+++		.glo_cfg	= 0x6204,
+++		.rst_idx	= 0x6208,
+++		.delay_irq	= 0x620c,
+++		.irq_status	= 0x6220,
+++		.irq_mask	= 0x6228,
+++		.int_grp	= 0x6250,
+++	},
+++	.qdma = {
+++		.qtx_cfg	= 0x4400,
+++		.rx_ptr		= 0x4500,
+++		.rx_cnt_cfg	= 0x4504,
+++		.qcrx_ptr	= 0x4508,
+++		.glo_cfg	= 0x4604,
+++		.rst_idx	= 0x4608,
+++		.delay_irq	= 0x460c,
+++		.fc_th		= 0x4610,
+++		.int_grp	= 0x4620,
+++		.hred		= 0x4644,
+++		.ctx_ptr	= 0x4700,
+++		.dtx_ptr	= 0x4704,
+++		.crx_ptr	= 0x4710,
+++		.drx_ptr	= 0x4714,
+++		.fq_head	= 0x4720,
+++		.fq_tail	= 0x4724,
+++		.fq_count	= 0x4728,
+++		.fq_blen	= 0x472c,
+++	},
+++	.gdm1_cnt		= 0x1c00,
+++};
+++
++ /* strings used by ethtool */
++ static const struct mtk_ethtool_stats {
++ 	char str[ETH_GSTRING_LEN];
++@@ -110,7 +147,7 @@ static const char * const mtk_clks_sourc
++ 	"ethif", "sgmiitop", "esw", "gp0", "gp1", "gp2", "fe", "trgpll",
++ 	"sgmii_tx250m", "sgmii_rx250m", "sgmii_cdr_ref", "sgmii_cdr_fb",
++ 	"sgmii2_tx250m", "sgmii2_rx250m", "sgmii2_cdr_ref", "sgmii2_cdr_fb",
++-	"sgmii_ck", "eth2pll",
+++	"sgmii_ck", "eth2pll", "wocpu0", "wocpu1", "netsys0", "netsys1"
++ };
++ 
++ void mtk_w32(struct mtk_eth *eth, u32 val, unsigned reg)
++@@ -3709,6 +3746,21 @@ static const struct mtk_soc_data mt7629_
++ 	},
++ };
++ 
+++static const struct mtk_soc_data mt7986_data = {
+++	.reg_map = &mt7986_reg_map,
+++	.ana_rgc3 = 0x128,
+++	.caps = MT7986_CAPS,
+++	.required_clks = MT7986_CLKS_BITMAP,
+++	.required_pctl = false,
+++	.txrx = {
+++		.txd_size = sizeof(struct mtk_tx_dma_v2),
+++		.rxd_size = sizeof(struct mtk_rx_dma_v2),
+++		.rx_irq_done_mask = MTK_RX_DONE_INT_V2,
+++		.dma_max_len = MTK_TX_DMA_BUF_LEN_V2,
+++		.dma_len_offset = 8,
+++	},
+++};
+++
++ static const struct mtk_soc_data rt5350_data = {
++ 	.reg_map = &mt7628_reg_map,
++ 	.caps = MT7628_CAPS,
++@@ -3731,6 +3783,7 @@ const struct of_device_id of_mtk_match[]
++ 	{ .compatible = "mediatek,mt7622-eth", .data = &mt7622_data},
++ 	{ .compatible = "mediatek,mt7623-eth", .data = &mt7623_data},
++ 	{ .compatible = "mediatek,mt7629-eth", .data = &mt7629_data},
+++	{ .compatible = "mediatek,mt7986-eth", .data = &mt7986_data},
++ 	{ .compatible = "ralink,rt5350-eth", .data = &rt5350_data},
++ 	{},
++ };
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -623,6 +623,10 @@ enum mtk_clks_map {
++ 	MTK_CLK_SGMII2_CDR_FB,
++ 	MTK_CLK_SGMII_CK,
++ 	MTK_CLK_ETH2PLL,
+++	MTK_CLK_WOCPU0,
+++	MTK_CLK_WOCPU1,
+++	MTK_CLK_NETSYS0,
+++	MTK_CLK_NETSYS1,
++ 	MTK_CLK_MAX
++ };
++ 
++@@ -653,6 +657,16 @@ enum mtk_clks_map {
++ 				 BIT(MTK_CLK_SGMII2_CDR_FB) | \
++ 				 BIT(MTK_CLK_SGMII_CK) | \
++ 				 BIT(MTK_CLK_ETH2PLL) | BIT(MTK_CLK_SGMIITOP))
+++#define MT7986_CLKS_BITMAP	(BIT(MTK_CLK_FE) | BIT(MTK_CLK_GP2) | BIT(MTK_CLK_GP1) | \
+++				 BIT(MTK_CLK_WOCPU1) | BIT(MTK_CLK_WOCPU0) | \
+++				 BIT(MTK_CLK_SGMII_TX_250M) | \
+++				 BIT(MTK_CLK_SGMII_RX_250M) | \
+++				 BIT(MTK_CLK_SGMII_CDR_REF) | \
+++				 BIT(MTK_CLK_SGMII_CDR_FB) | \
+++				 BIT(MTK_CLK_SGMII2_TX_250M) | \
+++				 BIT(MTK_CLK_SGMII2_RX_250M) | \
+++				 BIT(MTK_CLK_SGMII2_CDR_REF) | \
+++				 BIT(MTK_CLK_SGMII2_CDR_FB))
++ 
++ enum mtk_dev_state {
++ 	MTK_HW_INIT,
++@@ -851,6 +865,10 @@ enum mkt_eth_capabilities {
++ 		      MTK_MUX_U3_GMAC2_TO_QPHY | \
++ 		      MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA)
++ 
+++#define MT7986_CAPS  (MTK_GMAC1_SGMII | MTK_GMAC2_SGMII | \
+++		      MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA | \
+++		      MTK_NETSYS_V2 | MTK_RSTCTRL_PPE1)
+++
++ struct mtk_tx_dma_desc_info {
++ 	dma_addr_t	addr;
++ 	u32		size;
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-31-net-ethernet-mtk_eth_soc-fix-error-code-in-mtk_flow_.patch b/target/linux/generic/backport-5.15/702-v5.19-31-net-ethernet-mtk_eth_soc-fix-error-code-in-mtk_flow_.patch
+new file mode 100644
+index 0000000000..e490333a9b
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-31-net-ethernet-mtk_eth_soc-fix-error-code-in-mtk_flow_.patch
+@@ -0,0 +1,25 @@
++From: Dan Carpenter <dan.carpenter@oracle.com>
++Date: Thu, 19 May 2022 17:08:00 +0300
++Subject: [PATCH] net: ethernet: mtk_eth_soc: fix error code in
++ mtk_flow_offload_replace()
++
++Preserve the error code from mtk_foe_entry_commit().  Do not return
++success.
++
++Fixes: c4f033d9e03e ("net: ethernet: mtk_eth_soc: rework hardware flow table management")
++Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++@@ -434,7 +434,8 @@ mtk_flow_offload_replace(struct mtk_eth
++ 	memcpy(&entry->data, &foe, sizeof(entry->data));
++ 	entry->wed_index = wed_index;
++ 
++-	if (mtk_foe_entry_commit(eth->ppe, entry) < 0)
+++	err = mtk_foe_entry_commit(eth->ppe, entry);
+++	if (err < 0)
++ 		goto free;
++ 
++ 	err = rhashtable_insert_fast(&eth->flow_table, &entry->node,
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-33-net-ethernet-mtk_eth_soc-enable-rx-cksum-offload-for.patch b/target/linux/generic/backport-5.15/702-v5.19-33-net-ethernet-mtk_eth_soc-enable-rx-cksum-offload-for.patch
+new file mode 100644
+index 0000000000..2e3ad698b5
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-33-net-ethernet-mtk_eth_soc-enable-rx-cksum-offload-for.patch
+@@ -0,0 +1,47 @@
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Mon, 6 Jun 2022 21:49:00 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: enable rx cksum offload for
++ MTK_NETSYS_V2
++
++Enable rx checksum offload for mt7986 chipset.
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Link: https://lore.kernel.org/r/c8699805c18f7fd38315fcb8da2787676d83a32c.1654544585.git.lorenzo@kernel.org
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -1462,8 +1462,8 @@ static int mtk_poll_rx(struct napi_struc
++ 	int done = 0, bytes = 0;
++ 
++ 	while (done < budget) {
+++		unsigned int pktlen, *rxdcsum;
++ 		struct net_device *netdev;
++-		unsigned int pktlen;
++ 		dma_addr_t dma_addr;
++ 		u32 hash, reason;
++ 		int mac = 0;
++@@ -1530,7 +1530,13 @@ static int mtk_poll_rx(struct napi_struc
++ 		pktlen = RX_DMA_GET_PLEN0(trxd.rxd2);
++ 		skb->dev = netdev;
++ 		skb_put(skb, pktlen);
++-		if (trxd.rxd4 & eth->soc->txrx.rx_dma_l4_valid)
+++
+++		if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
+++			rxdcsum = &trxd.rxd3;
+++		else
+++			rxdcsum = &trxd.rxd4;
+++
+++		if (*rxdcsum & eth->soc->txrx.rx_dma_l4_valid)
++ 			skb->ip_summed = CHECKSUM_UNNECESSARY;
++ 		else
++ 			skb_checksum_none_assert(skb);
++@@ -3756,6 +3762,7 @@ static const struct mtk_soc_data mt7986_
++ 		.txd_size = sizeof(struct mtk_tx_dma_v2),
++ 		.rxd_size = sizeof(struct mtk_rx_dma_v2),
++ 		.rx_irq_done_mask = MTK_RX_DONE_INT_V2,
+++		.rx_dma_l4_valid = RX_DMA_L4_VALID_V2,
++ 		.dma_max_len = MTK_TX_DMA_BUF_LEN_V2,
++ 		.dma_len_offset = 8,
++ 	},
+diff --git a/target/linux/generic/backport-5.15/702-v5.19-34-eth-mtk_ppe-fix-up-after-merge.patch b/target/linux/generic/backport-5.15/702-v5.19-34-eth-mtk_ppe-fix-up-after-merge.patch
+new file mode 100644
+index 0000000000..5303ca48a7
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/702-v5.19-34-eth-mtk_ppe-fix-up-after-merge.patch
+@@ -0,0 +1,28 @@
++From: Jakub Kicinski <kuba@kernel.org>
++Date: Thu, 19 May 2022 18:25:55 -0700
++Subject: [PATCH] eth: mtk_ppe: fix up after merge
++
++I missed this in the barrage of GCC 12 warnings. Commit cf2df74e202d
++("net: fix dev_fill_forward_path with pppoe + bridge") changed
++the pointer into an array.
++
++Fixes: d7e6f5836038 ("Merge git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net")
++Link: https://lore.kernel.org/r/20220520012555.2262461-1-kuba@kernel.org
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++@@ -90,10 +90,11 @@ mtk_flow_get_wdma_info(struct net_device
++ {
++ 	struct net_device_path_ctx ctx = {
++ 		.dev = dev,
++-		.daddr = addr,
++ 	};
++ 	struct net_device_path path = {};
++ 
+++	memcpy(ctx.daddr, addr, sizeof(ctx.daddr));
+++
++ 	if (!IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED))
++ 		return -1;
++ 
+diff --git a/target/linux/generic/backport-5.15/703-00-v5.16-net-convert-users-of-bitmap_foo-to-linkmode_foo.patch b/target/linux/generic/backport-5.15/703-00-v5.16-net-convert-users-of-bitmap_foo-to-linkmode_foo.patch
+new file mode 100644
+index 0000000000..555e7eaa95
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-00-v5.16-net-convert-users-of-bitmap_foo-to-linkmode_foo.patch
+@@ -0,0 +1,948 @@
++From 4973056cceacc70966396039fae99867dfafd796 Mon Sep 17 00:00:00 2001
++From: Sean Anderson <sean.anderson@seco.com>
++Date: Fri, 22 Oct 2021 18:41:04 -0400
++Subject: [PATCH] net: convert users of bitmap_foo() to linkmode_foo()
++
++This converts instances of
++	bitmap_foo(args..., __ETHTOOL_LINK_MODE_MASK_NBITS)
++to
++	linkmode_foo(args...)
++
++I manually fixed up some lines to prevent them from being excessively
++long. Otherwise, this change was generated with the following semantic
++patch:
++
++// Generated with
++// echo linux/linkmode.h > includes
++// git grep -Flf includes include/ | cut -f 2- -d / | cat includes - \
++// | sort | uniq | tee new_includes | wc -l && mv new_includes includes
++// and repeating until the number stopped going up
++@i@
++@@
++
++(
++ #include <linux/acpi_mdio.h>
++|
++ #include <linux/brcmphy.h>
++|
++ #include <linux/dsa/loop.h>
++|
++ #include <linux/dsa/sja1105.h>
++|
++ #include <linux/ethtool.h>
++|
++ #include <linux/ethtool_netlink.h>
++|
++ #include <linux/fec.h>
++|
++ #include <linux/fs_enet_pd.h>
++|
++ #include <linux/fsl/enetc_mdio.h>
++|
++ #include <linux/fwnode_mdio.h>
++|
++ #include <linux/linkmode.h>
++|
++ #include <linux/lsm_audit.h>
++|
++ #include <linux/mdio-bitbang.h>
++|
++ #include <linux/mdio.h>
++|
++ #include <linux/mdio-mux.h>
++|
++ #include <linux/mii.h>
++|
++ #include <linux/mii_timestamper.h>
++|
++ #include <linux/mlx5/accel.h>
++|
++ #include <linux/mlx5/cq.h>
++|
++ #include <linux/mlx5/device.h>
++|
++ #include <linux/mlx5/driver.h>
++|
++ #include <linux/mlx5/eswitch.h>
++|
++ #include <linux/mlx5/fs.h>
++|
++ #include <linux/mlx5/port.h>
++|
++ #include <linux/mlx5/qp.h>
++|
++ #include <linux/mlx5/rsc_dump.h>
++|
++ #include <linux/mlx5/transobj.h>
++|
++ #include <linux/mlx5/vport.h>
++|
++ #include <linux/of_mdio.h>
++|
++ #include <linux/of_net.h>
++|
++ #include <linux/pcs-lynx.h>
++|
++ #include <linux/pcs/pcs-xpcs.h>
++|
++ #include <linux/phy.h>
++|
++ #include <linux/phy_led_triggers.h>
++|
++ #include <linux/phylink.h>
++|
++ #include <linux/platform_data/bcmgenet.h>
++|
++ #include <linux/platform_data/xilinx-ll-temac.h>
++|
++ #include <linux/pxa168_eth.h>
++|
++ #include <linux/qed/qed_eth_if.h>
++|
++ #include <linux/qed/qed_fcoe_if.h>
++|
++ #include <linux/qed/qed_if.h>
++|
++ #include <linux/qed/qed_iov_if.h>
++|
++ #include <linux/qed/qed_iscsi_if.h>
++|
++ #include <linux/qed/qed_ll2_if.h>
++|
++ #include <linux/qed/qed_nvmetcp_if.h>
++|
++ #include <linux/qed/qed_rdma_if.h>
++|
++ #include <linux/sfp.h>
++|
++ #include <linux/sh_eth.h>
++|
++ #include <linux/smsc911x.h>
++|
++ #include <linux/soc/nxp/lpc32xx-misc.h>
++|
++ #include <linux/stmmac.h>
++|
++ #include <linux/sunrpc/svc_rdma.h>
++|
++ #include <linux/sxgbe_platform.h>
++|
++ #include <net/cfg80211.h>
++|
++ #include <net/dsa.h>
++|
++ #include <net/mac80211.h>
++|
++ #include <net/selftests.h>
++|
++ #include <rdma/ib_addr.h>
++|
++ #include <rdma/ib_cache.h>
++|
++ #include <rdma/ib_cm.h>
++|
++ #include <rdma/ib_hdrs.h>
++|
++ #include <rdma/ib_mad.h>
++|
++ #include <rdma/ib_marshall.h>
++|
++ #include <rdma/ib_pack.h>
++|
++ #include <rdma/ib_pma.h>
++|
++ #include <rdma/ib_sa.h>
++|
++ #include <rdma/ib_smi.h>
++|
++ #include <rdma/ib_umem.h>
++|
++ #include <rdma/ib_umem_odp.h>
++|
++ #include <rdma/ib_verbs.h>
++|
++ #include <rdma/iw_cm.h>
++|
++ #include <rdma/mr_pool.h>
++|
++ #include <rdma/opa_addr.h>
++|
++ #include <rdma/opa_port_info.h>
++|
++ #include <rdma/opa_smi.h>
++|
++ #include <rdma/opa_vnic.h>
++|
++ #include <rdma/rdma_cm.h>
++|
++ #include <rdma/rdma_cm_ib.h>
++|
++ #include <rdma/rdmavt_cq.h>
++|
++ #include <rdma/rdma_vt.h>
++|
++ #include <rdma/rdmavt_qp.h>
++|
++ #include <rdma/rw.h>
++|
++ #include <rdma/tid_rdma_defs.h>
++|
++ #include <rdma/uverbs_ioctl.h>
++|
++ #include <rdma/uverbs_named_ioctl.h>
++|
++ #include <rdma/uverbs_std_types.h>
++|
++ #include <rdma/uverbs_types.h>
++|
++ #include <soc/mscc/ocelot.h>
++|
++ #include <soc/mscc/ocelot_ptp.h>
++|
++ #include <soc/mscc/ocelot_vcap.h>
++|
++ #include <trace/events/ib_mad.h>
++|
++ #include <trace/events/rdma_core.h>
++|
++ #include <trace/events/rdma.h>
++|
++ #include <trace/events/rpcrdma.h>
++|
++ #include <uapi/linux/ethtool.h>
++|
++ #include <uapi/linux/ethtool_netlink.h>
++|
++ #include <uapi/linux/mdio.h>
++|
++ #include <uapi/linux/mii.h>
++)
++
++@depends on i@
++expression list args;
++@@
++
++(
++- bitmap_zero(args, __ETHTOOL_LINK_MODE_MASK_NBITS)
+++ linkmode_zero(args)
++|
++- bitmap_copy(args, __ETHTOOL_LINK_MODE_MASK_NBITS)
+++ linkmode_copy(args)
++|
++- bitmap_and(args, __ETHTOOL_LINK_MODE_MASK_NBITS)
+++ linkmode_and(args)
++|
++- bitmap_or(args, __ETHTOOL_LINK_MODE_MASK_NBITS)
+++ linkmode_or(args)
++|
++- bitmap_empty(args, ETHTOOL_LINK_MODE_MASK_NBITS)
+++ linkmode_empty(args)
++|
++- bitmap_andnot(args, __ETHTOOL_LINK_MODE_MASK_NBITS)
+++ linkmode_andnot(args)
++|
++- bitmap_equal(args, __ETHTOOL_LINK_MODE_MASK_NBITS)
+++ linkmode_equal(args)
++|
++- bitmap_intersects(args, __ETHTOOL_LINK_MODE_MASK_NBITS)
+++ linkmode_intersects(args)
++|
++- bitmap_subset(args, __ETHTOOL_LINK_MODE_MASK_NBITS)
+++ linkmode_subset(args)
++)
++
++Add missing linux/mii.h include to mellanox. -DaveM
++
++Signed-off-by: Sean Anderson <sean.anderson@seco.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/b53/b53_common.c              |  6 ++----
++ drivers/net/dsa/bcm_sf2.c                     |  8 +++----
++ drivers/net/dsa/hirschmann/hellcreek.c        |  6 ++----
++ drivers/net/dsa/lantiq_gswip.c                | 14 ++++++-------
++ drivers/net/dsa/microchip/ksz8795.c           |  8 +++----
++ drivers/net/dsa/mv88e6xxx/chip.c              |  5 ++---
++ drivers/net/dsa/ocelot/felix_vsc9959.c        |  8 +++----
++ drivers/net/dsa/ocelot/seville_vsc9953.c      |  8 +++----
++ drivers/net/dsa/qca/ar9331.c                  | 10 ++++-----
++ drivers/net/dsa/sja1105/sja1105_main.c        |  7 +++----
++ drivers/net/dsa/xrs700x/xrs700x.c             |  8 +++----
++ drivers/net/ethernet/amd/xgbe/xgbe-ethtool.c  |  8 +++----
++ drivers/net/ethernet/atheros/ag71xx.c         |  8 +++----
++ drivers/net/ethernet/cadence/macb_main.c      | 11 +++++-----
++ .../net/ethernet/freescale/enetc/enetc_pf.c   |  8 +++----
++ .../net/ethernet/huawei/hinic/hinic_ethtool.c | 10 ++++-----
++ .../net/ethernet/intel/ixgbe/ixgbe_ethtool.c  |  5 ++---
++ drivers/net/ethernet/marvell/mvneta.c         | 10 ++++-----
++ .../net/ethernet/marvell/mvpp2/mvpp2_main.c   |  7 +++----
++ .../marvell/octeontx2/nic/otx2_ethtool.c      |  5 ++---
++ drivers/net/ethernet/marvell/pxa168_eth.c     |  3 +--
++ .../net/ethernet/mellanox/mlx4/en_ethtool.c   | 21 +++++++------------
++ .../microchip/sparx5/sparx5_phylink.c         |  7 +++----
++ drivers/net/ethernet/mscc/ocelot_net.c        |  7 +++----
++ .../ethernet/pensando/ionic/ionic_ethtool.c   |  3 +--
++ .../net/ethernet/xilinx/xilinx_axienet_main.c |  8 +++----
++ drivers/net/pcs/pcs-xpcs.c                    |  2 +-
++ drivers/net/phy/sfp-bus.c                     |  2 +-
++ net/ethtool/ioctl.c                           |  7 +++----
++ 29 files changed, 87 insertions(+), 133 deletions(-)
++
++--- a/drivers/net/dsa/b53/b53_common.c
+++++ b/drivers/net/dsa/b53/b53_common.c
++@@ -1349,10 +1349,8 @@ void b53_phylink_validate(struct dsa_swi
++ 		phylink_set(mask, 100baseT_Full);
++ 	}
++ 
++-	bitmap_and(supported, supported, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ 
++ 	phylink_helper_basex_speed(state);
++ }
++--- a/drivers/net/dsa/bcm_sf2.c
+++++ b/drivers/net/dsa/bcm_sf2.c
++@@ -686,7 +686,7 @@ static void bcm_sf2_sw_validate(struct d
++ 	    state->interface != PHY_INTERFACE_MODE_GMII &&
++ 	    state->interface != PHY_INTERFACE_MODE_INTERNAL &&
++ 	    state->interface != PHY_INTERFACE_MODE_MOCA) {
++-		bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++		linkmode_zero(supported);
++ 		if (port != core_readl(priv, CORE_IMP0_PRT_ID))
++ 			dev_err(ds->dev,
++ 				"Unsupported interface: %d for port %d\n",
++@@ -714,10 +714,8 @@ static void bcm_sf2_sw_validate(struct d
++ 	phylink_set(mask, 100baseT_Half);
++ 	phylink_set(mask, 100baseT_Full);
++ 
++-	bitmap_and(supported, supported, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ }
++ 
++ static void bcm_sf2_sw_mac_config(struct dsa_switch *ds, int port,
++--- a/drivers/net/dsa/hirschmann/hellcreek.c
+++++ b/drivers/net/dsa/hirschmann/hellcreek.c
++@@ -1476,10 +1476,8 @@ static void hellcreek_phylink_validate(s
++ 	else
++ 		phylink_set(mask, 1000baseT_Full);
++ 
++-	bitmap_and(supported, supported, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ }
++ 
++ static int
++--- a/drivers/net/dsa/lantiq_gswip.c
+++++ b/drivers/net/dsa/lantiq_gswip.c
++@@ -1452,10 +1452,8 @@ static void gswip_phylink_set_capab(unsi
++ 	phylink_set(mask, 100baseT_Half);
++ 	phylink_set(mask, 100baseT_Full);
++ 
++-	bitmap_and(supported, supported, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ }
++ 
++ static void gswip_xrx200_phylink_validate(struct dsa_switch *ds, int port,
++@@ -1483,7 +1481,7 @@ static void gswip_xrx200_phylink_validat
++ 			goto unsupported;
++ 		break;
++ 	default:
++-		bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++		linkmode_zero(supported);
++ 		dev_err(ds->dev, "Unsupported port: %i\n", port);
++ 		return;
++ 	}
++@@ -1493,7 +1491,7 @@ static void gswip_xrx200_phylink_validat
++ 	return;
++ 
++ unsupported:
++-	bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_zero(supported);
++ 	dev_err(ds->dev, "Unsupported interface '%s' for port %d\n",
++ 		phy_modes(state->interface), port);
++ }
++@@ -1523,7 +1521,7 @@ static void gswip_xrx300_phylink_validat
++ 			goto unsupported;
++ 		break;
++ 	default:
++-		bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++		linkmode_zero(supported);
++ 		dev_err(ds->dev, "Unsupported port: %i\n", port);
++ 		return;
++ 	}
++@@ -1533,7 +1531,7 @@ static void gswip_xrx300_phylink_validat
++ 	return;
++ 
++ unsupported:
++-	bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_zero(supported);
++ 	dev_err(ds->dev, "Unsupported interface '%s' for port %d\n",
++ 		phy_modes(state->interface), port);
++ }
++--- a/drivers/net/dsa/microchip/ksz8795.c
+++++ b/drivers/net/dsa/microchip/ksz8795.c
++@@ -1542,15 +1542,13 @@ static void ksz8_validate(struct dsa_swi
++ 	phylink_set(mask, 100baseT_Half);
++ 	phylink_set(mask, 100baseT_Full);
++ 
++-	bitmap_and(supported, supported, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ 
++ 	return;
++ 
++ unsupported:
++-	bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_zero(supported);
++ 	dev_err(ds->dev, "Unsupported interface: %s, port: %d\n",
++ 		phy_modes(state->interface), port);
++ }
++--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++++ b/drivers/net/dsa/mv88e6xxx/chip.c
++@@ -683,9 +683,8 @@ static void mv88e6xxx_validate(struct ds
++ 	if (chip->info->ops->phylink_validate)
++ 		chip->info->ops->phylink_validate(chip, port, mask, state);
++ 
++-	bitmap_and(supported, supported, mask, __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ 
++ 	/* We can only operate at 2500BaseX or 1000BaseX.  If requested
++ 	 * to advertise both, only report advertising at 2500BaseX.
++--- a/drivers/net/dsa/ocelot/felix_vsc9959.c
+++++ b/drivers/net/dsa/ocelot/felix_vsc9959.c
++@@ -944,7 +944,7 @@ static void vsc9959_phylink_validate(str
++ 
++ 	if (state->interface != PHY_INTERFACE_MODE_NA &&
++ 	    state->interface != ocelot_port->phy_mode) {
++-		bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++		linkmode_zero(supported);
++ 		return;
++ 	}
++ 
++@@ -966,10 +966,8 @@ static void vsc9959_phylink_validate(str
++ 		phylink_set(mask, 2500baseX_Full);
++ 	}
++ 
++-	bitmap_and(supported, supported, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ }
++ 
++ static int vsc9959_prevalidate_phy_mode(struct ocelot *ocelot, int port,
++--- a/drivers/net/dsa/ocelot/seville_vsc9953.c
+++++ b/drivers/net/dsa/ocelot/seville_vsc9953.c
++@@ -1000,7 +1000,7 @@ static void vsc9953_phylink_validate(str
++ 
++ 	if (state->interface != PHY_INTERFACE_MODE_NA &&
++ 	    state->interface != ocelot_port->phy_mode) {
++-		bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++		linkmode_zero(supported);
++ 		return;
++ 	}
++ 
++@@ -1019,10 +1019,8 @@ static void vsc9953_phylink_validate(str
++ 		phylink_set(mask, 2500baseX_Full);
++ 	}
++ 
++-	bitmap_and(supported, supported, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ }
++ 
++ static int vsc9953_prevalidate_phy_mode(struct ocelot *ocelot, int port,
++--- a/drivers/net/dsa/qca/ar9331.c
+++++ b/drivers/net/dsa/qca/ar9331.c
++@@ -522,7 +522,7 @@ static void ar9331_sw_phylink_validate(s
++ 			goto unsupported;
++ 		break;
++ 	default:
++-		bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++		linkmode_zero(supported);
++ 		dev_err(ds->dev, "Unsupported port: %i\n", port);
++ 		return;
++ 	}
++@@ -536,15 +536,13 @@ static void ar9331_sw_phylink_validate(s
++ 	phylink_set(mask, 100baseT_Half);
++ 	phylink_set(mask, 100baseT_Full);
++ 
++-	bitmap_and(supported, supported, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ 
++ 	return;
++ 
++ unsupported:
++-	bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_zero(supported);
++ 	dev_err(ds->dev, "Unsupported interface: %d, port: %d\n",
++ 		state->interface, port);
++ }
++--- a/drivers/net/dsa/sja1105/sja1105_main.c
+++++ b/drivers/net/dsa/sja1105/sja1105_main.c
++@@ -1360,7 +1360,7 @@ static void sja1105_phylink_validate(str
++ 	 */
++ 	if (state->interface != PHY_INTERFACE_MODE_NA &&
++ 	    sja1105_phy_mode_mismatch(priv, port, state->interface)) {
++-		bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++		linkmode_zero(supported);
++ 		return;
++ 	}
++ 
++@@ -1380,9 +1380,8 @@ static void sja1105_phylink_validate(str
++ 		phylink_set(mask, 2500baseX_Full);
++ 	}
++ 
++-	bitmap_and(supported, supported, mask, __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ }
++ 
++ static int
++--- a/drivers/net/dsa/xrs700x/xrs700x.c
+++++ b/drivers/net/dsa/xrs700x/xrs700x.c
++@@ -457,7 +457,7 @@ static void xrs700x_phylink_validate(str
++ 		phylink_set(mask, 1000baseT_Full);
++ 		break;
++ 	default:
++-		bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++		linkmode_zero(supported);
++ 		dev_err(ds->dev, "Unsupported port: %i\n", port);
++ 		return;
++ 	}
++@@ -468,10 +468,8 @@ static void xrs700x_phylink_validate(str
++ 	phylink_set(mask, 10baseT_Full);
++ 	phylink_set(mask, 100baseT_Full);
++ 
++-	bitmap_and(supported, supported, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ }
++ 
++ static void xrs700x_mac_link_up(struct dsa_switch *ds, int port,
++--- a/drivers/net/ethernet/amd/xgbe/xgbe-ethtool.c
+++++ b/drivers/net/ethernet/amd/xgbe/xgbe-ethtool.c
++@@ -369,9 +369,8 @@ static int xgbe_set_link_ksettings(struc
++ 		  __ETHTOOL_LINK_MODE_MASK_NBITS, cmd->link_modes.advertising,
++ 		  __ETHTOOL_LINK_MODE_MASK_NBITS, lks->link_modes.supported);
++ 
++-	bitmap_and(advertising,
++-		   cmd->link_modes.advertising, lks->link_modes.supported,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(advertising, cmd->link_modes.advertising,
+++		     lks->link_modes.supported);
++ 
++ 	if ((cmd->base.autoneg == AUTONEG_ENABLE) &&
++ 	    bitmap_empty(advertising, __ETHTOOL_LINK_MODE_MASK_NBITS)) {
++@@ -384,8 +383,7 @@ static int xgbe_set_link_ksettings(struc
++ 	pdata->phy.autoneg = cmd->base.autoneg;
++ 	pdata->phy.speed = speed;
++ 	pdata->phy.duplex = cmd->base.duplex;
++-	bitmap_copy(lks->link_modes.advertising, advertising,
++-		    __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_copy(lks->link_modes.advertising, advertising);
++ 
++ 	if (cmd->base.autoneg == AUTONEG_ENABLE)
++ 		XGBE_SET_ADV(lks, Autoneg);
++--- a/drivers/net/ethernet/atheros/ag71xx.c
+++++ b/drivers/net/ethernet/atheros/ag71xx.c
++@@ -1082,14 +1082,12 @@ static void ag71xx_mac_validate(struct p
++ 		phylink_set(mask, 1000baseX_Full);
++ 	}
++ 
++-	bitmap_and(supported, supported, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ 
++ 	return;
++ unsupported:
++-	bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_zero(supported);
++ }
++ 
++ static void ag71xx_mac_pcs_get_state(struct phylink_config *config,
++--- a/drivers/net/ethernet/cadence/macb_main.c
+++++ b/drivers/net/ethernet/cadence/macb_main.c
++@@ -523,21 +523,21 @@ static void macb_validate(struct phylink
++ 	    state->interface != PHY_INTERFACE_MODE_SGMII &&
++ 	    state->interface != PHY_INTERFACE_MODE_10GBASER &&
++ 	    !phy_interface_mode_is_rgmii(state->interface)) {
++-		bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++		linkmode_zero(supported);
++ 		return;
++ 	}
++ 
++ 	if (!macb_is_gem(bp) &&
++ 	    (state->interface == PHY_INTERFACE_MODE_GMII ||
++ 	     phy_interface_mode_is_rgmii(state->interface))) {
++-		bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++		linkmode_zero(supported);
++ 		return;
++ 	}
++ 
++ 	if (state->interface == PHY_INTERFACE_MODE_10GBASER &&
++ 	    !(bp->caps & MACB_CAPS_HIGH_SPEED &&
++ 	      bp->caps & MACB_CAPS_PCS)) {
++-		bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++		linkmode_zero(supported);
++ 		return;
++ 	}
++ 
++@@ -576,9 +576,8 @@ static void macb_validate(struct phylink
++ 			phylink_set(mask, 1000baseT_Half);
++ 	}
++ out:
++-	bitmap_and(supported, supported, mask, __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ }
++ 
++ static void macb_usx_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode,
++--- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c
+++++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c
++@@ -968,7 +968,7 @@ static void enetc_pl_mac_validate(struct
++ 	    state->interface != PHY_INTERFACE_MODE_2500BASEX &&
++ 	    state->interface != PHY_INTERFACE_MODE_USXGMII &&
++ 	    !phy_interface_mode_is_rgmii(state->interface)) {
++-		bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++		linkmode_zero(supported);
++ 		return;
++ 	}
++ 
++@@ -991,10 +991,8 @@ static void enetc_pl_mac_validate(struct
++ 		phylink_set(mask, 2500baseX_Full);
++ 	}
++ 
++-	bitmap_and(supported, supported, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ }
++ 
++ static void enetc_pl_mac_config(struct phylink_config *config,
++--- a/drivers/net/ethernet/huawei/hinic/hinic_ethtool.c
+++++ b/drivers/net/ethernet/huawei/hinic/hinic_ethtool.c
++@@ -322,12 +322,10 @@ static int hinic_get_link_ksettings(stru
++ 		}
++ 	}
++ 
++-	bitmap_copy(link_ksettings->link_modes.supported,
++-		    (unsigned long *)&settings.supported,
++-		    __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_copy(link_ksettings->link_modes.advertising,
++-		    (unsigned long *)&settings.advertising,
++-		    __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_copy(link_ksettings->link_modes.supported,
+++		      (unsigned long *)&settings.supported);
+++	linkmode_copy(link_ksettings->link_modes.advertising,
+++		      (unsigned long *)&settings.advertising);
++ 
++ 	return 0;
++ }
++--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c
+++++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c
++@@ -467,9 +467,8 @@ static int ixgbe_set_link_ksettings(stru
++ 		 * this function does not support duplex forcing, but can
++ 		 * limit the advertising of the adapter to the specified speed
++ 		 */
++-		if (!bitmap_subset(cmd->link_modes.advertising,
++-				   cmd->link_modes.supported,
++-				   __ETHTOOL_LINK_MODE_MASK_NBITS))
+++		if (!linkmode_subset(cmd->link_modes.advertising,
+++				     cmd->link_modes.supported))
++ 			return -EINVAL;
++ 
++ 		/* only allow one speed at a time if no autoneg */
++--- a/drivers/net/ethernet/marvell/mvneta.c
+++++ b/drivers/net/ethernet/marvell/mvneta.c
++@@ -3835,14 +3835,14 @@ static void mvneta_validate(struct phyli
++ 	 */
++ 	if (phy_interface_mode_is_8023z(state->interface)) {
++ 		if (!phylink_test(state->advertising, Autoneg)) {
++-			bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++			linkmode_zero(supported);
++ 			return;
++ 		}
++ 	} else if (state->interface != PHY_INTERFACE_MODE_NA &&
++ 		   state->interface != PHY_INTERFACE_MODE_QSGMII &&
++ 		   state->interface != PHY_INTERFACE_MODE_SGMII &&
++ 		   !phy_interface_mode_is_rgmii(state->interface)) {
++-		bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++		linkmode_zero(supported);
++ 		return;
++ 	}
++ 
++@@ -3871,10 +3871,8 @@ static void mvneta_validate(struct phyli
++ 		phylink_set(mask, 100baseT_Full);
++ 	}
++ 
++-	bitmap_and(supported, supported, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ 
++ 	/* We can only operate at 2500BaseX or 1000BaseX.  If requested
++ 	 * to advertise both, only report advertising at 2500BaseX.
++--- a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
+++++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
++@@ -6347,15 +6347,14 @@ static void mvpp2_phylink_validate(struc
++ 		goto empty_set;
++ 	}
++ 
++-	bitmap_and(supported, supported, mask, __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ 
++ 	phylink_helper_basex_speed(state);
++ 	return;
++ 
++ empty_set:
++-	bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_zero(supported);
++ }
++ 
++ static void mvpp2_xlg_config(struct mvpp2_port *port, unsigned int mode,
++--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c
+++++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c
++@@ -1168,9 +1168,8 @@ static int otx2_set_link_ksettings(struc
++ 	otx2_get_link_ksettings(netdev, &cur_ks);
++ 
++ 	/* Check requested modes against supported modes by hardware */
++-	if (!bitmap_subset(cmd->link_modes.advertising,
++-			   cur_ks.link_modes.supported,
++-			   __ETHTOOL_LINK_MODE_MASK_NBITS))
+++	if (!linkmode_subset(cmd->link_modes.advertising,
+++			     cur_ks.link_modes.supported))
++ 		return -EINVAL;
++ 
++ 	mutex_lock(&mbox->lock);
++--- a/drivers/net/ethernet/marvell/pxa168_eth.c
+++++ b/drivers/net/ethernet/marvell/pxa168_eth.c
++@@ -977,8 +977,7 @@ static int pxa168_init_phy(struct net_de
++ 	cmd.base.phy_address = pep->phy_addr;
++ 	cmd.base.speed = pep->phy_speed;
++ 	cmd.base.duplex = pep->phy_duplex;
++-	bitmap_copy(cmd.link_modes.advertising, PHY_BASIC_FEATURES,
++-		    __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_copy(cmd.link_modes.advertising, PHY_BASIC_FEATURES);
++ 	cmd.base.autoneg = AUTONEG_ENABLE;
++ 
++ 	if (cmd.base.speed != 0)
++--- a/drivers/net/ethernet/mellanox/mlx4/en_ethtool.c
+++++ b/drivers/net/ethernet/mellanox/mlx4/en_ethtool.c
++@@ -39,6 +39,7 @@
++ #include <linux/in.h>
++ #include <net/ip.h>
++ #include <linux/bitmap.h>
+++#include <linux/mii.h>
++ 
++ #include "mlx4_en.h"
++ #include "en_port.h"
++@@ -643,10 +644,8 @@ static unsigned long *ptys2ethtool_link_
++ 		unsigned int i;						\
++ 		cfg = &ptys2ethtool_map[reg_];				\
++ 		cfg->speed = speed_;					\
++-		bitmap_zero(cfg->supported,				\
++-			    __ETHTOOL_LINK_MODE_MASK_NBITS);		\
++-		bitmap_zero(cfg->advertised,				\
++-			    __ETHTOOL_LINK_MODE_MASK_NBITS);		\
+++		linkmode_zero(cfg->supported);				\
+++		linkmode_zero(cfg->advertised);				\
++ 		for (i = 0 ; i < ARRAY_SIZE(modes) ; ++i) {		\
++ 			__set_bit(modes[i], cfg->supported);		\
++ 			__set_bit(modes[i], cfg->advertised);		\
++@@ -702,10 +701,8 @@ static void ptys2ethtool_update_link_mod
++ 	int i;
++ 	for (i = 0; i < MLX4_LINK_MODES_SZ; i++) {
++ 		if (eth_proto & MLX4_PROT_MASK(i))
++-			bitmap_or(link_modes, link_modes,
++-				  ptys2ethtool_link_mode(&ptys2ethtool_map[i],
++-							 report),
++-				  __ETHTOOL_LINK_MODE_MASK_NBITS);
+++			linkmode_or(link_modes, link_modes,
+++				    ptys2ethtool_link_mode(&ptys2ethtool_map[i], report));
++ 	}
++ }
++ 
++@@ -716,11 +713,9 @@ static u32 ethtool2ptys_link_modes(const
++ 	u32 ptys_modes = 0;
++ 
++ 	for (i = 0; i < MLX4_LINK_MODES_SZ; i++) {
++-		if (bitmap_intersects(
++-			    ptys2ethtool_link_mode(&ptys2ethtool_map[i],
++-						   report),
++-			    link_modes,
++-			    __ETHTOOL_LINK_MODE_MASK_NBITS))
+++		ulong *map_mode = ptys2ethtool_link_mode(&ptys2ethtool_map[i],
+++							 report);
+++		if (linkmode_intersects(map_mode, link_modes))
++ 			ptys_modes |= 1 << i;
++ 	}
++ 	return ptys_modes;
++--- a/drivers/net/ethernet/microchip/sparx5/sparx5_phylink.c
+++++ b/drivers/net/ethernet/microchip/sparx5/sparx5_phylink.c
++@@ -92,12 +92,11 @@ static void sparx5_phylink_validate(stru
++ 		}
++ 		break;
++ 	default:
++-		bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++		linkmode_zero(supported);
++ 		return;
++ 	}
++-	bitmap_and(supported, supported, mask, __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ }
++ 
++ static void sparx5_phylink_mac_config(struct phylink_config *config,
++--- a/drivers/net/ethernet/mscc/ocelot_net.c
+++++ b/drivers/net/ethernet/mscc/ocelot_net.c
++@@ -1509,7 +1509,7 @@ static void vsc7514_phylink_validate(str
++ 
++ 	if (state->interface != PHY_INTERFACE_MODE_NA &&
++ 	    state->interface != ocelot_port->phy_mode) {
++-		bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++		linkmode_zero(supported);
++ 		return;
++ 	}
++ 
++@@ -1528,9 +1528,8 @@ static void vsc7514_phylink_validate(str
++ 	phylink_set(mask, 2500baseT_Full);
++ 	phylink_set(mask, 2500baseX_Full);
++ 
++-	bitmap_and(supported, supported, mask, __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ }
++ 
++ static void vsc7514_phylink_mac_config(struct phylink_config *config,
++--- a/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c
+++++ b/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c
++@@ -228,8 +228,7 @@ static int ionic_get_link_ksettings(stru
++ 		break;
++ 	}
++ 
++-	bitmap_copy(ks->link_modes.advertising, ks->link_modes.supported,
++-		    __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_copy(ks->link_modes.advertising, ks->link_modes.supported);
++ 
++ 	ethtool_link_ksettings_add_link_mode(ks, supported, FEC_BASER);
++ 	ethtool_link_ksettings_add_link_mode(ks, supported, FEC_RS);
++--- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
+++++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
++@@ -1565,7 +1565,7 @@ static void axienet_validate(struct phyl
++ 			netdev_warn(ndev, "Cannot use PHY mode %s, supported: %s\n",
++ 				    phy_modes(state->interface),
++ 				    phy_modes(lp->phy_mode));
++-			bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++			linkmode_zero(supported);
++ 			return;
++ 		}
++ 	}
++@@ -1598,10 +1598,8 @@ static void axienet_validate(struct phyl
++ 		break;
++ 	}
++ 
++-	bitmap_and(supported, supported, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
++-	bitmap_and(state->advertising, state->advertising, mask,
++-		   __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
++ }
++ 
++ static void axienet_mac_pcs_get_state(struct phylink_config *config,
++--- a/drivers/net/pcs/pcs-xpcs.c
+++++ b/drivers/net/pcs/pcs-xpcs.c
++@@ -646,7 +646,7 @@ void xpcs_validate(struct dw_xpcs *xpcs,
++ 	if (state->interface == PHY_INTERFACE_MODE_NA)
++ 		return;
++ 
++-	bitmap_zero(xpcs_supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_zero(xpcs_supported);
++ 
++ 	compat = xpcs_find_compat(xpcs->id, state->interface);
++ 
++--- a/drivers/net/phy/sfp-bus.c
+++++ b/drivers/net/phy/sfp-bus.c
++@@ -379,7 +379,7 @@ void sfp_parse_support(struct sfp_bus *b
++ 	if (bus->sfp_quirk)
++ 		bus->sfp_quirk->modes(id, modes);
++ 
++-	bitmap_or(support, support, modes, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_or(support, support, modes);
++ 
++ 	phylink_set(support, Autoneg);
++ 	phylink_set(support, Pause);
++--- a/net/ethtool/ioctl.c
+++++ b/net/ethtool/ioctl.c
++@@ -335,7 +335,7 @@ EXPORT_SYMBOL(ethtool_intersect_link_mas
++ void ethtool_convert_legacy_u32_to_link_mode(unsigned long *dst,
++ 					     u32 legacy_u32)
++ {
++-	bitmap_zero(dst, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++	linkmode_zero(dst);
++ 	dst[0] = legacy_u32;
++ }
++ EXPORT_SYMBOL(ethtool_convert_legacy_u32_to_link_mode);
++@@ -350,11 +350,10 @@ bool ethtool_convert_link_mode_to_legacy
++ 	if (__ETHTOOL_LINK_MODE_MASK_NBITS > 32) {
++ 		__ETHTOOL_DECLARE_LINK_MODE_MASK(ext);
++ 
++-		bitmap_zero(ext, __ETHTOOL_LINK_MODE_MASK_NBITS);
+++		linkmode_zero(ext);
++ 		bitmap_fill(ext, 32);
++ 		bitmap_complement(ext, ext, __ETHTOOL_LINK_MODE_MASK_NBITS);
++-		if (bitmap_intersects(ext, src,
++-				      __ETHTOOL_LINK_MODE_MASK_NBITS)) {
+++		if (linkmode_intersects(ext, src)) {
++ 			/* src mask goes beyond bit 31 */
++ 			retval = false;
++ 		}
+diff --git a/target/linux/generic/backport-5.15/703-01-v5.16-net-phylink-add-MAC-phy_interface_t-bitmap.patch b/target/linux/generic/backport-5.15/703-01-v5.16-net-phylink-add-MAC-phy_interface_t-bitmap.patch
+new file mode 100644
+index 0000000000..885c2fcf25
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-01-v5.16-net-phylink-add-MAC-phy_interface_t-bitmap.patch
+@@ -0,0 +1,24 @@
++From 38c310eb46f5f80213a92093af11af270c209a76 Mon Sep 17 00:00:00 2001
++From: Russell King <rmk+kernel@armlinux.org.uk>
++Date: Tue, 26 Oct 2021 11:06:06 +0100
++Subject: [PATCH] net: phylink: add MAC phy_interface_t bitmap
++
++Add a phy_interface_t bitmap so the MAC driver can specifiy which PHY
++interface modes it supports.
++
++Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ include/linux/phylink.h | 1 +
++ 1 file changed, 1 insertion(+)
++
++--- a/include/linux/phylink.h
+++++ b/include/linux/phylink.h
++@@ -78,6 +78,7 @@ struct phylink_config {
++ 	bool ovr_an_inband;
++ 	void (*get_fixed_state)(struct phylink_config *config,
++ 				struct phylink_link_state *state);
+++	DECLARE_PHY_INTERFACE_MASK(supported_interfaces);
++ };
++ 
++ /**
+diff --git a/target/linux/generic/backport-5.15/703-02-v5.16-net-phylink-use-supported_interfaces-for-phylink-val.patch b/target/linux/generic/backport-5.15/703-02-v5.16-net-phylink-use-supported_interfaces-for-phylink-val.patch
+new file mode 100644
+index 0000000000..9800884f6e
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-02-v5.16-net-phylink-use-supported_interfaces-for-phylink-val.patch
+@@ -0,0 +1,98 @@
++From d25f3a74f30aace819163dfa54f2a4b8ca1dc932 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Tue, 26 Oct 2021 11:06:11 +0100
++Subject: [PATCH] net: phylink: use supported_interfaces for phylink
++ validation
++
++If the network device supplies a supported interface bitmap, we can use
++that during phylink's validation to simplify MAC drivers in two ways by
++using the supported_interfaces bitmap to:
++
++1. reject unsupported interfaces before calling into the MAC driver.
++2. generate the set of all supported link modes across all supported
++   interfaces (used mainly for SFP, but also some 10G PHYs.)
++
++Suggested-by: Sean Anderson <sean.anderson@seco.com>
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/phy/phylink.c | 36 ++++++++++++++++++++++++++++++++++++
++ include/linux/phylink.h   | 12 ++++++++++--
++ 2 files changed, 46 insertions(+), 2 deletions(-)
++
++--- a/drivers/net/phy/phylink.c
+++++ b/drivers/net/phy/phylink.c
++@@ -155,9 +155,45 @@ static const char *phylink_an_mode_str(u
++ 	return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown";
++ }
++ 
+++static int phylink_validate_any(struct phylink *pl, unsigned long *supported,
+++				struct phylink_link_state *state)
+++{
+++	__ETHTOOL_DECLARE_LINK_MODE_MASK(all_adv) = { 0, };
+++	__ETHTOOL_DECLARE_LINK_MODE_MASK(all_s) = { 0, };
+++	__ETHTOOL_DECLARE_LINK_MODE_MASK(s);
+++	struct phylink_link_state t;
+++	int intf;
+++
+++	for (intf = 0; intf < PHY_INTERFACE_MODE_MAX; intf++) {
+++		if (test_bit(intf, pl->config->supported_interfaces)) {
+++			linkmode_copy(s, supported);
+++
+++			t = *state;
+++			t.interface = intf;
+++			pl->mac_ops->validate(pl->config, s, &t);
+++			linkmode_or(all_s, all_s, s);
+++			linkmode_or(all_adv, all_adv, t.advertising);
+++		}
+++	}
+++
+++	linkmode_copy(supported, all_s);
+++	linkmode_copy(state->advertising, all_adv);
+++
+++	return phylink_is_empty_linkmode(supported) ? -EINVAL : 0;
+++}
+++
++ static int phylink_validate(struct phylink *pl, unsigned long *supported,
++ 			    struct phylink_link_state *state)
++ {
+++	if (!phy_interface_empty(pl->config->supported_interfaces)) {
+++		if (state->interface == PHY_INTERFACE_MODE_NA)
+++			return phylink_validate_any(pl, supported, state);
+++
+++		if (!test_bit(state->interface,
+++			      pl->config->supported_interfaces))
+++			return -EINVAL;
+++	}
+++
++ 	pl->mac_ops->validate(pl->config, supported, state);
++ 
++ 	return phylink_is_empty_linkmode(supported) ? -EINVAL : 0;
++--- a/include/linux/phylink.h
+++++ b/include/linux/phylink.h
++@@ -68,6 +68,8 @@ enum phylink_op_type {
++  * @ovr_an_inband: if true, override PCS to MLO_AN_INBAND
++  * @get_fixed_state: callback to execute to determine the fixed link state,
++  *		     if MAC link is at %MLO_AN_FIXED mode.
+++ * @supported_interfaces: bitmap describing which PHY_INTERFACE_MODE_xxx
+++ *                        are supported by the MAC/PCS.
++  */
++ struct phylink_config {
++ 	struct device *dev;
++@@ -136,8 +138,14 @@ struct phylink_mac_ops {
++  * based on @state->advertising and/or @state->speed and update
++  * @state->interface accordingly. See phylink_helper_basex_speed().
++  *
++- * When @state->interface is %PHY_INTERFACE_MODE_NA, phylink expects the
++- * MAC driver to return all supported link modes.
+++ * When @config->supported_interfaces has been set, phylink will iterate
+++ * over the supported interfaces to determine the full capability of the
+++ * MAC. The validation function must not print errors if @state->interface
+++ * is set to an unexpected value.
+++ *
+++ * When @config->supported_interfaces is empty, phylink will call this
+++ * function with @state->interface set to %PHY_INTERFACE_MODE_NA, and
+++ * expects the MAC driver to return all supported link modes.
++  *
++  * If the @state->interface mode is not supported, then the @supported
++  * mask must be cleared.
+diff --git a/target/linux/generic/backport-5.15/703-03-v5.16-net-dsa-populate-supported_interfaces-member.patch b/target/linux/generic/backport-5.15/703-03-v5.16-net-dsa-populate-supported_interfaces-member.patch
+new file mode 100644
+index 0000000000..b10e6da0e9
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-03-v5.16-net-dsa-populate-supported_interfaces-member.patch
+@@ -0,0 +1,63 @@
++From c07c6e8eb4b38bae921f9e2f108d1e7f8e14226e Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
++Date: Thu, 28 Oct 2021 18:00:14 +0100
++Subject: [PATCH] net: dsa: populate supported_interfaces member
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++Add a new DSA switch operation, phylink_get_interfaces, which should
++fill in which PHY_INTERFACE_MODE_* are supported by given port.
++
++Use this before phylink_create() to fill phylinks supported_interfaces
++member, allowing phylink to determine which PHY_INTERFACE_MODEs are
++supported.
++
++Signed-off-by: Marek Behún <kabel@kernel.org>
++[tweaked patch and description to add more complete support -- rmk]
++Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ include/net/dsa.h | 2 ++
++ net/dsa/port.c    | 4 ++++
++ net/dsa/slave.c   | 4 ++++
++ 3 files changed, 10 insertions(+)
++
++--- a/include/net/dsa.h
+++++ b/include/net/dsa.h
++@@ -626,6 +626,8 @@ struct dsa_switch_ops {
++ 	/*
++ 	 * PHYLINK integration
++ 	 */
+++	void	(*phylink_get_interfaces)(struct dsa_switch *ds, int port,
+++					  unsigned long *supported_interfaces);
++ 	void	(*phylink_validate)(struct dsa_switch *ds, int port,
++ 				    unsigned long *supported,
++ 				    struct phylink_link_state *state);
++--- a/net/dsa/port.c
+++++ b/net/dsa/port.c
++@@ -1188,6 +1188,10 @@ static int dsa_port_phylink_register(str
++ 	dp->pl_config.type = PHYLINK_DEV;
++ 	dp->pl_config.pcs_poll = ds->pcs_poll;
++ 
+++	if (ds->ops->phylink_get_interfaces)
+++		ds->ops->phylink_get_interfaces(ds, dp->index,
+++					dp->pl_config.supported_interfaces);
+++
++ 	dp->pl = phylink_create(&dp->pl_config, of_fwnode_handle(port_dn),
++ 				mode, &dsa_port_phylink_mac_ops);
++ 	if (IS_ERR(dp->pl)) {
++--- a/net/dsa/slave.c
+++++ b/net/dsa/slave.c
++@@ -1837,6 +1837,10 @@ static int dsa_slave_phy_setup(struct ne
++ 		dp->pl_config.poll_fixed_state = true;
++ 	}
++ 
+++	if (ds->ops->phylink_get_interfaces)
+++		ds->ops->phylink_get_interfaces(ds, dp->index,
+++					dp->pl_config.supported_interfaces);
+++
++ 	dp->pl = phylink_create(&dp->pl_config, of_fwnode_handle(port_dn), mode,
++ 				&dsa_port_phylink_mac_ops);
++ 	if (IS_ERR(dp->pl)) {
+diff --git a/target/linux/generic/backport-5.15/703-04-v5.17-net-dsa-consolidate-phylink-creation.patch b/target/linux/generic/backport-5.15/703-04-v5.17-net-dsa-consolidate-phylink-creation.patch
+new file mode 100644
+index 0000000000..9b67a8a518
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-04-v5.17-net-dsa-consolidate-phylink-creation.patch
+@@ -0,0 +1,149 @@
++From 21bd64bd717dedac96f53b668144cbe37d3c12d4 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Tue, 30 Nov 2021 13:09:55 +0000
++Subject: [PATCH] net: dsa: consolidate phylink creation
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++The code in port.c and slave.c creating the phylink instance is very
++similar - let's consolidate this into a single function.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Reviewed-by: Marek Behún <kabel@kernel.org>
++Reviewed-by: Andrew Lunn <andrew@lunn.ch>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ net/dsa/dsa_priv.h |  2 +-
++ net/dsa/port.c     | 44 ++++++++++++++++++++++++++++----------------
++ net/dsa/slave.c    | 19 +++----------------
++ 3 files changed, 32 insertions(+), 33 deletions(-)
++
++--- a/net/dsa/dsa_priv.h
+++++ b/net/dsa/dsa_priv.h
++@@ -261,13 +261,13 @@ int dsa_port_mrp_add_ring_role(const str
++ 			       const struct switchdev_obj_ring_role_mrp *mrp);
++ int dsa_port_mrp_del_ring_role(const struct dsa_port *dp,
++ 			       const struct switchdev_obj_ring_role_mrp *mrp);
+++int dsa_port_phylink_create(struct dsa_port *dp);
++ int dsa_port_link_register_of(struct dsa_port *dp);
++ void dsa_port_link_unregister_of(struct dsa_port *dp);
++ int dsa_port_hsr_join(struct dsa_port *dp, struct net_device *hsr);
++ void dsa_port_hsr_leave(struct dsa_port *dp, struct net_device *hsr);
++ int dsa_port_tag_8021q_vlan_add(struct dsa_port *dp, u16 vid, bool broadcast);
++ void dsa_port_tag_8021q_vlan_del(struct dsa_port *dp, u16 vid, bool broadcast);
++-extern const struct phylink_mac_ops dsa_port_phylink_mac_ops;
++ 
++ static inline bool dsa_port_offloads_bridge_port(struct dsa_port *dp,
++ 						 const struct net_device *dev)
++--- a/net/dsa/port.c
+++++ b/net/dsa/port.c
++@@ -1092,7 +1092,7 @@ static void dsa_port_phylink_mac_link_up
++ 				     speed, duplex, tx_pause, rx_pause);
++ }
++ 
++-const struct phylink_mac_ops dsa_port_phylink_mac_ops = {
+++static const struct phylink_mac_ops dsa_port_phylink_mac_ops = {
++ 	.validate = dsa_port_phylink_validate,
++ 	.mac_pcs_get_state = dsa_port_phylink_mac_pcs_get_state,
++ 	.mac_config = dsa_port_phylink_mac_config,
++@@ -1101,6 +1101,30 @@ const struct phylink_mac_ops dsa_port_ph
++ 	.mac_link_up = dsa_port_phylink_mac_link_up,
++ };
++ 
+++int dsa_port_phylink_create(struct dsa_port *dp)
+++{
+++	struct dsa_switch *ds = dp->ds;
+++	phy_interface_t mode;
+++	int err;
+++
+++	err = of_get_phy_mode(dp->dn, &mode);
+++	if (err)
+++		mode = PHY_INTERFACE_MODE_NA;
+++
+++	if (ds->ops->phylink_get_interfaces)
+++		ds->ops->phylink_get_interfaces(ds, dp->index,
+++					dp->pl_config.supported_interfaces);
+++
+++	dp->pl = phylink_create(&dp->pl_config, of_fwnode_handle(dp->dn),
+++				mode, &dsa_port_phylink_mac_ops);
+++	if (IS_ERR(dp->pl)) {
+++		pr_err("error creating PHYLINK: %ld\n", PTR_ERR(dp->pl));
+++		return PTR_ERR(dp->pl);
+++	}
+++
+++	return 0;
+++}
+++
++ static int dsa_port_setup_phy_of(struct dsa_port *dp, bool enable)
++ {
++ 	struct dsa_switch *ds = dp->ds;
++@@ -1177,27 +1201,15 @@ static int dsa_port_phylink_register(str
++ {
++ 	struct dsa_switch *ds = dp->ds;
++ 	struct device_node *port_dn = dp->dn;
++-	phy_interface_t mode;
++ 	int err;
++ 
++-	err = of_get_phy_mode(port_dn, &mode);
++-	if (err)
++-		mode = PHY_INTERFACE_MODE_NA;
++-
++ 	dp->pl_config.dev = ds->dev;
++ 	dp->pl_config.type = PHYLINK_DEV;
++ 	dp->pl_config.pcs_poll = ds->pcs_poll;
++ 
++-	if (ds->ops->phylink_get_interfaces)
++-		ds->ops->phylink_get_interfaces(ds, dp->index,
++-					dp->pl_config.supported_interfaces);
++-
++-	dp->pl = phylink_create(&dp->pl_config, of_fwnode_handle(port_dn),
++-				mode, &dsa_port_phylink_mac_ops);
++-	if (IS_ERR(dp->pl)) {
++-		pr_err("error creating PHYLINK: %ld\n", PTR_ERR(dp->pl));
++-		return PTR_ERR(dp->pl);
++-	}
+++	err = dsa_port_phylink_create(dp);
+++	if (err)
+++		return err;
++ 
++ 	err = phylink_of_phy_connect(dp->pl, port_dn, 0);
++ 	if (err && err != -ENODEV) {
++--- a/net/dsa/slave.c
+++++ b/net/dsa/slave.c
++@@ -1817,14 +1817,9 @@ static int dsa_slave_phy_setup(struct ne
++ 	struct dsa_port *dp = dsa_slave_to_port(slave_dev);
++ 	struct device_node *port_dn = dp->dn;
++ 	struct dsa_switch *ds = dp->ds;
++-	phy_interface_t mode;
++ 	u32 phy_flags = 0;
++ 	int ret;
++ 
++-	ret = of_get_phy_mode(port_dn, &mode);
++-	if (ret)
++-		mode = PHY_INTERFACE_MODE_NA;
++-
++ 	dp->pl_config.dev = &slave_dev->dev;
++ 	dp->pl_config.type = PHYLINK_NETDEV;
++ 
++@@ -1837,17 +1832,9 @@ static int dsa_slave_phy_setup(struct ne
++ 		dp->pl_config.poll_fixed_state = true;
++ 	}
++ 
++-	if (ds->ops->phylink_get_interfaces)
++-		ds->ops->phylink_get_interfaces(ds, dp->index,
++-					dp->pl_config.supported_interfaces);
++-
++-	dp->pl = phylink_create(&dp->pl_config, of_fwnode_handle(port_dn), mode,
++-				&dsa_port_phylink_mac_ops);
++-	if (IS_ERR(dp->pl)) {
++-		netdev_err(slave_dev,
++-			   "error creating PHYLINK: %ld\n", PTR_ERR(dp->pl));
++-		return PTR_ERR(dp->pl);
++-	}
+++	ret = dsa_port_phylink_create(dp);
+++	if (ret)
+++		return ret;
++ 
++ 	if (ds->ops->get_phy_flags)
++ 		phy_flags = ds->ops->get_phy_flags(ds, dp->index);
+diff --git a/target/linux/generic/backport-5.15/703-05-v5.17-net-dsa-replace-phylink_get_interfaces-with-phylink_.patch b/target/linux/generic/backport-5.15/703-05-v5.17-net-dsa-replace-phylink_get_interfaces-with-phylink_.patch
+new file mode 100644
+index 0000000000..d70b9aa6a4
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-05-v5.17-net-dsa-replace-phylink_get_interfaces-with-phylink_.patch
+@@ -0,0 +1,51 @@
++From 072eea6c22b2af680c3949e64f9adde278c71e68 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Tue, 30 Nov 2021 13:10:01 +0000
++Subject: [PATCH] net: dsa: replace phylink_get_interfaces() with
++ phylink_get_caps()
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++Phylink needs slightly more information than phylink_get_interfaces()
++allows us to get from the DSA drivers - we need the MAC capabilities.
++Replace the phylink_get_interfaces() method with phylink_get_caps() to
++allow DSA drivers to fill in the phylink_config MAC capabilities field
++as well.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Reviewed-by: Marek Behún <kabel@kernel.org>
++Reviewed-by: Andrew Lunn <andrew@lunn.ch>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ include/net/dsa.h | 4 ++--
++ net/dsa/port.c    | 5 ++---
++ 2 files changed, 4 insertions(+), 5 deletions(-)
++
++--- a/include/net/dsa.h
+++++ b/include/net/dsa.h
++@@ -626,8 +626,8 @@ struct dsa_switch_ops {
++ 	/*
++ 	 * PHYLINK integration
++ 	 */
++-	void	(*phylink_get_interfaces)(struct dsa_switch *ds, int port,
++-					  unsigned long *supported_interfaces);
+++	void	(*phylink_get_caps)(struct dsa_switch *ds, int port,
+++				    struct phylink_config *config);
++ 	void	(*phylink_validate)(struct dsa_switch *ds, int port,
++ 				    unsigned long *supported,
++ 				    struct phylink_link_state *state);
++--- a/net/dsa/port.c
+++++ b/net/dsa/port.c
++@@ -1111,9 +1111,8 @@ int dsa_port_phylink_create(struct dsa_p
++ 	if (err)
++ 		mode = PHY_INTERFACE_MODE_NA;
++ 
++-	if (ds->ops->phylink_get_interfaces)
++-		ds->ops->phylink_get_interfaces(ds, dp->index,
++-					dp->pl_config.supported_interfaces);
+++	if (ds->ops->phylink_get_caps)
+++		ds->ops->phylink_get_caps(ds, dp->index, &dp->pl_config);
++ 
++ 	dp->pl = phylink_create(&dp->pl_config, of_fwnode_handle(dp->dn),
++ 				mode, &dsa_port_phylink_mac_ops);
+diff --git a/target/linux/generic/backport-5.15/703-06-v5.18-net-dsa-add-support-for-phylink-mac_select_pcs.patch b/target/linux/generic/backport-5.15/703-06-v5.18-net-dsa-add-support-for-phylink-mac_select_pcs.patch
+new file mode 100644
+index 0000000000..2af5cb9d9d
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-06-v5.18-net-dsa-add-support-for-phylink-mac_select_pcs.patch
+@@ -0,0 +1,59 @@
++From bde018222c6b084ac32933a9f933581dd83da18e Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Thu, 17 Feb 2022 18:30:35 +0000
++Subject: [PATCH] net: dsa: add support for phylink mac_select_pcs()
++
++Add DSA support for the phylink mac_select_pcs() method so DSA drivers
++can return provide phylink with the appropriate PCS for the PHY
++interface mode.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ include/net/dsa.h |  3 +++
++ net/dsa/port.c    | 15 +++++++++++++++
++ 2 files changed, 18 insertions(+)
++
++--- a/include/net/dsa.h
+++++ b/include/net/dsa.h
++@@ -631,6 +631,9 @@ struct dsa_switch_ops {
++ 	void	(*phylink_validate)(struct dsa_switch *ds, int port,
++ 				    unsigned long *supported,
++ 				    struct phylink_link_state *state);
+++	struct phylink_pcs *(*phylink_mac_select_pcs)(struct dsa_switch *ds,
+++						      int port,
+++						      phy_interface_t iface);
++ 	int	(*phylink_mac_link_state)(struct dsa_switch *ds, int port,
++ 					  struct phylink_link_state *state);
++ 	void	(*phylink_mac_config)(struct dsa_switch *ds, int port,
++--- a/net/dsa/port.c
+++++ b/net/dsa/port.c
++@@ -1028,6 +1028,20 @@ static void dsa_port_phylink_mac_pcs_get
++ 	}
++ }
++ 
+++static struct phylink_pcs *
+++dsa_port_phylink_mac_select_pcs(struct phylink_config *config,
+++				phy_interface_t interface)
+++{
+++	struct dsa_port *dp = container_of(config, struct dsa_port, pl_config);
+++	struct dsa_switch *ds = dp->ds;
+++	struct phylink_pcs *pcs = NULL;
+++
+++	if (ds->ops->phylink_mac_select_pcs)
+++		pcs = ds->ops->phylink_mac_select_pcs(ds, dp->index, interface);
+++
+++	return pcs;
+++}
+++
++ static void dsa_port_phylink_mac_config(struct phylink_config *config,
++ 					unsigned int mode,
++ 					const struct phylink_link_state *state)
++@@ -1094,6 +1108,7 @@ static void dsa_port_phylink_mac_link_up
++ 
++ static const struct phylink_mac_ops dsa_port_phylink_mac_ops = {
++ 	.validate = dsa_port_phylink_validate,
+++	.mac_select_pcs = dsa_port_phylink_mac_select_pcs,
++ 	.mac_pcs_get_state = dsa_port_phylink_mac_pcs_get_state,
++ 	.mac_config = dsa_port_phylink_mac_config,
++ 	.mac_an_restart = dsa_port_phylink_mac_an_restart,
+diff --git a/target/linux/generic/backport-5.15/703-07-v5.16-net-phy-add-phy_interface_t-bitmap-support.patch b/target/linux/generic/backport-5.15/703-07-v5.16-net-phy-add-phy_interface_t-bitmap-support.patch
+new file mode 100644
+index 0000000000..1a7817b0f9
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-07-v5.16-net-phy-add-phy_interface_t-bitmap-support.patch
+@@ -0,0 +1,61 @@
++From 8e20f591f204f8db7f1182918f8e2285d3f589e0 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Tue, 26 Oct 2021 11:06:01 +0100
++Subject: [PATCH] net: phy: add phy_interface_t bitmap support
++
++Add support for a bitmap for phy interface modes, which includes:
++- a macro to declare the interface bitmap
++- an inline helper to zero the interface bitmap
++- an inline helper to detect an empty interface bitmap
++- inline helpers to do a bitwise AND and OR operations on two interface
++  bitmaps
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ include/linux/phy.h | 34 ++++++++++++++++++++++++++++++++++
++ 1 file changed, 34 insertions(+)
++
++--- a/include/linux/phy.h
+++++ b/include/linux/phy.h
++@@ -155,6 +155,40 @@ typedef enum {
++ 	PHY_INTERFACE_MODE_MAX,
++ } phy_interface_t;
++ 
+++/* PHY interface mode bitmap handling */
+++#define DECLARE_PHY_INTERFACE_MASK(name) \
+++	DECLARE_BITMAP(name, PHY_INTERFACE_MODE_MAX)
+++
+++static inline void phy_interface_zero(unsigned long *intf)
+++{
+++	bitmap_zero(intf, PHY_INTERFACE_MODE_MAX);
+++}
+++
+++static inline bool phy_interface_empty(const unsigned long *intf)
+++{
+++	return bitmap_empty(intf, PHY_INTERFACE_MODE_MAX);
+++}
+++
+++static inline void phy_interface_and(unsigned long *dst, const unsigned long *a,
+++				     const unsigned long *b)
+++{
+++	bitmap_and(dst, a, b, PHY_INTERFACE_MODE_MAX);
+++}
+++
+++static inline void phy_interface_or(unsigned long *dst, const unsigned long *a,
+++				    const unsigned long *b)
+++{
+++	bitmap_or(dst, a, b, PHY_INTERFACE_MODE_MAX);
+++}
+++
+++static inline void phy_interface_set_rgmii(unsigned long *intf)
+++{
+++	__set_bit(PHY_INTERFACE_MODE_RGMII, intf);
+++	__set_bit(PHY_INTERFACE_MODE_RGMII_ID, intf);
+++	__set_bit(PHY_INTERFACE_MODE_RGMII_RXID, intf);
+++	__set_bit(PHY_INTERFACE_MODE_RGMII_TXID, intf);
+++}
+++
++ /*
++  * phy_supported_speeds - return all speeds currently supported by a PHY device
++  */
+diff --git a/target/linux/generic/backport-5.15/703-08-v5.17-net-phylink-add-mac_select_pcs-method-to-phylink_mac.patch b/target/linux/generic/backport-5.15/703-08-v5.17-net-phylink-add-mac_select_pcs-method-to-phylink_mac.patch
+new file mode 100644
+index 0000000000..d826877e7d
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-08-v5.17-net-phylink-add-mac_select_pcs-method-to-phylink_mac.patch
+@@ -0,0 +1,197 @@
++From d1e86325af377129adb7fc6f34eb044ca6068b47 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Wed, 15 Dec 2021 15:34:15 +0000
++Subject: [PATCH] net: phylink: add mac_select_pcs() method to phylink_mac_ops
++
++mac_select_pcs() allows us to have an explicit point to query which
++PCS the MAC wishes to use for a particular PHY interface mode, thereby
++allowing us to add support to validate the link settings with the PCS.
++
++Phylink will also use this to select the PCS to be used during a major
++configuration event without the MAC driver needing to call
++phylink_set_pcs().
++
++Note that if mac_select_pcs() is present, the supported_interfaces
++bitmap must be filled in; this avoids mac_select_pcs() being called
++with PHY_INTERFACE_MODE_NA when we want to get support for all
++interface types. Phylink will return an error in phylink_create()
++unless this condition is satisfied.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/phy/phylink.c | 68 +++++++++++++++++++++++++++++++++------
++ include/linux/phylink.h   | 18 +++++++++++
++ 2 files changed, 77 insertions(+), 9 deletions(-)
++
++--- a/drivers/net/phy/phylink.c
+++++ b/drivers/net/phy/phylink.c
++@@ -155,6 +155,23 @@ static const char *phylink_an_mode_str(u
++ 	return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown";
++ }
++ 
+++static int phylink_validate_mac_and_pcs(struct phylink *pl,
+++					unsigned long *supported,
+++					struct phylink_link_state *state)
+++{
+++	struct phylink_pcs *pcs;
+++
+++	if (pl->mac_ops->mac_select_pcs) {
+++		pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
+++		if (IS_ERR(pcs))
+++			return PTR_ERR(pcs);
+++	}
+++
+++	pl->mac_ops->validate(pl->config, supported, state);
+++
+++	return phylink_is_empty_linkmode(supported) ? -EINVAL : 0;
+++}
+++
++ static int phylink_validate_any(struct phylink *pl, unsigned long *supported,
++ 				struct phylink_link_state *state)
++ {
++@@ -170,9 +187,10 @@ static int phylink_validate_any(struct p
++ 
++ 			t = *state;
++ 			t.interface = intf;
++-			pl->mac_ops->validate(pl->config, s, &t);
++-			linkmode_or(all_s, all_s, s);
++-			linkmode_or(all_adv, all_adv, t.advertising);
+++			if (!phylink_validate_mac_and_pcs(pl, s, &t)) {
+++				linkmode_or(all_s, all_s, s);
+++				linkmode_or(all_adv, all_adv, t.advertising);
+++			}
++ 		}
++ 	}
++ 
++@@ -194,9 +212,7 @@ static int phylink_validate(struct phyli
++ 			return -EINVAL;
++ 	}
++ 
++-	pl->mac_ops->validate(pl->config, supported, state);
++-
++-	return phylink_is_empty_linkmode(supported) ? -EINVAL : 0;
+++	return phylink_validate_mac_and_pcs(pl, supported, state);
++ }
++ 
++ static int phylink_parse_fixedlink(struct phylink *pl,
++@@ -486,10 +502,21 @@ static void phylink_mac_pcs_an_restart(s
++ static void phylink_major_config(struct phylink *pl, bool restart,
++ 				  const struct phylink_link_state *state)
++ {
+++	struct phylink_pcs *pcs = NULL;
++ 	int err;
++ 
++ 	phylink_dbg(pl, "major config %s\n", phy_modes(state->interface));
++ 
+++	if (pl->mac_ops->mac_select_pcs) {
+++		pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
+++		if (IS_ERR(pcs)) {
+++			phylink_err(pl,
+++				    "mac_select_pcs unexpectedly failed: %pe\n",
+++				    pcs);
+++			return;
+++		}
+++	}
+++
++ 	if (pl->mac_ops->mac_prepare) {
++ 		err = pl->mac_ops->mac_prepare(pl->config, pl->cur_link_an_mode,
++ 					       state->interface);
++@@ -500,6 +527,12 @@ static void phylink_major_config(struct
++ 		}
++ 	}
++ 
+++	/* If we have a new PCS, switch to the new PCS after preparing the MAC
+++	 * for the change.
+++	 */
+++	if (pcs)
+++		phylink_set_pcs(pl, pcs);
+++
++ 	phylink_mac_config(pl, state);
++ 
++ 	if (pl->pcs_ops) {
++@@ -879,6 +912,14 @@ struct phylink *phylink_create(struct ph
++ 	struct phylink *pl;
++ 	int ret;
++ 
+++	/* Validate the supplied configuration */
+++	if (mac_ops->mac_select_pcs &&
+++	    phy_interface_empty(config->supported_interfaces)) {
+++		dev_err(config->dev,
+++			"phylink: error: empty supported_interfaces but mac_select_pcs() method present\n");
+++		return ERR_PTR(-EINVAL);
+++	}
+++
++ 	pl = kzalloc(sizeof(*pl), GFP_KERNEL);
++ 	if (!pl)
++ 		return ERR_PTR(-ENOMEM);
++@@ -946,9 +987,10 @@ EXPORT_SYMBOL_GPL(phylink_create);
++  * @pl: a pointer to a &struct phylink returned from phylink_create()
++  * @pcs: a pointer to the &struct phylink_pcs
++  *
++- * Bind the MAC PCS to phylink.  This may be called after phylink_create(),
++- * in mac_prepare() or mac_config() methods if it is desired to dynamically
++- * change the PCS.
+++ * Bind the MAC PCS to phylink.  This may be called after phylink_create().
+++ * If it is desired to dynamically change the PCS, then the preferred method
+++ * is to use mac_select_pcs(), but it may also be called in mac_prepare()
+++ * or mac_config().
++  *
++  * Please note that there are behavioural changes with the mac_config()
++  * callback if a PCS is present (denoting a newer setup) so removing a PCS
++@@ -959,6 +1001,14 @@ void phylink_set_pcs(struct phylink *pl,
++ {
++ 	pl->pcs = pcs;
++ 	pl->pcs_ops = pcs->ops;
+++
+++	if (!pl->phylink_disable_state &&
+++	    pl->cfg_link_an_mode == MLO_AN_INBAND) {
+++		if (pl->config->pcs_poll || pcs->poll)
+++			mod_timer(&pl->link_poll, jiffies + HZ);
+++		else
+++			del_timer(&pl->link_poll);
+++	}
++ }
++ EXPORT_SYMBOL_GPL(phylink_set_pcs);
++ 
++--- a/include/linux/phylink.h
+++++ b/include/linux/phylink.h
++@@ -86,6 +86,7 @@ struct phylink_config {
++ /**
++  * struct phylink_mac_ops - MAC operations structure.
++  * @validate: Validate and update the link configuration.
+++ * @mac_select_pcs: Select a PCS for the interface mode.
++  * @mac_pcs_get_state: Read the current link state from the hardware.
++  * @mac_prepare: prepare for a major reconfiguration of the interface.
++  * @mac_config: configure the MAC for the selected mode and state.
++@@ -100,6 +101,8 @@ struct phylink_mac_ops {
++ 	void (*validate)(struct phylink_config *config,
++ 			 unsigned long *supported,
++ 			 struct phylink_link_state *state);
+++	struct phylink_pcs *(*mac_select_pcs)(struct phylink_config *config,
+++					      phy_interface_t interface);
++ 	void (*mac_pcs_get_state)(struct phylink_config *config,
++ 				  struct phylink_link_state *state);
++ 	int (*mac_prepare)(struct phylink_config *config, unsigned int mode,
++@@ -152,6 +155,21 @@ struct phylink_mac_ops {
++  */
++ void validate(struct phylink_config *config, unsigned long *supported,
++ 	      struct phylink_link_state *state);
+++/**
+++ * mac_select_pcs: Select a PCS for the interface mode.
+++ * @config: a pointer to a &struct phylink_config.
+++ * @interface: PHY interface mode for PCS
+++ *
+++ * Return the &struct phylink_pcs for the specified interface mode, or
+++ * NULL if none is required, or an error pointer on error.
+++ *
+++ * This must not modify any state. It is used to query which PCS should
+++ * be used. Phylink will use this during validation to ensure that the
+++ * configuration is valid, and when setting a configuration to internally
+++ * set the PCS that will be used.
+++ */
+++struct phylink_pcs *mac_select_pcs(struct phylink_config *config,
+++				   phy_interface_t interface);
++ 
++ /**
++  * mac_pcs_get_state() - Read the current inband link state from the hardware
+diff --git a/target/linux/generic/backport-5.15/703-09-v5.17-net-phylink-add-generic-validate-implementation.patch b/target/linux/generic/backport-5.15/703-09-v5.17-net-phylink-add-generic-validate-implementation.patch
+new file mode 100644
+index 0000000000..f30a566c81
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-09-v5.17-net-phylink-add-generic-validate-implementation.patch
+@@ -0,0 +1,341 @@
++From 34ae2c09d46a2d0abd907e139b466f798e4095a8 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Mon, 15 Nov 2021 10:00:27 +0000
++Subject: [PATCH] net: phylink: add generic validate implementation
++
++Add a generic validate() implementation using the supported_interfaces
++and a bitmask of MAC pause/speed/duplex capabilities. This allows us
++to entirely eliminate many driver private validate() implementations.
++
++We expose the underlying phylink_get_linkmodes() function so that
++drivers which have special needs can still benefit from conversion.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/phy/phylink.c | 252 ++++++++++++++++++++++++++++++++++++++
++ include/linux/phylink.h   |  31 +++++
++ 2 files changed, 283 insertions(+)
++
++--- a/drivers/net/phy/phylink.c
+++++ b/drivers/net/phy/phylink.c
++@@ -172,6 +172,258 @@ static int phylink_validate_mac_and_pcs(
++ 	return phylink_is_empty_linkmode(supported) ? -EINVAL : 0;
++ }
++ 
+++static void phylink_caps_to_linkmodes(unsigned long *linkmodes,
+++				      unsigned long caps)
+++{
+++	if (caps & MAC_SYM_PAUSE)
+++		__set_bit(ETHTOOL_LINK_MODE_Pause_BIT, linkmodes);
+++
+++	if (caps & MAC_ASYM_PAUSE)
+++		__set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, linkmodes);
+++
+++	if (caps & MAC_10HD)
+++		__set_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, linkmodes);
+++
+++	if (caps & MAC_10FD)
+++		__set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, linkmodes);
+++
+++	if (caps & MAC_100HD) {
+++		__set_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_100baseFX_Half_BIT, linkmodes);
+++	}
+++
+++	if (caps & MAC_100FD) {
+++		__set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_100baseT1_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_100baseFX_Full_BIT, linkmodes);
+++	}
+++
+++	if (caps & MAC_1000HD)
+++		__set_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, linkmodes);
+++
+++	if (caps & MAC_1000FD) {
+++		__set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_1000baseT1_Full_BIT, linkmodes);
+++	}
+++
+++	if (caps & MAC_2500FD) {
+++		__set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_2500baseX_Full_BIT, linkmodes);
+++	}
+++
+++	if (caps & MAC_5000FD)
+++		__set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT, linkmodes);
+++
+++	if (caps & MAC_10000FD) {
+++		__set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_10000baseR_FEC_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_10000baseCR_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_10000baseSR_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_10000baseLR_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_10000baseER_Full_BIT, linkmodes);
+++	}
+++
+++	if (caps & MAC_25000FD) {
+++		__set_bit(ETHTOOL_LINK_MODE_25000baseCR_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_25000baseKR_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_25000baseSR_Full_BIT, linkmodes);
+++	}
+++
+++	if (caps & MAC_40000FD) {
+++		__set_bit(ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT, linkmodes);
+++	}
+++
+++	if (caps & MAC_50000FD) {
+++		__set_bit(ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_50000baseKR_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_50000baseSR_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_50000baseCR_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT,
+++			  linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_50000baseDR_Full_BIT, linkmodes);
+++	}
+++
+++	if (caps & MAC_56000FD) {
+++		__set_bit(ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT, linkmodes);
+++	}
+++
+++	if (caps & MAC_100000FD) {
+++		__set_bit(ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT,
+++			  linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT,
+++			  linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_100000baseKR_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_100000baseSR_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_100000baseLR_ER_FR_Full_BIT,
+++			  linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_100000baseCR_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_100000baseDR_Full_BIT, linkmodes);
+++	}
+++
+++	if (caps & MAC_200000FD) {
+++		__set_bit(ETHTOOL_LINK_MODE_200000baseKR4_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_200000baseSR4_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_200000baseLR4_ER4_FR4_Full_BIT,
+++			  linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_200000baseDR4_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_200000baseKR2_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_200000baseSR2_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_200000baseLR2_ER2_FR2_Full_BIT,
+++			  linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_200000baseDR2_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_200000baseCR2_Full_BIT, linkmodes);
+++	}
+++
+++	if (caps & MAC_400000FD) {
+++		__set_bit(ETHTOOL_LINK_MODE_400000baseKR8_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_400000baseSR8_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_400000baseLR8_ER8_FR8_Full_BIT,
+++			  linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_400000baseDR8_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_400000baseCR8_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_400000baseKR4_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_400000baseSR4_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_400000baseLR4_ER4_FR4_Full_BIT,
+++			  linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_400000baseDR4_Full_BIT, linkmodes);
+++		__set_bit(ETHTOOL_LINK_MODE_400000baseCR4_Full_BIT, linkmodes);
+++	}
+++}
+++
+++/**
+++ * phylink_get_linkmodes() - get acceptable link modes
+++ * @linkmodes: ethtool linkmode mask (must be already initialised)
+++ * @interface: phy interface mode defined by &typedef phy_interface_t
+++ * @mac_capabilities: bitmask of MAC capabilities
+++ *
+++ * Set all possible pause, speed and duplex linkmodes in @linkmodes that
+++ * are supported by the @interface mode and @mac_capabilities. @linkmodes
+++ * must have been initialised previously.
+++ */
+++void phylink_get_linkmodes(unsigned long *linkmodes, phy_interface_t interface,
+++			   unsigned long mac_capabilities)
+++{
+++	unsigned long caps = MAC_SYM_PAUSE | MAC_ASYM_PAUSE;
+++
+++	switch (interface) {
+++	case PHY_INTERFACE_MODE_USXGMII:
+++		caps |= MAC_10000FD | MAC_5000FD | MAC_2500FD;
+++		fallthrough;
+++
+++	case PHY_INTERFACE_MODE_RGMII_TXID:
+++	case PHY_INTERFACE_MODE_RGMII_RXID:
+++	case PHY_INTERFACE_MODE_RGMII_ID:
+++	case PHY_INTERFACE_MODE_RGMII:
+++	case PHY_INTERFACE_MODE_QSGMII:
+++	case PHY_INTERFACE_MODE_SGMII:
+++	case PHY_INTERFACE_MODE_GMII:
+++		caps |= MAC_1000HD | MAC_1000FD;
+++		fallthrough;
+++
+++	case PHY_INTERFACE_MODE_REVRMII:
+++	case PHY_INTERFACE_MODE_RMII:
+++	case PHY_INTERFACE_MODE_REVMII:
+++	case PHY_INTERFACE_MODE_MII:
+++		caps |= MAC_10HD | MAC_10FD;
+++		fallthrough;
+++
+++	case PHY_INTERFACE_MODE_100BASEX:
+++		caps |= MAC_100HD | MAC_100FD;
+++		break;
+++
+++	case PHY_INTERFACE_MODE_TBI:
+++	case PHY_INTERFACE_MODE_MOCA:
+++	case PHY_INTERFACE_MODE_RTBI:
+++	case PHY_INTERFACE_MODE_1000BASEX:
+++		caps |= MAC_1000HD;
+++		fallthrough;
+++	case PHY_INTERFACE_MODE_TRGMII:
+++		caps |= MAC_1000FD;
+++		break;
+++
+++	case PHY_INTERFACE_MODE_2500BASEX:
+++		caps |= MAC_2500FD;
+++		break;
+++
+++	case PHY_INTERFACE_MODE_5GBASER:
+++		caps |= MAC_5000FD;
+++		break;
+++
+++	case PHY_INTERFACE_MODE_XGMII:
+++	case PHY_INTERFACE_MODE_RXAUI:
+++	case PHY_INTERFACE_MODE_XAUI:
+++	case PHY_INTERFACE_MODE_10GBASER:
+++	case PHY_INTERFACE_MODE_10GKR:
+++		caps |= MAC_10000FD;
+++		break;
+++
+++	case PHY_INTERFACE_MODE_25GBASER:
+++		caps |= MAC_25000FD;
+++		break;
+++
+++	case PHY_INTERFACE_MODE_XLGMII:
+++		caps |= MAC_40000FD;
+++		break;
+++
+++	case PHY_INTERFACE_MODE_INTERNAL:
+++		caps |= ~0;
+++		break;
+++
+++	case PHY_INTERFACE_MODE_NA:
+++	case PHY_INTERFACE_MODE_MAX:
+++	case PHY_INTERFACE_MODE_SMII:
+++		break;
+++	}
+++
+++	phylink_caps_to_linkmodes(linkmodes, caps & mac_capabilities);
+++}
+++EXPORT_SYMBOL_GPL(phylink_get_linkmodes);
+++
+++/**
+++ * phylink_generic_validate() - generic validate() callback implementation
+++ * @config: a pointer to a &struct phylink_config.
+++ * @supported: ethtool bitmask for supported link modes.
+++ * @state: a pointer to a &struct phylink_link_state.
+++ *
+++ * Generic implementation of the validate() callback that MAC drivers can
+++ * use when they pass the range of supported interfaces and MAC capabilities.
+++ * This makes use of phylink_get_linkmodes().
+++ */
+++void phylink_generic_validate(struct phylink_config *config,
+++			      unsigned long *supported,
+++			      struct phylink_link_state *state)
+++{
+++	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
+++
+++	phylink_set_port_modes(mask);
+++	phylink_set(mask, Autoneg);
+++	phylink_get_linkmodes(mask, state->interface, config->mac_capabilities);
+++
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
+++}
+++EXPORT_SYMBOL_GPL(phylink_generic_validate);
+++
++ static int phylink_validate_any(struct phylink *pl, unsigned long *supported,
++ 				struct phylink_link_state *state)
++ {
++--- a/include/linux/phylink.h
+++++ b/include/linux/phylink.h
++@@ -20,6 +20,29 @@ enum {
++ 	MLO_AN_PHY = 0,	/* Conventional PHY */
++ 	MLO_AN_FIXED,	/* Fixed-link mode */
++ 	MLO_AN_INBAND,	/* In-band protocol */
+++
+++	MAC_SYM_PAUSE	= BIT(0),
+++	MAC_ASYM_PAUSE	= BIT(1),
+++	MAC_10HD	= BIT(2),
+++	MAC_10FD	= BIT(3),
+++	MAC_10		= MAC_10HD | MAC_10FD,
+++	MAC_100HD	= BIT(4),
+++	MAC_100FD	= BIT(5),
+++	MAC_100		= MAC_100HD | MAC_100FD,
+++	MAC_1000HD	= BIT(6),
+++	MAC_1000FD	= BIT(7),
+++	MAC_1000	= MAC_1000HD | MAC_1000FD,
+++	MAC_2500FD	= BIT(8),
+++	MAC_5000FD	= BIT(9),
+++	MAC_10000FD	= BIT(10),
+++	MAC_20000FD	= BIT(11),
+++	MAC_25000FD	= BIT(12),
+++	MAC_40000FD	= BIT(13),
+++	MAC_50000FD	= BIT(14),
+++	MAC_56000FD	= BIT(15),
+++	MAC_100000FD	= BIT(16),
+++	MAC_200000FD	= BIT(17),
+++	MAC_400000FD	= BIT(18),
++ };
++ 
++ static inline bool phylink_autoneg_inband(unsigned int mode)
++@@ -70,6 +93,7 @@ enum phylink_op_type {
++  *		     if MAC link is at %MLO_AN_FIXED mode.
++  * @supported_interfaces: bitmap describing which PHY_INTERFACE_MODE_xxx
++  *                        are supported by the MAC/PCS.
+++ * @mac_capabilities: MAC pause/speed/duplex capabilities.
++  */
++ struct phylink_config {
++ 	struct device *dev;
++@@ -81,6 +105,7 @@ struct phylink_config {
++ 	void (*get_fixed_state)(struct phylink_config *config,
++ 				struct phylink_link_state *state);
++ 	DECLARE_PHY_INTERFACE_MASK(supported_interfaces);
+++	unsigned long mac_capabilities;
++ };
++ 
++ /**
++@@ -462,6 +487,12 @@ void pcs_link_up(struct phylink_pcs *pcs
++ 		 phy_interface_t interface, int speed, int duplex);
++ #endif
++ 
+++void phylink_get_linkmodes(unsigned long *linkmodes, phy_interface_t interface,
+++			   unsigned long mac_capabilities);
+++void phylink_generic_validate(struct phylink_config *config,
+++			      unsigned long *supported,
+++			      struct phylink_link_state *state);
+++
++ struct phylink *phylink_create(struct phylink_config *, struct fwnode_handle *,
++ 			       phy_interface_t iface,
++ 			       const struct phylink_mac_ops *mac_ops);
+diff --git a/target/linux/generic/backport-5.15/703-10-v5.16-net-dsa-introduce-helpers-for-iterating-through-port.patch b/target/linux/generic/backport-5.15/703-10-v5.16-net-dsa-introduce-helpers-for-iterating-through-port.patch
+new file mode 100644
+index 0000000000..a55623519c
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-10-v5.16-net-dsa-introduce-helpers-for-iterating-through-port.patch
+@@ -0,0 +1,68 @@
++From 82b318983c515f29b8b3a0dad9f6a5fe8a68a7f4 Mon Sep 17 00:00:00 2001
++From: Vladimir Oltean <vladimir.oltean@nxp.com>
++Date: Wed, 20 Oct 2021 20:49:49 +0300
++Subject: [PATCH] net: dsa: introduce helpers for iterating through ports using
++ dp
++
++Since the DSA conversion from the ds->ports array into the dst->ports
++list, the DSA API has encouraged driver writers, as well as the core
++itself, to write inefficient code.
++
++Currently, code that wants to filter by a specific type of port when
++iterating, like {!unused, user, cpu, dsa}, uses the dsa_is_*_port helper.
++Under the hood, this uses dsa_to_port which iterates again through
++dst->ports. But the driver iterates through the port list already, so
++the complexity is quadratic for the typical case of a single-switch
++tree.
++
++This patch introduces some iteration helpers where the iterator is
++already a struct dsa_port *dp, so that the other variant of the
++filtering functions, dsa_port_is_{unused,user,cpu_dsa}, can be used
++directly on the iterator. This eliminates the second lookup.
++
++These functions can be used both by the core and by drivers.
++
++Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ include/net/dsa.h | 28 ++++++++++++++++++++++++++++
++ 1 file changed, 28 insertions(+)
++
++--- a/include/net/dsa.h
+++++ b/include/net/dsa.h
++@@ -476,6 +476,34 @@ static inline bool dsa_is_user_port(stru
++ 	return dsa_to_port(ds, p)->type == DSA_PORT_TYPE_USER;
++ }
++ 
+++#define dsa_tree_for_each_user_port(_dp, _dst) \
+++	list_for_each_entry((_dp), &(_dst)->ports, list) \
+++		if (dsa_port_is_user((_dp)))
+++
+++#define dsa_switch_for_each_port(_dp, _ds) \
+++	list_for_each_entry((_dp), &(_ds)->dst->ports, list) \
+++		if ((_dp)->ds == (_ds))
+++
+++#define dsa_switch_for_each_port_safe(_dp, _next, _ds) \
+++	list_for_each_entry_safe((_dp), (_next), &(_ds)->dst->ports, list) \
+++		if ((_dp)->ds == (_ds))
+++
+++#define dsa_switch_for_each_port_continue_reverse(_dp, _ds) \
+++	list_for_each_entry_continue_reverse((_dp), &(_ds)->dst->ports, list) \
+++		if ((_dp)->ds == (_ds))
+++
+++#define dsa_switch_for_each_available_port(_dp, _ds) \
+++	dsa_switch_for_each_port((_dp), (_ds)) \
+++		if (!dsa_port_is_unused((_dp)))
+++
+++#define dsa_switch_for_each_user_port(_dp, _ds) \
+++	dsa_switch_for_each_port((_dp), (_ds)) \
+++		if (dsa_port_is_user((_dp)))
+++
+++#define dsa_switch_for_each_cpu_port(_dp, _ds) \
+++	dsa_switch_for_each_port((_dp), (_ds)) \
+++		if (dsa_port_is_cpu((_dp)))
+++
++ static inline u32 dsa_user_ports(struct dsa_switch *ds)
++ {
++ 	u32 mask = 0;
+diff --git a/target/linux/generic/backport-5.15/703-11-v5.17-net-phylink-add-pcs_validate-method.patch b/target/linux/generic/backport-5.15/703-11-v5.17-net-phylink-add-pcs_validate-method.patch
+new file mode 100644
+index 0000000000..524ce9bd92
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-11-v5.17-net-phylink-add-pcs_validate-method.patch
+@@ -0,0 +1,106 @@
++From 0d22d4b626a4eaa3196019092eb6c1919e9f8caa Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Wed, 15 Dec 2021 15:34:20 +0000
++Subject: [PATCH] net: phylink: add pcs_validate() method
++
++Add a hook for PCS to validate the link parameters. This avoids MAC
++drivers having to have knowledge of their PCS in their validate()
++method, thereby allowing several MAC drivers to be simplfied.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/phy/phylink.c | 31 +++++++++++++++++++++++++++++++
++ include/linux/phylink.h   | 20 ++++++++++++++++++++
++ 2 files changed, 51 insertions(+)
++
++--- a/drivers/net/phy/phylink.c
+++++ b/drivers/net/phy/phylink.c
++@@ -160,13 +160,44 @@ static int phylink_validate_mac_and_pcs(
++ 					struct phylink_link_state *state)
++ {
++ 	struct phylink_pcs *pcs;
+++	int ret;
++ 
+++	/* Get the PCS for this interface mode */
++ 	if (pl->mac_ops->mac_select_pcs) {
++ 		pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
++ 		if (IS_ERR(pcs))
++ 			return PTR_ERR(pcs);
+++	} else {
+++		pcs = pl->pcs;
++ 	}
++ 
+++	if (pcs) {
+++		/* The PCS, if present, must be setup before phylink_create()
+++		 * has been called. If the ops is not initialised, print an
+++		 * error and backtrace rather than oopsing the kernel.
+++		 */
+++		if (!pcs->ops) {
+++			phylink_err(pl, "interface %s: uninitialised PCS\n",
+++				    phy_modes(state->interface));
+++			dump_stack();
+++			return -EINVAL;
+++		}
+++
+++		/* Validate the link parameters with the PCS */
+++		if (pcs->ops->pcs_validate) {
+++			ret = pcs->ops->pcs_validate(pcs, supported, state);
+++			if (ret < 0 || phylink_is_empty_linkmode(supported))
+++				return -EINVAL;
+++
+++			/* Ensure the advertising mask is a subset of the
+++			 * supported mask.
+++			 */
+++			linkmode_and(state->advertising, state->advertising,
+++				     supported);
+++		}
+++	}
+++
+++	/* Then validate the link parameters with the MAC */
++ 	pl->mac_ops->validate(pl->config, supported, state);
++ 
++ 	return phylink_is_empty_linkmode(supported) ? -EINVAL : 0;
++--- a/include/linux/phylink.h
+++++ b/include/linux/phylink.h
++@@ -398,6 +398,7 @@ struct phylink_pcs {
++ 
++ /**
++  * struct phylink_pcs_ops - MAC PCS operations structure.
+++ * @pcs_validate: validate the link configuration.
++  * @pcs_get_state: read the current MAC PCS link state from the hardware.
++  * @pcs_config: configure the MAC PCS for the selected mode and state.
++  * @pcs_an_restart: restart 802.3z BaseX autonegotiation.
++@@ -405,6 +406,8 @@ struct phylink_pcs {
++  *               (where necessary).
++  */
++ struct phylink_pcs_ops {
+++	int (*pcs_validate)(struct phylink_pcs *pcs, unsigned long *supported,
+++			    const struct phylink_link_state *state);
++ 	void (*pcs_get_state)(struct phylink_pcs *pcs,
++ 			      struct phylink_link_state *state);
++ 	int (*pcs_config)(struct phylink_pcs *pcs, unsigned int mode,
++@@ -418,6 +421,23 @@ struct phylink_pcs_ops {
++ 
++ #if 0 /* For kernel-doc purposes only. */
++ /**
+++ * pcs_validate() - validate the link configuration.
+++ * @pcs: a pointer to a &struct phylink_pcs.
+++ * @supported: ethtool bitmask for supported link modes.
+++ * @state: a const pointer to a &struct phylink_link_state.
+++ *
+++ * Validate the interface mode, and advertising's autoneg bit, removing any
+++ * media ethtool link modes that would not be supportable from the supported
+++ * mask. Phylink will propagate the changes to the advertising mask. See the
+++ * &struct phylink_mac_ops validate() method.
+++ *
+++ * Returns -EINVAL if the interface mode/autoneg mode is not supported.
+++ * Returns non-zero positive if the link state can be supported.
+++ */
+++int pcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
+++		 const struct phylink_link_state *state);
+++
+++/**
++  * pcs_get_state() - Read the current inband link state from the hardware
++  * @pcs: a pointer to a &struct phylink_pcs.
++  * @state: a pointer to a &struct phylink_link_state.
+diff --git a/target/linux/generic/backport-5.15/703-12-v5.17-net-phylink-add-legacy_pre_march2020-indicator.patch b/target/linux/generic/backport-5.15/703-12-v5.17-net-phylink-add-legacy_pre_march2020-indicator.patch
+new file mode 100644
+index 0000000000..16d5da9c70
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-12-v5.17-net-phylink-add-legacy_pre_march2020-indicator.patch
+@@ -0,0 +1,43 @@
++From 3e5b1feccea7db576353ffc302f78d522e4116e6 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Thu, 9 Dec 2021 13:11:32 +0000
++Subject: [PATCH] net: phylink: add legacy_pre_march2020 indicator
++
++Add a boolean to phylink_config to indicate whether a driver has not
++been updated for the changes in commit 7cceb599d15d ("net: phylink:
++avoid mac_config calls"), and thus are reliant on the old behaviour.
++
++We were currently keying the phylink behaviour on the presence of a
++PCS, but this is sub-optimal for modern drivers that may not have a
++PCS.
++
++This commit merely introduces the new flag, but does not add any use,
++since we need all legacy drivers to set this flag before it can be
++used. Once these legacy drivers have been updated, we can remove this
++flag.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ include/linux/phylink.h | 3 +++
++ 1 file changed, 3 insertions(+)
++
++--- a/include/linux/phylink.h
+++++ b/include/linux/phylink.h
++@@ -84,6 +84,8 @@ enum phylink_op_type {
++  * struct phylink_config - PHYLINK configuration structure
++  * @dev: a pointer to a struct device associated with the MAC
++  * @type: operation type of PHYLINK instance
+++ * @legacy_pre_march2020: driver has not been updated for March 2020 updates
+++ *	(See commit 7cceb599d15d ("net: phylink: avoid mac_config calls")
++  * @pcs_poll: MAC PCS cannot provide link change interrupt
++  * @poll_fixed_state: if true, starts link_poll,
++  *		      if MAC link is at %MLO_AN_FIXED mode.
++@@ -98,6 +100,7 @@ enum phylink_op_type {
++ struct phylink_config {
++ 	struct device *dev;
++ 	enum phylink_op_type type;
+++	bool legacy_pre_march2020;
++ 	bool pcs_poll;
++ 	bool poll_fixed_state;
++ 	bool mac_managed_pm;
+diff --git a/target/linux/generic/backport-5.15/703-13-v5.17-net-dsa-mark-DSA-phylink-as-legacy_pre_march2020.patch b/target/linux/generic/backport-5.15/703-13-v5.17-net-dsa-mark-DSA-phylink-as-legacy_pre_march2020.patch
+new file mode 100644
+index 0000000000..849881942e
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-13-v5.17-net-dsa-mark-DSA-phylink-as-legacy_pre_march2020.patch
+@@ -0,0 +1,36 @@
++From 0a9f0794d9bd67e590a9488afe87fbb0419d9539 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Thu, 9 Dec 2021 13:11:38 +0000
++Subject: [PATCH] net: dsa: mark DSA phylink as legacy_pre_march2020
++
++The majority of DSA drivers do not make use of the PCS support, and
++thus operate in legacy mode. In order to preserve this behaviour in
++future, we need to set the legacy_pre_march2020 flag so phylink knows
++this may require the legacy calls.
++
++There are some DSA drivers that do make use of PCS support, and these
++will continue operating as before - legacy_pre_march2020 will not
++prevent split-PCS support enabling the newer phylink behaviour.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ net/dsa/port.c | 7 +++++++
++ 1 file changed, 7 insertions(+)
++
++--- a/net/dsa/port.c
+++++ b/net/dsa/port.c
++@@ -1126,6 +1126,13 @@ int dsa_port_phylink_create(struct dsa_p
++ 	if (err)
++ 		mode = PHY_INTERFACE_MODE_NA;
++ 
+++	/* Presence of phylink_mac_link_state or phylink_mac_an_restart is
+++	 * an indicator of a legacy phylink driver.
+++	 */
+++	if (ds->ops->phylink_mac_link_state ||
+++	    ds->ops->phylink_mac_an_restart)
+++		dp->pl_config.legacy_pre_march2020 = true;
+++
++ 	if (ds->ops->phylink_get_caps)
++ 		ds->ops->phylink_get_caps(ds, dp->index, &dp->pl_config);
++ 
+diff --git a/target/linux/generic/backport-5.15/703-14-v5.17-net-phylink-use-legacy_pre_march2020.patch b/target/linux/generic/backport-5.15/703-14-v5.17-net-phylink-use-legacy_pre_march2020.patch
+new file mode 100644
+index 0000000000..73e53068b8
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-14-v5.17-net-phylink-use-legacy_pre_march2020.patch
+@@ -0,0 +1,115 @@
++From 001f4261fe4d5ae710cf1f445b6cae6d9d3ae26e Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Thu, 9 Dec 2021 13:11:48 +0000
++Subject: [PATCH] net: phylink: use legacy_pre_march2020
++
++Use the legacy flag to indicate whether we should operate in legacy
++mode. This allows us to stop using the presence of a PCS as an
++indicator to the age of the phylink user, and make PCS presence
++optional.
++
++Legacy mode involves:
++1) calling mac_config() whenever the link comes up
++2) calling mac_config() whenever the inband advertisement changes,
++   possibly followed by a call to mac_an_restart()
++3) making use of mac_an_restart()
++4) making use of mac_pcs_get_state()
++
++All the above functionality was moved to a seperate "PCS" block of
++operations in March 2020.
++
++Update the documents to indicate that the differences that this flag
++makes.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/phy/phylink.c | 12 ++++++------
++ include/linux/phylink.h   | 17 +++++++++++++++++
++ 2 files changed, 23 insertions(+), 6 deletions(-)
++
++--- a/drivers/net/phy/phylink.c
+++++ b/drivers/net/phy/phylink.c
++@@ -777,7 +777,7 @@ static void phylink_mac_pcs_an_restart(s
++ 	    phylink_autoneg_inband(pl->cur_link_an_mode)) {
++ 		if (pl->pcs_ops)
++ 			pl->pcs_ops->pcs_an_restart(pl->pcs);
++-		else
+++		else if (pl->config->legacy_pre_march2020)
++ 			pl->mac_ops->mac_an_restart(pl->config);
++ 	}
++ }
++@@ -855,7 +855,7 @@ static int phylink_change_inband_advert(
++ 	if (test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state))
++ 		return 0;
++ 
++-	if (!pl->pcs_ops) {
+++	if (!pl->pcs_ops && pl->config->legacy_pre_march2020) {
++ 		/* Legacy method */
++ 		phylink_mac_config(pl, &pl->link_config);
++ 		phylink_mac_pcs_an_restart(pl);
++@@ -900,7 +900,8 @@ static void phylink_mac_pcs_get_state(st
++ 
++ 	if (pl->pcs_ops)
++ 		pl->pcs_ops->pcs_get_state(pl->pcs, state);
++-	else if (pl->mac_ops->mac_pcs_get_state)
+++	else if (pl->mac_ops->mac_pcs_get_state &&
+++		 pl->config->legacy_pre_march2020)
++ 		pl->mac_ops->mac_pcs_get_state(pl->config, state);
++ 	else
++ 		state->link = 0;
++@@ -1094,12 +1095,11 @@ static void phylink_resolve(struct work_
++ 			}
++ 			phylink_major_config(pl, false, &link_state);
++ 			pl->link_config.interface = link_state.interface;
++-		} else if (!pl->pcs_ops) {
+++		} else if (!pl->pcs_ops && pl->config->legacy_pre_march2020) {
++ 			/* The interface remains unchanged, only the speed,
++ 			 * duplex or pause settings have changed. Call the
++ 			 * old mac_config() method to configure the MAC/PCS
++-			 * only if we do not have a PCS installed (an
++-			 * unconverted user.)
+++			 * only if we do not have a legacy MAC driver.
++ 			 */
++ 			phylink_mac_config(pl, &link_state);
++ 		}
++--- a/include/linux/phylink.h
+++++ b/include/linux/phylink.h
++@@ -210,6 +210,10 @@ struct phylink_pcs *mac_select_pcs(struc
++  * negotiation completion state in @state->an_complete, and link up state
++  * in @state->link. If possible, @state->lp_advertising should also be
++  * populated.
+++ *
+++ * Note: This is a legacy method. This function will not be called unless
+++ * legacy_pre_march2020 is set in &struct phylink_config and there is no
+++ * PCS attached.
++  */
++ void mac_pcs_get_state(struct phylink_config *config,
++ 		       struct phylink_link_state *state);
++@@ -250,6 +254,15 @@ int mac_prepare(struct phylink_config *c
++  * guaranteed to be correct, and so any mac_config() implementation must
++  * never reference these fields.
++  *
+++ * Note: For legacy March 2020 drivers (drivers with legacy_pre_march2020 set
+++ * in their &phylnk_config and which don't have a PCS), this function will be
+++ * called on each link up event, and to also change the in-band advert. For
+++ * non-legacy drivers, it will only be called to reconfigure the MAC for a
+++ * "major" change in e.g. interface mode. It will not be called for changes
+++ * in speed, duplex or pause modes or to change the in-band advertisement.
+++ * In any case, it is strongly preferred that speed, duplex and pause settings
+++ * are handled in the mac_link_up() method and not in this method.
+++ *
++  * (this requires a rewrite - please refer to mac_link_up() for situations
++  *  where the PCS and MAC are not tightly integrated.)
++  *
++@@ -334,6 +347,10 @@ int mac_finish(struct phylink_config *co
++ /**
++  * mac_an_restart() - restart 802.3z BaseX autonegotiation
++  * @config: a pointer to a &struct phylink_config.
+++ *
+++ * Note: This is a legacy method. This function will not be called unless
+++ * legacy_pre_march2020 is set in &struct phylink_config and there is no
+++ * PCS attached.
++  */
++ void mac_an_restart(struct phylink_config *config);
++ 
+diff --git a/target/linux/generic/backport-5.15/703-15-v5.18-net-phy-phylink-fix-DSA-mac_select_pcs-introduction.patch b/target/linux/generic/backport-5.15/703-15-v5.18-net-phy-phylink-fix-DSA-mac_select_pcs-introduction.patch
+new file mode 100644
+index 0000000000..9e5061aaed
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-15-v5.18-net-phy-phylink-fix-DSA-mac_select_pcs-introduction.patch
+@@ -0,0 +1,88 @@
++From 1054457006d4a14de4ae4132030e33d7eedaeba1 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Mon, 21 Feb 2022 17:10:52 +0000
++Subject: [PATCH] net: phy: phylink: fix DSA mac_select_pcs() introduction
++
++Vladimir Oltean reports that probing on DSA drivers that aren't yet
++populating supported_interfaces now fails. Fix this by allowing
++phylink to detect whether DSA actually provides an underlying
++mac_select_pcs() implementation.
++
++Reported-by: Vladimir Oltean <olteanv@gmail.com>
++Fixes: bde018222c6b ("net: dsa: add support for phylink mac_select_pcs()")
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Tested-by: Vladimir Oltean <olteanv@gmail.com>
++Link: https://lore.kernel.org/r/E1nMCD6-00A0wC-FG@rmk-PC.armlinux.org.uk
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/phy/phylink.c | 14 +++++++++++---
++ net/dsa/port.c            |  2 +-
++ 2 files changed, 12 insertions(+), 4 deletions(-)
++
++--- a/drivers/net/phy/phylink.c
+++++ b/drivers/net/phy/phylink.c
++@@ -74,6 +74,7 @@ struct phylink {
++ 	struct work_struct resolve;
++ 
++ 	bool mac_link_dropped;
+++	bool using_mac_select_pcs;
++ 
++ 	struct sfp_bus *sfp_bus;
++ 	bool sfp_may_have_phy;
++@@ -163,7 +164,7 @@ static int phylink_validate_mac_and_pcs(
++ 	int ret;
++ 
++ 	/* Get the PCS for this interface mode */
++-	if (pl->mac_ops->mac_select_pcs) {
+++	if (pl->using_mac_select_pcs) {
++ 		pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
++ 		if (IS_ERR(pcs))
++ 			return PTR_ERR(pcs);
++@@ -790,7 +791,7 @@ static void phylink_major_config(struct
++ 
++ 	phylink_dbg(pl, "major config %s\n", phy_modes(state->interface));
++ 
++-	if (pl->mac_ops->mac_select_pcs) {
+++	if (pl->using_mac_select_pcs) {
++ 		pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
++ 		if (IS_ERR(pcs)) {
++ 			phylink_err(pl,
++@@ -1192,11 +1193,17 @@ struct phylink *phylink_create(struct ph
++ 			       phy_interface_t iface,
++ 			       const struct phylink_mac_ops *mac_ops)
++ {
+++	bool using_mac_select_pcs = false;
++ 	struct phylink *pl;
++ 	int ret;
++ 
++-	/* Validate the supplied configuration */
++ 	if (mac_ops->mac_select_pcs &&
+++	    mac_ops->mac_select_pcs(config, PHY_INTERFACE_MODE_NA) !=
+++	      ERR_PTR(-EOPNOTSUPP))
+++		using_mac_select_pcs = true;
+++
+++	/* Validate the supplied configuration */
+++	if (using_mac_select_pcs &&
++ 	    phy_interface_empty(config->supported_interfaces)) {
++ 		dev_err(config->dev,
++ 			"phylink: error: empty supported_interfaces but mac_select_pcs() method present\n");
++@@ -1220,6 +1227,7 @@ struct phylink *phylink_create(struct ph
++ 		return ERR_PTR(-EINVAL);
++ 	}
++ 
+++	pl->using_mac_select_pcs = using_mac_select_pcs;
++ 	pl->phy_state.interface = iface;
++ 	pl->link_interface = iface;
++ 	if (iface == PHY_INTERFACE_MODE_MOCA)
++--- a/net/dsa/port.c
+++++ b/net/dsa/port.c
++@@ -1033,8 +1033,8 @@ dsa_port_phylink_mac_select_pcs(struct p
++ 				phy_interface_t interface)
++ {
++ 	struct dsa_port *dp = container_of(config, struct dsa_port, pl_config);
+++	struct phylink_pcs *pcs = ERR_PTR(-EOPNOTSUPP);
++ 	struct dsa_switch *ds = dp->ds;
++-	struct phylink_pcs *pcs = NULL;
++ 
++ 	if (ds->ops->phylink_mac_select_pcs)
++ 		pcs = ds->ops->phylink_mac_select_pcs(ds, dp->index, interface);
+diff --git a/target/linux/generic/backport-5.15/703-16-v5.16-net-mvneta-populate-supported_interfaces-member.patch b/target/linux/generic/backport-5.15/703-16-v5.16-net-mvneta-populate-supported_interfaces-member.patch
+new file mode 100644
+index 0000000000..f21fa4b277
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-16-v5.16-net-mvneta-populate-supported_interfaces-member.patch
+@@ -0,0 +1,48 @@
++From fdedb695e6a8657302341cda81d519ef04f9acaa Mon Sep 17 00:00:00 2001
++From: Russell King <rmk+kernel@armlinux.org.uk>
++Date: Wed, 27 Oct 2021 10:03:43 +0100
++Subject: [PATCH] net: mvneta: populate supported_interfaces member
++
++Populate the phy_interface_t bitmap for the Marvell mvneta driver with
++interfaces modes supported by the MAC.
++
++Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/marvell/mvneta.c | 25 +++++++++++++++++++++++++
++ 1 file changed, 25 insertions(+)
++
++--- a/drivers/net/ethernet/marvell/mvneta.c
+++++ b/drivers/net/ethernet/marvell/mvneta.c
++@@ -5180,6 +5180,31 @@ static int mvneta_probe(struct platform_
++ 
++ 	pp->phylink_config.dev = &dev->dev;
++ 	pp->phylink_config.type = PHYLINK_NETDEV;
+++	phy_interface_set_rgmii(pp->phylink_config.supported_interfaces);
+++	__set_bit(PHY_INTERFACE_MODE_QSGMII,
+++		  pp->phylink_config.supported_interfaces);
+++	if (comphy) {
+++		/* If a COMPHY is present, we can support any of the serdes
+++		 * modes and switch between them.
+++		 */
+++		__set_bit(PHY_INTERFACE_MODE_SGMII,
+++			  pp->phylink_config.supported_interfaces);
+++		__set_bit(PHY_INTERFACE_MODE_1000BASEX,
+++			  pp->phylink_config.supported_interfaces);
+++		__set_bit(PHY_INTERFACE_MODE_2500BASEX,
+++			  pp->phylink_config.supported_interfaces);
+++	} else if (phy_mode == PHY_INTERFACE_MODE_2500BASEX) {
+++		/* No COMPHY, with only 2500BASE-X mode supported */
+++		__set_bit(PHY_INTERFACE_MODE_2500BASEX,
+++			  pp->phylink_config.supported_interfaces);
+++	} else if (phy_mode == PHY_INTERFACE_MODE_1000BASEX ||
+++		   phy_mode == PHY_INTERFACE_MODE_SGMII) {
+++		/* No COMPHY, we can switch between 1000BASE-X and SGMII */
+++		__set_bit(PHY_INTERFACE_MODE_1000BASEX,
+++			  pp->phylink_config.supported_interfaces);
+++		__set_bit(PHY_INTERFACE_MODE_SGMII,
+++			  pp->phylink_config.supported_interfaces);
+++	}
++ 
++ 	phylink = phylink_create(&pp->phylink_config, pdev->dev.fwnode,
++ 				 phy_mode, &mvneta_phylink_ops);
+diff --git a/target/linux/generic/backport-5.15/703-17-v5.16-net-mvneta-remove-interface-checks-in-mvneta_validat.patch b/target/linux/generic/backport-5.15/703-17-v5.16-net-mvneta-remove-interface-checks-in-mvneta_validat.patch
+new file mode 100644
+index 0000000000..e287e39d6a
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-17-v5.16-net-mvneta-remove-interface-checks-in-mvneta_validat.patch
+@@ -0,0 +1,35 @@
++From d9ca72807ecb236f679b960c70ef5b7d4a5f0222 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Wed, 27 Oct 2021 10:03:48 +0100
++Subject: [PATCH] net: mvneta: remove interface checks in mvneta_validate()
++
++As phylink checks the interface mode against the supported_interfaces
++bitmap, we no longer need to validate the interface mode in the
++validation function. Remove this to simplify it.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/marvell/mvneta.c | 11 ++---------
++ 1 file changed, 2 insertions(+), 9 deletions(-)
++
++--- a/drivers/net/ethernet/marvell/mvneta.c
+++++ b/drivers/net/ethernet/marvell/mvneta.c
++@@ -3833,15 +3833,8 @@ static void mvneta_validate(struct phyli
++ 	 * "Bit 2 Field InBandAnEn In-band Auto-Negotiation enable. ...
++ 	 * When <PortType> = 1 (1000BASE-X) this field must be set to 1."
++ 	 */
++-	if (phy_interface_mode_is_8023z(state->interface)) {
++-		if (!phylink_test(state->advertising, Autoneg)) {
++-			linkmode_zero(supported);
++-			return;
++-		}
++-	} else if (state->interface != PHY_INTERFACE_MODE_NA &&
++-		   state->interface != PHY_INTERFACE_MODE_QSGMII &&
++-		   state->interface != PHY_INTERFACE_MODE_SGMII &&
++-		   !phy_interface_mode_is_rgmii(state->interface)) {
+++	if (phy_interface_mode_is_8023z(state->interface) &&
+++	    !phylink_test(state->advertising, Autoneg)) {
++ 		linkmode_zero(supported);
++ 		return;
++ 	}
+diff --git a/target/linux/generic/backport-5.15/703-18-v5.16-net-mvneta-drop-use-of-phylink_helper_basex_speed.patch b/target/linux/generic/backport-5.15/703-18-v5.16-net-mvneta-drop-use-of-phylink_helper_basex_speed.patch
+new file mode 100644
+index 0000000000..9121612bf8
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-18-v5.16-net-mvneta-drop-use-of-phylink_helper_basex_speed.patch
+@@ -0,0 +1,55 @@
++From 099cbfa286ab937d8213c2dc5c0b401969b78042 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Wed, 27 Oct 2021 10:03:53 +0100
++Subject: [PATCH] net: mvneta: drop use of phylink_helper_basex_speed()
++
++Now that we have a better method to select SFP interface modes, we
++no longer need to use phylink_helper_basex_speed() in a driver's
++validation function, and we can also get rid of our hack to indicate
++both 1000base-X and 2500base-X if the comphy is present to make that
++work. Remove this hack and use of phylink_helper_basex_speed().
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/marvell/mvneta.c | 12 +++---------
++ 1 file changed, 3 insertions(+), 9 deletions(-)
++
++--- a/drivers/net/ethernet/marvell/mvneta.c
+++++ b/drivers/net/ethernet/marvell/mvneta.c
++@@ -3824,8 +3824,6 @@ static void mvneta_validate(struct phyli
++ 			    unsigned long *supported,
++ 			    struct phylink_link_state *state)
++ {
++-	struct net_device *ndev = to_net_dev(config->dev);
++-	struct mvneta_port *pp = netdev_priv(ndev);
++ 	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
++ 
++ 	/* We only support QSGMII, SGMII, 802.3z and RGMII modes.
++@@ -3847,11 +3845,12 @@ static void mvneta_validate(struct phyli
++ 	phylink_set(mask, Pause);
++ 
++ 	/* Half-duplex at speeds higher than 100Mbit is unsupported */
++-	if (pp->comphy || state->interface != PHY_INTERFACE_MODE_2500BASEX) {
+++	if (state->interface != PHY_INTERFACE_MODE_2500BASEX) {
++ 		phylink_set(mask, 1000baseT_Full);
++ 		phylink_set(mask, 1000baseX_Full);
++ 	}
++-	if (pp->comphy || state->interface == PHY_INTERFACE_MODE_2500BASEX) {
+++
+++	if (state->interface == PHY_INTERFACE_MODE_2500BASEX) {
++ 		phylink_set(mask, 2500baseT_Full);
++ 		phylink_set(mask, 2500baseX_Full);
++ 	}
++@@ -3866,11 +3865,6 @@ static void mvneta_validate(struct phyli
++ 
++ 	linkmode_and(supported, supported, mask);
++ 	linkmode_and(state->advertising, state->advertising, mask);
++-
++-	/* We can only operate at 2500BaseX or 1000BaseX.  If requested
++-	 * to advertise both, only report advertising at 2500BaseX.
++-	 */
++-	phylink_helper_basex_speed(state);
++ }
++ 
++ static void mvneta_mac_pcs_get_state(struct phylink_config *config,
+diff --git a/target/linux/generic/backport-5.15/703-19-v5.17-net-mvneta-use-phylink_generic_validate.patch b/target/linux/generic/backport-5.15/703-19-v5.17-net-mvneta-use-phylink_generic_validate.patch
+new file mode 100644
+index 0000000000..209dfbc0de
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-19-v5.17-net-mvneta-use-phylink_generic_validate.patch
+@@ -0,0 +1,72 @@
++From 02a0988b98930491db95966fb8086072e47dabb6 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Mon, 15 Nov 2021 10:00:32 +0000
++Subject: [PATCH] net: mvneta: use phylink_generic_validate()
++
++Convert mvneta to use phylink_generic_validate() for the bulk of its
++validate() implementation. This network adapter has a restriction
++that for 802.3z links, autonegotiation must be enabled.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/marvell/mvneta.c | 34 ++++-----------------------
++ 1 file changed, 4 insertions(+), 30 deletions(-)
++
++--- a/drivers/net/ethernet/marvell/mvneta.c
+++++ b/drivers/net/ethernet/marvell/mvneta.c
++@@ -3824,8 +3824,6 @@ static void mvneta_validate(struct phyli
++ 			    unsigned long *supported,
++ 			    struct phylink_link_state *state)
++ {
++-	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
++-
++ 	/* We only support QSGMII, SGMII, 802.3z and RGMII modes.
++ 	 * When in 802.3z mode, we must have AN enabled:
++ 	 * "Bit 2 Field InBandAnEn In-band Auto-Negotiation enable. ...
++@@ -3837,34 +3835,7 @@ static void mvneta_validate(struct phyli
++ 		return;
++ 	}
++ 
++-	/* Allow all the expected bits */
++-	phylink_set(mask, Autoneg);
++-	phylink_set_port_modes(mask);
++-
++-	/* Asymmetric pause is unsupported */
++-	phylink_set(mask, Pause);
++-
++-	/* Half-duplex at speeds higher than 100Mbit is unsupported */
++-	if (state->interface != PHY_INTERFACE_MODE_2500BASEX) {
++-		phylink_set(mask, 1000baseT_Full);
++-		phylink_set(mask, 1000baseX_Full);
++-	}
++-
++-	if (state->interface == PHY_INTERFACE_MODE_2500BASEX) {
++-		phylink_set(mask, 2500baseT_Full);
++-		phylink_set(mask, 2500baseX_Full);
++-	}
++-
++-	if (!phy_interface_mode_is_8023z(state->interface)) {
++-		/* 10M and 100M are only supported in non-802.3z mode */
++-		phylink_set(mask, 10baseT_Half);
++-		phylink_set(mask, 10baseT_Full);
++-		phylink_set(mask, 100baseT_Half);
++-		phylink_set(mask, 100baseT_Full);
++-	}
++-
++-	linkmode_and(supported, supported, mask);
++-	linkmode_and(state->advertising, state->advertising, mask);
+++	phylink_generic_validate(config, supported, state);
++ }
++ 
++ static void mvneta_mac_pcs_get_state(struct phylink_config *config,
++@@ -5167,6 +5138,9 @@ static int mvneta_probe(struct platform_
++ 
++ 	pp->phylink_config.dev = &dev->dev;
++ 	pp->phylink_config.type = PHYLINK_NETDEV;
+++	pp->phylink_config.mac_capabilities = MAC_SYM_PAUSE | MAC_10 |
+++		MAC_100 | MAC_1000FD | MAC_2500FD;
+++
++ 	phy_interface_set_rgmii(pp->phylink_config.supported_interfaces);
++ 	__set_bit(PHY_INTERFACE_MODE_QSGMII,
++ 		  pp->phylink_config.supported_interfaces);
+diff --git a/target/linux/generic/backport-5.15/703-20-v5.17-net-mvneta-mark-as-a-legacy_pre_march2020-driver.patch b/target/linux/generic/backport-5.15/703-20-v5.17-net-mvneta-mark-as-a-legacy_pre_march2020-driver.patch
+new file mode 100644
+index 0000000000..31717565bf
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/703-20-v5.17-net-mvneta-mark-as-a-legacy_pre_march2020-driver.patch
+@@ -0,0 +1,29 @@
++From 2106be4fdf3223d9c5bd485e6ef094139e3197ba Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Sun, 12 Dec 2021 13:01:21 +0000
++Subject: [PATCH] net: mvneta: mark as a legacy_pre_march2020 driver
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++mvneta provides mac_an_restart and mac_pcs_get_state methods, so needs
++to be marked as a legacy driver. Marek spotted that mvneta had stopped
++working in 2500base-X mode - thanks for reporting.
++
++Reported-by: Marek Behún <kabel@kernel.org>
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/marvell/mvneta.c | 1 +
++ 1 file changed, 1 insertion(+)
++
++--- a/drivers/net/ethernet/marvell/mvneta.c
+++++ b/drivers/net/ethernet/marvell/mvneta.c
++@@ -5138,6 +5138,7 @@ static int mvneta_probe(struct platform_
++ 
++ 	pp->phylink_config.dev = &dev->dev;
++ 	pp->phylink_config.type = PHYLINK_NETDEV;
+++	pp->phylink_config.legacy_pre_march2020 = true;
++ 	pp->phylink_config.mac_capabilities = MAC_SYM_PAUSE | MAC_10 |
++ 		MAC_100 | MAC_1000FD | MAC_2500FD;
++ 
+diff --git a/target/linux/generic/backport-5.15/704-01-v5.17-net-mtk_eth_soc-populate-supported_interfaces-member.patch b/target/linux/generic/backport-5.15/704-01-v5.17-net-mtk_eth_soc-populate-supported_interfaces-member.patch
+new file mode 100644
+index 0000000000..43e1f9cc31
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/704-01-v5.17-net-mtk_eth_soc-populate-supported_interfaces-member.patch
+@@ -0,0 +1,43 @@
++From 83800d29f0c578e82554e7d4c6bfdbdf9b6cf428 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Tue, 16 Nov 2021 10:06:43 +0000
++Subject: [PATCH] net: mtk_eth_soc: populate supported_interfaces member
++
++Populate the phy interface mode bitmap for the Mediatek driver with
++interfaces modes supported by the MAC.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 20 ++++++++++++++++++++
++ 1 file changed, 20 insertions(+)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -3352,6 +3352,26 @@ static int mtk_add_mac(struct mtk_eth *e
++ 
++ 	mac->phylink_config.dev = &eth->netdev[id]->dev;
++ 	mac->phylink_config.type = PHYLINK_NETDEV;
+++	__set_bit(PHY_INTERFACE_MODE_MII,
+++		  mac->phylink_config.supported_interfaces);
+++	__set_bit(PHY_INTERFACE_MODE_GMII,
+++		  mac->phylink_config.supported_interfaces);
+++
+++	if (MTK_HAS_CAPS(mac->hw->soc->caps, MTK_RGMII))
+++		phy_interface_set_rgmii(mac->phylink_config.supported_interfaces);
+++
+++	if (MTK_HAS_CAPS(mac->hw->soc->caps, MTK_TRGMII) && !mac->id)
+++		__set_bit(PHY_INTERFACE_MODE_TRGMII,
+++			  mac->phylink_config.supported_interfaces);
+++
+++	if (MTK_HAS_CAPS(mac->hw->soc->caps, MTK_SGMII)) {
+++		__set_bit(PHY_INTERFACE_MODE_SGMII,
+++			  mac->phylink_config.supported_interfaces);
+++		__set_bit(PHY_INTERFACE_MODE_1000BASEX,
+++			  mac->phylink_config.supported_interfaces);
+++		__set_bit(PHY_INTERFACE_MODE_2500BASEX,
+++			  mac->phylink_config.supported_interfaces);
+++	}
++ 
++ 	phylink = phylink_create(&mac->phylink_config,
++ 				 of_fwnode_handle(mac->of_node),
+diff --git a/target/linux/generic/backport-5.15/704-02-v5.17-net-mtk_eth_soc-remove-interface-checks-in-mtk_valid.patch b/target/linux/generic/backport-5.15/704-02-v5.17-net-mtk_eth_soc-remove-interface-checks-in-mtk_valid.patch
+new file mode 100644
+index 0000000000..05a84c4f67
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/704-02-v5.17-net-mtk_eth_soc-remove-interface-checks-in-mtk_valid.patch
+@@ -0,0 +1,75 @@
++From db81ca153814475d7e07365d46a4d1134bd122e2 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Tue, 16 Nov 2021 10:06:48 +0000
++Subject: [PATCH] net: mtk_eth_soc: remove interface checks in mtk_validate()
++
++As phylink checks the interface mode against the supported_interfaces
++bitmap, we no longer need to validate the interface mode, nor handle
++PHY_INTERFACE_MODE_NA in the validation function. Remove these to
++simplify the implementation.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 34 ---------------------
++ 1 file changed, 34 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -567,24 +567,8 @@ static void mtk_validate(struct phylink_
++ 			 unsigned long *supported,
++ 			 struct phylink_link_state *state)
++ {
++-	struct mtk_mac *mac = container_of(config, struct mtk_mac,
++-					   phylink_config);
++ 	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
++ 
++-	if (state->interface != PHY_INTERFACE_MODE_NA &&
++-	    state->interface != PHY_INTERFACE_MODE_MII &&
++-	    state->interface != PHY_INTERFACE_MODE_GMII &&
++-	    !(MTK_HAS_CAPS(mac->hw->soc->caps, MTK_RGMII) &&
++-	      phy_interface_mode_is_rgmii(state->interface)) &&
++-	    !(MTK_HAS_CAPS(mac->hw->soc->caps, MTK_TRGMII) &&
++-	      !mac->id && state->interface == PHY_INTERFACE_MODE_TRGMII) &&
++-	    !(MTK_HAS_CAPS(mac->hw->soc->caps, MTK_SGMII) &&
++-	      (state->interface == PHY_INTERFACE_MODE_SGMII ||
++-	       phy_interface_mode_is_8023z(state->interface)))) {
++-		linkmode_zero(supported);
++-		return;
++-	}
++-
++ 	phylink_set_port_modes(mask);
++ 	phylink_set(mask, Autoneg);
++ 
++@@ -611,7 +595,6 @@ static void mtk_validate(struct phylink_
++ 	case PHY_INTERFACE_MODE_MII:
++ 	case PHY_INTERFACE_MODE_RMII:
++ 	case PHY_INTERFACE_MODE_REVMII:
++-	case PHY_INTERFACE_MODE_NA:
++ 	default:
++ 		phylink_set(mask, 10baseT_Half);
++ 		phylink_set(mask, 10baseT_Full);
++@@ -620,23 +603,6 @@ static void mtk_validate(struct phylink_
++ 		break;
++ 	}
++ 
++-	if (state->interface == PHY_INTERFACE_MODE_NA) {
++-		if (MTK_HAS_CAPS(mac->hw->soc->caps, MTK_SGMII)) {
++-			phylink_set(mask, 1000baseT_Full);
++-			phylink_set(mask, 1000baseX_Full);
++-			phylink_set(mask, 2500baseX_Full);
++-		}
++-		if (MTK_HAS_CAPS(mac->hw->soc->caps, MTK_RGMII)) {
++-			phylink_set(mask, 1000baseT_Full);
++-			phylink_set(mask, 1000baseT_Half);
++-			phylink_set(mask, 1000baseX_Full);
++-		}
++-		if (MTK_HAS_CAPS(mac->hw->soc->caps, MTK_GEPHY)) {
++-			phylink_set(mask, 1000baseT_Full);
++-			phylink_set(mask, 1000baseT_Half);
++-		}
++-	}
++-
++ 	phylink_set(mask, Pause);
++ 	phylink_set(mask, Asym_Pause);
++ 
+diff --git a/target/linux/generic/backport-5.15/704-03-v5.17-net-mtk_eth_soc-drop-use-of-phylink_helper_basex_spe.patch b/target/linux/generic/backport-5.15/704-03-v5.17-net-mtk_eth_soc-drop-use-of-phylink_helper_basex_spe.patch
+new file mode 100644
+index 0000000000..a3cfab7f88
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/704-03-v5.17-net-mtk_eth_soc-drop-use-of-phylink_helper_basex_spe.patch
+@@ -0,0 +1,42 @@
++From 71d927494463c4f016d828e1134da26b7e961af5 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Tue, 16 Nov 2021 10:06:53 +0000
++Subject: [PATCH] net: mtk_eth_soc: drop use of phylink_helper_basex_speed()
++
++Now that we have a better method to select SFP interface modes, we
++no longer need to use phylink_helper_basex_speed() in a driver's
++validation function, and we can also get rid of our hack to indicate
++both 1000base-X and 2500base-X if the comphy is present to make that
++work. Remove this hack and use of phylink_helper_basex_speed().
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 8 ++------
++ 1 file changed, 2 insertions(+), 6 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -577,8 +577,9 @@ static void mtk_validate(struct phylink_
++ 		phylink_set(mask, 1000baseT_Full);
++ 		break;
++ 	case PHY_INTERFACE_MODE_1000BASEX:
++-	case PHY_INTERFACE_MODE_2500BASEX:
++ 		phylink_set(mask, 1000baseX_Full);
+++		break;
+++	case PHY_INTERFACE_MODE_2500BASEX:
++ 		phylink_set(mask, 2500baseX_Full);
++ 		break;
++ 	case PHY_INTERFACE_MODE_GMII:
++@@ -608,11 +609,6 @@ static void mtk_validate(struct phylink_
++ 
++ 	linkmode_and(supported, supported, mask);
++ 	linkmode_and(state->advertising, state->advertising, mask);
++-
++-	/* We can only operate at 2500BaseX or 1000BaseX. If requested
++-	 * to advertise both, only report advertising at 2500BaseX.
++-	 */
++-	phylink_helper_basex_speed(state);
++ }
++ 
++ static const struct phylink_mac_ops mtk_phylink_ops = {
+diff --git a/target/linux/generic/backport-5.15/704-04-v5.17-net-mtk_eth_soc-use-phylink_generic_validate.patch b/target/linux/generic/backport-5.15/704-04-v5.17-net-mtk_eth_soc-use-phylink_generic_validate.patch
+new file mode 100644
+index 0000000000..7f3734a765
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/704-04-v5.17-net-mtk_eth_soc-use-phylink_generic_validate.patch
+@@ -0,0 +1,84 @@
++From a4238f6ce151afa331375d74a5033b76da637644 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Tue, 16 Nov 2021 10:06:58 +0000
++Subject: [PATCH] net: mtk_eth_soc: use phylink_generic_validate()
++
++mtk_eth_soc has no special behaviour in its validation implementation,
++so can be switched to phylink_generic_validate().
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 53 ++-------------------
++ 1 file changed, 4 insertions(+), 49 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -563,56 +563,8 @@ static void mtk_mac_link_up(struct phyli
++ 	mtk_w32(mac->hw, mcr, MTK_MAC_MCR(mac->id));
++ }
++ 
++-static void mtk_validate(struct phylink_config *config,
++-			 unsigned long *supported,
++-			 struct phylink_link_state *state)
++-{
++-	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
++-
++-	phylink_set_port_modes(mask);
++-	phylink_set(mask, Autoneg);
++-
++-	switch (state->interface) {
++-	case PHY_INTERFACE_MODE_TRGMII:
++-		phylink_set(mask, 1000baseT_Full);
++-		break;
++-	case PHY_INTERFACE_MODE_1000BASEX:
++-		phylink_set(mask, 1000baseX_Full);
++-		break;
++-	case PHY_INTERFACE_MODE_2500BASEX:
++-		phylink_set(mask, 2500baseX_Full);
++-		break;
++-	case PHY_INTERFACE_MODE_GMII:
++-	case PHY_INTERFACE_MODE_RGMII:
++-	case PHY_INTERFACE_MODE_RGMII_ID:
++-	case PHY_INTERFACE_MODE_RGMII_RXID:
++-	case PHY_INTERFACE_MODE_RGMII_TXID:
++-		phylink_set(mask, 1000baseT_Half);
++-		fallthrough;
++-	case PHY_INTERFACE_MODE_SGMII:
++-		phylink_set(mask, 1000baseT_Full);
++-		phylink_set(mask, 1000baseX_Full);
++-		fallthrough;
++-	case PHY_INTERFACE_MODE_MII:
++-	case PHY_INTERFACE_MODE_RMII:
++-	case PHY_INTERFACE_MODE_REVMII:
++-	default:
++-		phylink_set(mask, 10baseT_Half);
++-		phylink_set(mask, 10baseT_Full);
++-		phylink_set(mask, 100baseT_Half);
++-		phylink_set(mask, 100baseT_Full);
++-		break;
++-	}
++-
++-	phylink_set(mask, Pause);
++-	phylink_set(mask, Asym_Pause);
++-
++-	linkmode_and(supported, supported, mask);
++-	linkmode_and(state->advertising, state->advertising, mask);
++-}
++-
++ static const struct phylink_mac_ops mtk_phylink_ops = {
++-	.validate = mtk_validate,
+++	.validate = phylink_generic_validate,
++ 	.mac_pcs_get_state = mtk_mac_pcs_get_state,
++ 	.mac_an_restart = mtk_mac_an_restart,
++ 	.mac_config = mtk_mac_config,
++@@ -3314,6 +3266,9 @@ static int mtk_add_mac(struct mtk_eth *e
++ 
++ 	mac->phylink_config.dev = &eth->netdev[id]->dev;
++ 	mac->phylink_config.type = PHYLINK_NETDEV;
+++	mac->phylink_config.mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+++		MAC_10 | MAC_100 | MAC_1000 | MAC_2500FD;
+++
++ 	__set_bit(PHY_INTERFACE_MODE_MII,
++ 		  mac->phylink_config.supported_interfaces);
++ 	__set_bit(PHY_INTERFACE_MODE_GMII,
+diff --git a/target/linux/generic/backport-5.15/704-05-v5.17-net-mtk_eth_soc-mark-as-a-legacy_pre_march2020-drive.patch b/target/linux/generic/backport-5.15/704-05-v5.17-net-mtk_eth_soc-mark-as-a-legacy_pre_march2020-drive.patch
+new file mode 100644
+index 0000000000..6aa99acf77
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/704-05-v5.17-net-mtk_eth_soc-mark-as-a-legacy_pre_march2020-drive.patch
+@@ -0,0 +1,29 @@
++From b06515367facfadcf5e70cf6f39db749cf4eb5e3 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Thu, 9 Dec 2021 13:11:43 +0000
++Subject: [PATCH] net: mtk_eth_soc: mark as a legacy_pre_march2020 driver
++
++mtk_eth_soc has not been updated for commit 7cceb599d15d ("net: phylink:
++avoid mac_config calls"), and makes use of state->speed and
++state->duplex in contravention of the phylink documentation. This makes
++reliant on the legacy behaviours, so mark it as a legacy driver.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 4 ++++
++ 1 file changed, 4 insertions(+)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -3266,6 +3266,10 @@ static int mtk_add_mac(struct mtk_eth *e
++ 
++ 	mac->phylink_config.dev = &eth->netdev[id]->dev;
++ 	mac->phylink_config.type = PHYLINK_NETDEV;
+++	/* This driver makes use of state->speed/state->duplex in
+++	 * mac_config
+++	 */
+++	mac->phylink_config.legacy_pre_march2020 = true;
++ 	mac->phylink_config.mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
++ 		MAC_10 | MAC_100 | MAC_1000 | MAC_2500FD;
++ 
+diff --git a/target/linux/generic/backport-5.15/704-06-v5.19-eth-mtk_eth_soc-remove-a-copy-of-the-NAPI_POLL_WEIGH.patch b/target/linux/generic/backport-5.15/704-06-v5.19-eth-mtk_eth_soc-remove-a-copy-of-the-NAPI_POLL_WEIGH.patch
+new file mode 100644
+index 0000000000..e5f70e36f0
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/704-06-v5.19-eth-mtk_eth_soc-remove-a-copy-of-the-NAPI_POLL_WEIGH.patch
+@@ -0,0 +1,40 @@
++From 889e3691b9d6573de133da1f5e78f590e52152cd Mon Sep 17 00:00:00 2001
++From: Jakub Kicinski <kuba@kernel.org>
++Date: Thu, 28 Apr 2022 14:23:13 -0700
++Subject: [PATCH] eth: mtk_eth_soc: remove a copy of the NAPI_POLL_WEIGHT
++ define
++
++Defining local versions of NAPI_POLL_WEIGHT with the same
++values in the drivers just makes refactoring harder.
++
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 4 ++--
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 1 -
++ 2 files changed, 2 insertions(+), 3 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -3565,9 +3565,9 @@ static int mtk_probe(struct platform_dev
++ 	 */
++ 	init_dummy_netdev(&eth->dummy_dev);
++ 	netif_napi_add(&eth->dummy_dev, &eth->tx_napi, mtk_napi_tx,
++-		       MTK_NAPI_WEIGHT);
+++		       NAPI_POLL_WEIGHT);
++ 	netif_napi_add(&eth->dummy_dev, &eth->rx_napi, mtk_napi_rx,
++-		       MTK_NAPI_WEIGHT);
+++		       NAPI_POLL_WEIGHT);
++ 
++ 	platform_set_drvdata(pdev, eth);
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -25,7 +25,6 @@
++ #define MTK_TX_DMA_BUF_LEN	0x3fff
++ #define MTK_TX_DMA_BUF_LEN_V2	0xffff
++ #define MTK_DMA_SIZE		512
++-#define MTK_NAPI_WEIGHT		64
++ #define MTK_MAC_COUNT		2
++ #define MTK_RX_ETH_HLEN		(ETH_HLEN + ETH_FCS_LEN)
++ #define MTK_RX_HLEN		(NET_SKB_PAD + MTK_RX_ETH_HLEN + NET_IP_ALIGN)
+diff --git a/target/linux/generic/backport-5.15/704-07-v5.19-mtk_eth_soc-remove-unused-mac-mode.patch b/target/linux/generic/backport-5.15/704-07-v5.19-mtk_eth_soc-remove-unused-mac-mode.patch
+new file mode 100644
+index 0000000000..4d896cdf39
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/704-07-v5.19-mtk_eth_soc-remove-unused-mac-mode.patch
+@@ -0,0 +1,35 @@
++From 0600bdde1fae75fb9bad72033d28edddc72b44b2 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Wed, 18 May 2022 15:54:31 +0100
++Subject: [PATCH 01/12] net: mtk_eth_soc: remove unused mac->mode
++
++mac->mode is only ever written to in one location, and is thus
++superflous. Remove it.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 1 -
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 1 -
++ 2 files changed, 2 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -3261,7 +3261,6 @@ static int mtk_add_mac(struct mtk_eth *e
++ 
++ 	/* mac config is not set */
++ 	mac->interface = PHY_INTERFACE_MODE_NA;
++-	mac->mode = MLO_AN_PHY;
++ 	mac->speed = SPEED_UNKNOWN;
++ 
++ 	mac->phylink_config.dev = &eth->netdev[id]->dev;
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -1085,7 +1085,6 @@ struct mtk_eth {
++ struct mtk_mac {
++ 	int				id;
++ 	phy_interface_t			interface;
++-	unsigned int			mode;
++ 	int				speed;
++ 	struct device_node		*of_node;
++ 	struct phylink			*phylink;
+diff --git a/target/linux/generic/backport-5.15/704-08-v5.19-net-mtk_eth_soc-remove-unused-sgmii-flags.patch b/target/linux/generic/backport-5.15/704-08-v5.19-net-mtk_eth_soc-remove-unused-sgmii-flags.patch
+new file mode 100644
+index 0000000000..39aa24157e
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/704-08-v5.19-net-mtk_eth_soc-remove-unused-sgmii-flags.patch
+@@ -0,0 +1,40 @@
++From 5a7a2f4b29d7546244da7d8bbc1962fce5b230f2 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Wed, 18 May 2022 15:54:36 +0100
++Subject: [PATCH 02/12] net: mtk_eth_soc: remove unused sgmii flags
++
++The "flags" member of struct mtk_sgmii appears to be unused, as are
++the MTK_SGMII_PHYSPEED_* and MTK_HAS_FLAGS() macros. Remove them.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 8 --------
++ 1 file changed, 8 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -956,23 +956,15 @@ struct mtk_soc_data {
++ /* currently no SoC has more than 2 macs */
++ #define MTK_MAX_DEVS			2
++ 
++-#define MTK_SGMII_PHYSPEED_AN          BIT(31)
++-#define MTK_SGMII_PHYSPEED_MASK        GENMASK(2, 0)
++-#define MTK_SGMII_PHYSPEED_1000        BIT(0)
++-#define MTK_SGMII_PHYSPEED_2500        BIT(1)
++-#define MTK_HAS_FLAGS(flags, _x)       (((flags) & (_x)) == (_x))
++-
++ /* struct mtk_sgmii -  This is the structure holding sgmii regmap and its
++  *                     characteristics
++  * @regmap:            The register map pointing at the range used to setup
++  *                     SGMII modes
++- * @flags:             The enum refers to which mode the sgmii wants to run on
++  * @ana_rgc3:          The offset refers to register ANA_RGC3 related to regmap
++  */
++ 
++ struct mtk_sgmii {
++ 	struct regmap   *regmap[MTK_MAX_DEVS];
++-	u32             flags[MTK_MAX_DEVS];
++ 	u32             ana_rgc3;
++ };
++ 
+diff --git a/target/linux/generic/backport-5.15/704-09-v5.19-net-mtk_eth_soc-add-mask-and-update-PCS-speed-defini.patch b/target/linux/generic/backport-5.15/704-09-v5.19-net-mtk_eth_soc-add-mask-and-update-PCS-speed-defini.patch
+new file mode 100644
+index 0000000000..f2e1f86bac
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/704-09-v5.19-net-mtk_eth_soc-add-mask-and-update-PCS-speed-defini.patch
+@@ -0,0 +1,40 @@
++From bc5e93e0cd22e360eda23859b939280205567580 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Wed, 18 May 2022 15:54:42 +0100
++Subject: [PATCH 03/12] net: mtk_eth_soc: add mask and update PCS speed
++ definitions
++
++The PCS speed setting is a two bit field, but it is defined as two
++separate bits. Add a bitfield mask for the speed definitions, an
++ use the FIELD_PREP() macro to define each PCS speed.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 8 +++++---
++ 1 file changed, 5 insertions(+), 3 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -17,6 +17,7 @@
++ #include <linux/phylink.h>
++ #include <linux/rhashtable.h>
++ #include <linux/dim.h>
+++#include <linux/bitfield.h>
++ #include "mtk_ppe.h"
++ 
++ #define MTK_QDMA_PAGE_SIZE	2048
++@@ -473,9 +474,10 @@
++ #define SGMSYS_SGMII_MODE		0x20
++ #define SGMII_IF_MODE_BIT0		BIT(0)
++ #define SGMII_SPEED_DUPLEX_AN		BIT(1)
++-#define SGMII_SPEED_10			0x0
++-#define SGMII_SPEED_100			BIT(2)
++-#define SGMII_SPEED_1000		BIT(3)
+++#define SGMII_SPEED_MASK		GENMASK(3, 2)
+++#define SGMII_SPEED_10			FIELD_PREP(SGMII_SPEED_MASK, 0)
+++#define SGMII_SPEED_100			FIELD_PREP(SGMII_SPEED_MASK, 1)
+++#define SGMII_SPEED_1000		FIELD_PREP(SGMII_SPEED_MASK, 2)
++ #define SGMII_DUPLEX_FULL		BIT(4)
++ #define SGMII_IF_MODE_BIT5		BIT(5)
++ #define SGMII_REMOTE_FAULT_DIS		BIT(8)
+diff --git a/target/linux/generic/backport-5.15/704-10-v5.19-net-mtk_eth_soc-correct-802.3z-speed-setting.patch b/target/linux/generic/backport-5.15/704-10-v5.19-net-mtk_eth_soc-correct-802.3z-speed-setting.patch
+new file mode 100644
+index 0000000000..fb1ee4e310
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/704-10-v5.19-net-mtk_eth_soc-correct-802.3z-speed-setting.patch
+@@ -0,0 +1,60 @@
++From 7da3f901f8ecb425105fad39a0f5de73306abe52 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Wed, 18 May 2022 15:54:47 +0100
++Subject: [PATCH 04/12] net: mtk_eth_soc: correct 802.3z speed setting
++
++Phylink does not guarantee that state->speed will be set correctly in
++the mac_config() call, so it's a bug that the driver makes use of it.
++Moreover, it is making use of it in a function that is only ever called
++for 1000BASE-X and 2500BASE-X which operate at a fixed speed which
++happens to be the same setting irrespective of the interface mode. We
++can simply remove the switch statement and just set the SGMII interface
++speed.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_sgmii.c | 18 +++++-------------
++ 1 file changed, 5 insertions(+), 13 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
+++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
++@@ -34,6 +34,7 @@ int mtk_sgmii_init(struct mtk_sgmii *ss,
++ 	return 0;
++ }
++ 
+++/* For SGMII interface mode */
++ int mtk_sgmii_setup_mode_an(struct mtk_sgmii *ss, int id)
++ {
++ 	unsigned int val;
++@@ -60,6 +61,9 @@ int mtk_sgmii_setup_mode_an(struct mtk_s
++ 	return 0;
++ }
++ 
+++/* For 1000BASE-X and 2500BASE-X interface modes, which operate at a
+++ * fixed speed.
+++ */
++ int mtk_sgmii_setup_mode_force(struct mtk_sgmii *ss, int id,
++ 			       const struct phylink_link_state *state)
++ {
++@@ -82,19 +86,7 @@ int mtk_sgmii_setup_mode_force(struct mt
++ 	/* SGMII force mode setting */
++ 	regmap_read(ss->regmap[id], SGMSYS_SGMII_MODE, &val);
++ 	val &= ~SGMII_IF_MODE_MASK;
++-
++-	switch (state->speed) {
++-	case SPEED_10:
++-		val |= SGMII_SPEED_10;
++-		break;
++-	case SPEED_100:
++-		val |= SGMII_SPEED_100;
++-		break;
++-	case SPEED_2500:
++-	case SPEED_1000:
++-		val |= SGMII_SPEED_1000;
++-		break;
++-	}
+++	val |= SGMII_SPEED_1000;
++ 
++ 	if (state->duplex == DUPLEX_FULL)
++ 		val |= SGMII_DUPLEX_FULL;
+diff --git a/target/linux/generic/backport-5.15/704-11-v5.19-net-mtk_eth_soc-correct-802.3z-duplex-setting.patch b/target/linux/generic/backport-5.15/704-11-v5.19-net-mtk_eth_soc-correct-802.3z-duplex-setting.patch
+new file mode 100644
+index 0000000000..140ff3cab5
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/704-11-v5.19-net-mtk_eth_soc-correct-802.3z-duplex-setting.patch
+@@ -0,0 +1,101 @@
++From a459187390bb221827f9c07866c3a5ffbdf9622b Mon Sep 17 00:00:00 2001
++From: Russell King <rmk+kernel@armlinux.org.uk>
++Date: Wed, 18 May 2022 15:54:52 +0100
++Subject: [PATCH 05/12] net: mtk_eth_soc: correct 802.3z duplex setting
++
++Phylink does not guarantee that state->duplex will be set correctly in
++the mac_config() call, so it's a bug that the driver makes use of it.
++
++Move the 802.3z PCS duplex configuration to mac_link_up().
++
++Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 16 +++++++++++----
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h |  1 +
++ drivers/net/ethernet/mediatek/mtk_sgmii.c   | 22 +++++++++++++++------
++ 3 files changed, 29 insertions(+), 10 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -532,8 +532,18 @@ static void mtk_mac_link_up(struct phyli
++ {
++ 	struct mtk_mac *mac = container_of(config, struct mtk_mac,
++ 					   phylink_config);
++-	u32 mcr = mtk_r32(mac->hw, MTK_MAC_MCR(mac->id));
+++	u32 mcr;
++ 
+++	if (phy_interface_mode_is_8023z(interface)) {
+++		struct mtk_eth *eth = mac->hw;
+++
+++		/* Decide how GMAC and SGMIISYS be mapped */
+++		int sid = (MTK_HAS_CAPS(eth->soc->caps, MTK_SHARED_SGMII)) ?
+++			   0 : mac->id;
+++		mtk_sgmii_link_up(eth->sgmii, sid, speed, duplex);
+++	}
+++
+++	mcr = mtk_r32(mac->hw, MTK_MAC_MCR(mac->id));
++ 	mcr &= ~(MAC_MCR_SPEED_100 | MAC_MCR_SPEED_1000 |
++ 		 MAC_MCR_FORCE_DPX | MAC_MCR_FORCE_TX_FC |
++ 		 MAC_MCR_FORCE_RX_FC);
++@@ -3265,9 +3275,7 @@ static int mtk_add_mac(struct mtk_eth *e
++ 
++ 	mac->phylink_config.dev = &eth->netdev[id]->dev;
++ 	mac->phylink_config.type = PHYLINK_NETDEV;
++-	/* This driver makes use of state->speed/state->duplex in
++-	 * mac_config
++-	 */
+++	/* This driver makes use of state->speed in mac_config */
++ 	mac->phylink_config.legacy_pre_march2020 = true;
++ 	mac->phylink_config.mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
++ 		MAC_10 | MAC_100 | MAC_1000 | MAC_2500FD;
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -1103,6 +1103,7 @@ int mtk_sgmii_init(struct mtk_sgmii *ss,
++ int mtk_sgmii_setup_mode_an(struct mtk_sgmii *ss, int id);
++ int mtk_sgmii_setup_mode_force(struct mtk_sgmii *ss, int id,
++ 			       const struct phylink_link_state *state);
+++void mtk_sgmii_link_up(struct mtk_sgmii *ss, int id, int speed, int duplex);
++ void mtk_sgmii_restart_an(struct mtk_eth *eth, int mac_id);
++ 
++ int mtk_gmac_sgmii_path_setup(struct mtk_eth *eth, int mac_id);
++--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
+++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
++@@ -83,14 +83,10 @@ int mtk_sgmii_setup_mode_force(struct mt
++ 	val &= ~SGMII_AN_ENABLE;
++ 	regmap_write(ss->regmap[id], SGMSYS_PCS_CONTROL_1, val);
++ 
++-	/* SGMII force mode setting */
+++	/* Set the speed etc but leave the duplex unchanged */
++ 	regmap_read(ss->regmap[id], SGMSYS_SGMII_MODE, &val);
++-	val &= ~SGMII_IF_MODE_MASK;
+++	val &= SGMII_DUPLEX_FULL | ~SGMII_IF_MODE_MASK;
++ 	val |= SGMII_SPEED_1000;
++-
++-	if (state->duplex == DUPLEX_FULL)
++-		val |= SGMII_DUPLEX_FULL;
++-
++ 	regmap_write(ss->regmap[id], SGMSYS_SGMII_MODE, val);
++ 
++ 	/* Release PHYA power down state */
++@@ -101,6 +97,20 @@ int mtk_sgmii_setup_mode_force(struct mt
++ 	return 0;
++ }
++ 
+++/* For 1000BASE-X and 2500BASE-X interface modes */
+++void mtk_sgmii_link_up(struct mtk_sgmii *ss, int id, int speed, int duplex)
+++{
+++	unsigned int val;
+++
+++	/* SGMII force duplex setting */
+++	regmap_read(ss->regmap[id], SGMSYS_SGMII_MODE, &val);
+++	val &= ~SGMII_DUPLEX_FULL;
+++	if (duplex == DUPLEX_FULL)
+++		val |= SGMII_DUPLEX_FULL;
+++
+++	regmap_write(ss->regmap[id], SGMSYS_SGMII_MODE, val);
+++}
+++
++ void mtk_sgmii_restart_an(struct mtk_eth *eth, int mac_id)
++ {
++ 	struct mtk_sgmii *ss = eth->sgmii;
+diff --git a/target/linux/generic/backport-5.15/704-12-v5.19-net-mtk_eth_soc-stop-passing-phylink-state-to-sgmii-.patch b/target/linux/generic/backport-5.15/704-12-v5.19-net-mtk_eth_soc-stop-passing-phylink-state-to-sgmii-.patch
+new file mode 100644
+index 0000000000..56b5e43e53
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/704-12-v5.19-net-mtk_eth_soc-stop-passing-phylink-state-to-sgmii-.patch
+@@ -0,0 +1,60 @@
++From 4ce5a0bd3958ed248f0325bfcb95339f7c74feb2 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Wed, 18 May 2022 15:54:57 +0100
++Subject: [PATCH 06/12] net: mtk_eth_soc: stop passing phylink state to sgmii
++ setup
++
++Now that mtk_sgmii_setup_mode_force() only uses the interface mode
++from the phylink state, pass just the interface mode into this
++function.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 2 +-
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 2 +-
++ drivers/net/ethernet/mediatek/mtk_sgmii.c   | 4 ++--
++ 3 files changed, 4 insertions(+), 4 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -437,7 +437,7 @@ static void mtk_mac_config(struct phylin
++ 		/* Setup SGMIISYS with the determined property */
++ 		if (state->interface != PHY_INTERFACE_MODE_SGMII)
++ 			err = mtk_sgmii_setup_mode_force(eth->sgmii, sid,
++-							 state);
+++							 state->interface);
++ 		else if (phylink_autoneg_inband(mode))
++ 			err = mtk_sgmii_setup_mode_an(eth->sgmii, sid);
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -1102,7 +1102,7 @@ int mtk_sgmii_init(struct mtk_sgmii *ss,
++ 		   u32 ana_rgc3);
++ int mtk_sgmii_setup_mode_an(struct mtk_sgmii *ss, int id);
++ int mtk_sgmii_setup_mode_force(struct mtk_sgmii *ss, int id,
++-			       const struct phylink_link_state *state);
+++			       phy_interface_t interface);
++ void mtk_sgmii_link_up(struct mtk_sgmii *ss, int id, int speed, int duplex);
++ void mtk_sgmii_restart_an(struct mtk_eth *eth, int mac_id);
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
+++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
++@@ -65,7 +65,7 @@ int mtk_sgmii_setup_mode_an(struct mtk_s
++  * fixed speed.
++  */
++ int mtk_sgmii_setup_mode_force(struct mtk_sgmii *ss, int id,
++-			       const struct phylink_link_state *state)
+++			       phy_interface_t interface)
++ {
++ 	unsigned int val;
++ 
++@@ -74,7 +74,7 @@ int mtk_sgmii_setup_mode_force(struct mt
++ 
++ 	regmap_read(ss->regmap[id], ss->ana_rgc3, &val);
++ 	val &= ~RG_PHY_SPEED_MASK;
++-	if (state->interface == PHY_INTERFACE_MODE_2500BASEX)
+++	if (interface == PHY_INTERFACE_MODE_2500BASEX)
++ 		val |= RG_PHY_SPEED_3_125G;
++ 	regmap_write(ss->regmap[id], ss->ana_rgc3, val);
++ 
+diff --git a/target/linux/generic/backport-5.15/704-13-v5.19-net-mtk_eth_soc-provide-mtk_sgmii_config.patch b/target/linux/generic/backport-5.15/704-13-v5.19-net-mtk_eth_soc-provide-mtk_sgmii_config.patch
+new file mode 100644
+index 0000000000..4c91cf68f4
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/704-13-v5.19-net-mtk_eth_soc-provide-mtk_sgmii_config.patch
+@@ -0,0 +1,89 @@
++From 1ec619ee4a052fb9ac48b57554ac2722a0bfe73c Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Wed, 18 May 2022 15:55:02 +0100
++Subject: [PATCH 07/12] net: mtk_eth_soc: provide mtk_sgmii_config()
++
++Provide mtk_sgmii_config() to wrap up the decisions about which SGMII
++configuration will be called.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c |  7 +------
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h |  5 ++---
++ drivers/net/ethernet/mediatek/mtk_sgmii.c   | 20 +++++++++++++++++---
++ 3 files changed, 20 insertions(+), 12 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -435,12 +435,7 @@ static void mtk_mac_config(struct phylin
++ 		       0 : mac->id;
++ 
++ 		/* Setup SGMIISYS with the determined property */
++-		if (state->interface != PHY_INTERFACE_MODE_SGMII)
++-			err = mtk_sgmii_setup_mode_force(eth->sgmii, sid,
++-							 state->interface);
++-		else if (phylink_autoneg_inband(mode))
++-			err = mtk_sgmii_setup_mode_an(eth->sgmii, sid);
++-
+++		err = mtk_sgmii_config(eth->sgmii, sid, mode, state->interface);
++ 		if (err)
++ 			goto init_err;
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -1100,9 +1100,8 @@ u32 mtk_r32(struct mtk_eth *eth, unsigne
++ 
++ int mtk_sgmii_init(struct mtk_sgmii *ss, struct device_node *np,
++ 		   u32 ana_rgc3);
++-int mtk_sgmii_setup_mode_an(struct mtk_sgmii *ss, int id);
++-int mtk_sgmii_setup_mode_force(struct mtk_sgmii *ss, int id,
++-			       phy_interface_t interface);
+++int mtk_sgmii_config(struct mtk_sgmii *ss, int id, unsigned int mode,
+++		     phy_interface_t interface);
++ void mtk_sgmii_link_up(struct mtk_sgmii *ss, int id, int speed, int duplex);
++ void mtk_sgmii_restart_an(struct mtk_eth *eth, int mac_id);
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
+++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
++@@ -35,7 +35,7 @@ int mtk_sgmii_init(struct mtk_sgmii *ss,
++ }
++ 
++ /* For SGMII interface mode */
++-int mtk_sgmii_setup_mode_an(struct mtk_sgmii *ss, int id)
+++static int mtk_sgmii_setup_mode_an(struct mtk_sgmii *ss, int id)
++ {
++ 	unsigned int val;
++ 
++@@ -64,8 +64,8 @@ int mtk_sgmii_setup_mode_an(struct mtk_s
++ /* For 1000BASE-X and 2500BASE-X interface modes, which operate at a
++  * fixed speed.
++  */
++-int mtk_sgmii_setup_mode_force(struct mtk_sgmii *ss, int id,
++-			       phy_interface_t interface)
+++static int mtk_sgmii_setup_mode_force(struct mtk_sgmii *ss, int id,
+++				      phy_interface_t interface)
++ {
++ 	unsigned int val;
++ 
++@@ -97,6 +97,20 @@ int mtk_sgmii_setup_mode_force(struct mt
++ 	return 0;
++ }
++ 
+++int mtk_sgmii_config(struct mtk_sgmii *ss, int id, unsigned int mode,
+++		     phy_interface_t interface)
+++{
+++	int err = 0;
+++
+++	/* Setup SGMIISYS with the determined property */
+++	if (interface != PHY_INTERFACE_MODE_SGMII)
+++		err = mtk_sgmii_setup_mode_force(ss, id, interface);
+++	else if (phylink_autoneg_inband(mode))
+++		err = mtk_sgmii_setup_mode_an(ss, id);
+++
+++	return err;
+++}
+++
++ /* For 1000BASE-X and 2500BASE-X interface modes */
++ void mtk_sgmii_link_up(struct mtk_sgmii *ss, int id, int speed, int duplex)
++ {
+diff --git a/target/linux/generic/backport-5.15/704-14-v5.19-net-mtk_eth_soc-add-fixme-comment-for-state-speed-us.patch b/target/linux/generic/backport-5.15/704-14-v5.19-net-mtk_eth_soc-add-fixme-comment-for-state-speed-us.patch
+new file mode 100644
+index 0000000000..8080a2ca44
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/704-14-v5.19-net-mtk_eth_soc-add-fixme-comment-for-state-speed-us.patch
+@@ -0,0 +1,38 @@
++From 650a49bc65df6b0e0051a8f62d7c22d95a8f350d Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Wed, 18 May 2022 15:55:07 +0100
++Subject: [PATCH 08/12] net: mtk_eth_soc: add fixme comment for state->speed
++ use
++
++Add a fixme comment for the last remaining incorrect usage of
++state->speed in the mac_config() method, which is strangely in a code
++path which is only run when the PHY interface mode changes.
++
++This means if we are in RGMII mode, changes in state->speed will not
++cause the INTF_MODE, TRGMII_RCK_CTRL and TRGMII_TCK_CTRL registers to
++be set according to the speed, nor will the TRGPLL clock be set to the
++correct value.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 8 ++++++++
++ 1 file changed, 8 insertions(+)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -374,6 +374,14 @@ static void mtk_mac_config(struct phylin
++ 							      state->interface))
++ 					goto err_phy;
++ 			} else {
+++				/* FIXME: this is incorrect. Not only does it
+++				 * use state->speed (which is not guaranteed
+++				 * to be correct) but it also makes use of it
+++				 * in a code path that will only be reachable
+++				 * when the PHY interface mode changes, not
+++				 * when the speed changes. Consequently, RGMII
+++				 * is probably broken.
+++				 */
++ 				mtk_gmac0_rgmii_adjust(mac->hw,
++ 						       state->interface,
++ 						       state->speed);
+diff --git a/target/linux/generic/backport-5.15/704-15-v5.19-net-mtk_eth_soc-move-MAC_MCR-setting-to-mac_finish.patch b/target/linux/generic/backport-5.15/704-15-v5.19-net-mtk_eth_soc-move-MAC_MCR-setting-to-mac_finish.patch
+new file mode 100644
+index 0000000000..368db4cca2
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/704-15-v5.19-net-mtk_eth_soc-move-MAC_MCR-setting-to-mac_finish.patch
+@@ -0,0 +1,79 @@
++From 0e37ad71b2ff772009595002da2860999e98e14e Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Wed, 18 May 2022 15:55:12 +0100
++Subject: [PATCH 09/12] net: mtk_eth_soc: move MAC_MCR setting to mac_finish()
++
++Move the setting of the MTK_MAC_MCR register from the end of mac_config
++into the phylink mac_finish() method, to keep it as the very last write
++that is done during configuration.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 33 ++++++++++++++-------
++ 1 file changed, 22 insertions(+), 11 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -316,8 +316,8 @@ static void mtk_mac_config(struct phylin
++ 	struct mtk_mac *mac = container_of(config, struct mtk_mac,
++ 					   phylink_config);
++ 	struct mtk_eth *eth = mac->hw;
++-	u32 mcr_cur, mcr_new, sid, i;
++ 	int val, ge_mode, err = 0;
+++	u32 sid, i;
++ 
++ 	/* MT76x8 has no hardware settings between for the MAC */
++ 	if (!MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628) &&
++@@ -455,16 +455,6 @@ static void mtk_mac_config(struct phylin
++ 		return;
++ 	}
++ 
++-	/* Setup gmac */
++-	mcr_cur = mtk_r32(mac->hw, MTK_MAC_MCR(mac->id));
++-	mcr_new = mcr_cur;
++-	mcr_new |= MAC_MCR_IPG_CFG | MAC_MCR_FORCE_MODE |
++-		   MAC_MCR_BACKOFF_EN | MAC_MCR_BACKPR_EN | MAC_MCR_FORCE_LINK;
++-
++-	/* Only update control register when needed! */
++-	if (mcr_new != mcr_cur)
++-		mtk_w32(mac->hw, mcr_new, MTK_MAC_MCR(mac->id));
++-
++ 	return;
++ 
++ err_phy:
++@@ -477,6 +467,26 @@ init_err:
++ 		mac->id, phy_modes(state->interface), err);
++ }
++ 
+++static int mtk_mac_finish(struct phylink_config *config, unsigned int mode,
+++			  phy_interface_t interface)
+++{
+++	struct mtk_mac *mac = container_of(config, struct mtk_mac,
+++					   phylink_config);
+++	u32 mcr_cur, mcr_new;
+++
+++	/* Setup gmac */
+++	mcr_cur = mtk_r32(mac->hw, MTK_MAC_MCR(mac->id));
+++	mcr_new = mcr_cur;
+++	mcr_new |= MAC_MCR_IPG_CFG | MAC_MCR_FORCE_MODE |
+++		   MAC_MCR_BACKOFF_EN | MAC_MCR_BACKPR_EN | MAC_MCR_FORCE_LINK;
+++
+++	/* Only update control register when needed! */
+++	if (mcr_new != mcr_cur)
+++		mtk_w32(mac->hw, mcr_new, MTK_MAC_MCR(mac->id));
+++
+++	return 0;
+++}
+++
++ static void mtk_mac_pcs_get_state(struct phylink_config *config,
++ 				  struct phylink_link_state *state)
++ {
++@@ -581,6 +591,7 @@ static const struct phylink_mac_ops mtk_
++ 	.mac_pcs_get_state = mtk_mac_pcs_get_state,
++ 	.mac_an_restart = mtk_mac_an_restart,
++ 	.mac_config = mtk_mac_config,
+++	.mac_finish = mtk_mac_finish,
++ 	.mac_link_down = mtk_mac_link_down,
++ 	.mac_link_up = mtk_mac_link_up,
++ };
+diff --git a/target/linux/generic/backport-5.15/704-16-v5.19-net-mtk_eth_soc-move-restoration-of-SYSCFG0-to-mac_f.patch b/target/linux/generic/backport-5.15/704-16-v5.19-net-mtk_eth_soc-move-restoration-of-SYSCFG0-to-mac_f.patch
+new file mode 100644
+index 0000000000..ad6ec60288
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/704-16-v5.19-net-mtk_eth_soc-move-restoration-of-SYSCFG0-to-mac_f.patch
+@@ -0,0 +1,57 @@
++From 21089867278deb2a110b685e3cd33f64f9ce41e2 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Wed, 18 May 2022 15:55:17 +0100
++Subject: [PATCH 10/12] net: mtk_eth_soc: move restoration of SYSCFG0 to
++ mac_finish()
++
++The SGMIISYS configuration is performed while ETHSYS_SYSCFG0 is in a
++disabled state. In order to preserve this when we switch to phylink_pcs
++we need to move the restoration of this register to the mac_finish()
++callback.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 11 +++++++++--
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h |  1 +
++ 2 files changed, 10 insertions(+), 2 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -447,8 +447,8 @@ static void mtk_mac_config(struct phylin
++ 		if (err)
++ 			goto init_err;
++ 
++-		regmap_update_bits(eth->ethsys, ETHSYS_SYSCFG0,
++-				   SYSCFG0_SGMII_MASK, val);
+++		/* Save the syscfg0 value for mac_finish */
+++		mac->syscfg0 = val;
++ 	} else if (phylink_autoneg_inband(mode)) {
++ 		dev_err(eth->dev,
++ 			"In-band mode not supported in non SGMII mode!\n");
++@@ -472,8 +472,15 @@ static int mtk_mac_finish(struct phylink
++ {
++ 	struct mtk_mac *mac = container_of(config, struct mtk_mac,
++ 					   phylink_config);
+++	struct mtk_eth *eth = mac->hw;
++ 	u32 mcr_cur, mcr_new;
++ 
+++	/* Enable SGMII */
+++	if (interface == PHY_INTERFACE_MODE_SGMII ||
+++	    phy_interface_mode_is_8023z(interface))
+++		regmap_update_bits(eth->ethsys, ETHSYS_SYSCFG0,
+++				   SYSCFG0_SGMII_MASK, mac->syscfg0);
+++
++ 	/* Setup gmac */
++ 	mcr_cur = mtk_r32(mac->hw, MTK_MAC_MCR(mac->id));
++ 	mcr_new = mcr_cur;
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -1087,6 +1087,7 @@ struct mtk_mac {
++ 	struct mtk_hw_stats		*hw_stats;
++ 	__be32				hwlro_ip[MTK_MAX_LRO_IP_CNT];
++ 	int				hwlro_ip_cnt;
+++	unsigned int			syscfg0;
++ };
++ 
++ /* the struct describing the SoC. these are declared in the soc_xyz.c files */
+diff --git a/target/linux/generic/backport-5.15/704-17-v5.19-net-mtk_eth_soc-convert-code-structure-to-suit-split.patch b/target/linux/generic/backport-5.15/704-17-v5.19-net-mtk_eth_soc-convert-code-structure-to-suit-split.patch
+new file mode 100644
+index 0000000000..623658f459
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/704-17-v5.19-net-mtk_eth_soc-convert-code-structure-to-suit-split.patch
+@@ -0,0 +1,254 @@
++From 901f3fbe13c3e56f0742e02717ccbfabbc95c463 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Wed, 18 May 2022 15:55:22 +0100
++Subject: [PATCH 11/12] net: mtk_eth_soc: convert code structure to suit split
++ PCS support
++
++Provide a mtk_pcs structure which encapsulates everything that the PCS
++functions need (the regmap and ana_rgc3 offset), and use this in the
++PCS functions. Provide shim functions to convert from the existing
++"mtk_sgmii_*" interface to the converted PCS functions.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h |  15 ++-
++ drivers/net/ethernet/mediatek/mtk_sgmii.c   | 123 +++++++++++---------
++ 2 files changed, 79 insertions(+), 59 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -958,16 +958,23 @@ struct mtk_soc_data {
++ /* currently no SoC has more than 2 macs */
++ #define MTK_MAX_DEVS			2
++ 
++-/* struct mtk_sgmii -  This is the structure holding sgmii regmap and its
++- *                     characteristics
+++/* struct mtk_pcs -    This structure holds each sgmii regmap and associated
+++ *                     data
++  * @regmap:            The register map pointing at the range used to setup
++  *                     SGMII modes
++  * @ana_rgc3:          The offset refers to register ANA_RGC3 related to regmap
++  */
+++struct mtk_pcs {
+++	struct regmap	*regmap;
+++	u32             ana_rgc3;
+++};
++ 
+++/* struct mtk_sgmii -  This is the structure holding sgmii regmap and its
+++ *                     characteristics
+++ * @pcs                Array of individual PCS structures
+++ */
++ struct mtk_sgmii {
++-	struct regmap   *regmap[MTK_MAX_DEVS];
++-	u32             ana_rgc3;
+++	struct mtk_pcs	pcs[MTK_MAX_DEVS];
++ };
++ 
++ /* struct mtk_eth -	This is the main datasructure for holding the state
++--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
+++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
++@@ -9,90 +9,71 @@
++ 
++ #include <linux/mfd/syscon.h>
++ #include <linux/of.h>
+++#include <linux/phylink.h>
++ #include <linux/regmap.h>
++ 
++ #include "mtk_eth_soc.h"
++ 
++-int mtk_sgmii_init(struct mtk_sgmii *ss, struct device_node *r, u32 ana_rgc3)
++-{
++-	struct device_node *np;
++-	int i;
++-
++-	ss->ana_rgc3 = ana_rgc3;
++-
++-	for (i = 0; i < MTK_MAX_DEVS; i++) {
++-		np = of_parse_phandle(r, "mediatek,sgmiisys", i);
++-		if (!np)
++-			break;
++-
++-		ss->regmap[i] = syscon_node_to_regmap(np);
++-		of_node_put(np);
++-		if (IS_ERR(ss->regmap[i]))
++-			return PTR_ERR(ss->regmap[i]);
++-	}
++-
++-	return 0;
++-}
++-
++ /* For SGMII interface mode */
++-static int mtk_sgmii_setup_mode_an(struct mtk_sgmii *ss, int id)
+++static int mtk_pcs_setup_mode_an(struct mtk_pcs *mpcs)
++ {
++ 	unsigned int val;
++ 
++-	if (!ss->regmap[id])
+++	if (!mpcs->regmap)
++ 		return -EINVAL;
++ 
++ 	/* Setup the link timer and QPHY power up inside SGMIISYS */
++-	regmap_write(ss->regmap[id], SGMSYS_PCS_LINK_TIMER,
+++	regmap_write(mpcs->regmap, SGMSYS_PCS_LINK_TIMER,
++ 		     SGMII_LINK_TIMER_DEFAULT);
++ 
++-	regmap_read(ss->regmap[id], SGMSYS_SGMII_MODE, &val);
+++	regmap_read(mpcs->regmap, SGMSYS_SGMII_MODE, &val);
++ 	val |= SGMII_REMOTE_FAULT_DIS;
++-	regmap_write(ss->regmap[id], SGMSYS_SGMII_MODE, val);
+++	regmap_write(mpcs->regmap, SGMSYS_SGMII_MODE, val);
++ 
++-	regmap_read(ss->regmap[id], SGMSYS_PCS_CONTROL_1, &val);
+++	regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &val);
++ 	val |= SGMII_AN_RESTART;
++-	regmap_write(ss->regmap[id], SGMSYS_PCS_CONTROL_1, val);
+++	regmap_write(mpcs->regmap, SGMSYS_PCS_CONTROL_1, val);
++ 
++-	regmap_read(ss->regmap[id], SGMSYS_QPHY_PWR_STATE_CTRL, &val);
+++	regmap_read(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, &val);
++ 	val &= ~SGMII_PHYA_PWD;
++-	regmap_write(ss->regmap[id], SGMSYS_QPHY_PWR_STATE_CTRL, val);
+++	regmap_write(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, val);
++ 
++ 	return 0;
+++
++ }
++ 
++ /* For 1000BASE-X and 2500BASE-X interface modes, which operate at a
++  * fixed speed.
++  */
++-static int mtk_sgmii_setup_mode_force(struct mtk_sgmii *ss, int id,
++-				      phy_interface_t interface)
+++static int mtk_pcs_setup_mode_force(struct mtk_pcs *mpcs,
+++				    phy_interface_t interface)
++ {
++ 	unsigned int val;
++ 
++-	if (!ss->regmap[id])
+++	if (!mpcs->regmap)
++ 		return -EINVAL;
++ 
++-	regmap_read(ss->regmap[id], ss->ana_rgc3, &val);
+++	regmap_read(mpcs->regmap, mpcs->ana_rgc3, &val);
++ 	val &= ~RG_PHY_SPEED_MASK;
++ 	if (interface == PHY_INTERFACE_MODE_2500BASEX)
++ 		val |= RG_PHY_SPEED_3_125G;
++-	regmap_write(ss->regmap[id], ss->ana_rgc3, val);
+++	regmap_write(mpcs->regmap, mpcs->ana_rgc3, val);
++ 
++ 	/* Disable SGMII AN */
++-	regmap_read(ss->regmap[id], SGMSYS_PCS_CONTROL_1, &val);
+++	regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &val);
++ 	val &= ~SGMII_AN_ENABLE;
++-	regmap_write(ss->regmap[id], SGMSYS_PCS_CONTROL_1, val);
+++	regmap_write(mpcs->regmap, SGMSYS_PCS_CONTROL_1, val);
++ 
++ 	/* Set the speed etc but leave the duplex unchanged */
++-	regmap_read(ss->regmap[id], SGMSYS_SGMII_MODE, &val);
+++	regmap_read(mpcs->regmap, SGMSYS_SGMII_MODE, &val);
++ 	val &= SGMII_DUPLEX_FULL | ~SGMII_IF_MODE_MASK;
++ 	val |= SGMII_SPEED_1000;
++-	regmap_write(ss->regmap[id], SGMSYS_SGMII_MODE, val);
+++	regmap_write(mpcs->regmap, SGMSYS_SGMII_MODE, val);
++ 
++ 	/* Release PHYA power down state */
++-	regmap_read(ss->regmap[id], SGMSYS_QPHY_PWR_STATE_CTRL, &val);
+++	regmap_read(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, &val);
++ 	val &= ~SGMII_PHYA_PWD;
++-	regmap_write(ss->regmap[id], SGMSYS_QPHY_PWR_STATE_CTRL, val);
+++	regmap_write(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, val);
++ 
++ 	return 0;
++ }
++@@ -100,44 +81,76 @@ static int mtk_sgmii_setup_mode_force(st
++ int mtk_sgmii_config(struct mtk_sgmii *ss, int id, unsigned int mode,
++ 		     phy_interface_t interface)
++ {
+++	struct mtk_pcs *mpcs = &ss->pcs[id];
++ 	int err = 0;
++ 
++ 	/* Setup SGMIISYS with the determined property */
++ 	if (interface != PHY_INTERFACE_MODE_SGMII)
++-		err = mtk_sgmii_setup_mode_force(ss, id, interface);
+++		err = mtk_pcs_setup_mode_force(mpcs, interface);
++ 	else if (phylink_autoneg_inband(mode))
++-		err = mtk_sgmii_setup_mode_an(ss, id);
+++		err = mtk_pcs_setup_mode_an(mpcs);
++ 
++ 	return err;
++ }
++ 
++-/* For 1000BASE-X and 2500BASE-X interface modes */
++-void mtk_sgmii_link_up(struct mtk_sgmii *ss, int id, int speed, int duplex)
+++static void mtk_pcs_restart_an(struct mtk_pcs *mpcs)
+++{
+++	unsigned int val;
+++
+++	if (!mpcs->regmap)
+++		return;
+++
+++	regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &val);
+++	val |= SGMII_AN_RESTART;
+++	regmap_write(mpcs->regmap, SGMSYS_PCS_CONTROL_1, val);
+++}
+++
+++static void mtk_pcs_link_up(struct mtk_pcs *mpcs, int speed, int duplex)
++ {
++ 	unsigned int val;
++ 
++ 	/* SGMII force duplex setting */
++-	regmap_read(ss->regmap[id], SGMSYS_SGMII_MODE, &val);
+++	regmap_read(mpcs->regmap, SGMSYS_SGMII_MODE, &val);
++ 	val &= ~SGMII_DUPLEX_FULL;
++ 	if (duplex == DUPLEX_FULL)
++ 		val |= SGMII_DUPLEX_FULL;
++ 
++-	regmap_write(ss->regmap[id], SGMSYS_SGMII_MODE, val);
+++	regmap_write(mpcs->regmap, SGMSYS_SGMII_MODE, val);
+++}
+++
+++/* For 1000BASE-X and 2500BASE-X interface modes */
+++void mtk_sgmii_link_up(struct mtk_sgmii *ss, int id, int speed, int duplex)
+++{
+++	mtk_pcs_link_up(&ss->pcs[id], speed, duplex);
+++}
+++
+++int mtk_sgmii_init(struct mtk_sgmii *ss, struct device_node *r, u32 ana_rgc3)
+++{
+++	struct device_node *np;
+++	int i;
+++
+++	for (i = 0; i < MTK_MAX_DEVS; i++) {
+++		np = of_parse_phandle(r, "mediatek,sgmiisys", i);
+++		if (!np)
+++			break;
+++
+++		ss->pcs[i].ana_rgc3 = ana_rgc3;
+++		ss->pcs[i].regmap = syscon_node_to_regmap(np);
+++		of_node_put(np);
+++		if (IS_ERR(ss->pcs[i].regmap))
+++			return PTR_ERR(ss->pcs[i].regmap);
+++	}
+++
+++	return 0;
++ }
++ 
++ void mtk_sgmii_restart_an(struct mtk_eth *eth, int mac_id)
++ {
++-	struct mtk_sgmii *ss = eth->sgmii;
++-	unsigned int val, sid;
+++	unsigned int sid;
++ 
++ 	/* Decide how GMAC and SGMIISYS be mapped */
++ 	sid = (MTK_HAS_CAPS(eth->soc->caps, MTK_SHARED_SGMII)) ?
++ 	       0 : mac_id;
++ 
++-	if (!ss->regmap[sid])
++-		return;
++-
++-	regmap_read(ss->regmap[sid], SGMSYS_PCS_CONTROL_1, &val);
++-	val |= SGMII_AN_RESTART;
++-	regmap_write(ss->regmap[sid], SGMSYS_PCS_CONTROL_1, val);
+++	mtk_pcs_restart_an(&eth->sgmii->pcs[sid]);
++ }
+diff --git a/target/linux/generic/backport-5.15/704-18-v5.19-net-mtk_eth_soc-partially-convert-to-phylink_pcs.patch b/target/linux/generic/backport-5.15/704-18-v5.19-net-mtk_eth_soc-partially-convert-to-phylink_pcs.patch
+new file mode 100644
+index 0000000000..df675e2899
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/704-18-v5.19-net-mtk_eth_soc-partially-convert-to-phylink_pcs.patch
+@@ -0,0 +1,262 @@
++From 14a44ab0330d290fade1403a920e299cc56d7300 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Wed, 18 May 2022 15:55:28 +0100
++Subject: [PATCH 12/12] net: mtk_eth_soc: partially convert to phylink_pcs
++
++Partially convert mtk_eth_soc to phylink_pcs, moving the configuration,
++link up and AN restart over. However, it seems mac_pcs_get_state()
++doesn't actually get the state from the PCS, so we can't convert that
++over without a better understanding of the hardware.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 49 ++++++++----------
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h |  7 ++-
++ drivers/net/ethernet/mediatek/mtk_sgmii.c   | 55 +++++++++++----------
++ 3 files changed, 53 insertions(+), 58 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -310,6 +310,25 @@ static void mtk_gmac0_rgmii_adjust(struc
++ 	mtk_w32(eth, val, TRGMII_TCK_CTRL);
++ }
++ 
+++static struct phylink_pcs *mtk_mac_select_pcs(struct phylink_config *config,
+++					      phy_interface_t interface)
+++{
+++	struct mtk_mac *mac = container_of(config, struct mtk_mac,
+++					   phylink_config);
+++	struct mtk_eth *eth = mac->hw;
+++	unsigned int sid;
+++
+++	if (interface == PHY_INTERFACE_MODE_SGMII ||
+++	    phy_interface_mode_is_8023z(interface)) {
+++		sid = (MTK_HAS_CAPS(eth->soc->caps, MTK_SHARED_SGMII)) ?
+++		       0 : mac->id;
+++
+++		return mtk_sgmii_select_pcs(eth->sgmii, sid);
+++	}
+++
+++	return NULL;
+++}
+++
++ static void mtk_mac_config(struct phylink_config *config, unsigned int mode,
++ 			   const struct phylink_link_state *state)
++ {
++@@ -317,7 +336,7 @@ static void mtk_mac_config(struct phylin
++ 					   phylink_config);
++ 	struct mtk_eth *eth = mac->hw;
++ 	int val, ge_mode, err = 0;
++-	u32 sid, i;
+++	u32 i;
++ 
++ 	/* MT76x8 has no hardware settings between for the MAC */
++ 	if (!MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628) &&
++@@ -438,15 +457,6 @@ static void mtk_mac_config(struct phylin
++ 				   SYSCFG0_SGMII_MASK,
++ 				   ~(u32)SYSCFG0_SGMII_MASK);
++ 
++-		/* Decide how GMAC and SGMIISYS be mapped */
++-		sid = (MTK_HAS_CAPS(eth->soc->caps, MTK_SHARED_SGMII)) ?
++-		       0 : mac->id;
++-
++-		/* Setup SGMIISYS with the determined property */
++-		err = mtk_sgmii_config(eth->sgmii, sid, mode, state->interface);
++-		if (err)
++-			goto init_err;
++-
++ 		/* Save the syscfg0 value for mac_finish */
++ 		mac->syscfg0 = val;
++ 	} else if (phylink_autoneg_inband(mode)) {
++@@ -526,14 +536,6 @@ static void mtk_mac_pcs_get_state(struct
++ 		state->pause |= MLO_PAUSE_TX;
++ }
++ 
++-static void mtk_mac_an_restart(struct phylink_config *config)
++-{
++-	struct mtk_mac *mac = container_of(config, struct mtk_mac,
++-					   phylink_config);
++-
++-	mtk_sgmii_restart_an(mac->hw, mac->id);
++-}
++-
++ static void mtk_mac_link_down(struct phylink_config *config, unsigned int mode,
++ 			      phy_interface_t interface)
++ {
++@@ -554,15 +556,6 @@ static void mtk_mac_link_up(struct phyli
++ 					   phylink_config);
++ 	u32 mcr;
++ 
++-	if (phy_interface_mode_is_8023z(interface)) {
++-		struct mtk_eth *eth = mac->hw;
++-
++-		/* Decide how GMAC and SGMIISYS be mapped */
++-		int sid = (MTK_HAS_CAPS(eth->soc->caps, MTK_SHARED_SGMII)) ?
++-			   0 : mac->id;
++-		mtk_sgmii_link_up(eth->sgmii, sid, speed, duplex);
++-	}
++-
++ 	mcr = mtk_r32(mac->hw, MTK_MAC_MCR(mac->id));
++ 	mcr &= ~(MAC_MCR_SPEED_100 | MAC_MCR_SPEED_1000 |
++ 		 MAC_MCR_FORCE_DPX | MAC_MCR_FORCE_TX_FC |
++@@ -595,8 +588,8 @@ static void mtk_mac_link_up(struct phyli
++ 
++ static const struct phylink_mac_ops mtk_phylink_ops = {
++ 	.validate = phylink_generic_validate,
+++	.mac_select_pcs = mtk_mac_select_pcs,
++ 	.mac_pcs_get_state = mtk_mac_pcs_get_state,
++-	.mac_an_restart = mtk_mac_an_restart,
++ 	.mac_config = mtk_mac_config,
++ 	.mac_finish = mtk_mac_finish,
++ 	.mac_link_down = mtk_mac_link_down,
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -963,10 +963,12 @@ struct mtk_soc_data {
++  * @regmap:            The register map pointing at the range used to setup
++  *                     SGMII modes
++  * @ana_rgc3:          The offset refers to register ANA_RGC3 related to regmap
+++ * @pcs:               Phylink PCS structure
++  */
++ struct mtk_pcs {
++ 	struct regmap	*regmap;
++ 	u32             ana_rgc3;
+++	struct phylink_pcs pcs;
++ };
++ 
++ /* struct mtk_sgmii -  This is the structure holding sgmii regmap and its
++@@ -1106,12 +1108,9 @@ void mtk_stats_update_mac(struct mtk_mac
++ void mtk_w32(struct mtk_eth *eth, u32 val, unsigned reg);
++ u32 mtk_r32(struct mtk_eth *eth, unsigned reg);
++ 
+++struct phylink_pcs *mtk_sgmii_select_pcs(struct mtk_sgmii *ss, int id);
++ int mtk_sgmii_init(struct mtk_sgmii *ss, struct device_node *np,
++ 		   u32 ana_rgc3);
++-int mtk_sgmii_config(struct mtk_sgmii *ss, int id, unsigned int mode,
++-		     phy_interface_t interface);
++-void mtk_sgmii_link_up(struct mtk_sgmii *ss, int id, int speed, int duplex);
++-void mtk_sgmii_restart_an(struct mtk_eth *eth, int mac_id);
++ 
++ int mtk_gmac_sgmii_path_setup(struct mtk_eth *eth, int mac_id);
++ int mtk_gmac_gephy_path_setup(struct mtk_eth *eth, int mac_id);
++--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
+++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
++@@ -14,14 +14,16 @@
++ 
++ #include "mtk_eth_soc.h"
++ 
+++static struct mtk_pcs *pcs_to_mtk_pcs(struct phylink_pcs *pcs)
+++{
+++	return container_of(pcs, struct mtk_pcs, pcs);
+++}
+++
++ /* For SGMII interface mode */
++ static int mtk_pcs_setup_mode_an(struct mtk_pcs *mpcs)
++ {
++ 	unsigned int val;
++ 
++-	if (!mpcs->regmap)
++-		return -EINVAL;
++-
++ 	/* Setup the link timer and QPHY power up inside SGMIISYS */
++ 	regmap_write(mpcs->regmap, SGMSYS_PCS_LINK_TIMER,
++ 		     SGMII_LINK_TIMER_DEFAULT);
++@@ -50,9 +52,6 @@ static int mtk_pcs_setup_mode_force(stru
++ {
++ 	unsigned int val;
++ 
++-	if (!mpcs->regmap)
++-		return -EINVAL;
++-
++ 	regmap_read(mpcs->regmap, mpcs->ana_rgc3, &val);
++ 	val &= ~RG_PHY_SPEED_MASK;
++ 	if (interface == PHY_INTERFACE_MODE_2500BASEX)
++@@ -78,10 +77,12 @@ static int mtk_pcs_setup_mode_force(stru
++ 	return 0;
++ }
++ 
++-int mtk_sgmii_config(struct mtk_sgmii *ss, int id, unsigned int mode,
++-		     phy_interface_t interface)
+++static int mtk_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
+++			  phy_interface_t interface,
+++			  const unsigned long *advertising,
+++			  bool permit_pause_to_mac)
++ {
++-	struct mtk_pcs *mpcs = &ss->pcs[id];
+++	struct mtk_pcs *mpcs = pcs_to_mtk_pcs(pcs);
++ 	int err = 0;
++ 
++ 	/* Setup SGMIISYS with the determined property */
++@@ -93,22 +94,25 @@ int mtk_sgmii_config(struct mtk_sgmii *s
++ 	return err;
++ }
++ 
++-static void mtk_pcs_restart_an(struct mtk_pcs *mpcs)
+++static void mtk_pcs_restart_an(struct phylink_pcs *pcs)
++ {
+++	struct mtk_pcs *mpcs = pcs_to_mtk_pcs(pcs);
++ 	unsigned int val;
++ 
++-	if (!mpcs->regmap)
++-		return;
++-
++ 	regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &val);
++ 	val |= SGMII_AN_RESTART;
++ 	regmap_write(mpcs->regmap, SGMSYS_PCS_CONTROL_1, val);
++ }
++ 
++-static void mtk_pcs_link_up(struct mtk_pcs *mpcs, int speed, int duplex)
+++static void mtk_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode,
+++			    phy_interface_t interface, int speed, int duplex)
++ {
+++	struct mtk_pcs *mpcs = pcs_to_mtk_pcs(pcs);
++ 	unsigned int val;
++ 
+++	if (!phy_interface_mode_is_8023z(interface))
+++		return;
+++
++ 	/* SGMII force duplex setting */
++ 	regmap_read(mpcs->regmap, SGMSYS_SGMII_MODE, &val);
++ 	val &= ~SGMII_DUPLEX_FULL;
++@@ -118,11 +122,11 @@ static void mtk_pcs_link_up(struct mtk_p
++ 	regmap_write(mpcs->regmap, SGMSYS_SGMII_MODE, val);
++ }
++ 
++-/* For 1000BASE-X and 2500BASE-X interface modes */
++-void mtk_sgmii_link_up(struct mtk_sgmii *ss, int id, int speed, int duplex)
++-{
++-	mtk_pcs_link_up(&ss->pcs[id], speed, duplex);
++-}
+++static const struct phylink_pcs_ops mtk_pcs_ops = {
+++	.pcs_config = mtk_pcs_config,
+++	.pcs_an_restart = mtk_pcs_restart_an,
+++	.pcs_link_up = mtk_pcs_link_up,
+++};
++ 
++ int mtk_sgmii_init(struct mtk_sgmii *ss, struct device_node *r, u32 ana_rgc3)
++ {
++@@ -139,18 +143,17 @@ int mtk_sgmii_init(struct mtk_sgmii *ss,
++ 		of_node_put(np);
++ 		if (IS_ERR(ss->pcs[i].regmap))
++ 			return PTR_ERR(ss->pcs[i].regmap);
+++
+++		ss->pcs[i].pcs.ops = &mtk_pcs_ops;
++ 	}
++ 
++ 	return 0;
++ }
++ 
++-void mtk_sgmii_restart_an(struct mtk_eth *eth, int mac_id)
+++struct phylink_pcs *mtk_sgmii_select_pcs(struct mtk_sgmii *ss, int id)
++ {
++-	unsigned int sid;
++-
++-	/* Decide how GMAC and SGMIISYS be mapped */
++-	sid = (MTK_HAS_CAPS(eth->soc->caps, MTK_SHARED_SGMII)) ?
++-	       0 : mac_id;
+++	if (!ss->pcs[id].regmap)
+++		return NULL;
++ 
++-	mtk_pcs_restart_an(&eth->sgmii->pcs[sid]);
+++	return &ss->pcs[id].pcs;
++ }
+diff --git a/target/linux/generic/backport-5.15/705-01-v5.17-net-dsa-mt7530-iterate-using-dsa_switch_for_each_use.patch b/target/linux/generic/backport-5.15/705-01-v5.17-net-dsa-mt7530-iterate-using-dsa_switch_for_each_use.patch
+new file mode 100644
+index 0000000000..e3f01ada6d
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/705-01-v5.17-net-dsa-mt7530-iterate-using-dsa_switch_for_each_use.patch
+@@ -0,0 +1,106 @@
++From 505560028b6deb9b4385cf6100f05ca6f4aacaf8 Mon Sep 17 00:00:00 2001
++From: Vladimir Oltean <vladimir.oltean@nxp.com>
++Date: Mon, 6 Dec 2021 18:57:49 +0200
++Subject: [PATCH 01/13] net: dsa: mt7530: iterate using
++ dsa_switch_for_each_user_port in bridging ops
++
++Avoid repeated calls to dsa_to_port() (some hidden behind dsa_is_user_port
++and some in plain sight) by keeping two struct dsa_port references: one
++to the port passed as argument, and another to the other ports of the
++switch that we're iterating over.
++
++dsa_to_port(ds, i) gets replaced by other_dp, i gets replaced by
++other_port which is derived from other_dp->index, dsa_is_user_port is
++handled by the DSA iterator.
++
++Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/dsa/mt7530.c | 52 +++++++++++++++++++++++-----------------
++ 1 file changed, 30 insertions(+), 22 deletions(-)
++
++--- a/drivers/net/dsa/mt7530.c
+++++ b/drivers/net/dsa/mt7530.c
++@@ -1191,27 +1191,31 @@ static int
++ mt7530_port_bridge_join(struct dsa_switch *ds, int port,
++ 			struct net_device *bridge)
++ {
++-	struct mt7530_priv *priv = ds->priv;
+++	struct dsa_port *dp = dsa_to_port(ds, port), *other_dp;
++ 	u32 port_bitmap = BIT(MT7530_CPU_PORT);
++-	int i;
+++	struct mt7530_priv *priv = ds->priv;
++ 
++ 	mutex_lock(&priv->reg_mutex);
++ 
++-	for (i = 0; i < MT7530_NUM_PORTS; i++) {
+++	dsa_switch_for_each_user_port(other_dp, ds) {
+++		int other_port = other_dp->index;
+++
+++		if (dp == other_dp)
+++			continue;
+++
++ 		/* Add this port to the port matrix of the other ports in the
++ 		 * same bridge. If the port is disabled, port matrix is kept
++ 		 * and not being setup until the port becomes enabled.
++ 		 */
++-		if (dsa_is_user_port(ds, i) && i != port) {
++-			if (dsa_to_port(ds, i)->bridge_dev != bridge)
++-				continue;
++-			if (priv->ports[i].enable)
++-				mt7530_set(priv, MT7530_PCR_P(i),
++-					   PCR_MATRIX(BIT(port)));
++-			priv->ports[i].pm |= PCR_MATRIX(BIT(port));
+++		if (other_dp->bridge_dev != bridge)
+++			continue;
++ 
++-			port_bitmap |= BIT(i);
++-		}
+++		if (priv->ports[other_port].enable)
+++			mt7530_set(priv, MT7530_PCR_P(other_port),
+++				   PCR_MATRIX(BIT(port)));
+++		priv->ports[other_port].pm |= PCR_MATRIX(BIT(port));
+++
+++		port_bitmap |= BIT(other_port);
++ 	}
++ 
++ 	/* Add the all other ports to this port matrix. */
++@@ -1304,24 +1308,28 @@ static void
++ mt7530_port_bridge_leave(struct dsa_switch *ds, int port,
++ 			 struct net_device *bridge)
++ {
+++	struct dsa_port *dp = dsa_to_port(ds, port), *other_dp;
++ 	struct mt7530_priv *priv = ds->priv;
++-	int i;
++ 
++ 	mutex_lock(&priv->reg_mutex);
++ 
++-	for (i = 0; i < MT7530_NUM_PORTS; i++) {
+++	dsa_switch_for_each_user_port(other_dp, ds) {
+++		int other_port = other_dp->index;
+++
+++		if (dp == other_dp)
+++			continue;
+++
++ 		/* Remove this port from the port matrix of the other ports
++ 		 * in the same bridge. If the port is disabled, port matrix
++ 		 * is kept and not being setup until the port becomes enabled.
++ 		 */
++-		if (dsa_is_user_port(ds, i) && i != port) {
++-			if (dsa_to_port(ds, i)->bridge_dev != bridge)
++-				continue;
++-			if (priv->ports[i].enable)
++-				mt7530_clear(priv, MT7530_PCR_P(i),
++-					     PCR_MATRIX(BIT(port)));
++-			priv->ports[i].pm &= ~PCR_MATRIX(BIT(port));
++-		}
+++		if (other_dp->bridge_dev != bridge)
+++			continue;
+++
+++		if (priv->ports[other_port].enable)
+++			mt7530_clear(priv, MT7530_PCR_P(other_port),
+++				     PCR_MATRIX(BIT(port)));
+++		priv->ports[other_port].pm &= ~PCR_MATRIX(BIT(port));
++ 	}
++ 
++ 	/* Set the cpu port to be the only one in the port matrix of
+diff --git a/target/linux/generic/backport-5.15/705-02-v5.19-net-dsa-mt7530-populate-supported_interfaces-and-mac.patch b/target/linux/generic/backport-5.15/705-02-v5.19-net-dsa-mt7530-populate-supported_interfaces-and-mac.patch
+new file mode 100644
+index 0000000000..94b8cb053d
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/705-02-v5.19-net-dsa-mt7530-populate-supported_interfaces-and-mac.patch
+@@ -0,0 +1,166 @@
++From a1da54bcd664fc27169386db966575675ac3ccb0 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Mon, 11 Apr 2022 10:46:01 +0100
++Subject: [PATCH 02/13] net: dsa: mt7530: populate supported_interfaces and
++ mac_capabilities
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++Populate the supported interfaces and MAC capabilities for mt7530,
++mt7531 and mt7621 DSA switches. Filling this in will enable phylink
++to pre-check the PHY interface mode against the the supported
++interfaces bitmap prior to calling the validate function, and will
++eventually allow us to convert to using the generic validation.
++
++Tested-by: Marek Behún <kabel@kernel.org>
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Paolo Abeni <pabeni@redhat.com>
++---
++ drivers/net/dsa/mt7530.c | 74 ++++++++++++++++++++++++++++++++++++++++
++ drivers/net/dsa/mt7530.h |  2 ++
++ 2 files changed, 76 insertions(+)
++
++--- a/drivers/net/dsa/mt7530.c
+++++ b/drivers/net/dsa/mt7530.c
++@@ -2390,6 +2390,32 @@ mt7531_setup(struct dsa_switch *ds)
++ 	return 0;
++ }
++ 
+++static void mt7530_mac_port_get_caps(struct dsa_switch *ds, int port,
+++				     struct phylink_config *config)
+++{
+++	switch (port) {
+++	case 0 ... 4: /* Internal phy */
+++		__set_bit(PHY_INTERFACE_MODE_GMII,
+++			  config->supported_interfaces);
+++		break;
+++
+++	case 5: /* 2nd cpu port with phy of port 0 or 4 / external phy */
+++		phy_interface_set_rgmii(config->supported_interfaces);
+++		__set_bit(PHY_INTERFACE_MODE_MII,
+++			  config->supported_interfaces);
+++		__set_bit(PHY_INTERFACE_MODE_GMII,
+++			  config->supported_interfaces);
+++		break;
+++
+++	case 6: /* 1st cpu port */
+++		__set_bit(PHY_INTERFACE_MODE_RGMII,
+++			  config->supported_interfaces);
+++		__set_bit(PHY_INTERFACE_MODE_TRGMII,
+++			  config->supported_interfaces);
+++		break;
+++	}
+++}
+++
++ static bool
++ mt7530_phy_mode_supported(struct dsa_switch *ds, int port,
++ 			  const struct phylink_link_state *state)
++@@ -2426,6 +2452,37 @@ static bool mt7531_is_rgmii_port(struct
++ 	return (port == 5) && (priv->p5_intf_sel != P5_INTF_SEL_GMAC5_SGMII);
++ }
++ 
+++static void mt7531_mac_port_get_caps(struct dsa_switch *ds, int port,
+++				     struct phylink_config *config)
+++{
+++	struct mt7530_priv *priv = ds->priv;
+++
+++	switch (port) {
+++	case 0 ... 4: /* Internal phy */
+++		__set_bit(PHY_INTERFACE_MODE_GMII,
+++			  config->supported_interfaces);
+++		break;
+++
+++	case 5: /* 2nd cpu port supports either rgmii or sgmii/8023z */
+++		if (mt7531_is_rgmii_port(priv, port)) {
+++			phy_interface_set_rgmii(config->supported_interfaces);
+++			break;
+++		}
+++		fallthrough;
+++
+++	case 6: /* 1st cpu port supports sgmii/8023z only */
+++		__set_bit(PHY_INTERFACE_MODE_SGMII,
+++			  config->supported_interfaces);
+++		__set_bit(PHY_INTERFACE_MODE_1000BASEX,
+++			  config->supported_interfaces);
+++		__set_bit(PHY_INTERFACE_MODE_2500BASEX,
+++			  config->supported_interfaces);
+++
+++		config->mac_capabilities |= MAC_2500FD;
+++		break;
+++	}
+++}
+++
++ static bool
++ mt7531_phy_mode_supported(struct dsa_switch *ds, int port,
++ 			  const struct phylink_link_state *state)
++@@ -2902,6 +2959,18 @@ mt7531_cpu_port_config(struct dsa_switch
++ 	return 0;
++ }
++ 
+++static void mt753x_phylink_get_caps(struct dsa_switch *ds, int port,
+++				    struct phylink_config *config)
+++{
+++	struct mt7530_priv *priv = ds->priv;
+++
+++	/* This switch only supports full-duplex at 1Gbps */
+++	config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+++				   MAC_10 | MAC_100 | MAC_1000FD;
+++
+++	priv->info->mac_port_get_caps(ds, port, config);
+++}
+++
++ static void
++ mt7530_mac_port_validate(struct dsa_switch *ds, int port,
++ 			 unsigned long *supported)
++@@ -3137,6 +3206,7 @@ static const struct dsa_switch_ops mt753
++ 	.port_vlan_del		= mt7530_port_vlan_del,
++ 	.port_mirror_add	= mt753x_port_mirror_add,
++ 	.port_mirror_del	= mt753x_port_mirror_del,
+++	.phylink_get_caps	= mt753x_phylink_get_caps,
++ 	.phylink_validate	= mt753x_phylink_validate,
++ 	.phylink_mac_link_state	= mt753x_phylink_mac_link_state,
++ 	.phylink_mac_config	= mt753x_phylink_mac_config,
++@@ -3154,6 +3224,7 @@ static const struct mt753x_info mt753x_t
++ 		.phy_read = mt7530_phy_read,
++ 		.phy_write = mt7530_phy_write,
++ 		.pad_setup = mt7530_pad_clk_setup,
+++		.mac_port_get_caps = mt7530_mac_port_get_caps,
++ 		.phy_mode_supported = mt7530_phy_mode_supported,
++ 		.mac_port_validate = mt7530_mac_port_validate,
++ 		.mac_port_get_state = mt7530_phylink_mac_link_state,
++@@ -3165,6 +3236,7 @@ static const struct mt753x_info mt753x_t
++ 		.phy_read = mt7530_phy_read,
++ 		.phy_write = mt7530_phy_write,
++ 		.pad_setup = mt7530_pad_clk_setup,
+++		.mac_port_get_caps = mt7530_mac_port_get_caps,
++ 		.phy_mode_supported = mt7530_phy_mode_supported,
++ 		.mac_port_validate = mt7530_mac_port_validate,
++ 		.mac_port_get_state = mt7530_phylink_mac_link_state,
++@@ -3177,6 +3249,7 @@ static const struct mt753x_info mt753x_t
++ 		.phy_write = mt7531_ind_phy_write,
++ 		.pad_setup = mt7531_pad_setup,
++ 		.cpu_port_config = mt7531_cpu_port_config,
+++		.mac_port_get_caps = mt7531_mac_port_get_caps,
++ 		.phy_mode_supported = mt7531_phy_mode_supported,
++ 		.mac_port_validate = mt7531_mac_port_validate,
++ 		.mac_port_get_state = mt7531_phylink_mac_link_state,
++@@ -3239,6 +3312,7 @@ mt7530_probe(struct mdio_device *mdiodev
++ 	 */
++ 	if (!priv->info->sw_setup || !priv->info->pad_setup ||
++ 	    !priv->info->phy_read || !priv->info->phy_write ||
+++	    !priv->info->mac_port_get_caps ||
++ 	    !priv->info->phy_mode_supported ||
++ 	    !priv->info->mac_port_validate ||
++ 	    !priv->info->mac_port_get_state || !priv->info->mac_port_config)
++--- a/drivers/net/dsa/mt7530.h
+++++ b/drivers/net/dsa/mt7530.h
++@@ -769,6 +769,8 @@ struct mt753x_info {
++ 	int (*phy_write)(struct mt7530_priv *priv, int port, int regnum, u16 val);
++ 	int (*pad_setup)(struct dsa_switch *ds, phy_interface_t interface);
++ 	int (*cpu_port_config)(struct dsa_switch *ds, int port);
+++	void (*mac_port_get_caps)(struct dsa_switch *ds, int port,
+++				  struct phylink_config *config);
++ 	bool (*phy_mode_supported)(struct dsa_switch *ds, int port,
++ 				   const struct phylink_link_state *state);
++ 	void (*mac_port_validate)(struct dsa_switch *ds, int port,
+diff --git a/target/linux/generic/backport-5.15/705-03-v5.19-net-dsa-mt7530-remove-interface-checks.patch b/target/linux/generic/backport-5.15/705-03-v5.19-net-dsa-mt7530-remove-interface-checks.patch
+new file mode 100644
+index 0000000000..322dc6f6ef
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/705-03-v5.19-net-dsa-mt7530-remove-interface-checks.patch
+@@ -0,0 +1,172 @@
++From e3f6719e2269868ca129b05da50cd55786848954 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Mon, 11 Apr 2022 10:46:06 +0100
++Subject: [PATCH 03/13] net: dsa: mt7530: remove interface checks
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++As phylink checks the interface mode against the supported_interfaces
++bitmap, we no longer need to validate the interface mode, nor handle
++PHY_INTERFACE_MODE_NA in the validation function. Remove these to
++simplify the implementation.
++
++Tested-by: Marek Behún <kabel@kernel.org>
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Paolo Abeni <pabeni@redhat.com>
++---
++ drivers/net/dsa/mt7530.c | 82 ----------------------------------------
++ drivers/net/dsa/mt7530.h |  2 -
++ 2 files changed, 84 deletions(-)
++
++--- a/drivers/net/dsa/mt7530.c
+++++ b/drivers/net/dsa/mt7530.c
++@@ -2416,37 +2416,6 @@ static void mt7530_mac_port_get_caps(str
++ 	}
++ }
++ 
++-static bool
++-mt7530_phy_mode_supported(struct dsa_switch *ds, int port,
++-			  const struct phylink_link_state *state)
++-{
++-	struct mt7530_priv *priv = ds->priv;
++-
++-	switch (port) {
++-	case 0 ... 4: /* Internal phy */
++-		if (state->interface != PHY_INTERFACE_MODE_GMII)
++-			return false;
++-		break;
++-	case 5: /* 2nd cpu port with phy of port 0 or 4 / external phy */
++-		if (!phy_interface_mode_is_rgmii(state->interface) &&
++-		    state->interface != PHY_INTERFACE_MODE_MII &&
++-		    state->interface != PHY_INTERFACE_MODE_GMII)
++-			return false;
++-		break;
++-	case 6: /* 1st cpu port */
++-		if (state->interface != PHY_INTERFACE_MODE_RGMII &&
++-		    state->interface != PHY_INTERFACE_MODE_TRGMII)
++-			return false;
++-		break;
++-	default:
++-		dev_err(priv->dev, "%s: unsupported port: %i\n", __func__,
++-			port);
++-		return false;
++-	}
++-
++-	return true;
++-}
++-
++ static bool mt7531_is_rgmii_port(struct mt7530_priv *priv, u32 port)
++ {
++ 	return (port == 5) && (priv->p5_intf_sel != P5_INTF_SEL_GMAC5_SGMII);
++@@ -2483,44 +2452,6 @@ static void mt7531_mac_port_get_caps(str
++ 	}
++ }
++ 
++-static bool
++-mt7531_phy_mode_supported(struct dsa_switch *ds, int port,
++-			  const struct phylink_link_state *state)
++-{
++-	struct mt7530_priv *priv = ds->priv;
++-
++-	switch (port) {
++-	case 0 ... 4: /* Internal phy */
++-		if (state->interface != PHY_INTERFACE_MODE_GMII)
++-			return false;
++-		break;
++-	case 5: /* 2nd cpu port supports either rgmii or sgmii/8023z */
++-		if (mt7531_is_rgmii_port(priv, port))
++-			return phy_interface_mode_is_rgmii(state->interface);
++-		fallthrough;
++-	case 6: /* 1st cpu port supports sgmii/8023z only */
++-		if (state->interface != PHY_INTERFACE_MODE_SGMII &&
++-		    !phy_interface_mode_is_8023z(state->interface))
++-			return false;
++-		break;
++-	default:
++-		dev_err(priv->dev, "%s: unsupported port: %i\n", __func__,
++-			port);
++-		return false;
++-	}
++-
++-	return true;
++-}
++-
++-static bool
++-mt753x_phy_mode_supported(struct dsa_switch *ds, int port,
++-			  const struct phylink_link_state *state)
++-{
++-	struct mt7530_priv *priv = ds->priv;
++-
++-	return priv->info->phy_mode_supported(ds, port, state);
++-}
++-
++ static int
++ mt753x_pad_setup(struct dsa_switch *ds, const struct phylink_link_state *state)
++ {
++@@ -2775,9 +2706,6 @@ mt753x_phylink_mac_config(struct dsa_swi
++ 	struct mt7530_priv *priv = ds->priv;
++ 	u32 mcr_cur, mcr_new;
++ 
++-	if (!mt753x_phy_mode_supported(ds, port, state))
++-		goto unsupported;
++-
++ 	switch (port) {
++ 	case 0 ... 4: /* Internal phy */
++ 		if (state->interface != PHY_INTERFACE_MODE_GMII)
++@@ -2993,12 +2921,6 @@ mt753x_phylink_validate(struct dsa_switc
++ 	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
++ 	struct mt7530_priv *priv = ds->priv;
++ 
++-	if (state->interface != PHY_INTERFACE_MODE_NA &&
++-	    !mt753x_phy_mode_supported(ds, port, state)) {
++-		linkmode_zero(supported);
++-		return;
++-	}
++-
++ 	phylink_set_port_modes(mask);
++ 
++ 	if (state->interface != PHY_INTERFACE_MODE_TRGMII &&
++@@ -3225,7 +3147,6 @@ static const struct mt753x_info mt753x_t
++ 		.phy_write = mt7530_phy_write,
++ 		.pad_setup = mt7530_pad_clk_setup,
++ 		.mac_port_get_caps = mt7530_mac_port_get_caps,
++-		.phy_mode_supported = mt7530_phy_mode_supported,
++ 		.mac_port_validate = mt7530_mac_port_validate,
++ 		.mac_port_get_state = mt7530_phylink_mac_link_state,
++ 		.mac_port_config = mt7530_mac_config,
++@@ -3237,7 +3158,6 @@ static const struct mt753x_info mt753x_t
++ 		.phy_write = mt7530_phy_write,
++ 		.pad_setup = mt7530_pad_clk_setup,
++ 		.mac_port_get_caps = mt7530_mac_port_get_caps,
++-		.phy_mode_supported = mt7530_phy_mode_supported,
++ 		.mac_port_validate = mt7530_mac_port_validate,
++ 		.mac_port_get_state = mt7530_phylink_mac_link_state,
++ 		.mac_port_config = mt7530_mac_config,
++@@ -3250,7 +3170,6 @@ static const struct mt753x_info mt753x_t
++ 		.pad_setup = mt7531_pad_setup,
++ 		.cpu_port_config = mt7531_cpu_port_config,
++ 		.mac_port_get_caps = mt7531_mac_port_get_caps,
++-		.phy_mode_supported = mt7531_phy_mode_supported,
++ 		.mac_port_validate = mt7531_mac_port_validate,
++ 		.mac_port_get_state = mt7531_phylink_mac_link_state,
++ 		.mac_port_config = mt7531_mac_config,
++@@ -3313,7 +3232,6 @@ mt7530_probe(struct mdio_device *mdiodev
++ 	if (!priv->info->sw_setup || !priv->info->pad_setup ||
++ 	    !priv->info->phy_read || !priv->info->phy_write ||
++ 	    !priv->info->mac_port_get_caps ||
++-	    !priv->info->phy_mode_supported ||
++ 	    !priv->info->mac_port_validate ||
++ 	    !priv->info->mac_port_get_state || !priv->info->mac_port_config)
++ 		return -EINVAL;
++--- a/drivers/net/dsa/mt7530.h
+++++ b/drivers/net/dsa/mt7530.h
++@@ -771,8 +771,6 @@ struct mt753x_info {
++ 	int (*cpu_port_config)(struct dsa_switch *ds, int port);
++ 	void (*mac_port_get_caps)(struct dsa_switch *ds, int port,
++ 				  struct phylink_config *config);
++-	bool (*phy_mode_supported)(struct dsa_switch *ds, int port,
++-				   const struct phylink_link_state *state);
++ 	void (*mac_port_validate)(struct dsa_switch *ds, int port,
++ 				  unsigned long *supported);
++ 	int (*mac_port_get_state)(struct dsa_switch *ds, int port,
+diff --git a/target/linux/generic/backport-5.15/705-04-v5.19-net-dsa-mt7530-drop-use-of-phylink_helper_basex_spee.patch b/target/linux/generic/backport-5.15/705-04-v5.19-net-dsa-mt7530-drop-use-of-phylink_helper_basex_spee.patch
+new file mode 100644
+index 0000000000..36b3aab33b
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/705-04-v5.19-net-dsa-mt7530-drop-use-of-phylink_helper_basex_spee.patch
+@@ -0,0 +1,34 @@
++From 58344a3b85f1bd5ffddfc2c11f6f2bf688b5f990 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Mon, 11 Apr 2022 10:46:12 +0100
++Subject: [PATCH 04/13] net: dsa: mt7530: drop use of
++ phylink_helper_basex_speed()
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++Now that we have a better method to select SFP interface modes, we
++no longer need to use phylink_helper_basex_speed() in a driver's
++validation function.
++
++Tested-by: Marek Behún <kabel@kernel.org>
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Paolo Abeni <pabeni@redhat.com>
++---
++ drivers/net/dsa/mt7530.c | 5 -----
++ 1 file changed, 5 deletions(-)
++
++--- a/drivers/net/dsa/mt7530.c
+++++ b/drivers/net/dsa/mt7530.c
++@@ -2945,11 +2945,6 @@ mt753x_phylink_validate(struct dsa_switc
++ 
++ 	linkmode_and(supported, supported, mask);
++ 	linkmode_and(state->advertising, state->advertising, mask);
++-
++-	/* We can only operate at 2500BaseX or 1000BaseX.  If requested
++-	 * to advertise both, only report advertising at 2500BaseX.
++-	 */
++-	phylink_helper_basex_speed(state);
++ }
++ 
++ static int
+diff --git a/target/linux/generic/backport-5.15/705-05-v5.19-net-dsa-mt7530-only-indicate-linkmodes-that-can-be-s.patch b/target/linux/generic/backport-5.15/705-05-v5.19-net-dsa-mt7530-only-indicate-linkmodes-that-can-be-s.patch
+new file mode 100644
+index 0000000000..831f509341
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/705-05-v5.19-net-dsa-mt7530-only-indicate-linkmodes-that-can-be-s.patch
+@@ -0,0 +1,86 @@
++From 3c1d788a62dc648d1846049b66119ebb69dedd52 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Mon, 11 Apr 2022 10:46:17 +0100
++Subject: [PATCH 05/13] net: dsa: mt7530: only indicate linkmodes that can be
++ supported
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++Now that mt7530 is not using the basex helper, it becomes unnecessary to
++indicate support for both 1000baseX and 2500baseX when one of the 803.3z
++PHY interface modes is being selected. Ensure that the driver indicates
++only those linkmodes that can actually be supported by the PHY interface
++mode.
++
++Tested-by: Marek Behún <kabel@kernel.org>
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Paolo Abeni <pabeni@redhat.com>
++---
++ drivers/net/dsa/mt7530.c | 12 ++++++++----
++ drivers/net/dsa/mt7530.h |  1 +
++ 2 files changed, 9 insertions(+), 4 deletions(-)
++
++--- a/drivers/net/dsa/mt7530.c
+++++ b/drivers/net/dsa/mt7530.c
++@@ -2523,12 +2523,13 @@ static int mt7531_rgmii_setup(struct mt7
++ }
++ 
++ static void mt7531_sgmii_validate(struct mt7530_priv *priv, int port,
+++				  phy_interface_t interface,
++ 				  unsigned long *supported)
++ {
++ 	/* Port5 supports ethier RGMII or SGMII.
++ 	 * Port6 supports SGMII only.
++ 	 */
++-	if (port == 6) {
+++	if (port == 6 && interface == PHY_INTERFACE_MODE_2500BASEX) {
++ 		phylink_set(supported, 2500baseX_Full);
++ 		phylink_set(supported, 2500baseT_Full);
++ 	}
++@@ -2901,16 +2902,18 @@ static void mt753x_phylink_get_caps(stru
++ 
++ static void
++ mt7530_mac_port_validate(struct dsa_switch *ds, int port,
+++			 phy_interface_t interface,
++ 			 unsigned long *supported)
++ {
++ }
++ 
++ static void mt7531_mac_port_validate(struct dsa_switch *ds, int port,
+++				     phy_interface_t interface,
++ 				     unsigned long *supported)
++ {
++ 	struct mt7530_priv *priv = ds->priv;
++ 
++-	mt7531_sgmii_validate(priv, port, supported);
+++	mt7531_sgmii_validate(priv, port, interface, supported);
++ }
++ 
++ static void
++@@ -2933,12 +2936,13 @@ mt753x_phylink_validate(struct dsa_switc
++ 	}
++ 
++ 	/* This switch only supports 1G full-duplex. */
++-	if (state->interface != PHY_INTERFACE_MODE_MII) {
+++	if (state->interface != PHY_INTERFACE_MODE_MII &&
+++	    state->interface != PHY_INTERFACE_MODE_2500BASEX) {
++ 		phylink_set(mask, 1000baseT_Full);
++ 		phylink_set(mask, 1000baseX_Full);
++ 	}
++ 
++-	priv->info->mac_port_validate(ds, port, mask);
+++	priv->info->mac_port_validate(ds, port, state->interface, mask);
++ 
++ 	phylink_set(mask, Pause);
++ 	phylink_set(mask, Asym_Pause);
++--- a/drivers/net/dsa/mt7530.h
+++++ b/drivers/net/dsa/mt7530.h
++@@ -772,6 +772,7 @@ struct mt753x_info {
++ 	void (*mac_port_get_caps)(struct dsa_switch *ds, int port,
++ 				  struct phylink_config *config);
++ 	void (*mac_port_validate)(struct dsa_switch *ds, int port,
+++				  phy_interface_t interface,
++ 				  unsigned long *supported);
++ 	int (*mac_port_get_state)(struct dsa_switch *ds, int port,
++ 				  struct phylink_link_state *state);
+diff --git a/target/linux/generic/backport-5.15/705-06-v5.19-net-dsa-mt7530-switch-to-use-phylink_get_linkmodes.patch b/target/linux/generic/backport-5.15/705-06-v5.19-net-dsa-mt7530-switch-to-use-phylink_get_linkmodes.patch
+new file mode 100644
+index 0000000000..9f54f2789b
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/705-06-v5.19-net-dsa-mt7530-switch-to-use-phylink_get_linkmodes.patch
+@@ -0,0 +1,131 @@
++From 1c2211cb15dd3957fb26c0e1615eceb5db851ad6 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Mon, 11 Apr 2022 10:46:22 +0100
++Subject: [PATCH 06/13] net: dsa: mt7530: switch to use phylink_get_linkmodes()
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++Switch mt7530 to use phylink_get_linkmodes() to generate the ethtool
++linkmodes that can be supported. We are unable to use the generic
++helper for this as pause modes are dependent on the interface as
++the Autoneg bit depends on the interface mode.
++
++Tested-by: Marek Behún <kabel@kernel.org>
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Paolo Abeni <pabeni@redhat.com>
++---
++ drivers/net/dsa/mt7530.c | 57 ++++------------------------------------
++ 1 file changed, 5 insertions(+), 52 deletions(-)
++
++--- a/drivers/net/dsa/mt7530.c
+++++ b/drivers/net/dsa/mt7530.c
++@@ -2522,19 +2522,6 @@ static int mt7531_rgmii_setup(struct mt7
++ 	return 0;
++ }
++ 
++-static void mt7531_sgmii_validate(struct mt7530_priv *priv, int port,
++-				  phy_interface_t interface,
++-				  unsigned long *supported)
++-{
++-	/* Port5 supports ethier RGMII or SGMII.
++-	 * Port6 supports SGMII only.
++-	 */
++-	if (port == 6 && interface == PHY_INTERFACE_MODE_2500BASEX) {
++-		phylink_set(supported, 2500baseX_Full);
++-		phylink_set(supported, 2500baseT_Full);
++-	}
++-}
++-
++ static void
++ mt7531_sgmii_link_up_force(struct dsa_switch *ds, int port,
++ 			   unsigned int mode, phy_interface_t interface,
++@@ -2901,51 +2888,21 @@ static void mt753x_phylink_get_caps(stru
++ }
++ 
++ static void
++-mt7530_mac_port_validate(struct dsa_switch *ds, int port,
++-			 phy_interface_t interface,
++-			 unsigned long *supported)
++-{
++-}
++-
++-static void mt7531_mac_port_validate(struct dsa_switch *ds, int port,
++-				     phy_interface_t interface,
++-				     unsigned long *supported)
++-{
++-	struct mt7530_priv *priv = ds->priv;
++-
++-	mt7531_sgmii_validate(priv, port, interface, supported);
++-}
++-
++-static void
++ mt753x_phylink_validate(struct dsa_switch *ds, int port,
++ 			unsigned long *supported,
++ 			struct phylink_link_state *state)
++ {
++ 	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
++-	struct mt7530_priv *priv = ds->priv;
+++	u32 caps;
+++
+++	caps = dsa_to_port(ds, port)->pl_config.mac_capabilities;
++ 
++ 	phylink_set_port_modes(mask);
+++	phylink_get_linkmodes(mask, state->interface, caps);
++ 
++ 	if (state->interface != PHY_INTERFACE_MODE_TRGMII &&
++-	    !phy_interface_mode_is_8023z(state->interface)) {
++-		phylink_set(mask, 10baseT_Half);
++-		phylink_set(mask, 10baseT_Full);
++-		phylink_set(mask, 100baseT_Half);
++-		phylink_set(mask, 100baseT_Full);
+++	    !phy_interface_mode_is_8023z(state->interface))
++ 		phylink_set(mask, Autoneg);
++-	}
++-
++-	/* This switch only supports 1G full-duplex. */
++-	if (state->interface != PHY_INTERFACE_MODE_MII &&
++-	    state->interface != PHY_INTERFACE_MODE_2500BASEX) {
++-		phylink_set(mask, 1000baseT_Full);
++-		phylink_set(mask, 1000baseX_Full);
++-	}
++-
++-	priv->info->mac_port_validate(ds, port, state->interface, mask);
++-
++-	phylink_set(mask, Pause);
++-	phylink_set(mask, Asym_Pause);
++ 
++ 	linkmode_and(supported, supported, mask);
++ 	linkmode_and(state->advertising, state->advertising, mask);
++@@ -3146,7 +3103,6 @@ static const struct mt753x_info mt753x_t
++ 		.phy_write = mt7530_phy_write,
++ 		.pad_setup = mt7530_pad_clk_setup,
++ 		.mac_port_get_caps = mt7530_mac_port_get_caps,
++-		.mac_port_validate = mt7530_mac_port_validate,
++ 		.mac_port_get_state = mt7530_phylink_mac_link_state,
++ 		.mac_port_config = mt7530_mac_config,
++ 	},
++@@ -3157,7 +3113,6 @@ static const struct mt753x_info mt753x_t
++ 		.phy_write = mt7530_phy_write,
++ 		.pad_setup = mt7530_pad_clk_setup,
++ 		.mac_port_get_caps = mt7530_mac_port_get_caps,
++-		.mac_port_validate = mt7530_mac_port_validate,
++ 		.mac_port_get_state = mt7530_phylink_mac_link_state,
++ 		.mac_port_config = mt7530_mac_config,
++ 	},
++@@ -3169,7 +3124,6 @@ static const struct mt753x_info mt753x_t
++ 		.pad_setup = mt7531_pad_setup,
++ 		.cpu_port_config = mt7531_cpu_port_config,
++ 		.mac_port_get_caps = mt7531_mac_port_get_caps,
++-		.mac_port_validate = mt7531_mac_port_validate,
++ 		.mac_port_get_state = mt7531_phylink_mac_link_state,
++ 		.mac_port_config = mt7531_mac_config,
++ 		.mac_pcs_an_restart = mt7531_sgmii_restart_an,
++@@ -3231,7 +3185,6 @@ mt7530_probe(struct mdio_device *mdiodev
++ 	if (!priv->info->sw_setup || !priv->info->pad_setup ||
++ 	    !priv->info->phy_read || !priv->info->phy_write ||
++ 	    !priv->info->mac_port_get_caps ||
++-	    !priv->info->mac_port_validate ||
++ 	    !priv->info->mac_port_get_state || !priv->info->mac_port_config)
++ 		return -EINVAL;
++ 
+diff --git a/target/linux/generic/backport-5.15/705-07-v5.19-net-dsa-mt7530-partially-convert-to-phylink_pcs.patch b/target/linux/generic/backport-5.15/705-07-v5.19-net-dsa-mt7530-partially-convert-to-phylink_pcs.patch
+new file mode 100644
+index 0000000000..c07e627c77
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/705-07-v5.19-net-dsa-mt7530-partially-convert-to-phylink_pcs.patch
+@@ -0,0 +1,385 @@
++From fd993fd59d96d5e2d5972ec4ca1f9651025c987b Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Mon, 11 Apr 2022 10:46:27 +0100
++Subject: [PATCH 07/13] net: dsa: mt7530: partially convert to phylink_pcs
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++Partially convert the mt7530 driver to use phylink's PCS support. This
++is a partial implementation as we don't move anything into the
++pcs_config method yet - this driver supports SGMII or 1000BASE-X
++without in-band.
++
++Tested-by: Marek Behún <kabel@kernel.org>
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Paolo Abeni <pabeni@redhat.com>
++---
++ drivers/net/dsa/mt7530.c | 144 +++++++++++++++++++++++----------------
++ drivers/net/dsa/mt7530.h |  21 +++---
++ 2 files changed, 95 insertions(+), 70 deletions(-)
++
++--- a/drivers/net/dsa/mt7530.c
+++++ b/drivers/net/dsa/mt7530.c
++@@ -24,6 +24,11 @@
++ 
++ #include "mt7530.h"
++ 
+++static struct mt753x_pcs *pcs_to_mt753x_pcs(struct phylink_pcs *pcs)
+++{
+++	return container_of(pcs, struct mt753x_pcs, pcs);
+++}
+++
++ /* String, offset, and register size in bytes if different from 4 bytes */
++ static const struct mt7530_mib_desc mt7530_mib[] = {
++ 	MIB_DESC(1, 0x00, "TxDrop"),
++@@ -2522,12 +2527,11 @@ static int mt7531_rgmii_setup(struct mt7
++ 	return 0;
++ }
++ 
++-static void
++-mt7531_sgmii_link_up_force(struct dsa_switch *ds, int port,
++-			   unsigned int mode, phy_interface_t interface,
++-			   int speed, int duplex)
+++static void mt7531_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode,
+++			       phy_interface_t interface, int speed, int duplex)
++ {
++-	struct mt7530_priv *priv = ds->priv;
+++	struct mt7530_priv *priv = pcs_to_mt753x_pcs(pcs)->priv;
+++	int port = pcs_to_mt753x_pcs(pcs)->port;
++ 	unsigned int val;
++ 
++ 	/* For adjusting speed and duplex of SGMII force mode. */
++@@ -2553,6 +2557,9 @@ mt7531_sgmii_link_up_force(struct dsa_sw
++ 
++ 	/* MT7531 SGMII 1G force mode can only work in full duplex mode,
++ 	 * no matter MT7531_SGMII_FORCE_HALF_DUPLEX is set or not.
+++	 *
+++	 * The speed check is unnecessary as the MAC capabilities apply
+++	 * this restriction. --rmk
++ 	 */
++ 	if ((speed == SPEED_10 || speed == SPEED_100) &&
++ 	    duplex != DUPLEX_FULL)
++@@ -2628,9 +2635,10 @@ static int mt7531_sgmii_setup_mode_an(st
++ 	return 0;
++ }
++ 
++-static void mt7531_sgmii_restart_an(struct dsa_switch *ds, int port)
+++static void mt7531_pcs_an_restart(struct phylink_pcs *pcs)
++ {
++-	struct mt7530_priv *priv = ds->priv;
+++	struct mt7530_priv *priv = pcs_to_mt753x_pcs(pcs)->priv;
+++	int port = pcs_to_mt753x_pcs(pcs)->port;
++ 	u32 val;
++ 
++ 	/* Only restart AN when AN is enabled */
++@@ -2687,6 +2695,24 @@ mt753x_mac_config(struct dsa_switch *ds,
++ 	return priv->info->mac_port_config(ds, port, mode, state->interface);
++ }
++ 
+++static struct phylink_pcs *
+++mt753x_phylink_mac_select_pcs(struct dsa_switch *ds, int port,
+++			      phy_interface_t interface)
+++{
+++	struct mt7530_priv *priv = ds->priv;
+++
+++	switch (interface) {
+++	case PHY_INTERFACE_MODE_TRGMII:
+++	case PHY_INTERFACE_MODE_SGMII:
+++	case PHY_INTERFACE_MODE_1000BASEX:
+++	case PHY_INTERFACE_MODE_2500BASEX:
+++		return &priv->pcs[port].pcs;
+++
+++	default:
+++		return NULL;
+++	}
+++}
+++
++ static void
++ mt753x_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode,
++ 			  const struct phylink_link_state *state)
++@@ -2748,17 +2774,6 @@ unsupported:
++ 		mt7530_write(priv, MT7530_PMCR_P(port), mcr_new);
++ }
++ 
++-static void
++-mt753x_phylink_mac_an_restart(struct dsa_switch *ds, int port)
++-{
++-	struct mt7530_priv *priv = ds->priv;
++-
++-	if (!priv->info->mac_pcs_an_restart)
++-		return;
++-
++-	priv->info->mac_pcs_an_restart(ds, port);
++-}
++-
++ static void mt753x_phylink_mac_link_down(struct dsa_switch *ds, int port,
++ 					 unsigned int mode,
++ 					 phy_interface_t interface)
++@@ -2768,16 +2783,13 @@ static void mt753x_phylink_mac_link_down
++ 	mt7530_clear(priv, MT7530_PMCR_P(port), PMCR_LINK_SETTINGS_MASK);
++ }
++ 
++-static void mt753x_mac_pcs_link_up(struct dsa_switch *ds, int port,
++-				   unsigned int mode, phy_interface_t interface,
++-				   int speed, int duplex)
+++static void mt753x_phylink_pcs_link_up(struct phylink_pcs *pcs,
+++				       unsigned int mode,
+++				       phy_interface_t interface,
+++				       int speed, int duplex)
++ {
++-	struct mt7530_priv *priv = ds->priv;
++-
++-	if (!priv->info->mac_pcs_link_up)
++-		return;
++-
++-	priv->info->mac_pcs_link_up(ds, port, mode, interface, speed, duplex);
+++	if (pcs->ops->pcs_link_up)
+++		pcs->ops->pcs_link_up(pcs, mode, interface, speed, duplex);
++ }
++ 
++ static void mt753x_phylink_mac_link_up(struct dsa_switch *ds, int port,
++@@ -2790,8 +2802,6 @@ static void mt753x_phylink_mac_link_up(s
++ 	struct mt7530_priv *priv = ds->priv;
++ 	u32 mcr;
++ 
++-	mt753x_mac_pcs_link_up(ds, port, mode, interface, speed, duplex);
++-
++ 	mcr = PMCR_RX_EN | PMCR_TX_EN | PMCR_FORCE_LNK;
++ 
++ 	/* MT753x MAC works in 1G full duplex mode for all up-clocked
++@@ -2869,6 +2879,8 @@ mt7531_cpu_port_config(struct dsa_switch
++ 		return ret;
++ 	mt7530_write(priv, MT7530_PMCR_P(port),
++ 		     PMCR_CPU_PORT_SETTING(priv->id));
+++	mt753x_phylink_pcs_link_up(&priv->pcs[port].pcs, MLO_AN_FIXED,
+++				   interface, speed, DUPLEX_FULL);
++ 	mt753x_phylink_mac_link_up(ds, port, MLO_AN_FIXED, interface, NULL,
++ 				   speed, DUPLEX_FULL, true, true);
++ 
++@@ -2908,16 +2920,13 @@ mt753x_phylink_validate(struct dsa_switc
++ 	linkmode_and(state->advertising, state->advertising, mask);
++ }
++ 
++-static int
++-mt7530_phylink_mac_link_state(struct dsa_switch *ds, int port,
++-			      struct phylink_link_state *state)
+++static void mt7530_pcs_get_state(struct phylink_pcs *pcs,
+++				 struct phylink_link_state *state)
++ {
++-	struct mt7530_priv *priv = ds->priv;
+++	struct mt7530_priv *priv = pcs_to_mt753x_pcs(pcs)->priv;
+++	int port = pcs_to_mt753x_pcs(pcs)->port;
++ 	u32 pmsr;
++ 
++-	if (port < 0 || port >= MT7530_NUM_PORTS)
++-		return -EINVAL;
++-
++ 	pmsr = mt7530_read(priv, MT7530_PMSR_P(port));
++ 
++ 	state->link = (pmsr & PMSR_LINK);
++@@ -2944,8 +2953,6 @@ mt7530_phylink_mac_link_state(struct dsa
++ 		state->pause |= MLO_PAUSE_RX;
++ 	if (pmsr & PMSR_TX_FC)
++ 		state->pause |= MLO_PAUSE_TX;
++-
++-	return 1;
++ }
++ 
++ static int
++@@ -2987,32 +2994,49 @@ mt7531_sgmii_pcs_get_state_an(struct mt7
++ 	return 0;
++ }
++ 
++-static int
++-mt7531_phylink_mac_link_state(struct dsa_switch *ds, int port,
++-			      struct phylink_link_state *state)
+++static void mt7531_pcs_get_state(struct phylink_pcs *pcs,
+++				 struct phylink_link_state *state)
++ {
++-	struct mt7530_priv *priv = ds->priv;
+++	struct mt7530_priv *priv = pcs_to_mt753x_pcs(pcs)->priv;
+++	int port = pcs_to_mt753x_pcs(pcs)->port;
++ 
++ 	if (state->interface == PHY_INTERFACE_MODE_SGMII)
++-		return mt7531_sgmii_pcs_get_state_an(priv, port, state);
++-
++-	return -EOPNOTSUPP;
+++		mt7531_sgmii_pcs_get_state_an(priv, port, state);
+++	else
+++		state->link = false;
++ }
++ 
++-static int
++-mt753x_phylink_mac_link_state(struct dsa_switch *ds, int port,
++-			      struct phylink_link_state *state)
+++static int mt753x_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
+++			     phy_interface_t interface,
+++			     const unsigned long *advertising,
+++			     bool permit_pause_to_mac)
++ {
++-	struct mt7530_priv *priv = ds->priv;
+++	return 0;
+++}
++ 
++-	return priv->info->mac_port_get_state(ds, port, state);
+++static void mt7530_pcs_an_restart(struct phylink_pcs *pcs)
+++{
++ }
++ 
+++static const struct phylink_pcs_ops mt7530_pcs_ops = {
+++	.pcs_get_state = mt7530_pcs_get_state,
+++	.pcs_config = mt753x_pcs_config,
+++	.pcs_an_restart = mt7530_pcs_an_restart,
+++};
+++
+++static const struct phylink_pcs_ops mt7531_pcs_ops = {
+++	.pcs_get_state = mt7531_pcs_get_state,
+++	.pcs_config = mt753x_pcs_config,
+++	.pcs_an_restart = mt7531_pcs_an_restart,
+++	.pcs_link_up = mt7531_pcs_link_up,
+++};
+++
++ static int
++ mt753x_setup(struct dsa_switch *ds)
++ {
++ 	struct mt7530_priv *priv = ds->priv;
++ 	int ret = priv->info->sw_setup(ds);
+++	int i;
++ 
++ 	if (ret)
++ 		return ret;
++@@ -3025,6 +3049,13 @@ mt753x_setup(struct dsa_switch *ds)
++ 	if (ret && priv->irq)
++ 		mt7530_free_irq_common(priv);
++ 
+++	/* Initialise the PCS devices */
+++	for (i = 0; i < priv->ds->num_ports; i++) {
+++		priv->pcs[i].pcs.ops = priv->info->pcs_ops;
+++		priv->pcs[i].priv = priv;
+++		priv->pcs[i].port = i;
+++	}
+++
++ 	return ret;
++ }
++ 
++@@ -3086,9 +3117,8 @@ static const struct dsa_switch_ops mt753
++ 	.port_mirror_del	= mt753x_port_mirror_del,
++ 	.phylink_get_caps	= mt753x_phylink_get_caps,
++ 	.phylink_validate	= mt753x_phylink_validate,
++-	.phylink_mac_link_state	= mt753x_phylink_mac_link_state,
+++	.phylink_mac_select_pcs	= mt753x_phylink_mac_select_pcs,
++ 	.phylink_mac_config	= mt753x_phylink_mac_config,
++-	.phylink_mac_an_restart	= mt753x_phylink_mac_an_restart,
++ 	.phylink_mac_link_down	= mt753x_phylink_mac_link_down,
++ 	.phylink_mac_link_up	= mt753x_phylink_mac_link_up,
++ 	.get_mac_eee		= mt753x_get_mac_eee,
++@@ -3098,36 +3128,34 @@ static const struct dsa_switch_ops mt753
++ static const struct mt753x_info mt753x_table[] = {
++ 	[ID_MT7621] = {
++ 		.id = ID_MT7621,
+++		.pcs_ops = &mt7530_pcs_ops,
++ 		.sw_setup = mt7530_setup,
++ 		.phy_read = mt7530_phy_read,
++ 		.phy_write = mt7530_phy_write,
++ 		.pad_setup = mt7530_pad_clk_setup,
++ 		.mac_port_get_caps = mt7530_mac_port_get_caps,
++-		.mac_port_get_state = mt7530_phylink_mac_link_state,
++ 		.mac_port_config = mt7530_mac_config,
++ 	},
++ 	[ID_MT7530] = {
++ 		.id = ID_MT7530,
+++		.pcs_ops = &mt7530_pcs_ops,
++ 		.sw_setup = mt7530_setup,
++ 		.phy_read = mt7530_phy_read,
++ 		.phy_write = mt7530_phy_write,
++ 		.pad_setup = mt7530_pad_clk_setup,
++ 		.mac_port_get_caps = mt7530_mac_port_get_caps,
++-		.mac_port_get_state = mt7530_phylink_mac_link_state,
++ 		.mac_port_config = mt7530_mac_config,
++ 	},
++ 	[ID_MT7531] = {
++ 		.id = ID_MT7531,
+++		.pcs_ops = &mt7531_pcs_ops,
++ 		.sw_setup = mt7531_setup,
++ 		.phy_read = mt7531_ind_phy_read,
++ 		.phy_write = mt7531_ind_phy_write,
++ 		.pad_setup = mt7531_pad_setup,
++ 		.cpu_port_config = mt7531_cpu_port_config,
++ 		.mac_port_get_caps = mt7531_mac_port_get_caps,
++-		.mac_port_get_state = mt7531_phylink_mac_link_state,
++ 		.mac_port_config = mt7531_mac_config,
++-		.mac_pcs_an_restart = mt7531_sgmii_restart_an,
++-		.mac_pcs_link_up = mt7531_sgmii_link_up_force,
++ 	},
++ };
++ 
++@@ -3185,7 +3213,7 @@ mt7530_probe(struct mdio_device *mdiodev
++ 	if (!priv->info->sw_setup || !priv->info->pad_setup ||
++ 	    !priv->info->phy_read || !priv->info->phy_write ||
++ 	    !priv->info->mac_port_get_caps ||
++-	    !priv->info->mac_port_get_state || !priv->info->mac_port_config)
+++	    !priv->info->mac_port_config)
++ 		return -EINVAL;
++ 
++ 	priv->id = priv->info->id;
++--- a/drivers/net/dsa/mt7530.h
+++++ b/drivers/net/dsa/mt7530.h
++@@ -741,6 +741,12 @@ static const char *p5_intf_modes(unsigne
++ 
++ struct mt7530_priv;
++ 
+++struct mt753x_pcs {
+++	struct phylink_pcs pcs;
+++	struct mt7530_priv *priv;
+++	int port;
+++};
+++
++ /* struct mt753x_info -	This is the main data structure for holding the specific
++  *			part for each supported device
++  * @sw_setup:		Holding the handler to a device initialization
++@@ -752,18 +758,14 @@ struct mt7530_priv;
++  *			port
++  * @mac_port_validate:	Holding the way to set addition validate type for a
++  *			certan MAC port
++- * @mac_port_get_state: Holding the way getting the MAC/PCS state for a certain
++- *			MAC port
++  * @mac_port_config:	Holding the way setting up the PHY attribute to a
++  *			certain MAC port
++- * @mac_pcs_an_restart	Holding the way restarting PCS autonegotiation for a
++- *			certain MAC port
++- * @mac_pcs_link_up:	Holding the way setting up the PHY attribute to the pcs
++- *			of the certain MAC port
++  */
++ struct mt753x_info {
++ 	enum mt753x_id id;
++ 
+++	const struct phylink_pcs_ops *pcs_ops;
+++
++ 	int (*sw_setup)(struct dsa_switch *ds);
++ 	int (*phy_read)(struct mt7530_priv *priv, int port, int regnum);
++ 	int (*phy_write)(struct mt7530_priv *priv, int port, int regnum, u16 val);
++@@ -774,15 +776,9 @@ struct mt753x_info {
++ 	void (*mac_port_validate)(struct dsa_switch *ds, int port,
++ 				  phy_interface_t interface,
++ 				  unsigned long *supported);
++-	int (*mac_port_get_state)(struct dsa_switch *ds, int port,
++-				  struct phylink_link_state *state);
++ 	int (*mac_port_config)(struct dsa_switch *ds, int port,
++ 			       unsigned int mode,
++ 			       phy_interface_t interface);
++-	void (*mac_pcs_an_restart)(struct dsa_switch *ds, int port);
++-	void (*mac_pcs_link_up)(struct dsa_switch *ds, int port,
++-				unsigned int mode, phy_interface_t interface,
++-				int speed, int duplex);
++ };
++ 
++ /* struct mt7530_priv -	This is the main data structure for holding the state
++@@ -824,6 +820,7 @@ struct mt7530_priv {
++ 	u8			mirror_tx;
++ 
++ 	struct mt7530_port	ports[MT7530_NUM_PORTS];
+++	struct mt753x_pcs	pcs[MT7530_NUM_PORTS];
++ 	/* protect among processes for registers access*/
++ 	struct mutex reg_mutex;
++ 	int irq;
+diff --git a/target/linux/generic/backport-5.15/705-08-v5.19-net-dsa-mt7530-move-autoneg-handling-to-PCS-validati.patch b/target/linux/generic/backport-5.15/705-08-v5.19-net-dsa-mt7530-move-autoneg-handling-to-PCS-validati.patch
+new file mode 100644
+index 0000000000..2d57bc7a03
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/705-08-v5.19-net-dsa-mt7530-move-autoneg-handling-to-PCS-validati.patch
+@@ -0,0 +1,80 @@
++From 2b0ee6768f3ac09072e5fd60b36580924e1cfa1c Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Mon, 11 Apr 2022 10:46:32 +0100
++Subject: [PATCH 08/13] net: dsa: mt7530: move autoneg handling to PCS
++ validation
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++Move the autoneg bit handling to the PCS validation, which allows us to
++get rid of mt753x_phylink_validate() and rely on the default
++phylink_generic_validate() implementation for the MAC side.
++
++Tested-by: Marek Behún <kabel@kernel.org>
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Paolo Abeni <pabeni@redhat.com>
++---
++ drivers/net/dsa/mt7530.c | 28 ++++++++++------------------
++ 1 file changed, 10 insertions(+), 18 deletions(-)
++
++--- a/drivers/net/dsa/mt7530.c
+++++ b/drivers/net/dsa/mt7530.c
++@@ -2899,25 +2899,16 @@ static void mt753x_phylink_get_caps(stru
++ 	priv->info->mac_port_get_caps(ds, port, config);
++ }
++ 
++-static void
++-mt753x_phylink_validate(struct dsa_switch *ds, int port,
++-			unsigned long *supported,
++-			struct phylink_link_state *state)
++-{
++-	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
++-	u32 caps;
++-
++-	caps = dsa_to_port(ds, port)->pl_config.mac_capabilities;
++-
++-	phylink_set_port_modes(mask);
++-	phylink_get_linkmodes(mask, state->interface, caps);
+++static int mt753x_pcs_validate(struct phylink_pcs *pcs,
+++			       unsigned long *supported,
+++			       const struct phylink_link_state *state)
+++{
+++	/* Autonegotiation is not supported in TRGMII nor 802.3z modes */
+++	if (state->interface == PHY_INTERFACE_MODE_TRGMII ||
+++	    phy_interface_mode_is_8023z(state->interface))
+++		phylink_clear(supported, Autoneg);
++ 
++-	if (state->interface != PHY_INTERFACE_MODE_TRGMII &&
++-	    !phy_interface_mode_is_8023z(state->interface))
++-		phylink_set(mask, Autoneg);
++-
++-	linkmode_and(supported, supported, mask);
++-	linkmode_and(state->advertising, state->advertising, mask);
+++	return 0;
++ }
++ 
++ static void mt7530_pcs_get_state(struct phylink_pcs *pcs,
++@@ -3019,12 +3010,14 @@ static void mt7530_pcs_an_restart(struct
++ }
++ 
++ static const struct phylink_pcs_ops mt7530_pcs_ops = {
+++	.pcs_validate = mt753x_pcs_validate,
++ 	.pcs_get_state = mt7530_pcs_get_state,
++ 	.pcs_config = mt753x_pcs_config,
++ 	.pcs_an_restart = mt7530_pcs_an_restart,
++ };
++ 
++ static const struct phylink_pcs_ops mt7531_pcs_ops = {
+++	.pcs_validate = mt753x_pcs_validate,
++ 	.pcs_get_state = mt7531_pcs_get_state,
++ 	.pcs_config = mt753x_pcs_config,
++ 	.pcs_an_restart = mt7531_pcs_an_restart,
++@@ -3116,7 +3109,6 @@ static const struct dsa_switch_ops mt753
++ 	.port_mirror_add	= mt753x_port_mirror_add,
++ 	.port_mirror_del	= mt753x_port_mirror_del,
++ 	.phylink_get_caps	= mt753x_phylink_get_caps,
++-	.phylink_validate	= mt753x_phylink_validate,
++ 	.phylink_mac_select_pcs	= mt753x_phylink_mac_select_pcs,
++ 	.phylink_mac_config	= mt753x_phylink_mac_config,
++ 	.phylink_mac_link_down	= mt753x_phylink_mac_link_down,
+diff --git a/target/linux/generic/backport-5.15/705-09-v5.19-net-dsa-mt7530-mark-as-non-legacy.patch b/target/linux/generic/backport-5.15/705-09-v5.19-net-dsa-mt7530-mark-as-non-legacy.patch
+new file mode 100644
+index 0000000000..bddc984a1f
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/705-09-v5.19-net-dsa-mt7530-mark-as-non-legacy.patch
+@@ -0,0 +1,34 @@
++From 5bc26de9bfaa6bb5539c09d4435dced98f429cfc Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Mon, 11 Apr 2022 10:46:37 +0100
++Subject: [PATCH 09/13] net: dsa: mt7530: mark as non-legacy
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++The mt7530 driver does not make use of the speed, duplex, pause or
++advertisement in its phylink_mac_config() implementation, so it can be
++marked as a non-legacy driver.
++
++Tested-by: Marek Behún <kabel@kernel.org>
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Paolo Abeni <pabeni@redhat.com>
++---
++ drivers/net/dsa/mt7530.c | 6 ++++++
++ 1 file changed, 6 insertions(+)
++
++--- a/drivers/net/dsa/mt7530.c
+++++ b/drivers/net/dsa/mt7530.c
++@@ -2896,6 +2896,12 @@ static void mt753x_phylink_get_caps(stru
++ 	config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
++ 				   MAC_10 | MAC_100 | MAC_1000FD;
++ 
+++	/* This driver does not make use of the speed, duplex, pause or the
+++	 * advertisement in its mac_config, so it is safe to mark this driver
+++	 * as non-legacy.
+++	 */
+++	config->legacy_pre_march2020 = false;
+++
++ 	priv->info->mac_port_get_caps(ds, port, config);
++ }
++ 
+diff --git a/target/linux/generic/backport-5.15/705-10-v5.19-net-dsa-mt753x-fix-pcs-conversion-regression.patch b/target/linux/generic/backport-5.15/705-10-v5.19-net-dsa-mt753x-fix-pcs-conversion-regression.patch
+new file mode 100644
+index 0000000000..dcf946120e
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/705-10-v5.19-net-dsa-mt753x-fix-pcs-conversion-regression.patch
+@@ -0,0 +1,116 @@
++From 1f15b5e8733115cee65342bcaafeaf0368809fae Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Mon, 25 Apr 2022 22:28:02 +0100
++Subject: [PATCH 10/13] net: dsa: mt753x: fix pcs conversion regression
++
++Daniel Golle reports that the conversion of mt753x to phylink PCS caused
++an oops as below.
++
++The problem is with the placement of the PCS initialisation, which
++occurs after mt7531_setup() has been called. However, burited in this
++function is a call to setup the CPU port, which requires the PCS
++structure to be already setup.
++
++Fix this by changing the initialisation order.
++
++Unable to handle kernel NULL pointer dereference at virtual address 0000000000000020
++Mem abort info:
++  ESR = 0x96000005
++  EC = 0x25: DABT (current EL), IL = 32 bits
++  SET = 0, FnV = 0
++  EA = 0, S1PTW = 0
++  FSC = 0x05: level 1 translation fault
++Data abort info:
++  ISV = 0, ISS = 0x00000005
++  CM = 0, WnR = 0
++user pgtable: 4k pages, 39-bit VAs, pgdp=0000000046057000
++[0000000000000020] pgd=0000000000000000, p4d=0000000000000000, pud=0000000000000000
++Internal error: Oops: 96000005 [#1] SMP
++Modules linked in:
++CPU: 0 PID: 32 Comm: kworker/u4:1 Tainted: G S 5.18.0-rc3-next-20220422+ #0
++Hardware name: Bananapi BPI-R64 (DT)
++Workqueue: events_unbound deferred_probe_work_func
++pstate: 60000005 (nZCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
++pc : mt7531_cpu_port_config+0xcc/0x1b0
++lr : mt7531_cpu_port_config+0xc0/0x1b0
++sp : ffffffc008d5b980
++x29: ffffffc008d5b990 x28: ffffff80060562c8 x27: 00000000f805633b
++x26: ffffff80001a8880 x25: 00000000000009c4 x24: 0000000000000016
++x23: ffffff8005eb6470 x22: 0000000000003600 x21: ffffff8006948080
++x20: 0000000000000000 x19: 0000000000000006 x18: 0000000000000000
++x17: 0000000000000001 x16: 0000000000000001 x15: 02963607fcee069e
++x14: 0000000000000000 x13: 0000000000000030 x12: 0101010101010101
++x11: ffffffc037302000 x10: 0000000000000870 x9 : ffffffc008d5b800
++x8 : ffffff800028f950 x7 : 0000000000000001 x6 : 00000000662b3000
++x5 : 00000000000002f0 x4 : 0000000000000000 x3 : ffffff800028f080
++x2 : 0000000000000000 x1 : ffffff800028f080 x0 : 0000000000000000
++Call trace:
++ mt7531_cpu_port_config+0xcc/0x1b0
++ mt753x_cpu_port_enable+0x24/0x1f0
++ mt7531_setup+0x49c/0x5c0
++ mt753x_setup+0x20/0x31c
++ dsa_register_switch+0x8bc/0x1020
++ mt7530_probe+0x118/0x200
++ mdio_probe+0x30/0x64
++ really_probe.part.0+0x98/0x280
++ __driver_probe_device+0x94/0x140
++ driver_probe_device+0x40/0x114
++ __device_attach_driver+0xb0/0x10c
++ bus_for_each_drv+0x64/0xa0
++ __device_attach+0xa8/0x16c
++ device_initial_probe+0x10/0x20
++ bus_probe_device+0x94/0x9c
++ deferred_probe_work_func+0x80/0xb4
++ process_one_work+0x200/0x3a0
++ worker_thread+0x260/0x4c0
++ kthread+0xd4/0xe0
++ ret_from_fork+0x10/0x20
++Code: 9409e911 937b7e60 8b0002a0 f9405800 (f9401005)
++---[ end trace 0000000000000000 ]---
++
++Reported-by: Daniel Golle <daniel@makrotopia.org>
++Tested-by: Daniel Golle <daniel@makrotopia.org>
++Fixes: cbd1f243bc41 ("net: dsa: mt7530: partially convert to phylink_pcs")
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Link: https://lore.kernel.org/r/E1nj6FW-007WZB-5Y@rmk-PC.armlinux.org.uk
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/dsa/mt7530.c | 18 +++++++++---------
++ 1 file changed, 9 insertions(+), 9 deletions(-)
++
++--- a/drivers/net/dsa/mt7530.c
+++++ b/drivers/net/dsa/mt7530.c
++@@ -3034,9 +3034,16 @@ static int
++ mt753x_setup(struct dsa_switch *ds)
++ {
++ 	struct mt7530_priv *priv = ds->priv;
++-	int ret = priv->info->sw_setup(ds);
++-	int i;
+++	int i, ret;
++ 
+++	/* Initialise the PCS devices */
+++	for (i = 0; i < priv->ds->num_ports; i++) {
+++		priv->pcs[i].pcs.ops = priv->info->pcs_ops;
+++		priv->pcs[i].priv = priv;
+++		priv->pcs[i].port = i;
+++	}
+++
+++	ret = priv->info->sw_setup(ds);
++ 	if (ret)
++ 		return ret;
++ 
++@@ -3048,13 +3055,6 @@ mt753x_setup(struct dsa_switch *ds)
++ 	if (ret && priv->irq)
++ 		mt7530_free_irq_common(priv);
++ 
++-	/* Initialise the PCS devices */
++-	for (i = 0; i < priv->ds->num_ports; i++) {
++-		priv->pcs[i].pcs.ops = priv->info->pcs_ops;
++-		priv->pcs[i].priv = priv;
++-		priv->pcs[i].port = i;
++-	}
++-
++ 	return ret;
++ }
++ 
+diff --git a/target/linux/generic/backport-5.15/705-11-v6.0-net-dsa-mt7530-rework-mt7530_hw_vlan_-add-del.patch b/target/linux/generic/backport-5.15/705-11-v6.0-net-dsa-mt7530-rework-mt7530_hw_vlan_-add-del.patch
+new file mode 100644
+index 0000000000..5fd109329c
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/705-11-v6.0-net-dsa-mt7530-rework-mt7530_hw_vlan_-add-del.patch
+@@ -0,0 +1,87 @@
++From e26be16262e1fc1e9f1798c12762663bd9c265c6 Mon Sep 17 00:00:00 2001
++From: Frank Wunderlich <frank-w@public-files.de>
++Date: Fri, 10 Jun 2022 19:05:37 +0200
++Subject: [PATCH 11/13] net: dsa: mt7530: rework mt7530_hw_vlan_{add,del}
++
++Rework vlan_add/vlan_del functions in preparation for dynamic cpu port.
++
++Currently BIT(MT7530_CPU_PORT) is added to new_members, even though
++mt7530_port_vlan_add() will be called on the CPU port too.
++
++Let DSA core decide when to call port_vlan_add for the CPU port, rather
++than doing it implicitly.
++
++We can do autonomous forwarding in a certain VLAN, but not add br0 to that
++VLAN and avoid flooding the CPU with those packets, if software knows it
++doesn't need to process them.
++
++Suggested-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: Frank Wunderlich <frank-w@public-files.de>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/dsa/mt7530.c | 30 ++++++++++++------------------
++ 1 file changed, 12 insertions(+), 18 deletions(-)
++
++--- a/drivers/net/dsa/mt7530.c
+++++ b/drivers/net/dsa/mt7530.c
++@@ -1525,11 +1525,11 @@ static void
++ mt7530_hw_vlan_add(struct mt7530_priv *priv,
++ 		   struct mt7530_hw_vlan_entry *entry)
++ {
+++	struct dsa_port *dp = dsa_to_port(priv->ds, entry->port);
++ 	u8 new_members;
++ 	u32 val;
++ 
++-	new_members = entry->old_members | BIT(entry->port) |
++-		      BIT(MT7530_CPU_PORT);
+++	new_members = entry->old_members | BIT(entry->port);
++ 
++ 	/* Validate the entry with independent learning, create egress tag per
++ 	 * VLAN and joining the port as one of the port members.
++@@ -1540,22 +1540,20 @@ mt7530_hw_vlan_add(struct mt7530_priv *p
++ 
++ 	/* Decide whether adding tag or not for those outgoing packets from the
++ 	 * port inside the VLAN.
++-	 */
++-	val = entry->untagged ? MT7530_VLAN_EGRESS_UNTAG :
++-				MT7530_VLAN_EGRESS_TAG;
++-	mt7530_rmw(priv, MT7530_VAWD2,
++-		   ETAG_CTRL_P_MASK(entry->port),
++-		   ETAG_CTRL_P(entry->port, val));
++-
++-	/* CPU port is always taken as a tagged port for serving more than one
+++	 * CPU port is always taken as a tagged port for serving more than one
++ 	 * VLANs across and also being applied with egress type stack mode for
++ 	 * that VLAN tags would be appended after hardware special tag used as
++ 	 * DSA tag.
++ 	 */
+++	if (dsa_port_is_cpu(dp))
+++		val = MT7530_VLAN_EGRESS_STACK;
+++	else if (entry->untagged)
+++		val = MT7530_VLAN_EGRESS_UNTAG;
+++	else
+++		val = MT7530_VLAN_EGRESS_TAG;
++ 	mt7530_rmw(priv, MT7530_VAWD2,
++-		   ETAG_CTRL_P_MASK(MT7530_CPU_PORT),
++-		   ETAG_CTRL_P(MT7530_CPU_PORT,
++-			       MT7530_VLAN_EGRESS_STACK));
+++		   ETAG_CTRL_P_MASK(entry->port),
+++		   ETAG_CTRL_P(entry->port, val));
++ }
++ 
++ static void
++@@ -1574,11 +1572,7 @@ mt7530_hw_vlan_del(struct mt7530_priv *p
++ 		return;
++ 	}
++ 
++-	/* If certain member apart from CPU port is still alive in the VLAN,
++-	 * the entry would be kept valid. Otherwise, the entry is got to be
++-	 * disabled.
++-	 */
++-	if (new_members && new_members != BIT(MT7530_CPU_PORT)) {
+++	if (new_members) {
++ 		val = IVL_MAC | VTAG_EN | PORT_MEM(new_members) |
++ 		      VLAN_VALID;
++ 		mt7530_write(priv, MT7530_VAWD1, val);
+diff --git a/target/linux/generic/backport-5.15/705-12-v6.0-net-dsa-mt7530-rework-mt753-01-_setup.patch b/target/linux/generic/backport-5.15/705-12-v6.0-net-dsa-mt7530-rework-mt753-01-_setup.patch
+new file mode 100644
+index 0000000000..165d1d035f
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/705-12-v6.0-net-dsa-mt7530-rework-mt753-01-_setup.patch
+@@ -0,0 +1,75 @@
++From 1f0dfd443eea7fc3e818e96f7c8264913ba41859 Mon Sep 17 00:00:00 2001
++From: Frank Wunderlich <frank-w@public-files.de>
++Date: Fri, 10 Jun 2022 19:05:38 +0200
++Subject: [PATCH 12/13] net: dsa: mt7530: rework mt753[01]_setup
++
++Enumerate available cpu-ports instead of using hardcoded constant.
++
++Suggested-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: Frank Wunderlich <frank-w@public-files.de>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/dsa/mt7530.c | 25 +++++++++++++++++++++----
++ 1 file changed, 21 insertions(+), 4 deletions(-)
++
++--- a/drivers/net/dsa/mt7530.c
+++++ b/drivers/net/dsa/mt7530.c
++@@ -2090,11 +2090,12 @@ static int
++ mt7530_setup(struct dsa_switch *ds)
++ {
++ 	struct mt7530_priv *priv = ds->priv;
+++	struct device_node *dn = NULL;
++ 	struct device_node *phy_node;
++ 	struct device_node *mac_np;
++ 	struct mt7530_dummy_poll p;
++ 	phy_interface_t interface;
++-	struct device_node *dn;
+++	struct dsa_port *cpu_dp;
++ 	u32 id, val;
++ 	int ret, i;
++ 
++@@ -2102,7 +2103,19 @@ mt7530_setup(struct dsa_switch *ds)
++ 	 * controller also is the container for two GMACs nodes representing
++ 	 * as two netdev instances.
++ 	 */
++-	dn = dsa_to_port(ds, MT7530_CPU_PORT)->master->dev.of_node->parent;
+++	dsa_switch_for_each_cpu_port(cpu_dp, ds) {
+++		dn = cpu_dp->master->dev.of_node->parent;
+++		/* It doesn't matter which CPU port is found first,
+++		 * their masters should share the same parent OF node
+++		 */
+++		break;
+++	}
+++
+++	if (!dn) {
+++		dev_err(ds->dev, "parent OF node of DSA master not found");
+++		return -EINVAL;
+++	}
+++
++ 	ds->assisted_learning_on_cpu_port = true;
++ 	ds->mtu_enforcement_ingress = true;
++ 
++@@ -2264,6 +2277,7 @@ mt7531_setup(struct dsa_switch *ds)
++ {
++ 	struct mt7530_priv *priv = ds->priv;
++ 	struct mt7530_dummy_poll p;
+++	struct dsa_port *cpu_dp;
++ 	u32 val, id;
++ 	int ret, i;
++ 
++@@ -2338,8 +2352,11 @@ mt7531_setup(struct dsa_switch *ds)
++ 				 CORE_PLL_GROUP4, val);
++ 
++ 	/* BPDU to CPU port */
++-	mt7530_rmw(priv, MT7531_CFC, MT7531_CPU_PMAP_MASK,
++-		   BIT(MT7530_CPU_PORT));
+++	dsa_switch_for_each_cpu_port(cpu_dp, ds) {
+++		mt7530_rmw(priv, MT7531_CFC, MT7531_CPU_PMAP_MASK,
+++			   BIT(cpu_dp->index));
+++		break;
+++	}
++ 	mt7530_rmw(priv, MT753X_BPC, MT753X_BPDU_PORT_FW_MASK,
++ 		   MT753X_BPDU_CPU_ONLY);
++ 
+diff --git a/target/linux/generic/backport-5.15/705-13-v6.0-net-dsa-mt7530-get-cpu-port-via-dp-cpu_dp-instead-of.patch b/target/linux/generic/backport-5.15/705-13-v6.0-net-dsa-mt7530-get-cpu-port-via-dp-cpu_dp-instead-of.patch
+new file mode 100644
+index 0000000000..5af91a0f7f
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/705-13-v6.0-net-dsa-mt7530-get-cpu-port-via-dp-cpu_dp-instead-of.patch
+@@ -0,0 +1,117 @@
++From ad2606f6fafae3a7d41c4f2af5554c8f6adecbc7 Mon Sep 17 00:00:00 2001
++From: Frank Wunderlich <frank-w@public-files.de>
++Date: Fri, 10 Jun 2022 19:05:39 +0200
++Subject: [PATCH 13/13] net: dsa: mt7530: get cpu-port via dp->cpu_dp instead
++ of constant
++
++Replace last occurences of hardcoded cpu-port by cpu_dp member of
++dsa_port struct.
++
++Now the constant can be dropped.
++
++Suggested-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: Frank Wunderlich <frank-w@public-files.de>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/dsa/mt7530.c | 27 ++++++++++++++++++++-------
++ drivers/net/dsa/mt7530.h |  1 -
++ 2 files changed, 20 insertions(+), 8 deletions(-)
++
++--- a/drivers/net/dsa/mt7530.c
+++++ b/drivers/net/dsa/mt7530.c
++@@ -1041,6 +1041,7 @@ static int
++ mt7530_port_enable(struct dsa_switch *ds, int port,
++ 		   struct phy_device *phy)
++ {
+++	struct dsa_port *dp = dsa_to_port(ds, port);
++ 	struct mt7530_priv *priv = ds->priv;
++ 
++ 	mutex_lock(&priv->reg_mutex);
++@@ -1049,7 +1050,11 @@ mt7530_port_enable(struct dsa_switch *ds
++ 	 * restore the port matrix if the port is the member of a certain
++ 	 * bridge.
++ 	 */
++-	priv->ports[port].pm |= PCR_MATRIX(BIT(MT7530_CPU_PORT));
+++	if (dsa_port_is_user(dp)) {
+++		struct dsa_port *cpu_dp = dp->cpu_dp;
+++
+++		priv->ports[port].pm |= PCR_MATRIX(BIT(cpu_dp->index));
+++	}
++ 	priv->ports[port].enable = true;
++ 	mt7530_rmw(priv, MT7530_PCR_P(port), PCR_MATRIX_MASK,
++ 		   priv->ports[port].pm);
++@@ -1197,7 +1202,8 @@ mt7530_port_bridge_join(struct dsa_switc
++ 			struct net_device *bridge)
++ {
++ 	struct dsa_port *dp = dsa_to_port(ds, port), *other_dp;
++-	u32 port_bitmap = BIT(MT7530_CPU_PORT);
+++	struct dsa_port *cpu_dp = dp->cpu_dp;
+++	u32 port_bitmap = BIT(cpu_dp->index);
++ 	struct mt7530_priv *priv = ds->priv;
++ 
++ 	mutex_lock(&priv->reg_mutex);
++@@ -1274,9 +1280,12 @@ mt7530_port_set_vlan_unaware(struct dsa_
++ 	 * the CPU port get out of VLAN filtering mode.
++ 	 */
++ 	if (all_user_ports_removed) {
++-		mt7530_write(priv, MT7530_PCR_P(MT7530_CPU_PORT),
+++		struct dsa_port *dp = dsa_to_port(ds, port);
+++		struct dsa_port *cpu_dp = dp->cpu_dp;
+++
+++		mt7530_write(priv, MT7530_PCR_P(cpu_dp->index),
++ 			     PCR_MATRIX(dsa_user_ports(priv->ds)));
++-		mt7530_write(priv, MT7530_PVC_P(MT7530_CPU_PORT), PORT_SPEC_TAG
+++		mt7530_write(priv, MT7530_PVC_P(cpu_dp->index), PORT_SPEC_TAG
++ 			     | PVC_EG_TAG(MT7530_VLAN_EG_CONSISTENT));
++ 	}
++ }
++@@ -1314,6 +1323,7 @@ mt7530_port_bridge_leave(struct dsa_swit
++ 			 struct net_device *bridge)
++ {
++ 	struct dsa_port *dp = dsa_to_port(ds, port), *other_dp;
+++	struct dsa_port *cpu_dp = dp->cpu_dp;
++ 	struct mt7530_priv *priv = ds->priv;
++ 
++ 	mutex_lock(&priv->reg_mutex);
++@@ -1342,8 +1352,8 @@ mt7530_port_bridge_leave(struct dsa_swit
++ 	 */
++ 	if (priv->ports[port].enable)
++ 		mt7530_rmw(priv, MT7530_PCR_P(port), PCR_MATRIX_MASK,
++-			   PCR_MATRIX(BIT(MT7530_CPU_PORT)));
++-	priv->ports[port].pm = PCR_MATRIX(BIT(MT7530_CPU_PORT));
+++			   PCR_MATRIX(BIT(cpu_dp->index)));
+++	priv->ports[port].pm = PCR_MATRIX(BIT(cpu_dp->index));
++ 
++ 	/* When a port is removed from the bridge, the port would be set up
++ 	 * back to the default as is at initial boot which is a VLAN-unaware
++@@ -1506,6 +1516,9 @@ static int
++ mt7530_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
++ 			   struct netlink_ext_ack *extack)
++ {
+++	struct dsa_port *dp = dsa_to_port(ds, port);
+++	struct dsa_port *cpu_dp = dp->cpu_dp;
+++
++ 	if (vlan_filtering) {
++ 		/* The port is being kept as VLAN-unaware port when bridge is
++ 		 * set up with vlan_filtering not being set, Otherwise, the
++@@ -1513,7 +1526,7 @@ mt7530_port_vlan_filtering(struct dsa_sw
++ 		 * for becoming a VLAN-aware port.
++ 		 */
++ 		mt7530_port_set_vlan_aware(ds, port);
++-		mt7530_port_set_vlan_aware(ds, MT7530_CPU_PORT);
+++		mt7530_port_set_vlan_aware(ds, cpu_dp->index);
++ 	} else {
++ 		mt7530_port_set_vlan_unaware(ds, port);
++ 	}
++--- a/drivers/net/dsa/mt7530.h
+++++ b/drivers/net/dsa/mt7530.h
++@@ -8,7 +8,6 @@
++ 
++ #define MT7530_NUM_PORTS		7
++ #define MT7530_NUM_PHYS			5
++-#define MT7530_CPU_PORT			6
++ #define MT7530_NUM_FDB_RECORDS		2048
++ #define MT7530_ALL_MEMBERS		0xff
++ 
+diff --git a/target/linux/generic/backport-5.15/706-00-v6.0-net-ethernet-mtk_eth_soc-rely-on-page_pool-for-singl.patch b/target/linux/generic/backport-5.15/706-00-v6.0-net-ethernet-mtk_eth_soc-rely-on-page_pool-for-singl.patch
+new file mode 100644
+index 0000000000..f9aa6fb67b
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/706-00-v6.0-net-ethernet-mtk_eth_soc-rely-on-page_pool-for-singl.patch
+@@ -0,0 +1,330 @@
++From 23233e577ef973c2c5d0dd757a0a4605e34ecb57 Mon Sep 17 00:00:00 2001
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 22 Jul 2022 09:19:36 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: rely on page_pool for single page
++ buffers
++
++Rely on page_pool allocator for single page buffers in order to keep
++them dma mapped and add skb recycling support.
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/mediatek/Kconfig       |   1 +
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 185 +++++++++++++++-----
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h |  10 ++
++ 3 files changed, 156 insertions(+), 40 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/Kconfig
+++++ b/drivers/net/ethernet/mediatek/Kconfig
++@@ -16,6 +16,7 @@ config NET_MEDIATEK_SOC
++ 	depends on NET_DSA || !NET_DSA
++ 	select PHYLINK
++ 	select DIMLIB
+++	select PAGE_POOL
++ 	help
++ 	  This driver supports the gigabit ethernet MACs in the
++ 	  MediaTek SoC family.
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -1388,6 +1388,68 @@ static void mtk_update_rx_cpu_idx(struct
++ 	}
++ }
++ 
+++static struct page_pool *mtk_create_page_pool(struct mtk_eth *eth,
+++					      struct xdp_rxq_info *xdp_q,
+++					      int id, int size)
+++{
+++	struct page_pool_params pp_params = {
+++		.order = 0,
+++		.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
+++		.pool_size = size,
+++		.nid = NUMA_NO_NODE,
+++		.dev = eth->dma_dev,
+++		.dma_dir = DMA_FROM_DEVICE,
+++		.offset = MTK_PP_HEADROOM,
+++		.max_len = MTK_PP_MAX_BUF_SIZE,
+++	};
+++	struct page_pool *pp;
+++	int err;
+++
+++	pp = page_pool_create(&pp_params);
+++	if (IS_ERR(pp))
+++		return pp;
+++
+++	err = xdp_rxq_info_reg(xdp_q, &eth->dummy_dev, id,
+++				eth->rx_napi.napi_id);
+++	if (err < 0)
+++		goto err_free_pp;
+++
+++	err = xdp_rxq_info_reg_mem_model(xdp_q, MEM_TYPE_PAGE_POOL, pp);
+++	if (err)
+++		goto err_unregister_rxq;
+++
+++	return pp;
+++
+++err_unregister_rxq:
+++	xdp_rxq_info_unreg(xdp_q);
+++err_free_pp:
+++	page_pool_destroy(pp);
+++
+++	return ERR_PTR(err);
+++}
+++
+++static void *mtk_page_pool_get_buff(struct page_pool *pp, dma_addr_t *dma_addr,
+++				    gfp_t gfp_mask)
+++{
+++	struct page *page;
+++
+++	page = page_pool_alloc_pages(pp, gfp_mask | __GFP_NOWARN);
+++	if (!page)
+++		return NULL;
+++
+++	*dma_addr = page_pool_get_dma_addr(page) + MTK_PP_HEADROOM;
+++	return page_address(page);
+++}
+++
+++static void mtk_rx_put_buff(struct mtk_rx_ring *ring, void *data, bool napi)
+++{
+++	if (ring->page_pool)
+++		page_pool_put_full_page(ring->page_pool,
+++					virt_to_head_page(data), napi);
+++	else
+++		skb_free_frag(data);
+++}
+++
++ static int mtk_poll_rx(struct napi_struct *napi, int budget,
++ 		       struct mtk_eth *eth)
++ {
++@@ -1401,9 +1463,9 @@ static int mtk_poll_rx(struct napi_struc
++ 
++ 	while (done < budget) {
++ 		unsigned int pktlen, *rxdcsum;
+++		u32 hash, reason, reserve_len;
++ 		struct net_device *netdev;
++ 		dma_addr_t dma_addr;
++-		u32 hash, reason;
++ 		int mac = 0;
++ 
++ 		ring = mtk_get_rx_ring(eth);
++@@ -1434,36 +1496,54 @@ static int mtk_poll_rx(struct napi_struc
++ 			goto release_desc;
++ 
++ 		/* alloc new buffer */
++-		if (ring->frag_size <= PAGE_SIZE)
++-			new_data = napi_alloc_frag(ring->frag_size);
++-		else
++-			new_data = mtk_max_lro_buf_alloc(GFP_ATOMIC);
++-		if (unlikely(!new_data)) {
++-			netdev->stats.rx_dropped++;
++-			goto release_desc;
++-		}
++-		dma_addr = dma_map_single(eth->dma_dev,
++-					  new_data + NET_SKB_PAD +
++-					  eth->ip_align,
++-					  ring->buf_size,
++-					  DMA_FROM_DEVICE);
++-		if (unlikely(dma_mapping_error(eth->dma_dev, dma_addr))) {
++-			skb_free_frag(new_data);
++-			netdev->stats.rx_dropped++;
++-			goto release_desc;
++-		}
+++		if (ring->page_pool) {
+++			new_data = mtk_page_pool_get_buff(ring->page_pool,
+++							  &dma_addr,
+++							  GFP_ATOMIC);
+++			if (unlikely(!new_data)) {
+++				netdev->stats.rx_dropped++;
+++				goto release_desc;
+++			}
+++		} else {
+++			if (ring->frag_size <= PAGE_SIZE)
+++				new_data = napi_alloc_frag(ring->frag_size);
+++			else
+++				new_data = mtk_max_lro_buf_alloc(GFP_ATOMIC);
+++
+++			if (unlikely(!new_data)) {
+++				netdev->stats.rx_dropped++;
+++				goto release_desc;
+++			}
++ 
++-		dma_unmap_single(eth->dma_dev, trxd.rxd1,
++-				 ring->buf_size, DMA_FROM_DEVICE);
+++			dma_addr = dma_map_single(eth->dma_dev,
+++				new_data + NET_SKB_PAD + eth->ip_align,
+++				ring->buf_size, DMA_FROM_DEVICE);
+++			if (unlikely(dma_mapping_error(eth->dma_dev,
+++						       dma_addr))) {
+++				skb_free_frag(new_data);
+++				netdev->stats.rx_dropped++;
+++				goto release_desc;
+++			}
+++
+++			dma_unmap_single(eth->dma_dev, trxd.rxd1,
+++					 ring->buf_size, DMA_FROM_DEVICE);
+++		}
++ 
++ 		/* receive data */
++ 		skb = build_skb(data, ring->frag_size);
++ 		if (unlikely(!skb)) {
++-			skb_free_frag(data);
+++			mtk_rx_put_buff(ring, data, true);
++ 			netdev->stats.rx_dropped++;
++ 			goto skip_rx;
++ 		}
++-		skb_reserve(skb, NET_SKB_PAD + NET_IP_ALIGN);
+++
+++		if (ring->page_pool) {
+++			reserve_len = MTK_PP_HEADROOM;
+++			skb_mark_for_recycle(skb);
+++		} else {
+++			reserve_len = NET_SKB_PAD + NET_IP_ALIGN;
+++		}
+++		skb_reserve(skb, reserve_len);
++ 
++ 		pktlen = RX_DMA_GET_PLEN0(trxd.rxd2);
++ 		skb->dev = netdev;
++@@ -1517,7 +1597,6 @@ static int mtk_poll_rx(struct napi_struc
++ skip_rx:
++ 		ring->data[idx] = new_data;
++ 		rxd->rxd1 = (unsigned int)dma_addr;
++-
++ release_desc:
++ 		if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628))
++ 			rxd->rxd2 = RX_DMA_LSO;
++@@ -1525,7 +1604,6 @@ release_desc:
++ 			rxd->rxd2 = RX_DMA_PREP_PLEN0(ring->buf_size);
++ 
++ 		ring->calc_idx = idx;
++-
++ 		done++;
++ 	}
++ 
++@@ -1889,13 +1967,15 @@ static int mtk_rx_alloc(struct mtk_eth *
++ 	if (!ring->data)
++ 		return -ENOMEM;
++ 
++-	for (i = 0; i < rx_dma_size; i++) {
++-		if (ring->frag_size <= PAGE_SIZE)
++-			ring->data[i] = netdev_alloc_frag(ring->frag_size);
++-		else
++-			ring->data[i] = mtk_max_lro_buf_alloc(GFP_KERNEL);
++-		if (!ring->data[i])
++-			return -ENOMEM;
+++	if (!eth->hwlro) {
+++		struct page_pool *pp;
+++
+++		pp = mtk_create_page_pool(eth, &ring->xdp_q, ring_no,
+++					  rx_dma_size);
+++		if (IS_ERR(pp))
+++			return PTR_ERR(pp);
+++
+++		ring->page_pool = pp;
++ 	}
++ 
++ 	ring->dma = dma_alloc_coherent(eth->dma_dev,
++@@ -1906,16 +1986,33 @@ static int mtk_rx_alloc(struct mtk_eth *
++ 
++ 	for (i = 0; i < rx_dma_size; i++) {
++ 		struct mtk_rx_dma_v2 *rxd;
++-
++-		dma_addr_t dma_addr = dma_map_single(eth->dma_dev,
++-				ring->data[i] + NET_SKB_PAD + eth->ip_align,
++-				ring->buf_size,
++-				DMA_FROM_DEVICE);
++-		if (unlikely(dma_mapping_error(eth->dma_dev, dma_addr)))
++-			return -ENOMEM;
+++		dma_addr_t dma_addr;
+++		void *data;
++ 
++ 		rxd = ring->dma + i * eth->soc->txrx.rxd_size;
+++		if (ring->page_pool) {
+++			data = mtk_page_pool_get_buff(ring->page_pool,
+++						      &dma_addr, GFP_KERNEL);
+++			if (!data)
+++				return -ENOMEM;
+++		} else {
+++			if (ring->frag_size <= PAGE_SIZE)
+++				data = netdev_alloc_frag(ring->frag_size);
+++			else
+++				data = mtk_max_lro_buf_alloc(GFP_KERNEL);
+++
+++			if (!data)
+++				return -ENOMEM;
+++
+++			dma_addr = dma_map_single(eth->dma_dev,
+++				data + NET_SKB_PAD + eth->ip_align,
+++				ring->buf_size, DMA_FROM_DEVICE);
+++			if (unlikely(dma_mapping_error(eth->dma_dev,
+++						       dma_addr)))
+++				return -ENOMEM;
+++		}
++ 		rxd->rxd1 = (unsigned int)dma_addr;
+++		ring->data[i] = data;
++ 
++ 		if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628))
++ 			rxd->rxd2 = RX_DMA_LSO;
++@@ -1931,6 +2028,7 @@ static int mtk_rx_alloc(struct mtk_eth *
++ 			rxd->rxd8 = 0;
++ 		}
++ 	}
+++
++ 	ring->dma_size = rx_dma_size;
++ 	ring->calc_idx_update = false;
++ 	ring->calc_idx = rx_dma_size - 1;
++@@ -1982,7 +2080,7 @@ static void mtk_rx_clean(struct mtk_eth
++ 
++ 			dma_unmap_single(eth->dma_dev, rxd->rxd1,
++ 					 ring->buf_size, DMA_FROM_DEVICE);
++-			skb_free_frag(ring->data[i]);
+++			mtk_rx_put_buff(ring, ring->data[i], false);
++ 		}
++ 		kfree(ring->data);
++ 		ring->data = NULL;
++@@ -1994,6 +2092,13 @@ static void mtk_rx_clean(struct mtk_eth
++ 				  ring->dma, ring->phys);
++ 		ring->dma = NULL;
++ 	}
+++
+++	if (ring->page_pool) {
+++		if (xdp_rxq_info_is_reg(&ring->xdp_q))
+++			xdp_rxq_info_unreg(&ring->xdp_q);
+++		page_pool_destroy(ring->page_pool);
+++		ring->page_pool = NULL;
+++	}
++ }
++ 
++ static int mtk_hwlro_rx_init(struct mtk_eth *eth)
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -18,6 +18,8 @@
++ #include <linux/rhashtable.h>
++ #include <linux/dim.h>
++ #include <linux/bitfield.h>
+++#include <net/page_pool.h>
+++#include <linux/bpf_trace.h>
++ #include "mtk_ppe.h"
++ 
++ #define MTK_QDMA_PAGE_SIZE	2048
++@@ -49,6 +51,11 @@
++ #define MTK_HW_FEATURES_MT7628	(NETIF_F_SG | NETIF_F_RXCSUM)
++ #define NEXT_DESP_IDX(X, Y)	(((X) + 1) & ((Y) - 1))
++ 
+++#define MTK_PP_HEADROOM		XDP_PACKET_HEADROOM
+++#define MTK_PP_PAD		(MTK_PP_HEADROOM + \
+++				 SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))
+++#define MTK_PP_MAX_BUF_SIZE	(PAGE_SIZE - MTK_PP_PAD)
+++
++ #define MTK_QRX_OFFSET		0x10
++ 
++ #define MTK_MAX_RX_RING_NUM	4
++@@ -742,6 +749,9 @@ struct mtk_rx_ring {
++ 	bool calc_idx_update;
++ 	u16 calc_idx;
++ 	u32 crx_idx_reg;
+++	/* page_pool */
+++	struct page_pool *page_pool;
+++	struct xdp_rxq_info xdp_q;
++ };
++ 
++ enum mkt_eth_capabilities {
+diff --git a/target/linux/generic/backport-5.15/706-01-v6.0-net-ethernet-mtk_eth_soc-add-basic-XDP-support.patch b/target/linux/generic/backport-5.15/706-01-v6.0-net-ethernet-mtk_eth_soc-add-basic-XDP-support.patch
+new file mode 100644
+index 0000000000..b930152dd1
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/706-01-v6.0-net-ethernet-mtk_eth_soc-add-basic-XDP-support.patch
+@@ -0,0 +1,291 @@
++From 7c26c20da5d420cde55618263be4aa2f6de53056 Mon Sep 17 00:00:00 2001
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 22 Jul 2022 09:19:37 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: add basic XDP support
++
++Introduce basic XDP support to mtk_eth_soc driver.
++Supported XDP verdicts:
++- XDP_PASS
++- XDP_DROP
++- XDP_REDIRECT
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 162 +++++++++++++++++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h |   2 +
++ 2 files changed, 145 insertions(+), 19 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -1388,6 +1388,11 @@ static void mtk_update_rx_cpu_idx(struct
++ 	}
++ }
++ 
+++static bool mtk_page_pool_enabled(struct mtk_eth *eth)
+++{
+++	return !eth->hwlro;
+++}
+++
++ static struct page_pool *mtk_create_page_pool(struct mtk_eth *eth,
++ 					      struct xdp_rxq_info *xdp_q,
++ 					      int id, int size)
++@@ -1450,11 +1455,52 @@ static void mtk_rx_put_buff(struct mtk_r
++ 		skb_free_frag(data);
++ }
++ 
+++static u32 mtk_xdp_run(struct mtk_eth *eth, struct mtk_rx_ring *ring,
+++		       struct xdp_buff *xdp, struct net_device *dev)
+++{
+++	struct bpf_prog *prog;
+++	u32 act = XDP_PASS;
+++
+++	rcu_read_lock();
+++
+++	prog = rcu_dereference(eth->prog);
+++	if (!prog)
+++		goto out;
+++
+++	act = bpf_prog_run_xdp(prog, xdp);
+++	switch (act) {
+++	case XDP_PASS:
+++		goto out;
+++	case XDP_REDIRECT:
+++		if (unlikely(xdp_do_redirect(dev, xdp, prog))) {
+++			act = XDP_DROP;
+++			break;
+++		}
+++		goto out;
+++	default:
+++		bpf_warn_invalid_xdp_action(act);
+++		fallthrough;
+++	case XDP_ABORTED:
+++		trace_xdp_exception(dev, prog, act);
+++		fallthrough;
+++	case XDP_DROP:
+++		break;
+++	}
+++
+++	page_pool_put_full_page(ring->page_pool,
+++				virt_to_head_page(xdp->data), true);
+++out:
+++	rcu_read_unlock();
+++
+++	return act;
+++}
+++
++ static int mtk_poll_rx(struct napi_struct *napi, int budget,
++ 		       struct mtk_eth *eth)
++ {
++ 	struct dim_sample dim_sample = {};
++ 	struct mtk_rx_ring *ring;
+++	bool xdp_flush = false;
++ 	int idx;
++ 	struct sk_buff *skb;
++ 	u8 *data, *new_data;
++@@ -1463,9 +1509,9 @@ static int mtk_poll_rx(struct napi_struc
++ 
++ 	while (done < budget) {
++ 		unsigned int pktlen, *rxdcsum;
++-		u32 hash, reason, reserve_len;
++ 		struct net_device *netdev;
++ 		dma_addr_t dma_addr;
+++		u32 hash, reason;
++ 		int mac = 0;
++ 
++ 		ring = mtk_get_rx_ring(eth);
++@@ -1495,8 +1541,14 @@ static int mtk_poll_rx(struct napi_struc
++ 		if (unlikely(test_bit(MTK_RESETTING, &eth->state)))
++ 			goto release_desc;
++ 
+++		pktlen = RX_DMA_GET_PLEN0(trxd.rxd2);
+++
++ 		/* alloc new buffer */
++ 		if (ring->page_pool) {
+++			struct page *page = virt_to_head_page(data);
+++			struct xdp_buff xdp;
+++			u32 ret;
+++
++ 			new_data = mtk_page_pool_get_buff(ring->page_pool,
++ 							  &dma_addr,
++ 							  GFP_ATOMIC);
++@@ -1504,6 +1556,34 @@ static int mtk_poll_rx(struct napi_struc
++ 				netdev->stats.rx_dropped++;
++ 				goto release_desc;
++ 			}
+++
+++			dma_sync_single_for_cpu(eth->dma_dev,
+++				page_pool_get_dma_addr(page) + MTK_PP_HEADROOM,
+++				pktlen, page_pool_get_dma_dir(ring->page_pool));
+++
+++			xdp_init_buff(&xdp, PAGE_SIZE, &ring->xdp_q);
+++			xdp_prepare_buff(&xdp, data, MTK_PP_HEADROOM, pktlen,
+++					 false);
+++			xdp_buff_clear_frags_flag(&xdp);
+++
+++			ret = mtk_xdp_run(eth, ring, &xdp, netdev);
+++			if (ret == XDP_REDIRECT)
+++				xdp_flush = true;
+++
+++			if (ret != XDP_PASS)
+++				goto skip_rx;
+++
+++			skb = build_skb(data, PAGE_SIZE);
+++			if (unlikely(!skb)) {
+++				page_pool_put_full_page(ring->page_pool,
+++							page, true);
+++				netdev->stats.rx_dropped++;
+++				goto skip_rx;
+++			}
+++
+++			skb_reserve(skb, xdp.data - xdp.data_hard_start);
+++			skb_put(skb, xdp.data_end - xdp.data);
+++			skb_mark_for_recycle(skb);
++ 		} else {
++ 			if (ring->frag_size <= PAGE_SIZE)
++ 				new_data = napi_alloc_frag(ring->frag_size);
++@@ -1527,27 +1607,20 @@ static int mtk_poll_rx(struct napi_struc
++ 
++ 			dma_unmap_single(eth->dma_dev, trxd.rxd1,
++ 					 ring->buf_size, DMA_FROM_DEVICE);
++-		}
++ 
++-		/* receive data */
++-		skb = build_skb(data, ring->frag_size);
++-		if (unlikely(!skb)) {
++-			mtk_rx_put_buff(ring, data, true);
++-			netdev->stats.rx_dropped++;
++-			goto skip_rx;
++-		}
+++			skb = build_skb(data, ring->frag_size);
+++			if (unlikely(!skb)) {
+++				netdev->stats.rx_dropped++;
+++				skb_free_frag(data);
+++				goto skip_rx;
+++			}
++ 
++-		if (ring->page_pool) {
++-			reserve_len = MTK_PP_HEADROOM;
++-			skb_mark_for_recycle(skb);
++-		} else {
++-			reserve_len = NET_SKB_PAD + NET_IP_ALIGN;
+++			skb_reserve(skb, NET_SKB_PAD + NET_IP_ALIGN);
+++			skb_put(skb, pktlen);
++ 		}
++-		skb_reserve(skb, reserve_len);
++ 
++-		pktlen = RX_DMA_GET_PLEN0(trxd.rxd2);
++ 		skb->dev = netdev;
++-		skb_put(skb, pktlen);
+++		bytes += skb->len;
++ 
++ 		if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ 			rxdcsum = &trxd.rxd3;
++@@ -1559,7 +1632,6 @@ static int mtk_poll_rx(struct napi_struc
++ 		else
++ 			skb_checksum_none_assert(skb);
++ 		skb->protocol = eth_type_trans(skb, netdev);
++-		bytes += pktlen;
++ 
++ 		hash = trxd.rxd4 & MTK_RXD4_FOE_ENTRY;
++ 		if (hash != MTK_RXD4_FOE_ENTRY) {
++@@ -1622,6 +1694,9 @@ rx_done:
++ 			  &dim_sample);
++ 	net_dim(&eth->rx_dim, dim_sample);
++ 
+++	if (xdp_flush)
+++		xdp_do_flush_map();
+++
++ 	return done;
++ }
++ 
++@@ -1967,7 +2042,7 @@ static int mtk_rx_alloc(struct mtk_eth *
++ 	if (!ring->data)
++ 		return -ENOMEM;
++ 
++-	if (!eth->hwlro) {
+++	if (mtk_page_pool_enabled(eth)) {
++ 		struct page_pool *pp;
++ 
++ 		pp = mtk_create_page_pool(eth, &ring->xdp_q, ring_no,
++@@ -2707,6 +2782,48 @@ static int mtk_stop(struct net_device *d
++ 	return 0;
++ }
++ 
+++static int mtk_xdp_setup(struct net_device *dev, struct bpf_prog *prog,
+++			 struct netlink_ext_ack *extack)
+++{
+++	struct mtk_mac *mac = netdev_priv(dev);
+++	struct mtk_eth *eth = mac->hw;
+++	struct bpf_prog *old_prog;
+++	bool need_update;
+++
+++	if (eth->hwlro) {
+++		NL_SET_ERR_MSG_MOD(extack, "XDP not supported with HWLRO");
+++		return -EOPNOTSUPP;
+++	}
+++
+++	if (dev->mtu > MTK_PP_MAX_BUF_SIZE) {
+++		NL_SET_ERR_MSG_MOD(extack, "MTU too large for XDP");
+++		return -EOPNOTSUPP;
+++	}
+++
+++	need_update = !!eth->prog != !!prog;
+++	if (netif_running(dev) && need_update)
+++		mtk_stop(dev);
+++
+++	old_prog = rcu_replace_pointer(eth->prog, prog, lockdep_rtnl_is_held());
+++	if (old_prog)
+++		bpf_prog_put(old_prog);
+++
+++	if (netif_running(dev) && need_update)
+++		return mtk_open(dev);
+++
+++	return 0;
+++}
+++
+++static int mtk_xdp(struct net_device *dev, struct netdev_bpf *xdp)
+++{
+++	switch (xdp->command) {
+++	case XDP_SETUP_PROG:
+++		return mtk_xdp_setup(dev, xdp->prog, xdp->extack);
+++	default:
+++		return -EINVAL;
+++	}
+++}
+++
++ static void ethsys_reset(struct mtk_eth *eth, u32 reset_bits)
++ {
++ 	regmap_update_bits(eth->ethsys, ETHSYS_RSTCTRL,
++@@ -3002,6 +3119,12 @@ static int mtk_change_mtu(struct net_dev
++ 	struct mtk_eth *eth = mac->hw;
++ 	u32 mcr_cur, mcr_new;
++ 
+++	if (rcu_access_pointer(eth->prog) &&
+++	    length > MTK_PP_MAX_BUF_SIZE) {
+++		netdev_err(dev, "Invalid MTU for XDP mode\n");
+++		return -EINVAL;
+++	}
+++
++ 	if (!MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628)) {
++ 		mcr_cur = mtk_r32(mac->hw, MTK_MAC_MCR(mac->id));
++ 		mcr_new = mcr_cur & ~MAC_MCR_MAX_RX_MASK;
++@@ -3329,6 +3452,7 @@ static const struct net_device_ops mtk_n
++ 	.ndo_poll_controller	= mtk_poll_controller,
++ #endif
++ 	.ndo_setup_tc		= mtk_eth_setup_tc,
+++	.ndo_bpf		= mtk_xdp,
++ };
++ 
++ static int mtk_add_mac(struct mtk_eth *eth, struct device_node *np)
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -1085,6 +1085,8 @@ struct mtk_eth {
++ 
++ 	struct mtk_ppe			*ppe;
++ 	struct rhashtable		flow_table;
+++
+++	struct bpf_prog			__rcu *prog;
++ };
++ 
++ /* struct mtk_mac -	the structure that holds the info about the MACs of the
+diff --git a/target/linux/generic/backport-5.15/706-02-v6.0-net-ethernet-mtk_eth_soc-introduce-xdp-ethtool-count.patch b/target/linux/generic/backport-5.15/706-02-v6.0-net-ethernet-mtk_eth_soc-introduce-xdp-ethtool-count.patch
+new file mode 100644
+index 0000000000..b68f44aae2
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/706-02-v6.0-net-ethernet-mtk_eth_soc-introduce-xdp-ethtool-count.patch
+@@ -0,0 +1,110 @@
++From 916a6ee836d6b7b8ef1ed5f0515e256ca60e9968 Mon Sep 17 00:00:00 2001
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 22 Jul 2022 09:19:38 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: introduce xdp ethtool counters
++
++Report xdp stats through ethtool
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 26 +++++++++++++++++++--
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 12 ++++++++++
++ 2 files changed, 36 insertions(+), 2 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -34,6 +34,10 @@ MODULE_PARM_DESC(msg_level, "Message lev
++ #define MTK_ETHTOOL_STAT(x) { #x, \
++ 			      offsetof(struct mtk_hw_stats, x) / sizeof(u64) }
++ 
+++#define MTK_ETHTOOL_XDP_STAT(x) { #x, \
+++				  offsetof(struct mtk_hw_stats, xdp_stats.x) / \
+++				  sizeof(u64) }
+++
++ static const struct mtk_reg_map mtk_reg_map = {
++ 	.tx_irq_mask		= 0x1a1c,
++ 	.tx_irq_status		= 0x1a18,
++@@ -141,6 +145,13 @@ static const struct mtk_ethtool_stats {
++ 	MTK_ETHTOOL_STAT(rx_long_errors),
++ 	MTK_ETHTOOL_STAT(rx_checksum_errors),
++ 	MTK_ETHTOOL_STAT(rx_flow_control_packets),
+++	MTK_ETHTOOL_XDP_STAT(rx_xdp_redirect),
+++	MTK_ETHTOOL_XDP_STAT(rx_xdp_pass),
+++	MTK_ETHTOOL_XDP_STAT(rx_xdp_drop),
+++	MTK_ETHTOOL_XDP_STAT(rx_xdp_tx),
+++	MTK_ETHTOOL_XDP_STAT(rx_xdp_tx_errors),
+++	MTK_ETHTOOL_XDP_STAT(tx_xdp_xmit),
+++	MTK_ETHTOOL_XDP_STAT(tx_xdp_xmit_errors),
++ };
++ 
++ static const char * const mtk_clks_source_name[] = {
++@@ -1458,6 +1469,9 @@ static void mtk_rx_put_buff(struct mtk_r
++ static u32 mtk_xdp_run(struct mtk_eth *eth, struct mtk_rx_ring *ring,
++ 		       struct xdp_buff *xdp, struct net_device *dev)
++ {
+++	struct mtk_mac *mac = netdev_priv(dev);
+++	struct mtk_hw_stats *hw_stats = mac->hw_stats;
+++	u64 *count = &hw_stats->xdp_stats.rx_xdp_drop;
++ 	struct bpf_prog *prog;
++ 	u32 act = XDP_PASS;
++ 
++@@ -1470,13 +1484,16 @@ static u32 mtk_xdp_run(struct mtk_eth *e
++ 	act = bpf_prog_run_xdp(prog, xdp);
++ 	switch (act) {
++ 	case XDP_PASS:
++-		goto out;
+++		count = &hw_stats->xdp_stats.rx_xdp_pass;
+++		goto update_stats;
++ 	case XDP_REDIRECT:
++ 		if (unlikely(xdp_do_redirect(dev, xdp, prog))) {
++ 			act = XDP_DROP;
++ 			break;
++ 		}
++-		goto out;
+++
+++		count = &hw_stats->xdp_stats.rx_xdp_redirect;
+++		goto update_stats;
++ 	default:
++ 		bpf_warn_invalid_xdp_action(act);
++ 		fallthrough;
++@@ -1489,6 +1506,11 @@ static u32 mtk_xdp_run(struct mtk_eth *e
++ 
++ 	page_pool_put_full_page(ring->page_pool,
++ 				virt_to_head_page(xdp->data), true);
+++
+++update_stats:
+++	u64_stats_update_begin(&hw_stats->syncp);
+++	*count = *count + 1;
+++	u64_stats_update_end(&hw_stats->syncp);
++ out:
++ 	rcu_read_unlock();
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -567,6 +567,16 @@ struct mtk_tx_dma_v2 {
++ struct mtk_eth;
++ struct mtk_mac;
++ 
+++struct mtk_xdp_stats {
+++	u64 rx_xdp_redirect;
+++	u64 rx_xdp_pass;
+++	u64 rx_xdp_drop;
+++	u64 rx_xdp_tx;
+++	u64 rx_xdp_tx_errors;
+++	u64 tx_xdp_xmit;
+++	u64 tx_xdp_xmit_errors;
+++};
+++
++ /* struct mtk_hw_stats - the structure that holds the traffic statistics.
++  * @stats_lock:		make sure that stats operations are atomic
++  * @reg_offset:		the status register offset of the SoC
++@@ -590,6 +600,8 @@ struct mtk_hw_stats {
++ 	u64 rx_checksum_errors;
++ 	u64 rx_flow_control_packets;
++ 
+++	struct mtk_xdp_stats	xdp_stats;
+++
++ 	spinlock_t		stats_lock;
++ 	u32			reg_offset;
++ 	struct u64_stats_sync	syncp;
+diff --git a/target/linux/generic/backport-5.15/706-03-v6.0-net-ethernet-mtk_eth_soc-add-xmit-XDP-support.patch b/target/linux/generic/backport-5.15/706-03-v6.0-net-ethernet-mtk_eth_soc-add-xmit-XDP-support.patch
+new file mode 100644
+index 0000000000..42b3ea215d
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/706-03-v6.0-net-ethernet-mtk_eth_soc-add-xmit-XDP-support.patch
+@@ -0,0 +1,340 @@
++From 5886d26fd25bbe26130e3e5f7474b9b3e98a3469 Mon Sep 17 00:00:00 2001
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 22 Jul 2022 09:19:39 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: add xmit XDP support
++
++Introduce XDP support for XDP_TX verdict and ndo_xdp_xmit function
++pointer.
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 192 +++++++++++++++++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h |  10 +-
++ 2 files changed, 180 insertions(+), 22 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -987,15 +987,26 @@ static void mtk_tx_unmap(struct mtk_eth
++ 		}
++ 	}
++ 
++-	tx_buf->flags = 0;
++-	if (tx_buf->skb &&
++-	    (tx_buf->skb != (struct sk_buff *)MTK_DMA_DUMMY_DESC)) {
++-		if (napi)
++-			napi_consume_skb(tx_buf->skb, napi);
+++	if (tx_buf->type == MTK_TYPE_SKB) {
+++		if (tx_buf->data &&
+++		    tx_buf->data != (void *)MTK_DMA_DUMMY_DESC) {
+++			struct sk_buff *skb = tx_buf->data;
+++
+++			if (napi)
+++				napi_consume_skb(skb, napi);
+++			else
+++				dev_kfree_skb_any(skb);
+++		}
+++	} else if (tx_buf->data) {
+++		struct xdp_frame *xdpf = tx_buf->data;
+++
+++		if (napi && tx_buf->type == MTK_TYPE_XDP_TX)
+++			xdp_return_frame_rx_napi(xdpf);
++ 		else
++-			dev_kfree_skb_any(tx_buf->skb);
+++			xdp_return_frame(xdpf);
++ 	}
++-	tx_buf->skb = NULL;
+++	tx_buf->flags = 0;
+++	tx_buf->data = NULL;
++ }
++ 
++ static void setup_tx_buf(struct mtk_eth *eth, struct mtk_tx_buf *tx_buf,
++@@ -1012,7 +1023,7 @@ static void setup_tx_buf(struct mtk_eth
++ 			dma_unmap_addr_set(tx_buf, dma_addr1, mapped_addr);
++ 			dma_unmap_len_set(tx_buf, dma_len1, size);
++ 		} else {
++-			tx_buf->skb = (struct sk_buff *)MTK_DMA_DUMMY_DESC;
+++			tx_buf->data = (void *)MTK_DMA_DUMMY_DESC;
++ 			txd->txd1 = mapped_addr;
++ 			txd->txd2 = TX_DMA_PLEN0(size);
++ 			dma_unmap_addr_set(tx_buf, dma_addr0, mapped_addr);
++@@ -1188,7 +1199,7 @@ static int mtk_tx_map(struct sk_buff *sk
++ 						    soc->txrx.txd_size);
++ 			if (new_desc)
++ 				memset(tx_buf, 0, sizeof(*tx_buf));
++-			tx_buf->skb = (struct sk_buff *)MTK_DMA_DUMMY_DESC;
+++			tx_buf->data = (void *)MTK_DMA_DUMMY_DESC;
++ 			tx_buf->flags |= MTK_TX_FLAGS_PAGE0;
++ 			tx_buf->flags |= (!mac->id) ? MTK_TX_FLAGS_FPORT0 :
++ 					 MTK_TX_FLAGS_FPORT1;
++@@ -1202,7 +1213,8 @@ static int mtk_tx_map(struct sk_buff *sk
++ 	}
++ 
++ 	/* store skb to cleanup */
++-	itx_buf->skb = skb;
+++	itx_buf->type = MTK_TYPE_SKB;
+++	itx_buf->data = skb;
++ 
++ 	if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
++ 		if (k & 0x1)
++@@ -1414,13 +1426,14 @@ static struct page_pool *mtk_create_page
++ 		.pool_size = size,
++ 		.nid = NUMA_NO_NODE,
++ 		.dev = eth->dma_dev,
++-		.dma_dir = DMA_FROM_DEVICE,
++ 		.offset = MTK_PP_HEADROOM,
++ 		.max_len = MTK_PP_MAX_BUF_SIZE,
++ 	};
++ 	struct page_pool *pp;
++ 	int err;
++ 
+++	pp_params.dma_dir = rcu_access_pointer(eth->prog) ? DMA_BIDIRECTIONAL
+++							  : DMA_FROM_DEVICE;
++ 	pp = page_pool_create(&pp_params);
++ 	if (IS_ERR(pp))
++ 		return pp;
++@@ -1466,6 +1479,122 @@ static void mtk_rx_put_buff(struct mtk_r
++ 		skb_free_frag(data);
++ }
++ 
+++static int mtk_xdp_submit_frame(struct mtk_eth *eth, struct xdp_frame *xdpf,
+++				struct net_device *dev, bool dma_map)
+++{
+++	const struct mtk_soc_data *soc = eth->soc;
+++	struct mtk_tx_ring *ring = &eth->tx_ring;
+++	struct mtk_tx_dma_desc_info txd_info = {
+++		.size	= xdpf->len,
+++		.first	= true,
+++		.last	= true,
+++	};
+++	struct mtk_mac *mac = netdev_priv(dev);
+++	struct mtk_tx_dma *txd, *txd_pdma;
+++	int err = 0, index = 0, n_desc = 1;
+++	struct mtk_tx_buf *tx_buf;
+++
+++	if (unlikely(test_bit(MTK_RESETTING, &eth->state)))
+++		return -EBUSY;
+++
+++	if (unlikely(atomic_read(&ring->free_count) <= 1))
+++		return -EBUSY;
+++
+++	spin_lock(&eth->page_lock);
+++
+++	txd = ring->next_free;
+++	if (txd == ring->last_free) {
+++		err = -ENOMEM;
+++		goto out;
+++	}
+++
+++	tx_buf = mtk_desc_to_tx_buf(ring, txd, soc->txrx.txd_size);
+++	memset(tx_buf, 0, sizeof(*tx_buf));
+++
+++	if (dma_map) {  /* ndo_xdp_xmit */
+++		txd_info.addr = dma_map_single(eth->dma_dev, xdpf->data,
+++					       txd_info.size, DMA_TO_DEVICE);
+++		if (unlikely(dma_mapping_error(eth->dma_dev, txd_info.addr))) {
+++			err = -ENOMEM;
+++			goto out;
+++		}
+++		tx_buf->flags |= MTK_TX_FLAGS_SINGLE0;
+++	} else {
+++		struct page *page = virt_to_head_page(xdpf->data);
+++
+++		txd_info.addr = page_pool_get_dma_addr(page) +
+++				sizeof(*xdpf) + xdpf->headroom;
+++		dma_sync_single_for_device(eth->dma_dev, txd_info.addr,
+++					   txd_info.size,
+++					   DMA_BIDIRECTIONAL);
+++	}
+++	mtk_tx_set_dma_desc(dev, txd, &txd_info);
+++
+++	tx_buf->flags |= !mac->id ? MTK_TX_FLAGS_FPORT0 : MTK_TX_FLAGS_FPORT1;
+++
+++	txd_pdma = qdma_to_pdma(ring, txd);
+++	setup_tx_buf(eth, tx_buf, txd_pdma, txd_info.addr, txd_info.size,
+++		     index++);
+++
+++	/* store xdpf for cleanup */
+++	tx_buf->type = dma_map ? MTK_TYPE_XDP_NDO : MTK_TYPE_XDP_TX;
+++	tx_buf->data = xdpf;
+++
+++	if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
+++		if (index & 1)
+++			txd_pdma->txd2 |= TX_DMA_LS0;
+++		else
+++			txd_pdma->txd2 |= TX_DMA_LS1;
+++	}
+++
+++	ring->next_free = mtk_qdma_phys_to_virt(ring, txd->txd2);
+++	atomic_sub(n_desc, &ring->free_count);
+++
+++	/* make sure that all changes to the dma ring are flushed before we
+++	 * continue
+++	 */
+++	wmb();
+++
+++	if (MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
+++		mtk_w32(eth, txd->txd2, soc->reg_map->qdma.ctx_ptr);
+++	} else {
+++		int idx;
+++
+++		idx = txd_to_idx(ring, txd, soc->txrx.txd_size);
+++		mtk_w32(eth, NEXT_DESP_IDX(idx, ring->dma_size),
+++			MT7628_TX_CTX_IDX0);
+++	}
+++out:
+++	spin_unlock(&eth->page_lock);
+++
+++	return err;
+++}
+++
+++static int mtk_xdp_xmit(struct net_device *dev, int num_frame,
+++			struct xdp_frame **frames, u32 flags)
+++{
+++	struct mtk_mac *mac = netdev_priv(dev);
+++	struct mtk_hw_stats *hw_stats = mac->hw_stats;
+++	struct mtk_eth *eth = mac->hw;
+++	int i, nxmit = 0;
+++
+++	if (unlikely(flags & ~XDP_XMIT_FLAGS_MASK))
+++		return -EINVAL;
+++
+++	for (i = 0; i < num_frame; i++) {
+++		if (mtk_xdp_submit_frame(eth, frames[i], dev, true))
+++			break;
+++		nxmit++;
+++	}
+++
+++	u64_stats_update_begin(&hw_stats->syncp);
+++	hw_stats->xdp_stats.tx_xdp_xmit += nxmit;
+++	hw_stats->xdp_stats.tx_xdp_xmit_errors += num_frame - nxmit;
+++	u64_stats_update_end(&hw_stats->syncp);
+++
+++	return nxmit;
+++}
+++
++ static u32 mtk_xdp_run(struct mtk_eth *eth, struct mtk_rx_ring *ring,
++ 		       struct xdp_buff *xdp, struct net_device *dev)
++ {
++@@ -1494,6 +1623,18 @@ static u32 mtk_xdp_run(struct mtk_eth *e
++ 
++ 		count = &hw_stats->xdp_stats.rx_xdp_redirect;
++ 		goto update_stats;
+++	case XDP_TX: {
+++		struct xdp_frame *xdpf = xdp_convert_buff_to_frame(xdp);
+++
+++		if (mtk_xdp_submit_frame(eth, xdpf, dev, false)) {
+++			count = &hw_stats->xdp_stats.rx_xdp_tx_errors;
+++			act = XDP_DROP;
+++			break;
+++		}
+++
+++		count = &hw_stats->xdp_stats.rx_xdp_tx;
+++		goto update_stats;
+++	}
++ 	default:
++ 		bpf_warn_invalid_xdp_action(act);
++ 		fallthrough;
++@@ -1727,9 +1868,8 @@ static int mtk_poll_tx_qdma(struct mtk_e
++ {
++ 	const struct mtk_reg_map *reg_map = eth->soc->reg_map;
++ 	struct mtk_tx_ring *ring = &eth->tx_ring;
++-	struct mtk_tx_dma *desc;
++-	struct sk_buff *skb;
++ 	struct mtk_tx_buf *tx_buf;
+++	struct mtk_tx_dma *desc;
++ 	u32 cpu, dma;
++ 
++ 	cpu = ring->last_free_ptr;
++@@ -1750,15 +1890,21 @@ static int mtk_poll_tx_qdma(struct mtk_e
++ 		if (tx_buf->flags & MTK_TX_FLAGS_FPORT1)
++ 			mac = 1;
++ 
++-		skb = tx_buf->skb;
++-		if (!skb)
+++		if (!tx_buf->data)
++ 			break;
++ 
++-		if (skb != (struct sk_buff *)MTK_DMA_DUMMY_DESC) {
+++		if (tx_buf->type == MTK_TYPE_SKB &&
+++		    tx_buf->data != (void *)MTK_DMA_DUMMY_DESC) {
+++			struct sk_buff *skb = tx_buf->data;
+++
++ 			bytes[mac] += skb->len;
++ 			done[mac]++;
++ 			budget--;
+++		} else if (tx_buf->type == MTK_TYPE_XDP_TX ||
+++			   tx_buf->type == MTK_TYPE_XDP_NDO) {
+++			budget--;
++ 		}
+++
++ 		mtk_tx_unmap(eth, tx_buf, true);
++ 
++ 		ring->last_free = desc;
++@@ -1777,9 +1923,8 @@ static int mtk_poll_tx_pdma(struct mtk_e
++ 			    unsigned int *done, unsigned int *bytes)
++ {
++ 	struct mtk_tx_ring *ring = &eth->tx_ring;
++-	struct mtk_tx_dma *desc;
++-	struct sk_buff *skb;
++ 	struct mtk_tx_buf *tx_buf;
+++	struct mtk_tx_dma *desc;
++ 	u32 cpu, dma;
++ 
++ 	cpu = ring->cpu_idx;
++@@ -1787,14 +1932,18 @@ static int mtk_poll_tx_pdma(struct mtk_e
++ 
++ 	while ((cpu != dma) && budget) {
++ 		tx_buf = &ring->buf[cpu];
++-		skb = tx_buf->skb;
++-		if (!skb)
+++		if (!tx_buf->data)
++ 			break;
++ 
++-		if (skb != (struct sk_buff *)MTK_DMA_DUMMY_DESC) {
+++		if (tx_buf->type == MTK_TYPE_SKB &&
+++		    tx_buf->data != (void *)MTK_DMA_DUMMY_DESC) {
+++			struct sk_buff *skb = tx_buf->data;
++ 			bytes[0] += skb->len;
++ 			done[0]++;
++ 			budget--;
+++		} else if (tx_buf->type == MTK_TYPE_XDP_TX ||
+++			   tx_buf->type == MTK_TYPE_XDP_NDO) {
+++			budget--;
++ 		}
++ 
++ 		mtk_tx_unmap(eth, tx_buf, true);
++@@ -3475,6 +3624,7 @@ static const struct net_device_ops mtk_n
++ #endif
++ 	.ndo_setup_tc		= mtk_eth_setup_tc,
++ 	.ndo_bpf		= mtk_xdp,
+++	.ndo_xdp_xmit		= mtk_xdp_xmit,
++ };
++ 
++ static int mtk_add_mac(struct mtk_eth *eth, struct device_node *np)
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -693,6 +693,12 @@ enum mtk_dev_state {
++ 	MTK_RESETTING
++ };
++ 
+++enum mtk_tx_buf_type {
+++	MTK_TYPE_SKB,
+++	MTK_TYPE_XDP_TX,
+++	MTK_TYPE_XDP_NDO,
+++};
+++
++ /* struct mtk_tx_buf -	This struct holds the pointers to the memory pointed at
++  *			by the TX descriptor	s
++  * @skb:		The SKB pointer of the packet being sent
++@@ -702,7 +708,9 @@ enum mtk_dev_state {
++  * @dma_len1:		The length of the second segment
++  */
++ struct mtk_tx_buf {
++-	struct sk_buff *skb;
+++	enum mtk_tx_buf_type type;
+++	void *data;
+++
++ 	u32 flags;
++ 	DEFINE_DMA_UNMAP_ADDR(dma_addr0);
++ 	DEFINE_DMA_UNMAP_LEN(dma_len0);
+diff --git a/target/linux/generic/backport-5.15/706-04-v6.0-net-ethernet-mtk_eth_soc-add-support-for-page_pool_g.patch b/target/linux/generic/backport-5.15/706-04-v6.0-net-ethernet-mtk_eth_soc-add-support-for-page_pool_g.patch
+new file mode 100644
+index 0000000000..d372f022c7
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/706-04-v6.0-net-ethernet-mtk_eth_soc-add-support-for-page_pool_g.patch
+@@ -0,0 +1,95 @@
++From 84b9cd389036d4a262d8cee794d56c04095358a7 Mon Sep 17 00:00:00 2001
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 22 Jul 2022 09:19:40 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: add support for
++ page_pool_get_stats
++
++Introduce support for the page_pool stats API into mtk_eth_soc driver.
++Report page_pool stats through ethtool.
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/mediatek/Kconfig       |  1 +
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 37 +++++++++++++++++++--
++ 2 files changed, 35 insertions(+), 3 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/Kconfig
+++++ b/drivers/net/ethernet/mediatek/Kconfig
++@@ -17,6 +17,7 @@ config NET_MEDIATEK_SOC
++ 	select PHYLINK
++ 	select DIMLIB
++ 	select PAGE_POOL
+++	select PAGE_POOL_STATS
++ 	help
++ 	  This driver supports the gigabit ethernet MACs in the
++ 	  MediaTek SoC family.
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -3485,11 +3485,18 @@ static void mtk_get_strings(struct net_d
++ 	int i;
++ 
++ 	switch (stringset) {
++-	case ETH_SS_STATS:
+++	case ETH_SS_STATS: {
+++		struct mtk_mac *mac = netdev_priv(dev);
+++
++ 		for (i = 0; i < ARRAY_SIZE(mtk_ethtool_stats); i++) {
++ 			memcpy(data, mtk_ethtool_stats[i].str, ETH_GSTRING_LEN);
++ 			data += ETH_GSTRING_LEN;
++ 		}
+++		if (mtk_page_pool_enabled(mac->hw))
+++			page_pool_ethtool_stats_get_strings(data);
+++		break;
+++	}
+++	default:
++ 		break;
++ 	}
++ }
++@@ -3497,13 +3504,35 @@ static void mtk_get_strings(struct net_d
++ static int mtk_get_sset_count(struct net_device *dev, int sset)
++ {
++ 	switch (sset) {
++-	case ETH_SS_STATS:
++-		return ARRAY_SIZE(mtk_ethtool_stats);
+++	case ETH_SS_STATS: {
+++		int count = ARRAY_SIZE(mtk_ethtool_stats);
+++		struct mtk_mac *mac = netdev_priv(dev);
+++
+++		if (mtk_page_pool_enabled(mac->hw))
+++			count += page_pool_ethtool_stats_get_count();
+++		return count;
+++	}
++ 	default:
++ 		return -EOPNOTSUPP;
++ 	}
++ }
++ 
+++static void mtk_ethtool_pp_stats(struct mtk_eth *eth, u64 *data)
+++{
+++	struct page_pool_stats stats = {};
+++	int i;
+++
+++	for (i = 0; i < ARRAY_SIZE(eth->rx_ring); i++) {
+++		struct mtk_rx_ring *ring = &eth->rx_ring[i];
+++
+++		if (!ring->page_pool)
+++			continue;
+++
+++		page_pool_get_stats(ring->page_pool, &stats);
+++	}
+++	page_pool_ethtool_stats_get(data, &stats);
+++}
+++
++ static void mtk_get_ethtool_stats(struct net_device *dev,
++ 				  struct ethtool_stats *stats, u64 *data)
++ {
++@@ -3531,6 +3560,8 @@ static void mtk_get_ethtool_stats(struct
++ 
++ 		for (i = 0; i < ARRAY_SIZE(mtk_ethtool_stats); i++)
++ 			*data_dst++ = *(data_src + mtk_ethtool_stats[i].offset);
+++		if (mtk_page_pool_enabled(mac->hw))
+++			mtk_ethtool_pp_stats(mac->hw, data_dst);
++ 	} while (u64_stats_fetch_retry_irq(&hwstats->syncp, start));
++ }
++ 
+diff --git a/target/linux/generic/backport-5.15/706-05-v6.0-net-ethernet-mtk_eth_soc-introduce-mtk_xdp_frame_map.patch b/target/linux/generic/backport-5.15/706-05-v6.0-net-ethernet-mtk_eth_soc-introduce-mtk_xdp_frame_map.patch
+new file mode 100644
+index 0000000000..33a7616652
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/706-05-v6.0-net-ethernet-mtk_eth_soc-introduce-mtk_xdp_frame_map.patch
+@@ -0,0 +1,113 @@
++From b16fe6d82b71fa0dd5c957bc22d66a694976d6eb Mon Sep 17 00:00:00 2001
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Wed, 27 Jul 2022 23:20:50 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: introduce mtk_xdp_frame_map
++ utility routine
++
++This is a preliminary patch to add xdp multi-frag support to mtk_eth_soc
++driver
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 68 +++++++++++++--------
++ 1 file changed, 42 insertions(+), 26 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -1479,6 +1479,41 @@ static void mtk_rx_put_buff(struct mtk_r
++ 		skb_free_frag(data);
++ }
++ 
+++static int mtk_xdp_frame_map(struct mtk_eth *eth, struct net_device *dev,
+++			     struct mtk_tx_dma_desc_info *txd_info,
+++			     struct mtk_tx_dma *txd, struct mtk_tx_buf *tx_buf,
+++			     void *data, u16 headroom, int index, bool dma_map)
+++{
+++	struct mtk_tx_ring *ring = &eth->tx_ring;
+++	struct mtk_mac *mac = netdev_priv(dev);
+++	struct mtk_tx_dma *txd_pdma;
+++
+++	if (dma_map) {  /* ndo_xdp_xmit */
+++		txd_info->addr = dma_map_single(eth->dma_dev, data,
+++						txd_info->size, DMA_TO_DEVICE);
+++		if (unlikely(dma_mapping_error(eth->dma_dev, txd_info->addr)))
+++			return -ENOMEM;
+++
+++		tx_buf->flags |= MTK_TX_FLAGS_SINGLE0;
+++	} else {
+++		struct page *page = virt_to_head_page(data);
+++
+++		txd_info->addr = page_pool_get_dma_addr(page) +
+++				 sizeof(struct xdp_frame) + headroom;
+++		dma_sync_single_for_device(eth->dma_dev, txd_info->addr,
+++					   txd_info->size, DMA_BIDIRECTIONAL);
+++	}
+++	mtk_tx_set_dma_desc(dev, txd, txd_info);
+++
+++	tx_buf->flags |= !mac->id ? MTK_TX_FLAGS_FPORT0 : MTK_TX_FLAGS_FPORT1;
+++
+++	txd_pdma = qdma_to_pdma(ring, txd);
+++	setup_tx_buf(eth, tx_buf, txd_pdma, txd_info->addr, txd_info->size,
+++		     index);
+++
+++	return 0;
+++}
+++
++ static int mtk_xdp_submit_frame(struct mtk_eth *eth, struct xdp_frame *xdpf,
++ 				struct net_device *dev, bool dma_map)
++ {
++@@ -1489,9 +1524,8 @@ static int mtk_xdp_submit_frame(struct m
++ 		.first	= true,
++ 		.last	= true,
++ 	};
++-	struct mtk_mac *mac = netdev_priv(dev);
++-	struct mtk_tx_dma *txd, *txd_pdma;
++ 	int err = 0, index = 0, n_desc = 1;
+++	struct mtk_tx_dma *txd, *txd_pdma;
++ 	struct mtk_tx_buf *tx_buf;
++ 
++ 	if (unlikely(test_bit(MTK_RESETTING, &eth->state)))
++@@ -1511,36 +1545,18 @@ static int mtk_xdp_submit_frame(struct m
++ 	tx_buf = mtk_desc_to_tx_buf(ring, txd, soc->txrx.txd_size);
++ 	memset(tx_buf, 0, sizeof(*tx_buf));
++ 
++-	if (dma_map) {  /* ndo_xdp_xmit */
++-		txd_info.addr = dma_map_single(eth->dma_dev, xdpf->data,
++-					       txd_info.size, DMA_TO_DEVICE);
++-		if (unlikely(dma_mapping_error(eth->dma_dev, txd_info.addr))) {
++-			err = -ENOMEM;
++-			goto out;
++-		}
++-		tx_buf->flags |= MTK_TX_FLAGS_SINGLE0;
++-	} else {
++-		struct page *page = virt_to_head_page(xdpf->data);
++-
++-		txd_info.addr = page_pool_get_dma_addr(page) +
++-				sizeof(*xdpf) + xdpf->headroom;
++-		dma_sync_single_for_device(eth->dma_dev, txd_info.addr,
++-					   txd_info.size,
++-					   DMA_BIDIRECTIONAL);
++-	}
++-	mtk_tx_set_dma_desc(dev, txd, &txd_info);
++-
++-	tx_buf->flags |= !mac->id ? MTK_TX_FLAGS_FPORT0 : MTK_TX_FLAGS_FPORT1;
++-
++-	txd_pdma = qdma_to_pdma(ring, txd);
++-	setup_tx_buf(eth, tx_buf, txd_pdma, txd_info.addr, txd_info.size,
++-		     index++);
+++	err = mtk_xdp_frame_map(eth, dev, &txd_info, txd, tx_buf,
+++				xdpf->data, xdpf->headroom, index,
+++				dma_map);
+++	if (err < 0)
+++		goto out;
++ 
++ 	/* store xdpf for cleanup */
++ 	tx_buf->type = dma_map ? MTK_TYPE_XDP_NDO : MTK_TYPE_XDP_TX;
++ 	tx_buf->data = xdpf;
++ 
++ 	if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
+++		txd_pdma = qdma_to_pdma(ring, txd);
++ 		if (index & 1)
++ 			txd_pdma->txd2 |= TX_DMA_LS0;
++ 		else
+diff --git a/target/linux/generic/backport-5.15/706-06-v6.0-net-ethernet-mtk_eth_soc-introduce-xdp-multi-frag-su.patch b/target/linux/generic/backport-5.15/706-06-v6.0-net-ethernet-mtk_eth_soc-introduce-xdp-multi-frag-su.patch
+new file mode 100644
+index 0000000000..e75861bc82
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/706-06-v6.0-net-ethernet-mtk_eth_soc-introduce-xdp-multi-frag-su.patch
+@@ -0,0 +1,218 @@
++From 155738a4f319538a09f734ce1f5a2eac3ada1de2 Mon Sep 17 00:00:00 2001
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Wed, 27 Jul 2022 23:20:51 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: introduce xdp multi-frag support
++
++Add the capability to map non-linear xdp frames in XDP_TX and
++ndo_xdp_xmit callback.
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 125 +++++++++++++-------
++ 1 file changed, 82 insertions(+), 43 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -987,23 +987,22 @@ static void mtk_tx_unmap(struct mtk_eth
++ 		}
++ 	}
++ 
++-	if (tx_buf->type == MTK_TYPE_SKB) {
++-		if (tx_buf->data &&
++-		    tx_buf->data != (void *)MTK_DMA_DUMMY_DESC) {
+++	if (tx_buf->data && tx_buf->data != (void *)MTK_DMA_DUMMY_DESC) {
+++		if (tx_buf->type == MTK_TYPE_SKB) {
++ 			struct sk_buff *skb = tx_buf->data;
++ 
++ 			if (napi)
++ 				napi_consume_skb(skb, napi);
++ 			else
++ 				dev_kfree_skb_any(skb);
++-		}
++-	} else if (tx_buf->data) {
++-		struct xdp_frame *xdpf = tx_buf->data;
+++		} else {
+++			struct xdp_frame *xdpf = tx_buf->data;
++ 
++-		if (napi && tx_buf->type == MTK_TYPE_XDP_TX)
++-			xdp_return_frame_rx_napi(xdpf);
++-		else
++-			xdp_return_frame(xdpf);
+++			if (napi && tx_buf->type == MTK_TYPE_XDP_TX)
+++				xdp_return_frame_rx_napi(xdpf);
+++			else
+++				xdp_return_frame(xdpf);
+++		}
++ 	}
++ 	tx_buf->flags = 0;
++ 	tx_buf->data = NULL;
++@@ -1506,6 +1505,8 @@ static int mtk_xdp_frame_map(struct mtk_
++ 	mtk_tx_set_dma_desc(dev, txd, txd_info);
++ 
++ 	tx_buf->flags |= !mac->id ? MTK_TX_FLAGS_FPORT0 : MTK_TX_FLAGS_FPORT1;
+++	tx_buf->type = dma_map ? MTK_TYPE_XDP_NDO : MTK_TYPE_XDP_TX;
+++	tx_buf->data = (void *)MTK_DMA_DUMMY_DESC;
++ 
++ 	txd_pdma = qdma_to_pdma(ring, txd);
++ 	setup_tx_buf(eth, tx_buf, txd_pdma, txd_info->addr, txd_info->size,
++@@ -1517,43 +1518,69 @@ static int mtk_xdp_frame_map(struct mtk_
++ static int mtk_xdp_submit_frame(struct mtk_eth *eth, struct xdp_frame *xdpf,
++ 				struct net_device *dev, bool dma_map)
++ {
+++	struct skb_shared_info *sinfo = xdp_get_shared_info_from_frame(xdpf);
++ 	const struct mtk_soc_data *soc = eth->soc;
++ 	struct mtk_tx_ring *ring = &eth->tx_ring;
++ 	struct mtk_tx_dma_desc_info txd_info = {
++ 		.size	= xdpf->len,
++ 		.first	= true,
++-		.last	= true,
+++		.last	= !xdp_frame_has_frags(xdpf),
++ 	};
++-	int err = 0, index = 0, n_desc = 1;
++-	struct mtk_tx_dma *txd, *txd_pdma;
++-	struct mtk_tx_buf *tx_buf;
+++	int err, index = 0, n_desc = 1, nr_frags;
+++	struct mtk_tx_dma *htxd, *txd, *txd_pdma;
+++	struct mtk_tx_buf *htx_buf, *tx_buf;
+++	void *data = xdpf->data;
++ 
++ 	if (unlikely(test_bit(MTK_RESETTING, &eth->state)))
++ 		return -EBUSY;
++ 
++-	if (unlikely(atomic_read(&ring->free_count) <= 1))
+++	nr_frags = unlikely(xdp_frame_has_frags(xdpf)) ? sinfo->nr_frags : 0;
+++	if (unlikely(atomic_read(&ring->free_count) <= 1 + nr_frags))
++ 		return -EBUSY;
++ 
++ 	spin_lock(&eth->page_lock);
++ 
++ 	txd = ring->next_free;
++ 	if (txd == ring->last_free) {
++-		err = -ENOMEM;
++-		goto out;
+++		spin_unlock(&eth->page_lock);
+++		return -ENOMEM;
++ 	}
+++	htxd = txd;
++ 
++ 	tx_buf = mtk_desc_to_tx_buf(ring, txd, soc->txrx.txd_size);
++ 	memset(tx_buf, 0, sizeof(*tx_buf));
+++	htx_buf = tx_buf;
++ 
++-	err = mtk_xdp_frame_map(eth, dev, &txd_info, txd, tx_buf,
++-				xdpf->data, xdpf->headroom, index,
++-				dma_map);
++-	if (err < 0)
++-		goto out;
+++	for (;;) {
+++		err = mtk_xdp_frame_map(eth, dev, &txd_info, txd, tx_buf,
+++					data, xdpf->headroom, index, dma_map);
+++		if (err < 0)
+++			goto unmap;
+++
+++		if (txd_info.last)
+++			break;
++ 
+++		if (MTK_HAS_CAPS(soc->caps, MTK_QDMA) || (index & 0x1)) {
+++			txd = mtk_qdma_phys_to_virt(ring, txd->txd2);
+++			txd_pdma = qdma_to_pdma(ring, txd);
+++			if (txd == ring->last_free)
+++				goto unmap;
+++
+++			tx_buf = mtk_desc_to_tx_buf(ring, txd,
+++						    soc->txrx.txd_size);
+++			memset(tx_buf, 0, sizeof(*tx_buf));
+++			n_desc++;
+++		}
+++
+++		memset(&txd_info, 0, sizeof(struct mtk_tx_dma_desc_info));
+++		txd_info.size = skb_frag_size(&sinfo->frags[index]);
+++		txd_info.last = index + 1 == nr_frags;
+++		data = skb_frag_address(&sinfo->frags[index]);
+++
+++		index++;
+++	}
++ 	/* store xdpf for cleanup */
++-	tx_buf->type = dma_map ? MTK_TYPE_XDP_NDO : MTK_TYPE_XDP_TX;
++-	tx_buf->data = xdpf;
+++	htx_buf->data = xdpf;
++ 
++ 	if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
++ 		txd_pdma = qdma_to_pdma(ring, txd);
++@@ -1580,7 +1607,24 @@ static int mtk_xdp_submit_frame(struct m
++ 		mtk_w32(eth, NEXT_DESP_IDX(idx, ring->dma_size),
++ 			MT7628_TX_CTX_IDX0);
++ 	}
++-out:
+++
+++	spin_unlock(&eth->page_lock);
+++
+++	return 0;
+++
+++unmap:
+++	while (htxd != txd) {
+++		txd_pdma = qdma_to_pdma(ring, htxd);
+++		tx_buf = mtk_desc_to_tx_buf(ring, htxd, soc->txrx.txd_size);
+++		mtk_tx_unmap(eth, tx_buf, false);
+++
+++		htxd->txd3 = TX_DMA_LS0 | TX_DMA_OWNER_CPU;
+++		if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA))
+++			txd_pdma->txd2 = TX_DMA_DESP2_DEF;
+++
+++		htxd = mtk_qdma_phys_to_virt(ring, htxd->txd2);
+++	}
+++
++ 	spin_unlock(&eth->page_lock);
++ 
++ 	return err;
++@@ -1909,18 +1953,15 @@ static int mtk_poll_tx_qdma(struct mtk_e
++ 		if (!tx_buf->data)
++ 			break;
++ 
++-		if (tx_buf->type == MTK_TYPE_SKB &&
++-		    tx_buf->data != (void *)MTK_DMA_DUMMY_DESC) {
++-			struct sk_buff *skb = tx_buf->data;
+++		if (tx_buf->data != (void *)MTK_DMA_DUMMY_DESC) {
+++			if (tx_buf->type == MTK_TYPE_SKB) {
+++				struct sk_buff *skb = tx_buf->data;
++ 
++-			bytes[mac] += skb->len;
++-			done[mac]++;
++-			budget--;
++-		} else if (tx_buf->type == MTK_TYPE_XDP_TX ||
++-			   tx_buf->type == MTK_TYPE_XDP_NDO) {
+++				bytes[mac] += skb->len;
+++				done[mac]++;
+++			}
++ 			budget--;
++ 		}
++-
++ 		mtk_tx_unmap(eth, tx_buf, true);
++ 
++ 		ring->last_free = desc;
++@@ -1951,17 +1992,15 @@ static int mtk_poll_tx_pdma(struct mtk_e
++ 		if (!tx_buf->data)
++ 			break;
++ 
++-		if (tx_buf->type == MTK_TYPE_SKB &&
++-		    tx_buf->data != (void *)MTK_DMA_DUMMY_DESC) {
++-			struct sk_buff *skb = tx_buf->data;
++-			bytes[0] += skb->len;
++-			done[0]++;
++-			budget--;
++-		} else if (tx_buf->type == MTK_TYPE_XDP_TX ||
++-			   tx_buf->type == MTK_TYPE_XDP_NDO) {
+++		if (tx_buf->data != (void *)MTK_DMA_DUMMY_DESC) {
+++			if (tx_buf->type == MTK_TYPE_SKB) {
+++				struct sk_buff *skb = tx_buf->data;
+++
+++				bytes[0] += skb->len;
+++				done[0]++;
+++			}
++ 			budget--;
++ 		}
++-
++ 		mtk_tx_unmap(eth, tx_buf, true);
++ 
++ 		desc = ring->dma + cpu * eth->soc->txrx.txd_size;
+diff --git a/target/linux/generic/backport-5.15/710-v6.0-net-ethernet-mtk_eth_soc-fix-hw-hash-reporting-for-M.patch b/target/linux/generic/backport-5.15/710-v6.0-net-ethernet-mtk_eth_soc-fix-hw-hash-reporting-for-M.patch
+new file mode 100644
+index 0000000000..a3842d35f5
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/710-v6.0-net-ethernet-mtk_eth_soc-fix-hw-hash-reporting-for-M.patch
+@@ -0,0 +1,74 @@
++From 0cf731f9ebb5bf6f252055bebf4463a5c0bd490b Mon Sep 17 00:00:00 2001
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Tue, 23 Aug 2022 14:24:07 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: fix hw hash reporting for
++ MTK_NETSYS_V2
++
++Properly report hw rx hash for mt7986 chipset accroding to the new dma
++descriptor layout.
++
++Fixes: 197c9e9b17b11 ("net: ethernet: mtk_eth_soc: introduce support for mt7986 chipset")
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Link: https://lore.kernel.org/r/091394ea4e705fbb35f828011d98d0ba33808f69.1661257293.git.lorenzo@kernel.org
++Signed-off-by: Paolo Abeni <pabeni@redhat.com>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 22 +++++++++++----------
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h |  5 +++++
++ 2 files changed, 17 insertions(+), 10 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -1845,10 +1845,19 @@ static int mtk_poll_rx(struct napi_struc
++ 		skb->dev = netdev;
++ 		bytes += skb->len;
++ 
++-		if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
+++		if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+++			hash = trxd.rxd5 & MTK_RXD5_FOE_ENTRY;
+++			if (hash != MTK_RXD5_FOE_ENTRY)
+++				skb_set_hash(skb, jhash_1word(hash, 0),
+++					     PKT_HASH_TYPE_L4);
++ 			rxdcsum = &trxd.rxd3;
++-		else
+++		} else {
+++			hash = trxd.rxd4 & MTK_RXD4_FOE_ENTRY;
+++			if (hash != MTK_RXD4_FOE_ENTRY)
+++				skb_set_hash(skb, jhash_1word(hash, 0),
+++					     PKT_HASH_TYPE_L4);
++ 			rxdcsum = &trxd.rxd4;
+++		}
++ 
++ 		if (*rxdcsum & eth->soc->txrx.rx_dma_l4_valid)
++ 			skb->ip_summed = CHECKSUM_UNNECESSARY;
++@@ -1856,16 +1865,9 @@ static int mtk_poll_rx(struct napi_struc
++ 			skb_checksum_none_assert(skb);
++ 		skb->protocol = eth_type_trans(skb, netdev);
++ 
++-		hash = trxd.rxd4 & MTK_RXD4_FOE_ENTRY;
++-		if (hash != MTK_RXD4_FOE_ENTRY) {
++-			hash = jhash_1word(hash, 0);
++-			skb_set_hash(skb, hash, PKT_HASH_TYPE_L4);
++-		}
++-
++ 		reason = FIELD_GET(MTK_RXD4_PPE_CPU_REASON, trxd.rxd4);
++ 		if (reason == MTK_PPE_CPU_REASON_HIT_UNBIND_RATE_REACHED)
++-			mtk_ppe_check_skb(eth->ppe, skb,
++-					  trxd.rxd4 & MTK_RXD4_FOE_ENTRY);
+++			mtk_ppe_check_skb(eth->ppe, skb, hash);
++ 
++ 		if (netdev->features & NETIF_F_HW_VLAN_CTAG_RX) {
++ 			if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -314,6 +314,11 @@
++ #define RX_DMA_L4_VALID_PDMA	BIT(30)		/* when PDMA is used */
++ #define RX_DMA_SPECIAL_TAG	BIT(22)
++ 
+++/* PDMA descriptor rxd5 */
+++#define MTK_RXD5_FOE_ENTRY	GENMASK(14, 0)
+++#define MTK_RXD5_PPE_CPU_REASON	GENMASK(22, 18)
+++#define MTK_RXD5_SRC_PORT	GENMASK(29, 26)
+++
++ #define RX_DMA_GET_SPORT(x)	(((x) >> 19) & 0xf)
++ #define RX_DMA_GET_SPORT_V2(x)	(((x) >> 26) & 0x7)
++ 
+diff --git a/target/linux/generic/backport-5.15/711-v6.0-01-net-ethernet-mtk_eth_soc-fix-off-by-one-check-of-ARR.patch b/target/linux/generic/backport-5.15/711-v6.0-01-net-ethernet-mtk_eth_soc-fix-off-by-one-check-of-ARR.patch
+new file mode 100644
+index 0000000000..0de8ab4376
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/711-v6.0-01-net-ethernet-mtk_eth_soc-fix-off-by-one-check-of-ARR.patch
+@@ -0,0 +1,31 @@
++From: Tom Rix <trix@redhat.com>
++Date: Sat, 16 Jul 2022 17:46:54 -0400
++Subject: [PATCH] net: ethernet: mtk_eth_soc: fix off by one check of
++ ARRAY_SIZE
++
++In mtk_wed_tx_ring_setup(.., int idx, ..), idx is used as an index here
++  struct mtk_wed_ring *ring = &dev->tx_ring[idx];
++
++The bounds of idx are checked here
++  BUG_ON(idx > ARRAY_SIZE(dev->tx_ring));
++
++If idx is the size of the array, it will pass this check and overflow.
++So change the check to >= .
++
++Fixes: 804775dfc288 ("net: ethernet: mtk_eth_soc: add support for Wireless Ethernet Dispatch (WED)")
++Signed-off-by: Tom Rix <trix@redhat.com>
++Link: https://lore.kernel.org/r/20220716214654.1540240-1-trix@redhat.com
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_wed.c
+++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
++@@ -651,7 +651,7 @@ mtk_wed_tx_ring_setup(struct mtk_wed_dev
++ 	 * WDMA RX.
++ 	 */
++ 
++-	BUG_ON(idx > ARRAY_SIZE(dev->tx_ring));
+++	BUG_ON(idx >= ARRAY_SIZE(dev->tx_ring));
++ 
++ 	if (mtk_wed_ring_alloc(dev, ring, MTK_WED_TX_RING_SIZE))
++ 		return -ENOMEM;
+diff --git a/target/linux/generic/backport-5.15/711-v6.0-02-net-ethernet-mtk_ppe-fix-possible-NULL-pointer-deref.patch b/target/linux/generic/backport-5.15/711-v6.0-02-net-ethernet-mtk_ppe-fix-possible-NULL-pointer-deref.patch
+new file mode 100644
+index 0000000000..fc6e246468
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/711-v6.0-02-net-ethernet-mtk_ppe-fix-possible-NULL-pointer-deref.patch
+@@ -0,0 +1,27 @@
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Mon, 18 Jul 2022 11:51:53 +0200
++Subject: [PATCH] net: ethernet: mtk_ppe: fix possible NULL pointer dereference
++ in mtk_flow_get_wdma_info
++
++odev pointer can be NULL in mtk_flow_offload_replace routine according
++to the flower action rules. Fix possible NULL pointer dereference in
++mtk_flow_get_wdma_info.
++
++Fixes: a333215e10cb5 ("net: ethernet: mtk_eth_soc: implement flow offloading to WED devices")
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Link: https://lore.kernel.org/r/4e1685bc4976e21e364055f6bee86261f8f9ee93.1658137753.git.lorenzo@kernel.org
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++@@ -93,6 +93,9 @@ mtk_flow_get_wdma_info(struct net_device
++ 	};
++ 	struct net_device_path path = {};
++ 
+++	if (!ctx.dev)
+++		return -ENODEV;
+++
++ 	memcpy(ctx.daddr, addr, sizeof(ctx.daddr));
++ 
++ 	if (!IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED))
+diff --git a/target/linux/generic/backport-5.15/711-v6.0-03-net-ethernet-mtk-ppe-fix-traffic-offload-with-bridge.patch b/target/linux/generic/backport-5.15/711-v6.0-03-net-ethernet-mtk-ppe-fix-traffic-offload-with-bridge.patch
+new file mode 100644
+index 0000000000..c0720152d6
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/711-v6.0-03-net-ethernet-mtk-ppe-fix-traffic-offload-with-bridge.patch
+@@ -0,0 +1,64 @@
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 22 Jul 2022 09:06:19 +0200
++Subject: [PATCH] net: ethernet: mtk-ppe: fix traffic offload with bridged wlan
++
++A typical flow offload scenario for OpenWrt users is routed traffic
++received by the wan interface that is redirected to a wlan device
++belonging to the lan bridge. Current implementation fails to
++fill wdma offload info in mtk_flow_get_wdma_info() since odev device is
++the local bridge. Fix the issue running dev_fill_forward_path routine in
++mtk_flow_get_wdma_info in order to identify the wlan device.
++
++Tested-by: Paolo Valerio <pvalerio@redhat.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++
++--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++@@ -88,32 +88,28 @@ mtk_flow_offload_mangle_eth(const struct
++ static int
++ mtk_flow_get_wdma_info(struct net_device *dev, const u8 *addr, struct mtk_wdma_info *info)
++ {
++-	struct net_device_path_ctx ctx = {
++-		.dev = dev,
++-	};
++-	struct net_device_path path = {};
+++	struct net_device_path_stack stack;
+++	struct net_device_path *path;
+++	int err;
++ 
++-	if (!ctx.dev)
+++	if (!dev)
++ 		return -ENODEV;
++ 
++-	memcpy(ctx.daddr, addr, sizeof(ctx.daddr));
++-
++ 	if (!IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED))
++ 		return -1;
++ 
++-	if (!dev->netdev_ops->ndo_fill_forward_path)
++-		return -1;
++-
++-	if (dev->netdev_ops->ndo_fill_forward_path(&ctx, &path))
++-		return -1;
+++	err = dev_fill_forward_path(dev, addr, &stack);
+++	if (err)
+++		return err;
++ 
++-	if (path.type != DEV_PATH_MTK_WDMA)
+++	path = &stack.path[stack.num_paths - 1];
+++	if (path->type != DEV_PATH_MTK_WDMA)
++ 		return -1;
++ 
++-	info->wdma_idx = path.mtk_wdma.wdma_idx;
++-	info->queue = path.mtk_wdma.queue;
++-	info->bss = path.mtk_wdma.bss;
++-	info->wcid = path.mtk_wdma.wcid;
+++	info->wdma_idx = path->mtk_wdma.wdma_idx;
+++	info->queue = path->mtk_wdma.queue;
+++	info->bss = path->mtk_wdma.bss;
+++	info->wcid = path->mtk_wdma.wcid;
++ 
++ 	return 0;
++ }
+diff --git a/target/linux/generic/backport-5.15/711-v6.0-04-net-ethernet-mtk_eth_soc-remove-mtk_foe_entry_timest.patch b/target/linux/generic/backport-5.15/711-v6.0-04-net-ethernet-mtk_eth_soc-remove-mtk_foe_entry_timest.patch
+new file mode 100644
+index 0000000000..3c28e83551
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/711-v6.0-04-net-ethernet-mtk_eth_soc-remove-mtk_foe_entry_timest.patch
+@@ -0,0 +1,33 @@
++From c9daab322313087afde8c46f41df3c628410ae20 Mon Sep 17 00:00:00 2001
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Mon, 5 Sep 2022 14:46:01 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: remove mtk_foe_entry_timestamp
++
++Get rid of mtk_foe_entry_timestamp routine since it is no longer used.
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/mediatek/mtk_ppe.h | 11 -----------
++ 1 file changed, 11 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
++@@ -302,17 +302,6 @@ mtk_ppe_check_skb(struct mtk_ppe *ppe, s
++ 	__mtk_ppe_check_skb(ppe, skb, hash);
++ }
++ 
++-static inline int
++-mtk_foe_entry_timestamp(struct mtk_ppe *ppe, u16 hash)
++-{
++-	u32 ib1 = READ_ONCE(ppe->foe_table[hash].ib1);
++-
++-	if (FIELD_GET(MTK_FOE_IB1_STATE, ib1) != MTK_FOE_STATE_BIND)
++-		return -1;
++-
++-	return FIELD_GET(MTK_FOE_IB1_BIND_TIMESTAMP, ib1);
++-}
++-
++ int mtk_foe_entry_prepare(struct mtk_foe_entry *entry, int type, int l4proto,
++ 			  u8 pse_port, u8 *src_mac, u8 *dest_mac);
++ int mtk_foe_entry_set_pse_port(struct mtk_foe_entry *entry, u8 port);
+diff --git a/target/linux/generic/backport-5.15/712-v6.0-net-ethernet-mtk_eth_soc-enable-XDP-support-just-for.patch b/target/linux/generic/backport-5.15/712-v6.0-net-ethernet-mtk_eth_soc-enable-XDP-support-just-for.patch
+new file mode 100644
+index 0000000000..f4eb030ef6
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/712-v6.0-net-ethernet-mtk_eth_soc-enable-XDP-support-just-for.patch
+@@ -0,0 +1,35 @@
++From 5e69163d3b9931098922b3fc2f8e786af8c1f37e Mon Sep 17 00:00:00 2001
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Tue, 13 Sep 2022 15:03:05 +0200
++Subject: [PATCH] net: ethernet: mtk_eth_soc: enable XDP support just for
++ MT7986 SoC
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++Disable page_pool/XDP support for MT7621 SoC in order fix a regression
++introduce adding XDP for MT7986 SoC. There is no a real use case for XDP
++on MT7621 since it is a low-end cpu. Moreover this patch reduces the
++memory footprint.
++
++Tested-by: Sergio Paracuellos <sergio.paracuellos@gmail.com>
++Tested-by: Arınç ÜNAL <arinc.unal@arinc9.com>
++Fixes: 23233e577ef9 ("net: ethernet: mtk_eth_soc: rely on page_pool for single page buffers")
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Link: https://lore.kernel.org/r/2bf31e27b888c43228b0d84dd2ef5033338269e2.1663074002.git.lorenzo@kernel.org
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -1412,7 +1412,7 @@ static void mtk_update_rx_cpu_idx(struct
++ 
++ static bool mtk_page_pool_enabled(struct mtk_eth *eth)
++ {
++-	return !eth->hwlro;
+++	return MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2);
++ }
++ 
++ static struct page_pool *mtk_create_page_pool(struct mtk_eth *eth,
+diff --git a/target/linux/generic/backport-5.15/713-v6.0-net-ethernet-mtk_eth_soc-move-gdma_to_ppe-and-ppe_ba.patch b/target/linux/generic/backport-5.15/713-v6.0-net-ethernet-mtk_eth_soc-move-gdma_to_ppe-and-ppe_ba.patch
+new file mode 100644
+index 0000000000..008e20416d
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/713-v6.0-net-ethernet-mtk_eth_soc-move-gdma_to_ppe-and-ppe_ba.patch
+@@ -0,0 +1,127 @@
++From patchwork Thu Sep  8 19:33:38 2022
++Content-Type: text/plain; charset="utf-8"
++MIME-Version: 1.0
++Content-Transfer-Encoding: 7bit
++X-Patchwork-Submitter: Lorenzo Bianconi <lorenzo@kernel.org>
++X-Patchwork-Id: 12970556
++X-Patchwork-Delegate: kuba@kernel.org
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++To: netdev@vger.kernel.org
++Cc: nbd@nbd.name, john@phrozen.org, sean.wang@mediatek.com,
++        Mark-MC.Lee@mediatek.com, davem@davemloft.net, edumazet@google.com,
++        kuba@kernel.org, pabeni@redhat.com, matthias.bgg@gmail.com,
++        linux-mediatek@lists.infradead.org, lorenzo.bianconi@redhat.com,
++        Bo.Jiao@mediatek.com, sujuan.chen@mediatek.com,
++        ryder.Lee@mediatek.com, evelyn.tsai@mediatek.com,
++        devicetree@vger.kernel.org, robh@kernel.org
++Subject: [PATCH net-next 03/12] net: ethernet: mtk_eth_soc: move gdma_to_ppe
++ and ppe_base definitions in mtk register map
++Date: Thu,  8 Sep 2022 21:33:37 +0200
++Message-Id: 
++ <95938fc9cbe0223714be2658a49ca58e9baace00.1662661555.git.lorenzo@kernel.org>
++X-Mailer: git-send-email 2.37.3
++In-Reply-To: <cover.1662661555.git.lorenzo@kernel.org>
++References: <cover.1662661555.git.lorenzo@kernel.org>
++MIME-Version: 1.0
++Precedence: bulk
++List-ID: <netdev.vger.kernel.org>
++X-Mailing-List: netdev@vger.kernel.org
++X-Patchwork-Delegate: kuba@kernel.org
++
++This is a preliminary patch to introduce mt7986 hw packet engine.
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 15 +++++++++++----
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h |  3 ++-
++ drivers/net/ethernet/mediatek/mtk_ppe.h     |  2 --
++ 3 files changed, 13 insertions(+), 7 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -73,6 +73,8 @@ static const struct mtk_reg_map mtk_reg_
++ 		.fq_blen	= 0x1b2c,
++ 	},
++ 	.gdm1_cnt		= 0x2400,
+++	.gdma_to_ppe0		= 0x4444,
+++	.ppe_base		= 0x0c00,
++ };
++ 
++ static const struct mtk_reg_map mt7628_reg_map = {
++@@ -126,6 +128,8 @@ static const struct mtk_reg_map mt7986_r
++ 		.fq_blen	= 0x472c,
++ 	},
++ 	.gdm1_cnt		= 0x1c00,
+++	.gdma_to_ppe0		= 0x3333,
+++	.ppe_base		= 0x2000,
++ };
++ 
++ /* strings used by ethtool */
++@@ -2924,6 +2928,7 @@ static int mtk_open(struct net_device *d
++ 
++ 	/* we run 2 netdevs on the same dma ring so we only bring it up once */
++ 	if (!refcount_read(&eth->dma_refcnt)) {
+++		const struct mtk_soc_data *soc = eth->soc;
++ 		u32 gdm_config = MTK_GDMA_TO_PDMA;
++ 		int err;
++ 
++@@ -2931,15 +2936,15 @@ static int mtk_open(struct net_device *d
++ 		if (err)
++ 			return err;
++ 
++-		if (eth->soc->offload_version && mtk_ppe_start(eth->ppe) == 0)
++-			gdm_config = MTK_GDMA_TO_PPE;
+++		if (soc->offload_version && mtk_ppe_start(eth->ppe) == 0)
+++			gdm_config = soc->reg_map->gdma_to_ppe0;
++ 
++ 		mtk_gdm_config(eth, gdm_config);
++ 
++ 		napi_enable(&eth->tx_napi);
++ 		napi_enable(&eth->rx_napi);
++ 		mtk_tx_irq_enable(eth, MTK_TX_DONE_INT);
++-		mtk_rx_irq_enable(eth, eth->soc->txrx.rx_irq_done_mask);
+++		mtk_rx_irq_enable(eth, soc->txrx.rx_irq_done_mask);
++ 		refcount_set(&eth->dma_refcnt, 1);
++ 	}
++ 	else
++@@ -4045,7 +4050,9 @@ static int mtk_probe(struct platform_dev
++ 	}
++ 
++ 	if (eth->soc->offload_version) {
++-		eth->ppe = mtk_ppe_init(eth, eth->base + MTK_ETH_PPE_BASE, 2);
+++		u32 ppe_addr = eth->soc->reg_map->ppe_base;
+++
+++		eth->ppe = mtk_ppe_init(eth, eth->base + ppe_addr, 2);
++ 		if (!eth->ppe) {
++ 			err = -ENOMEM;
++ 			goto err_free_dev;
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -105,7 +105,6 @@
++ #define MTK_GDMA_TCS_EN		BIT(21)
++ #define MTK_GDMA_UCS_EN		BIT(20)
++ #define MTK_GDMA_TO_PDMA	0x0
++-#define MTK_GDMA_TO_PPE		0x4444
++ #define MTK_GDMA_DROP_ALL       0x7777
++ 
++ /* Unicast Filter MAC Address Register - Low */
++@@ -952,6 +951,8 @@ struct mtk_reg_map {
++ 		u32	fq_blen;	/* fq free page buffer length */
++ 	} qdma;
++ 	u32	gdm1_cnt;
+++	u32	gdma_to_ppe0;
+++	u32	ppe_base;
++ };
++ 
++ /* struct mtk_eth_data -	This is the structure holding all differences
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
++@@ -8,8 +8,6 @@
++ #include <linux/bitfield.h>
++ #include <linux/rhashtable.h>
++ 
++-#define MTK_ETH_PPE_BASE		0xc00
++-
++ #define MTK_PPE_ENTRIES_SHIFT		3
++ #define MTK_PPE_ENTRIES			(1024 << MTK_PPE_ENTRIES_SHIFT)
++ #define MTK_PPE_HASH_MASK		(MTK_PPE_ENTRIES - 1)
+diff --git a/target/linux/generic/backport-5.15/714-v6.0-net-ethernet-mtk_eth_soc-move-ppe-table-hash-offset-.patch b/target/linux/generic/backport-5.15/714-v6.0-net-ethernet-mtk_eth_soc-move-ppe-table-hash-offset-.patch
+new file mode 100644
+index 0000000000..dd3bb15800
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/714-v6.0-net-ethernet-mtk_eth_soc-move-ppe-table-hash-offset-.patch
+@@ -0,0 +1,199 @@
++From patchwork Thu Sep  8 19:33:38 2022
++Content-Type: text/plain; charset="utf-8"
++MIME-Version: 1.0
++Content-Transfer-Encoding: 7bit
++X-Patchwork-Submitter: Lorenzo Bianconi <lorenzo@kernel.org>
++X-Patchwork-Id: 12970557
++X-Patchwork-Delegate: kuba@kernel.org
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++To: netdev@vger.kernel.org
++Cc: nbd@nbd.name, john@phrozen.org, sean.wang@mediatek.com,
++        Mark-MC.Lee@mediatek.com, davem@davemloft.net, edumazet@google.com,
++        kuba@kernel.org, pabeni@redhat.com, matthias.bgg@gmail.com,
++        linux-mediatek@lists.infradead.org, lorenzo.bianconi@redhat.com,
++        Bo.Jiao@mediatek.com, sujuan.chen@mediatek.com,
++        ryder.Lee@mediatek.com, evelyn.tsai@mediatek.com,
++        devicetree@vger.kernel.org, robh@kernel.org
++Subject: [PATCH net-next 04/12] net: ethernet: mtk_eth_soc: move ppe table
++ hash offset to mtk_soc_data structure
++Date: Thu,  8 Sep 2022 21:33:38 +0200
++Message-Id: 
++ <cc263ffeaa3e1d7314e36a4f941e96d38e41a6bf.1662661555.git.lorenzo@kernel.org>
++X-Mailer: git-send-email 2.37.3
++In-Reply-To: <cover.1662661555.git.lorenzo@kernel.org>
++References: <cover.1662661555.git.lorenzo@kernel.org>
++MIME-Version: 1.0
++Precedence: bulk
++List-ID: <netdev.vger.kernel.org>
++X-Mailing-List: netdev@vger.kernel.org
++X-Patchwork-Delegate: kuba@kernel.org
++
++This is a preliminary patch to introduce mt7986 hw packet engine.
++
++Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
++Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
++Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
++Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c |  4 ++++
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h |  2 ++
++ drivers/net/ethernet/mediatek/mtk_ppe.c     | 24 +++++++++++++++------
++ drivers/net/ethernet/mediatek/mtk_ppe.h     |  2 +-
++ 4 files changed, 25 insertions(+), 7 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -4148,6 +4148,7 @@ static const struct mtk_soc_data mt7621_
++ 	.required_clks = MT7621_CLKS_BITMAP,
++ 	.required_pctl = false,
++ 	.offload_version = 2,
+++	.hash_offset = 2,
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
++ 		.rxd_size = sizeof(struct mtk_rx_dma),
++@@ -4166,6 +4167,7 @@ static const struct mtk_soc_data mt7622_
++ 	.required_clks = MT7622_CLKS_BITMAP,
++ 	.required_pctl = false,
++ 	.offload_version = 2,
+++	.hash_offset = 2,
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
++ 		.rxd_size = sizeof(struct mtk_rx_dma),
++@@ -4183,6 +4185,7 @@ static const struct mtk_soc_data mt7623_
++ 	.required_clks = MT7623_CLKS_BITMAP,
++ 	.required_pctl = true,
++ 	.offload_version = 2,
+++	.hash_offset = 2,
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
++ 		.rxd_size = sizeof(struct mtk_rx_dma),
++@@ -4216,6 +4219,7 @@ static const struct mtk_soc_data mt7986_
++ 	.caps = MT7986_CAPS,
++ 	.required_clks = MT7986_CLKS_BITMAP,
++ 	.required_pctl = false,
+++	.hash_offset = 4,
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma_v2),
++ 		.rxd_size = sizeof(struct mtk_rx_dma_v2),
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -966,6 +966,7 @@ struct mtk_reg_map {
++  *				the target SoC
++  * @required_pctl		A bool value to show whether the SoC requires
++  *				the extra setup for those pins used by GMAC.
+++ * @hash_offset			Flow table hash offset.
++  * @txd_size			Tx DMA descriptor size.
++  * @rxd_size			Rx DMA descriptor size.
++  * @rx_irq_done_mask		Rx irq done register mask.
++@@ -980,6 +981,7 @@ struct mtk_soc_data {
++ 	u32		required_clks;
++ 	bool		required_pctl;
++ 	u8		offload_version;
+++	u8		hash_offset;
++ 	netdev_features_t hw_features;
++ 	struct {
++ 		u32	txd_size;
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
++@@ -88,7 +88,7 @@ static void mtk_ppe_cache_enable(struct
++ 		enable * MTK_PPE_CACHE_CTL_EN);
++ }
++ 
++-static u32 mtk_ppe_hash_entry(struct mtk_foe_entry *e)
+++static u32 mtk_ppe_hash_entry(struct mtk_eth *eth, struct mtk_foe_entry *e)
++ {
++ 	u32 hv1, hv2, hv3;
++ 	u32 hash;
++@@ -122,7 +122,7 @@ static u32 mtk_ppe_hash_entry(struct mtk
++ 	hash = (hash >> 24) | ((hash & 0xffffff) << 8);
++ 	hash ^= hv1 ^ hv2 ^ hv3;
++ 	hash ^= hash >> 16;
++-	hash <<= 1;
+++	hash <<= (ffs(eth->soc->hash_offset) - 1);
++ 	hash &= MTK_PPE_ENTRIES - 1;
++ 
++ 	return hash;
++@@ -540,15 +540,16 @@ mtk_foe_entry_commit_l2(struct mtk_ppe *
++ int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
++ {
++ 	int type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->data.ib1);
+++	const struct mtk_soc_data *soc = ppe->eth->soc;
++ 	u32 hash;
++ 
++ 	if (type == MTK_PPE_PKT_TYPE_BRIDGE)
++ 		return mtk_foe_entry_commit_l2(ppe, entry);
++ 
++-	hash = mtk_ppe_hash_entry(&entry->data);
+++	hash = mtk_ppe_hash_entry(ppe->eth, &entry->data);
++ 	entry->hash = 0xffff;
++ 	spin_lock_bh(&ppe_lock);
++-	hlist_add_head(&entry->list, &ppe->foe_flow[hash / 2]);
+++	hlist_add_head(&entry->list, &ppe->foe_flow[hash / soc->hash_offset]);
++ 	spin_unlock_bh(&ppe_lock);
++ 
++ 	return 0;
++@@ -558,6 +559,7 @@ static void
++ mtk_foe_entry_commit_subflow(struct mtk_ppe *ppe, struct mtk_flow_entry *entry,
++ 			     u16 hash)
++ {
+++	const struct mtk_soc_data *soc = ppe->eth->soc;
++ 	struct mtk_flow_entry *flow_info;
++ 	struct mtk_foe_entry foe, *hwe;
++ 	struct mtk_foe_mac_info *l2;
++@@ -572,7 +574,8 @@ mtk_foe_entry_commit_subflow(struct mtk_
++ 	flow_info->l2_data.base_flow = entry;
++ 	flow_info->type = MTK_FLOW_TYPE_L2_SUBFLOW;
++ 	flow_info->hash = hash;
++-	hlist_add_head(&flow_info->list, &ppe->foe_flow[hash / 2]);
+++	hlist_add_head(&flow_info->list,
+++		       &ppe->foe_flow[hash / soc->hash_offset]);
++ 	hlist_add_head(&flow_info->l2_data.list, &entry->l2_flows);
++ 
++ 	hwe = &ppe->foe_table[hash];
++@@ -596,7 +599,8 @@ mtk_foe_entry_commit_subflow(struct mtk_
++ 
++ void __mtk_ppe_check_skb(struct mtk_ppe *ppe, struct sk_buff *skb, u16 hash)
++ {
++-	struct hlist_head *head = &ppe->foe_flow[hash / 2];
+++	const struct mtk_soc_data *soc = ppe->eth->soc;
+++	struct hlist_head *head = &ppe->foe_flow[hash / soc->hash_offset];
++ 	struct mtk_foe_entry *hwe = &ppe->foe_table[hash];
++ 	struct mtk_flow_entry *entry;
++ 	struct mtk_foe_bridge key = {};
++@@ -680,9 +684,11 @@ int mtk_foe_entry_idle_time(struct mtk_p
++ struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base,
++ 		 int version)
++ {
+++	const struct mtk_soc_data *soc = eth->soc;
++ 	struct device *dev = eth->dev;
++ 	struct mtk_foe_entry *foe;
++ 	struct mtk_ppe *ppe;
+++	u32 foe_flow_size;
++ 
++ 	ppe = devm_kzalloc(dev, sizeof(*ppe), GFP_KERNEL);
++ 	if (!ppe)
++@@ -705,6 +711,12 @@ struct mtk_ppe *mtk_ppe_init(struct mtk_
++ 
++ 	ppe->foe_table = foe;
++ 
+++	foe_flow_size = (MTK_PPE_ENTRIES / soc->hash_offset) *
+++			sizeof(*ppe->foe_flow);
+++	ppe->foe_flow = devm_kzalloc(dev, foe_flow_size, GFP_KERNEL);
+++	if (!ppe->foe_flow)
+++		return NULL;
+++
++ 	mtk_ppe_debugfs_init(ppe);
++ 
++ 	return ppe;
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
++@@ -270,7 +270,7 @@ struct mtk_ppe {
++ 	dma_addr_t foe_phys;
++ 
++ 	u16 foe_check_time[MTK_PPE_ENTRIES];
++-	struct hlist_head foe_flow[MTK_PPE_ENTRIES / 2];
+++	struct hlist_head *foe_flow;
++ 
++ 	struct rhashtable l2_flows;
++ 
+diff --git a/target/linux/generic/backport-5.15/715-v6.0-net-ethernet-mtk_eth_soc-add-the-capability-to-run-m.patch b/target/linux/generic/backport-5.15/715-v6.0-net-ethernet-mtk_eth_soc-add-the-capability-to-run-m.patch
+new file mode 100644
+index 0000000000..0b207ff6a1
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/715-v6.0-net-ethernet-mtk_eth_soc-add-the-capability-to-run-m.patch
+@@ -0,0 +1,317 @@
++From patchwork Thu Sep  8 19:33:39 2022
++Content-Type: text/plain; charset="utf-8"
++MIME-Version: 1.0
++Content-Transfer-Encoding: 7bit
++X-Patchwork-Submitter: Lorenzo Bianconi <lorenzo@kernel.org>
++X-Patchwork-Id: 12970559
++X-Patchwork-Delegate: kuba@kernel.org
++Return-Path: <netdev-owner@kernel.org>
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++To: netdev@vger.kernel.org
++Cc: nbd@nbd.name, john@phrozen.org, sean.wang@mediatek.com,
++        Mark-MC.Lee@mediatek.com, davem@davemloft.net, edumazet@google.com,
++        kuba@kernel.org, pabeni@redhat.com, matthias.bgg@gmail.com,
++        linux-mediatek@lists.infradead.org, lorenzo.bianconi@redhat.com,
++        Bo.Jiao@mediatek.com, sujuan.chen@mediatek.com,
++        ryder.Lee@mediatek.com, evelyn.tsai@mediatek.com,
++        devicetree@vger.kernel.org, robh@kernel.org
++Subject: [PATCH net-next 05/12] net: ethernet: mtk_eth_soc: add the capability
++ to run multiple ppe
++Date: Thu,  8 Sep 2022 21:33:39 +0200
++Message-Id: 
++ <dd0254775390eb031c67c448df8b19e87df58558.1662661555.git.lorenzo@kernel.org>
++X-Mailer: git-send-email 2.37.3
++In-Reply-To: <cover.1662661555.git.lorenzo@kernel.org>
++References: <cover.1662661555.git.lorenzo@kernel.org>
++MIME-Version: 1.0
++Precedence: bulk
++List-ID: <netdev.vger.kernel.org>
++X-Mailing-List: netdev@vger.kernel.org
++X-Patchwork-Delegate: kuba@kernel.org
++
++mt7986 chipset support multiple packet engines for wlan <-> eth
++packet forwarding.
++
++Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
++Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
++Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
++Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c   | 35 ++++++++++++-------
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h   |  2 +-
++ drivers/net/ethernet/mediatek/mtk_ppe.c       | 14 +++++---
++ drivers/net/ethernet/mediatek/mtk_ppe.h       |  9 +++--
++ .../net/ethernet/mediatek/mtk_ppe_debugfs.c   |  8 ++---
++ .../net/ethernet/mediatek/mtk_ppe_offload.c   | 13 +++----
++ 6 files changed, 48 insertions(+), 33 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -1871,7 +1871,7 @@ static int mtk_poll_rx(struct napi_struc
++ 
++ 		reason = FIELD_GET(MTK_RXD4_PPE_CPU_REASON, trxd.rxd4);
++ 		if (reason == MTK_PPE_CPU_REASON_HIT_UNBIND_RATE_REACHED)
++-			mtk_ppe_check_skb(eth->ppe, skb, hash);
+++			mtk_ppe_check_skb(eth->ppe[0], skb, hash);
++ 
++ 		if (netdev->features & NETIF_F_HW_VLAN_CTAG_RX) {
++ 			if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
++@@ -2929,15 +2929,19 @@ static int mtk_open(struct net_device *d
++ 	/* we run 2 netdevs on the same dma ring so we only bring it up once */
++ 	if (!refcount_read(&eth->dma_refcnt)) {
++ 		const struct mtk_soc_data *soc = eth->soc;
++-		u32 gdm_config = MTK_GDMA_TO_PDMA;
+++		u32 gdm_config;
+++		int i;
++ 		int err;
++ 
++ 		err = mtk_start_dma(eth);
++ 		if (err)
++ 			return err;
++ 
++-		if (soc->offload_version && mtk_ppe_start(eth->ppe) == 0)
++-			gdm_config = soc->reg_map->gdma_to_ppe0;
+++		for (i = 0; i < ARRAY_SIZE(eth->ppe); i++)
+++			mtk_ppe_start(eth->ppe[i]);
+++
+++		gdm_config = soc->offload_version ? soc->reg_map->gdma_to_ppe0
+++						  : MTK_GDMA_TO_PDMA;
++ 
++ 		mtk_gdm_config(eth, gdm_config);
++ 
++@@ -2982,6 +2986,7 @@ static int mtk_stop(struct net_device *d
++ {
++ 	struct mtk_mac *mac = netdev_priv(dev);
++ 	struct mtk_eth *eth = mac->hw;
+++	int i;
++ 
++ 	phylink_stop(mac->phylink);
++ 
++@@ -3009,8 +3014,8 @@ static int mtk_stop(struct net_device *d
++ 
++ 	mtk_dma_free(eth);
++ 
++-	if (eth->soc->offload_version)
++-		mtk_ppe_stop(eth->ppe);
+++	for (i = 0; i < ARRAY_SIZE(eth->ppe); i++)
+++		mtk_ppe_stop(eth->ppe[i]);
++ 
++ 	return 0;
++ }
++@@ -4050,12 +4055,19 @@ static int mtk_probe(struct platform_dev
++ 	}
++ 
++ 	if (eth->soc->offload_version) {
++-		u32 ppe_addr = eth->soc->reg_map->ppe_base;
+++		u32 num_ppe;
++ 
++-		eth->ppe = mtk_ppe_init(eth, eth->base + ppe_addr, 2);
++-		if (!eth->ppe) {
++-			err = -ENOMEM;
++-			goto err_free_dev;
+++		num_ppe = MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2) ? 2 : 1;
+++		num_ppe = min_t(u32, ARRAY_SIZE(eth->ppe), num_ppe);
+++		for (i = 0; i < num_ppe; i++) {
+++			u32 ppe_addr = eth->soc->reg_map->ppe_base + i * 0x400;
+++
+++			eth->ppe[i] = mtk_ppe_init(eth, eth->base + ppe_addr,
+++						   eth->soc->offload_version, i);
+++			if (!eth->ppe[i]) {
+++				err = -ENOMEM;
+++				goto err_free_dev;
+++			}
++ 		}
++ 
++ 		err = mtk_eth_offload_init(eth);
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -1111,7 +1111,7 @@ struct mtk_eth {
++ 
++ 	int				ip_align;
++ 
++-	struct mtk_ppe			*ppe;
+++	struct mtk_ppe			*ppe[2];
++ 	struct rhashtable		flow_table;
++ 
++ 	struct bpf_prog			__rcu *prog;
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
++@@ -682,7 +682,7 @@ int mtk_foe_entry_idle_time(struct mtk_p
++ }
++ 
++ struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base,
++-		 int version)
+++			     int version, int index)
++ {
++ 	const struct mtk_soc_data *soc = eth->soc;
++ 	struct device *dev = eth->dev;
++@@ -717,7 +717,7 @@ struct mtk_ppe *mtk_ppe_init(struct mtk_
++ 	if (!ppe->foe_flow)
++ 		return NULL;
++ 
++-	mtk_ppe_debugfs_init(ppe);
+++	mtk_ppe_debugfs_init(ppe, index);
++ 
++ 	return ppe;
++ }
++@@ -738,10 +738,13 @@ static void mtk_ppe_init_foe_table(struc
++ 			ppe->foe_table[i + skip[k]].ib1 |= MTK_FOE_IB1_STATIC;
++ }
++ 
++-int mtk_ppe_start(struct mtk_ppe *ppe)
+++void mtk_ppe_start(struct mtk_ppe *ppe)
++ {
++ 	u32 val;
++ 
+++	if (!ppe)
+++		return;
+++
++ 	mtk_ppe_init_foe_table(ppe);
++ 	ppe_w32(ppe, MTK_PPE_TB_BASE, ppe->foe_phys);
++ 
++@@ -809,8 +812,6 @@ int mtk_ppe_start(struct mtk_ppe *ppe)
++ 	ppe_w32(ppe, MTK_PPE_GLO_CFG, val);
++ 
++ 	ppe_w32(ppe, MTK_PPE_DEFAULT_CPU_PORT, 0);
++-
++-	return 0;
++ }
++ 
++ int mtk_ppe_stop(struct mtk_ppe *ppe)
++@@ -818,6 +819,9 @@ int mtk_ppe_stop(struct mtk_ppe *ppe)
++ 	u32 val;
++ 	int i;
++ 
+++	if (!ppe)
+++		return 0;
+++
++ 	for (i = 0; i < MTK_PPE_ENTRIES; i++)
++ 		ppe->foe_table[i].ib1 = FIELD_PREP(MTK_FOE_IB1_STATE,
++ 						   MTK_FOE_STATE_INVALID);
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
++@@ -247,6 +247,7 @@ struct mtk_flow_entry {
++ 	};
++ 	u8 type;
++ 	s8 wed_index;
+++	u8 ppe_index;
++ 	u16 hash;
++ 	union {
++ 		struct mtk_foe_entry data;
++@@ -265,6 +266,7 @@ struct mtk_ppe {
++ 	struct device *dev;
++ 	void __iomem *base;
++ 	int version;
+++	char dirname[5];
++ 
++ 	struct mtk_foe_entry *foe_table;
++ 	dma_addr_t foe_phys;
++@@ -277,8 +279,9 @@ struct mtk_ppe {
++ 	void *acct_table;
++ };
++ 
++-struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base, int version);
++-int mtk_ppe_start(struct mtk_ppe *ppe);
+++struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base,
+++			     int version, int index);
+++void mtk_ppe_start(struct mtk_ppe *ppe);
++ int mtk_ppe_stop(struct mtk_ppe *ppe);
++ 
++ void __mtk_ppe_check_skb(struct mtk_ppe *ppe, struct sk_buff *skb, u16 hash);
++@@ -317,6 +320,6 @@ int mtk_foe_entry_set_wdma(struct mtk_fo
++ int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
++ void mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
++ int mtk_foe_entry_idle_time(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
++-int mtk_ppe_debugfs_init(struct mtk_ppe *ppe);
+++int mtk_ppe_debugfs_init(struct mtk_ppe *ppe, int index);
++ 
++ #endif
++--- a/drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c
++@@ -187,7 +187,7 @@ mtk_ppe_debugfs_foe_open_bind(struct ino
++ 			   inode->i_private);
++ }
++ 
++-int mtk_ppe_debugfs_init(struct mtk_ppe *ppe)
+++int mtk_ppe_debugfs_init(struct mtk_ppe *ppe, int index)
++ {
++ 	static const struct file_operations fops_all = {
++ 		.open = mtk_ppe_debugfs_foe_open_all,
++@@ -195,17 +195,17 @@ int mtk_ppe_debugfs_init(struct mtk_ppe
++ 		.llseek = seq_lseek,
++ 		.release = single_release,
++ 	};
++-
++ 	static const struct file_operations fops_bind = {
++ 		.open = mtk_ppe_debugfs_foe_open_bind,
++ 		.read = seq_read,
++ 		.llseek = seq_lseek,
++ 		.release = single_release,
++ 	};
++-
++ 	struct dentry *root;
++ 
++-	root = debugfs_create_dir("mtk_ppe", NULL);
+++	snprintf(ppe->dirname, sizeof(ppe->dirname), "ppe%d", index);
+++
+++	root = debugfs_create_dir(ppe->dirname, NULL);
++ 	if (!root)
++ 		return -ENOMEM;
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++@@ -434,7 +434,7 @@ mtk_flow_offload_replace(struct mtk_eth
++ 	memcpy(&entry->data, &foe, sizeof(entry->data));
++ 	entry->wed_index = wed_index;
++ 
++-	err = mtk_foe_entry_commit(eth->ppe, entry);
+++	err = mtk_foe_entry_commit(eth->ppe[entry->ppe_index], entry);
++ 	if (err < 0)
++ 		goto free;
++ 
++@@ -446,7 +446,7 @@ mtk_flow_offload_replace(struct mtk_eth
++ 	return 0;
++ 
++ clear:
++-	mtk_foe_entry_clear(eth->ppe, entry);
+++	mtk_foe_entry_clear(eth->ppe[entry->ppe_index], entry);
++ free:
++ 	kfree(entry);
++ 	if (wed_index >= 0)
++@@ -464,7 +464,7 @@ mtk_flow_offload_destroy(struct mtk_eth
++ 	if (!entry)
++ 		return -ENOENT;
++ 
++-	mtk_foe_entry_clear(eth->ppe, entry);
+++	mtk_foe_entry_clear(eth->ppe[entry->ppe_index], entry);
++ 	rhashtable_remove_fast(&eth->flow_table, &entry->node,
++ 			       mtk_flow_ht_params);
++ 	if (entry->wed_index >= 0)
++@@ -485,7 +485,7 @@ mtk_flow_offload_stats(struct mtk_eth *e
++ 	if (!entry)
++ 		return -ENOENT;
++ 
++-	idle = mtk_foe_entry_idle_time(eth->ppe, entry);
+++	idle = mtk_foe_entry_idle_time(eth->ppe[entry->ppe_index], entry);
++ 	f->stats.lastused = jiffies - idle * HZ;
++ 
++ 	return 0;
++@@ -537,7 +537,7 @@ mtk_eth_setup_tc_block(struct net_device
++ 	struct flow_block_cb *block_cb;
++ 	flow_setup_cb_t *cb;
++ 
++-	if (!eth->ppe || !eth->ppe->foe_table)
+++	if (!eth->soc->offload_version)
++ 		return -EOPNOTSUPP;
++ 
++ 	if (f->binder_type != FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS)
++@@ -589,8 +589,5 @@ int mtk_eth_setup_tc(struct net_device *
++ 
++ int mtk_eth_offload_init(struct mtk_eth *eth)
++ {
++-	if (!eth->ppe || !eth->ppe->foe_table)
++-		return 0;
++-
++ 	return rhashtable_init(&eth->flow_table, &mtk_flow_ht_params);
++ }
+diff --git a/target/linux/generic/backport-5.15/716-v6.0-net-ethernet-mtk_eth_soc-move-wdma_base-definitions-.patch b/target/linux/generic/backport-5.15/716-v6.0-net-ethernet-mtk_eth_soc-move-wdma_base-definitions-.patch
+new file mode 100644
+index 0000000000..e984f4b68b
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/716-v6.0-net-ethernet-mtk_eth_soc-move-wdma_base-definitions-.patch
+@@ -0,0 +1,80 @@
++From 0dcbe607cec32ccae23b02a641b8bd6191a328ae Mon Sep 17 00:00:00 2001
++Message-Id: <0dcbe607cec32ccae23b02a641b8bd6191a328ae.1662243796.git.lorenzo@kernel.org>
++In-Reply-To: <43a21841ce0175d29f23c34a65ceaaf9dd7eb8b7.1662243796.git.lorenzo@kernel.org>
++References: <43a21841ce0175d29f23c34a65ceaaf9dd7eb8b7.1662243796.git.lorenzo@kernel.org>
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Tue, 23 Aug 2022 23:09:05 +0200
++Subject: [PATCH net-next 2/4] net: ethernet: mtk_eth_soc: move wdma_base
++ definitions in mtk register map
++
++This is a preliminary patch to introduce mt7986 wed support.
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 16 ++++++++++------
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h |  4 +---
++ 2 files changed, 11 insertions(+), 9 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -75,6 +75,10 @@ static const struct mtk_reg_map mtk_reg_
++ 	.gdm1_cnt		= 0x2400,
++ 	.gdma_to_ppe0		= 0x4444,
++ 	.ppe_base		= 0x0c00,
+++	.wdma_base = {
+++		[0]		= 0x2800,
+++		[1]		= 0x2c00,
+++	},
++ };
++ 
++ static const struct mtk_reg_map mt7628_reg_map = {
++@@ -130,6 +134,10 @@ static const struct mtk_reg_map mt7986_r
++ 	.gdm1_cnt		= 0x1c00,
++ 	.gdma_to_ppe0		= 0x3333,
++ 	.ppe_base		= 0x2000,
+++	.wdma_base = {
+++		[0]		= 0x4800,
+++		[1]		= 0x4c00,
+++	},
++ };
++ 
++ /* strings used by ethtool */
++@@ -3967,16 +3975,12 @@ static int mtk_probe(struct platform_dev
++ 	for (i = 0;; i++) {
++ 		struct device_node *np = of_parse_phandle(pdev->dev.of_node,
++ 							  "mediatek,wed", i);
++-		static const u32 wdma_regs[] = {
++-			MTK_WDMA0_BASE,
++-			MTK_WDMA1_BASE
++-		};
++ 		void __iomem *wdma;
++ 
++-		if (!np || i >= ARRAY_SIZE(wdma_regs))
+++		if (!np || i >= ARRAY_SIZE(eth->soc->reg_map->wdma_base))
++ 			break;
++ 
++-		wdma = eth->base + wdma_regs[i];
+++		wdma = eth->base + eth->soc->reg_map->wdma_base[i];
++ 		mtk_wed_add_hw(np, eth, wdma, i);
++ 	}
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -268,9 +268,6 @@
++ #define TX_DMA_FPORT_MASK_V2	0xf
++ #define TX_DMA_SWC_V2		BIT(30)
++ 
++-#define MTK_WDMA0_BASE		0x2800
++-#define MTK_WDMA1_BASE		0x2c00
++-
++ /* QDMA descriptor txd4 */
++ #define TX_DMA_CHKSUM		(0x7 << 29)
++ #define TX_DMA_TSO		BIT(28)
++@@ -953,6 +950,7 @@ struct mtk_reg_map {
++ 	u32	gdm1_cnt;
++ 	u32	gdma_to_ppe0;
++ 	u32	ppe_base;
+++	u32	wdma_base[2];
++ };
++ 
++ /* struct mtk_eth_data -	This is the structure holding all differences
+diff --git a/target/linux/generic/backport-5.15/717-v6.0-net-ethernet-mtk_eth_soc-add-foe_entry_size-to-mtk_e.patch b/target/linux/generic/backport-5.15/717-v6.0-net-ethernet-mtk_eth_soc-add-foe_entry_size-to-mtk_e.patch
+new file mode 100644
+index 0000000000..c24bd8022c
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/717-v6.0-net-ethernet-mtk_eth_soc-add-foe_entry_size-to-mtk_e.patch
+@@ -0,0 +1,251 @@
++From e3c27d869fccc1f2b8d0b4cde4763ab223874e8c Mon Sep 17 00:00:00 2001
++Message-Id: <e3c27d869fccc1f2b8d0b4cde4763ab223874e8c.1662243796.git.lorenzo@kernel.org>
++In-Reply-To: <43a21841ce0175d29f23c34a65ceaaf9dd7eb8b7.1662243796.git.lorenzo@kernel.org>
++References: <43a21841ce0175d29f23c34a65ceaaf9dd7eb8b7.1662243796.git.lorenzo@kernel.org>
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Sun, 21 Aug 2022 17:51:17 +0200
++Subject: [PATCH net-next 3/4] net: ethernet: mtk_eth_soc: add foe_entry_size
++ to mtk_eth_soc
++
++Introduce foe_entry_size to mtk_eth_soc data structure since mt7986
++relies on a bigger mtk_foe_entry data structure.
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c   |  3 +
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h   | 10 ++++
++ drivers/net/ethernet/mediatek/mtk_ppe.c       | 55 +++++++++++--------
++ drivers/net/ethernet/mediatek/mtk_ppe.h       |  2 +-
++ .../net/ethernet/mediatek/mtk_ppe_debugfs.c   |  2 +-
++ 5 files changed, 48 insertions(+), 24 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -4165,6 +4165,7 @@ static const struct mtk_soc_data mt7621_
++ 	.required_pctl = false,
++ 	.offload_version = 2,
++ 	.hash_offset = 2,
+++	.foe_entry_size = sizeof(struct mtk_foe_entry),
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
++ 		.rxd_size = sizeof(struct mtk_rx_dma),
++@@ -4184,6 +4185,7 @@ static const struct mtk_soc_data mt7622_
++ 	.required_pctl = false,
++ 	.offload_version = 2,
++ 	.hash_offset = 2,
+++	.foe_entry_size = sizeof(struct mtk_foe_entry),
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
++ 		.rxd_size = sizeof(struct mtk_rx_dma),
++@@ -4202,6 +4204,7 @@ static const struct mtk_soc_data mt7623_
++ 	.required_pctl = true,
++ 	.offload_version = 2,
++ 	.hash_offset = 2,
+++	.foe_entry_size = sizeof(struct mtk_foe_entry),
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
++ 		.rxd_size = sizeof(struct mtk_rx_dma),
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -965,6 +965,7 @@ struct mtk_reg_map {
++  * @required_pctl		A bool value to show whether the SoC requires
++  *				the extra setup for those pins used by GMAC.
++  * @hash_offset			Flow table hash offset.
+++ * @foe_entry_size		Foe table entry size.
++  * @txd_size			Tx DMA descriptor size.
++  * @rxd_size			Rx DMA descriptor size.
++  * @rx_irq_done_mask		Rx irq done register mask.
++@@ -980,6 +981,7 @@ struct mtk_soc_data {
++ 	bool		required_pctl;
++ 	u8		offload_version;
++ 	u8		hash_offset;
+++	u16		foe_entry_size;
++ 	netdev_features_t hw_features;
++ 	struct {
++ 		u32	txd_size;
++@@ -1140,6 +1142,14 @@ struct mtk_mac {
++ /* the struct describing the SoC. these are declared in the soc_xyz.c files */
++ extern const struct of_device_id of_mtk_match[];
++ 
+++static inline struct mtk_foe_entry *
+++mtk_foe_get_entry(struct mtk_ppe *ppe, u16 hash)
+++{
+++	const struct mtk_soc_data *soc = ppe->eth->soc;
+++
+++	return ppe->foe_table + hash * soc->foe_entry_size;
+++}
+++
++ /* read the hardware status register */
++ void mtk_stats_update_mac(struct mtk_mac *mac);
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
++@@ -410,9 +410,10 @@ __mtk_foe_entry_clear(struct mtk_ppe *pp
++ 
++ 	hlist_del_init(&entry->list);
++ 	if (entry->hash != 0xffff) {
++-		ppe->foe_table[entry->hash].ib1 &= ~MTK_FOE_IB1_STATE;
++-		ppe->foe_table[entry->hash].ib1 |= FIELD_PREP(MTK_FOE_IB1_STATE,
++-							      MTK_FOE_STATE_BIND);
+++		struct mtk_foe_entry *hwe = mtk_foe_get_entry(ppe, entry->hash);
+++
+++		hwe->ib1 &= ~MTK_FOE_IB1_STATE;
+++		hwe->ib1 |= FIELD_PREP(MTK_FOE_IB1_STATE, MTK_FOE_STATE_BIND);
++ 		dma_wmb();
++ 	}
++ 	entry->hash = 0xffff;
++@@ -451,7 +452,7 @@ mtk_flow_entry_update_l2(struct mtk_ppe
++ 		int cur_idle;
++ 		u32 ib1;
++ 
++-		hwe = &ppe->foe_table[cur->hash];
+++		hwe = mtk_foe_get_entry(ppe, cur->hash);
++ 		ib1 = READ_ONCE(hwe->ib1);
++ 
++ 		if (FIELD_GET(MTK_FOE_IB1_STATE, ib1) != MTK_FOE_STATE_BIND) {
++@@ -473,8 +474,8 @@ mtk_flow_entry_update_l2(struct mtk_ppe
++ static void
++ mtk_flow_entry_update(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
++ {
+++	struct mtk_foe_entry foe = {};
++ 	struct mtk_foe_entry *hwe;
++-	struct mtk_foe_entry foe;
++ 
++ 	spin_lock_bh(&ppe_lock);
++ 
++@@ -486,8 +487,8 @@ mtk_flow_entry_update(struct mtk_ppe *pp
++ 	if (entry->hash == 0xffff)
++ 		goto out;
++ 
++-	hwe = &ppe->foe_table[entry->hash];
++-	memcpy(&foe, hwe, sizeof(foe));
+++	hwe = mtk_foe_get_entry(ppe, entry->hash);
+++	memcpy(&foe, hwe, ppe->eth->soc->foe_entry_size);
++ 	if (!mtk_flow_entry_match(entry, &foe)) {
++ 		entry->hash = 0xffff;
++ 		goto out;
++@@ -511,8 +512,8 @@ __mtk_foe_entry_commit(struct mtk_ppe *p
++ 	entry->ib1 &= ~MTK_FOE_IB1_BIND_TIMESTAMP;
++ 	entry->ib1 |= FIELD_PREP(MTK_FOE_IB1_BIND_TIMESTAMP, timestamp);
++ 
++-	hwe = &ppe->foe_table[hash];
++-	memcpy(&hwe->data, &entry->data, sizeof(hwe->data));
+++	hwe = mtk_foe_get_entry(ppe, hash);
+++	memcpy(&hwe->data, &entry->data, ppe->eth->soc->foe_entry_size);
++ 	wmb();
++ 	hwe->ib1 = entry->ib1;
++ 
++@@ -561,7 +562,7 @@ mtk_foe_entry_commit_subflow(struct mtk_
++ {
++ 	const struct mtk_soc_data *soc = ppe->eth->soc;
++ 	struct mtk_flow_entry *flow_info;
++-	struct mtk_foe_entry foe, *hwe;
+++	struct mtk_foe_entry foe = {}, *hwe;
++ 	struct mtk_foe_mac_info *l2;
++ 	u32 ib1_mask = MTK_FOE_IB1_PACKET_TYPE | MTK_FOE_IB1_UDP;
++ 	int type;
++@@ -578,8 +579,8 @@ mtk_foe_entry_commit_subflow(struct mtk_
++ 		       &ppe->foe_flow[hash / soc->hash_offset]);
++ 	hlist_add_head(&flow_info->l2_data.list, &entry->l2_flows);
++ 
++-	hwe = &ppe->foe_table[hash];
++-	memcpy(&foe, hwe, sizeof(foe));
+++	hwe = mtk_foe_get_entry(ppe, hash);
+++	memcpy(&foe, hwe, soc->foe_entry_size);
++ 	foe.ib1 &= ib1_mask;
++ 	foe.ib1 |= entry->data.ib1 & ~ib1_mask;
++ 
++@@ -601,7 +602,7 @@ void __mtk_ppe_check_skb(struct mtk_ppe
++ {
++ 	const struct mtk_soc_data *soc = ppe->eth->soc;
++ 	struct hlist_head *head = &ppe->foe_flow[hash / soc->hash_offset];
++-	struct mtk_foe_entry *hwe = &ppe->foe_table[hash];
+++	struct mtk_foe_entry *hwe = mtk_foe_get_entry(ppe, hash);
++ 	struct mtk_flow_entry *entry;
++ 	struct mtk_foe_bridge key = {};
++ 	struct hlist_node *n;
++@@ -686,9 +687,9 @@ struct mtk_ppe *mtk_ppe_init(struct mtk_
++ {
++ 	const struct mtk_soc_data *soc = eth->soc;
++ 	struct device *dev = eth->dev;
++-	struct mtk_foe_entry *foe;
++ 	struct mtk_ppe *ppe;
++ 	u32 foe_flow_size;
+++	void *foe;
++ 
++ 	ppe = devm_kzalloc(dev, sizeof(*ppe), GFP_KERNEL);
++ 	if (!ppe)
++@@ -704,7 +705,8 @@ struct mtk_ppe *mtk_ppe_init(struct mtk_
++ 	ppe->dev = dev;
++ 	ppe->version = version;
++ 
++-	foe = dmam_alloc_coherent(ppe->dev, MTK_PPE_ENTRIES * sizeof(*foe),
+++	foe = dmam_alloc_coherent(ppe->dev,
+++				  MTK_PPE_ENTRIES * soc->foe_entry_size,
++ 				  &ppe->foe_phys, GFP_KERNEL);
++ 	if (!foe)
++ 		return NULL;
++@@ -727,15 +729,21 @@ static void mtk_ppe_init_foe_table(struc
++ 	static const u8 skip[] = { 12, 25, 38, 51, 76, 89, 102 };
++ 	int i, k;
++ 
++-	memset(ppe->foe_table, 0, MTK_PPE_ENTRIES * sizeof(*ppe->foe_table));
+++	memset(ppe->foe_table, 0,
+++	       MTK_PPE_ENTRIES * ppe->eth->soc->foe_entry_size);
++ 
++ 	if (!IS_ENABLED(CONFIG_SOC_MT7621))
++ 		return;
++ 
++ 	/* skip all entries that cross the 1024 byte boundary */
++-	for (i = 0; i < MTK_PPE_ENTRIES; i += 128)
++-		for (k = 0; k < ARRAY_SIZE(skip); k++)
++-			ppe->foe_table[i + skip[k]].ib1 |= MTK_FOE_IB1_STATIC;
+++	for (i = 0; i < MTK_PPE_ENTRIES; i += 128) {
+++		for (k = 0; k < ARRAY_SIZE(skip); k++) {
+++			struct mtk_foe_entry *hwe;
+++
+++			hwe = mtk_foe_get_entry(ppe, i + skip[k]);
+++			hwe->ib1 |= MTK_FOE_IB1_STATIC;
+++		}
+++	}
++ }
++ 
++ void mtk_ppe_start(struct mtk_ppe *ppe)
++@@ -822,9 +830,12 @@ int mtk_ppe_stop(struct mtk_ppe *ppe)
++ 	if (!ppe)
++ 		return 0;
++ 
++-	for (i = 0; i < MTK_PPE_ENTRIES; i++)
++-		ppe->foe_table[i].ib1 = FIELD_PREP(MTK_FOE_IB1_STATE,
++-						   MTK_FOE_STATE_INVALID);
+++	for (i = 0; i < MTK_PPE_ENTRIES; i++) {
+++		struct mtk_foe_entry *hwe = mtk_foe_get_entry(ppe, i);
+++
+++		hwe->ib1 = FIELD_PREP(MTK_FOE_IB1_STATE,
+++				      MTK_FOE_STATE_INVALID);
+++	}
++ 
++ 	mtk_ppe_cache_enable(ppe, false);
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
++@@ -268,7 +268,7 @@ struct mtk_ppe {
++ 	int version;
++ 	char dirname[5];
++ 
++-	struct mtk_foe_entry *foe_table;
+++	void *foe_table;
++ 	dma_addr_t foe_phys;
++ 
++ 	u16 foe_check_time[MTK_PPE_ENTRIES];
++--- a/drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c
++@@ -79,7 +79,7 @@ mtk_ppe_debugfs_foe_show(struct seq_file
++ 	int i;
++ 
++ 	for (i = 0; i < MTK_PPE_ENTRIES; i++) {
++-		struct mtk_foe_entry *entry = &ppe->foe_table[i];
+++		struct mtk_foe_entry *entry = mtk_foe_get_entry(ppe, i);
++ 		struct mtk_foe_mac_info *l2;
++ 		struct mtk_flow_addr_info ai = {};
++ 		unsigned char h_source[ETH_ALEN];
+diff --git a/target/linux/generic/backport-5.15/718-v6.0-net-ethernet-mtk_eth_soc-fix-typo-in-__mtk_foe_entry.patch b/target/linux/generic/backport-5.15/718-v6.0-net-ethernet-mtk_eth_soc-fix-typo-in-__mtk_foe_entry.patch
+new file mode 100644
+index 0000000000..77daf6c8bf
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/718-v6.0-net-ethernet-mtk_eth_soc-fix-typo-in-__mtk_foe_entry.patch
+@@ -0,0 +1,27 @@
++From 12ff69304c83c679ca01ef3db963ab0db9de19fb Mon Sep 17 00:00:00 2001
++Message-Id: <12ff69304c83c679ca01ef3db963ab0db9de19fb.1662332102.git.lorenzo@kernel.org>
++In-Reply-To: <2a60545635c2705312299384f4e9fec2f2a3acd6.1662332102.git.lorenzo@kernel.org>
++References: <2a60545635c2705312299384f4e9fec2f2a3acd6.1662332102.git.lorenzo@kernel.org>
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Mon, 5 Sep 2022 00:43:43 +0200
++Subject: [PATCH net-next 2/6] net: ethernet: mtk_eth_soc: fix typo in
++ __mtk_foe_entry_clear
++
++Set ib1 state to MTK_FOE_STATE_UNBIND in __mtk_foe_entry_clear routine.
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_ppe.c | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
++@@ -413,7 +413,7 @@ __mtk_foe_entry_clear(struct mtk_ppe *pp
++ 		struct mtk_foe_entry *hwe = mtk_foe_get_entry(ppe, entry->hash);
++ 
++ 		hwe->ib1 &= ~MTK_FOE_IB1_STATE;
++-		hwe->ib1 |= FIELD_PREP(MTK_FOE_IB1_STATE, MTK_FOE_STATE_BIND);
+++		hwe->ib1 |= FIELD_PREP(MTK_FOE_IB1_STATE, MTK_FOE_STATE_UNBIND);
++ 		dma_wmb();
++ 	}
++ 	entry->hash = 0xffff;
+diff --git a/target/linux/generic/backport-5.15/719-v6.0-net-ethernet-mtk_eth_soc-check-max-allowed-value-in-.patch b/target/linux/generic/backport-5.15/719-v6.0-net-ethernet-mtk_eth_soc-check-max-allowed-value-in-.patch
+new file mode 100644
+index 0000000000..7ab6d486b2
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/719-v6.0-net-ethernet-mtk_eth_soc-check-max-allowed-value-in-.patch
+@@ -0,0 +1,28 @@
++From 4253e6e2b795a18ab534adcd5c313d3fc4150975 Mon Sep 17 00:00:00 2001
++Message-Id: <4253e6e2b795a18ab534adcd5c313d3fc4150975.1662332102.git.lorenzo@kernel.org>
++In-Reply-To: <2a60545635c2705312299384f4e9fec2f2a3acd6.1662332102.git.lorenzo@kernel.org>
++References: <2a60545635c2705312299384f4e9fec2f2a3acd6.1662332102.git.lorenzo@kernel.org>
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Mon, 5 Sep 2022 00:48:52 +0200
++Subject: [PATCH net-next 3/6] net: ethernet: mtk_eth_soc: check max allowed
++ value in mtk_ppe_check_skb
++
++Check theoretical OOB accesses in mtk_ppe_check_skb routine
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_ppe.h | 3 +++
++ 1 file changed, 3 insertions(+)
++
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
++@@ -294,6 +294,9 @@ mtk_ppe_check_skb(struct mtk_ppe *ppe, s
++ 	if (!ppe)
++ 		return;
++ 
+++	if (hash > MTK_PPE_HASH_MASK)
+++		return;
+++
++ 	now = (u16)jiffies;
++ 	diff = now - ppe->foe_check_time[hash];
++ 	if (diff < HZ / 10)
+diff --git a/target/linux/generic/backport-5.15/720-v6.0-net-ethernet-mtk_eth_wed-add-mtk_wed_configure_irq-a.patch b/target/linux/generic/backport-5.15/720-v6.0-net-ethernet-mtk_eth_wed-add-mtk_wed_configure_irq-a.patch
+new file mode 100644
+index 0000000000..a8c88daf1f
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/720-v6.0-net-ethernet-mtk_eth_wed-add-mtk_wed_configure_irq-a.patch
+@@ -0,0 +1,189 @@
++From e5ecb4f619197b93fa682d722452dc8412864cdb Mon Sep 17 00:00:00 2001
++Message-Id: <e5ecb4f619197b93fa682d722452dc8412864cdb.1662886033.git.lorenzo@kernel.org>
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Fri, 26 Aug 2022 01:12:57 +0200
++Subject: [PATCH net-next 1/5] net: ethernet: mtk_eth_wed: add
++ mtk_wed_configure_irq and mtk_wed_dma_{enable/disable}
++
++Introduce mtk_wed_configure_irq, mtk_wed_dma_enable and mtk_wed_dma_disable
++utility routines.
++This is a preliminary patch to introduce mt7986 wed support.
++
++Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
++Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
++Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
++Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_wed.c      | 87 +++++++++++++-------
++ drivers/net/ethernet/mediatek/mtk_wed_regs.h |  6 +-
++ 2 files changed, 64 insertions(+), 29 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_wed.c
+++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
++@@ -237,9 +237,30 @@ mtk_wed_set_ext_int(struct mtk_wed_devic
++ }
++ 
++ static void
++-mtk_wed_stop(struct mtk_wed_device *dev)
+++mtk_wed_dma_disable(struct mtk_wed_device *dev)
++ {
+++	wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
+++		MTK_WED_WPDMA_GLO_CFG_TX_DRV_EN |
+++		MTK_WED_WPDMA_GLO_CFG_RX_DRV_EN);
+++
+++	wed_clr(dev, MTK_WED_WDMA_GLO_CFG, MTK_WED_WDMA_GLO_CFG_RX_DRV_EN);
+++
+++	wed_clr(dev, MTK_WED_GLO_CFG,
+++		MTK_WED_GLO_CFG_TX_DMA_EN |
+++		MTK_WED_GLO_CFG_RX_DMA_EN);
+++
++ 	regmap_write(dev->hw->mirror, dev->hw->index * 4, 0);
+++	wdma_m32(dev, MTK_WDMA_GLO_CFG,
+++		 MTK_WDMA_GLO_CFG_TX_DMA_EN |
+++		 MTK_WDMA_GLO_CFG_RX_INFO1_PRERES |
+++		 MTK_WDMA_GLO_CFG_RX_INFO2_PRERES |
+++		 MTK_WDMA_GLO_CFG_RX_INFO3_PRERES, 0);
+++}
+++
+++static void
+++mtk_wed_stop(struct mtk_wed_device *dev)
+++{
+++	mtk_wed_dma_disable(dev);
++ 	mtk_wed_set_ext_int(dev, false);
++ 
++ 	wed_clr(dev, MTK_WED_CTRL,
++@@ -252,15 +273,6 @@ mtk_wed_stop(struct mtk_wed_device *dev)
++ 	wdma_w32(dev, MTK_WDMA_INT_MASK, 0);
++ 	wdma_w32(dev, MTK_WDMA_INT_GRP2, 0);
++ 	wed_w32(dev, MTK_WED_WPDMA_INT_MASK, 0);
++-
++-	wed_clr(dev, MTK_WED_GLO_CFG,
++-		MTK_WED_GLO_CFG_TX_DMA_EN |
++-		MTK_WED_GLO_CFG_RX_DMA_EN);
++-	wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
++-		MTK_WED_WPDMA_GLO_CFG_TX_DRV_EN |
++-		MTK_WED_WPDMA_GLO_CFG_RX_DRV_EN);
++-	wed_clr(dev, MTK_WED_WDMA_GLO_CFG,
++-		MTK_WED_WDMA_GLO_CFG_RX_DRV_EN);
++ }
++ 
++ static void
++@@ -313,7 +325,10 @@ mtk_wed_hw_init_early(struct mtk_wed_dev
++ 	      MTK_WED_WDMA_GLO_CFG_IDLE_DMAD_SUPPLY;
++ 	wed_m32(dev, MTK_WED_WDMA_GLO_CFG, mask, set);
++ 
++-	wdma_set(dev, MTK_WDMA_GLO_CFG, MTK_WDMA_GLO_CFG_RX_INFO_PRERES);
+++	wdma_set(dev, MTK_WDMA_GLO_CFG,
+++		 MTK_WDMA_GLO_CFG_RX_INFO1_PRERES |
+++		 MTK_WDMA_GLO_CFG_RX_INFO2_PRERES |
+++		 MTK_WDMA_GLO_CFG_RX_INFO3_PRERES);
++ 
++ 	offset = dev->hw->index ? 0x04000400 : 0;
++ 	wed_w32(dev, MTK_WED_WDMA_OFFSET0, 0x2a042a20 + offset);
++@@ -520,43 +535,38 @@ mtk_wed_wdma_ring_setup(struct mtk_wed_d
++ }
++ 
++ static void
++-mtk_wed_start(struct mtk_wed_device *dev, u32 irq_mask)
+++mtk_wed_configure_irq(struct mtk_wed_device *dev, u32 irq_mask)
++ {
++-	u32 wdma_mask;
++-	u32 val;
++-	int i;
++-
++-	for (i = 0; i < ARRAY_SIZE(dev->tx_wdma); i++)
++-		if (!dev->tx_wdma[i].desc)
++-			mtk_wed_wdma_ring_setup(dev, i, 16);
++-
++-	wdma_mask = FIELD_PREP(MTK_WDMA_INT_MASK_RX_DONE, GENMASK(1, 0));
++-
++-	mtk_wed_hw_init(dev);
+++	u32 wdma_mask = FIELD_PREP(MTK_WDMA_INT_MASK_RX_DONE, GENMASK(1, 0));
++ 
+++	/* wed control cr set */
++ 	wed_set(dev, MTK_WED_CTRL,
++ 		MTK_WED_CTRL_WDMA_INT_AGENT_EN |
++ 		MTK_WED_CTRL_WPDMA_INT_AGENT_EN |
++ 		MTK_WED_CTRL_WED_TX_BM_EN |
++ 		MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
++ 
++-	wed_w32(dev, MTK_WED_PCIE_INT_TRIGGER, MTK_WED_PCIE_INT_TRIGGER_STATUS);
+++	wed_w32(dev, MTK_WED_PCIE_INT_TRIGGER,
+++		MTK_WED_PCIE_INT_TRIGGER_STATUS);
++ 
++ 	wed_w32(dev, MTK_WED_WPDMA_INT_TRIGGER,
++ 		MTK_WED_WPDMA_INT_TRIGGER_RX_DONE |
++ 		MTK_WED_WPDMA_INT_TRIGGER_TX_DONE);
++ 
++-	wed_set(dev, MTK_WED_WPDMA_INT_CTRL,
++-		MTK_WED_WPDMA_INT_CTRL_SUBRT_ADV);
++-
+++	/* initail wdma interrupt agent */
++ 	wed_w32(dev, MTK_WED_WDMA_INT_TRIGGER, wdma_mask);
++ 	wed_clr(dev, MTK_WED_WDMA_INT_CTRL, wdma_mask);
++ 
++ 	wdma_w32(dev, MTK_WDMA_INT_MASK, wdma_mask);
++ 	wdma_w32(dev, MTK_WDMA_INT_GRP2, wdma_mask);
++-
++ 	wed_w32(dev, MTK_WED_WPDMA_INT_MASK, irq_mask);
++ 	wed_w32(dev, MTK_WED_INT_MASK, irq_mask);
+++}
+++
+++static void
+++mtk_wed_dma_enable(struct mtk_wed_device *dev)
+++{
+++	wed_set(dev, MTK_WED_WPDMA_INT_CTRL, MTK_WED_WPDMA_INT_CTRL_SUBRT_ADV);
++ 
++ 	wed_set(dev, MTK_WED_GLO_CFG,
++ 		MTK_WED_GLO_CFG_TX_DMA_EN |
++@@ -567,6 +577,26 @@ mtk_wed_start(struct mtk_wed_device *dev
++ 	wed_set(dev, MTK_WED_WDMA_GLO_CFG,
++ 		MTK_WED_WDMA_GLO_CFG_RX_DRV_EN);
++ 
+++	wdma_set(dev, MTK_WDMA_GLO_CFG,
+++		 MTK_WDMA_GLO_CFG_TX_DMA_EN |
+++		 MTK_WDMA_GLO_CFG_RX_INFO1_PRERES |
+++		 MTK_WDMA_GLO_CFG_RX_INFO2_PRERES |
+++		 MTK_WDMA_GLO_CFG_RX_INFO3_PRERES);
+++}
+++
+++static void
+++mtk_wed_start(struct mtk_wed_device *dev, u32 irq_mask)
+++{
+++	u32 val;
+++	int i;
+++
+++	for (i = 0; i < ARRAY_SIZE(dev->tx_wdma); i++)
+++		if (!dev->tx_wdma[i].desc)
+++			mtk_wed_wdma_ring_setup(dev, i, 16);
+++
+++	mtk_wed_hw_init(dev);
+++	mtk_wed_configure_irq(dev, irq_mask);
+++
++ 	mtk_wed_set_ext_int(dev, true);
++ 	val = dev->wlan.wpdma_phys |
++ 	      MTK_PCIE_MIRROR_MAP_EN |
++@@ -577,6 +607,7 @@ mtk_wed_start(struct mtk_wed_device *dev
++ 	val |= BIT(0);
++ 	regmap_write(dev->hw->mirror, dev->hw->index * 4, val);
++ 
+++	mtk_wed_dma_enable(dev);
++ 	dev->running = true;
++ }
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_wed_regs.h
+++++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
++@@ -224,7 +224,11 @@ struct mtk_wdma_desc {
++ #define MTK_WDMA_RING_RX(_n)				(0x100 + (_n) * 0x10)
++ 
++ #define MTK_WDMA_GLO_CFG				0x204
++-#define MTK_WDMA_GLO_CFG_RX_INFO_PRERES			GENMASK(28, 26)
+++#define MTK_WDMA_GLO_CFG_TX_DMA_EN			BIT(0)
+++#define MTK_WDMA_GLO_CFG_RX_DMA_EN			BIT(2)
+++#define MTK_WDMA_GLO_CFG_RX_INFO3_PRERES		BIT(26)
+++#define MTK_WDMA_GLO_CFG_RX_INFO2_PRERES		BIT(27)
+++#define MTK_WDMA_GLO_CFG_RX_INFO1_PRERES		BIT(28)
++ 
++ #define MTK_WDMA_RESET_IDX				0x208
++ #define MTK_WDMA_RESET_IDX_TX				GENMASK(3, 0)
+diff --git a/target/linux/generic/backport-5.15/721-v6.0-net-ethernet-mtk_eth_wed-add-wed-support-for-mt7986-.patch b/target/linux/generic/backport-5.15/721-v6.0-net-ethernet-mtk_eth_wed-add-wed-support-for-mt7986-.patch
+new file mode 100644
+index 0000000000..71bfcdbac8
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/721-v6.0-net-ethernet-mtk_eth_wed-add-wed-support-for-mt7986-.patch
+@@ -0,0 +1,942 @@
++From 463a71af080fbc77339bee2037fb1e081e3824f7 Mon Sep 17 00:00:00 2001
++Message-Id: <463a71af080fbc77339bee2037fb1e081e3824f7.1662886034.git.lorenzo@kernel.org>
++In-Reply-To: <e5ecb4f619197b93fa682d722452dc8412864cdb.1662886033.git.lorenzo@kernel.org>
++References: <e5ecb4f619197b93fa682d722452dc8412864cdb.1662886033.git.lorenzo@kernel.org>
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Sat, 27 Aug 2022 16:15:14 +0200
++Subject: [PATCH net-next 2/5] net: ethernet: mtk_eth_wed: add wed support for
++ mt7986 chipset
++
++Introduce Wireless Etherne Dispatcher support on transmission side
++for mt7986 chipset
++
++Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
++Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
++Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
++Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c   |  34 +-
++ drivers/net/ethernet/mediatek/mtk_wed.c       | 371 ++++++++++++++----
++ drivers/net/ethernet/mediatek/mtk_wed.h       |   8 +-
++ .../net/ethernet/mediatek/mtk_wed_debugfs.c   |   3 +
++ drivers/net/ethernet/mediatek/mtk_wed_regs.h  |  81 +++-
++ include/linux/soc/mediatek/mtk_wed.h          |   8 +
++ 6 files changed, 408 insertions(+), 97 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -3892,6 +3892,7 @@ void mtk_eth_set_dma_device(struct mtk_e
++ 
++ static int mtk_probe(struct platform_device *pdev)
++ {
+++	struct resource *res = NULL;
++ 	struct device_node *mac_np;
++ 	struct mtk_eth *eth;
++ 	int err, i;
++@@ -3972,16 +3973,31 @@ static int mtk_probe(struct platform_dev
++ 		}
++ 	}
++ 
++-	for (i = 0;; i++) {
++-		struct device_node *np = of_parse_phandle(pdev->dev.of_node,
++-							  "mediatek,wed", i);
++-		void __iomem *wdma;
++-
++-		if (!np || i >= ARRAY_SIZE(eth->soc->reg_map->wdma_base))
++-			break;
++-
++-		wdma = eth->base + eth->soc->reg_map->wdma_base[i];
++-		mtk_wed_add_hw(np, eth, wdma, i);
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+++		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+++		if (!res)
+++			return -EINVAL;
+++	}
+++
+++	if (eth->soc->offload_version) {
+++		for (i = 0;; i++) {
+++			struct device_node *np;
+++			phys_addr_t wdma_phy;
+++			u32 wdma_base;
+++
+++			if (i >= ARRAY_SIZE(eth->soc->reg_map->wdma_base))
+++				break;
+++
+++			np = of_parse_phandle(pdev->dev.of_node,
+++					      "mediatek,wed", i);
+++			if (!np)
+++				break;
+++
+++			wdma_base = eth->soc->reg_map->wdma_base[i];
+++			wdma_phy = res ? res->start + wdma_base : 0;
+++			mtk_wed_add_hw(np, eth, eth->base + wdma_base,
+++				       wdma_phy, i);
+++		}
++ 	}
++ 
++ 	for (i = 0; i < 3; i++) {
++--- a/drivers/net/ethernet/mediatek/mtk_wed.c
+++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
++@@ -25,6 +25,11 @@
++ 
++ #define MTK_WED_TX_RING_SIZE		2048
++ #define MTK_WED_WDMA_RING_SIZE		1024
+++#define MTK_WED_MAX_GROUP_SIZE		0x100
+++#define MTK_WED_VLD_GROUP_SIZE		0x40
+++#define MTK_WED_PER_GROUP_PKT		128
+++
+++#define MTK_WED_FBUF_SIZE		128
++ 
++ static struct mtk_wed_hw *hw_list[2];
++ static DEFINE_MUTEX(hw_lock);
++@@ -150,10 +155,17 @@ mtk_wed_buffer_alloc(struct mtk_wed_devi
++ 
++ 			desc->buf0 = cpu_to_le32(buf_phys);
++ 			desc->buf1 = cpu_to_le32(buf_phys + txd_size);
++-			ctrl = FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN0, txd_size) |
++-			       FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN1,
++-					  MTK_WED_BUF_SIZE - txd_size) |
++-			       MTK_WDMA_DESC_CTRL_LAST_SEG1;
+++
+++			if (dev->hw->version == 1)
+++				ctrl = FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN0, txd_size) |
+++				       FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN1,
+++						  MTK_WED_BUF_SIZE - txd_size) |
+++				       MTK_WDMA_DESC_CTRL_LAST_SEG1;
+++			else
+++				ctrl = FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN0, txd_size) |
+++				       FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN1_V2,
+++						  MTK_WED_BUF_SIZE - txd_size) |
+++				       MTK_WDMA_DESC_CTRL_LAST_SEG0;
++ 			desc->ctrl = cpu_to_le32(ctrl);
++ 			desc->info = 0;
++ 			desc++;
++@@ -209,7 +221,7 @@ mtk_wed_free_ring(struct mtk_wed_device
++ 	if (!ring->desc)
++ 		return;
++ 
++-	dma_free_coherent(dev->hw->dev, ring->size * sizeof(*ring->desc),
+++	dma_free_coherent(dev->hw->dev, ring->size * ring->desc_size,
++ 			  ring->desc, ring->desc_phys);
++ }
++ 
++@@ -229,6 +241,14 @@ mtk_wed_set_ext_int(struct mtk_wed_devic
++ {
++ 	u32 mask = MTK_WED_EXT_INT_STATUS_ERROR_MASK;
++ 
+++	if (dev->hw->version == 1)
+++		mask |= MTK_WED_EXT_INT_STATUS_TX_DRV_R_RESP_ERR;
+++	else
+++		mask |= MTK_WED_EXT_INT_STATUS_RX_FBUF_LO_TH |
+++			MTK_WED_EXT_INT_STATUS_RX_FBUF_HI_TH |
+++			MTK_WED_EXT_INT_STATUS_RX_DRV_COHERENT |
+++			MTK_WED_EXT_INT_STATUS_TX_DMA_W_RESP_ERR;
+++
++ 	if (!dev->hw->num_flows)
++ 		mask &= ~MTK_WED_EXT_INT_STATUS_TKID_WO_PYLD;
++ 
++@@ -237,6 +257,20 @@ mtk_wed_set_ext_int(struct mtk_wed_devic
++ }
++ 
++ static void
+++mtk_wed_set_512_support(struct mtk_wed_device *dev, bool enable)
+++{
+++	if (enable) {
+++		wed_w32(dev, MTK_WED_TXDP_CTRL, MTK_WED_TXDP_DW9_OVERWR);
+++		wed_w32(dev, MTK_WED_TXP_DW1,
+++			FIELD_PREP(MTK_WED_WPDMA_WRITE_TXP, 0x0103));
+++	} else {
+++		wed_w32(dev, MTK_WED_TXP_DW1,
+++			FIELD_PREP(MTK_WED_WPDMA_WRITE_TXP, 0x0100));
+++		wed_clr(dev, MTK_WED_TXDP_CTRL, MTK_WED_TXDP_DW9_OVERWR);
+++	}
+++}
+++
+++static void
++ mtk_wed_dma_disable(struct mtk_wed_device *dev)
++ {
++ 	wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
++@@ -249,12 +283,22 @@ mtk_wed_dma_disable(struct mtk_wed_devic
++ 		MTK_WED_GLO_CFG_TX_DMA_EN |
++ 		MTK_WED_GLO_CFG_RX_DMA_EN);
++ 
++-	regmap_write(dev->hw->mirror, dev->hw->index * 4, 0);
++ 	wdma_m32(dev, MTK_WDMA_GLO_CFG,
++ 		 MTK_WDMA_GLO_CFG_TX_DMA_EN |
++ 		 MTK_WDMA_GLO_CFG_RX_INFO1_PRERES |
++-		 MTK_WDMA_GLO_CFG_RX_INFO2_PRERES |
++-		 MTK_WDMA_GLO_CFG_RX_INFO3_PRERES, 0);
+++		 MTK_WDMA_GLO_CFG_RX_INFO2_PRERES, 0);
+++
+++	if (dev->hw->version == 1) {
+++		regmap_write(dev->hw->mirror, dev->hw->index * 4, 0);
+++		wdma_m32(dev, MTK_WDMA_GLO_CFG,
+++			 MTK_WDMA_GLO_CFG_RX_INFO3_PRERES, 0);
+++	} else {
+++		wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
+++			MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_PKT_PROC |
+++			MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_CRX_SYNC);
+++
+++		mtk_wed_set_512_support(dev, false);
+++	}
++ }
++ 
++ static void
++@@ -293,7 +337,7 @@ mtk_wed_detach(struct mtk_wed_device *de
++ 	mtk_wed_free_buffer(dev);
++ 	mtk_wed_free_tx_rings(dev);
++ 
++-	if (of_dma_is_coherent(wlan_node))
+++	if (of_dma_is_coherent(wlan_node) && hw->hifsys)
++ 		regmap_update_bits(hw->hifsys, HIFSYS_DMA_AG_MAP,
++ 				   BIT(hw->index), BIT(hw->index));
++ 
++@@ -308,14 +352,69 @@ mtk_wed_detach(struct mtk_wed_device *de
++ 	mutex_unlock(&hw_lock);
++ }
++ 
+++#define PCIE_BASE_ADDR0		0x11280000
+++static void
+++mtk_wed_bus_init(struct mtk_wed_device *dev)
+++{
+++	struct device_node *np = dev->hw->eth->dev->of_node;
+++	struct regmap *regs;
+++	u32 val;
+++
+++	regs = syscon_regmap_lookup_by_phandle(np, "mediatek,wed-pcie");
+++	if (IS_ERR(regs))
+++		return;
+++
+++	regmap_update_bits(regs, 0, BIT(0), BIT(0));
+++
+++	wed_w32(dev, MTK_WED_PCIE_INT_CTRL,
+++		FIELD_PREP(MTK_WED_PCIE_INT_CTRL_POLL_EN, 2));
+++
+++	/* pcie interrupt control: pola/source selection */
+++	wed_set(dev, MTK_WED_PCIE_INT_CTRL,
+++		MTK_WED_PCIE_INT_CTRL_MSK_EN_POLA |
+++		FIELD_PREP(MTK_WED_PCIE_INT_CTRL_SRC_SEL, 1));
+++	wed_r32(dev, MTK_WED_PCIE_INT_CTRL);
+++
+++	val = wed_r32(dev, MTK_WED_PCIE_CFG_INTM);
+++	val = wed_r32(dev, MTK_WED_PCIE_CFG_BASE);
+++	wed_w32(dev, MTK_WED_PCIE_CFG_INTM, PCIE_BASE_ADDR0 | 0x180);
+++	wed_w32(dev, MTK_WED_PCIE_CFG_BASE, PCIE_BASE_ADDR0 | 0x184);
+++
+++	val = wed_r32(dev, MTK_WED_PCIE_CFG_INTM);
+++	val = wed_r32(dev, MTK_WED_PCIE_CFG_BASE);
+++
+++	/* pcie interrupt status trigger register */
+++	wed_w32(dev, MTK_WED_PCIE_INT_TRIGGER, BIT(24));
+++	wed_r32(dev, MTK_WED_PCIE_INT_TRIGGER);
+++
+++	/* pola setting */
+++	val = wed_r32(dev, MTK_WED_PCIE_INT_CTRL);
+++	wed_set(dev, MTK_WED_PCIE_INT_CTRL, MTK_WED_PCIE_INT_CTRL_MSK_EN_POLA);
+++}
+++
+++static void
+++mtk_wed_set_wpdma(struct mtk_wed_device *dev)
+++{
+++	if (dev->hw->version == 1) {
+++		wed_w32(dev, MTK_WED_WPDMA_CFG_BASE,  dev->wlan.wpdma_phys);
+++	} else {
+++		mtk_wed_bus_init(dev);
+++
+++		wed_w32(dev, MTK_WED_WPDMA_CFG_BASE,  dev->wlan.wpdma_int);
+++		wed_w32(dev, MTK_WED_WPDMA_CFG_INT_MASK,  dev->wlan.wpdma_mask);
+++		wed_w32(dev, MTK_WED_WPDMA_CFG_TX,  dev->wlan.wpdma_tx);
+++		wed_w32(dev, MTK_WED_WPDMA_CFG_TX_FREE,  dev->wlan.wpdma_txfree);
+++	}
+++}
+++
++ static void
++ mtk_wed_hw_init_early(struct mtk_wed_device *dev)
++ {
++ 	u32 mask, set;
++-	u32 offset;
++ 
++ 	mtk_wed_stop(dev);
++ 	mtk_wed_reset(dev, MTK_WED_RESET_WED);
+++	mtk_wed_set_wpdma(dev);
++ 
++ 	mask = MTK_WED_WDMA_GLO_CFG_BT_SIZE |
++ 	       MTK_WED_WDMA_GLO_CFG_DYNAMIC_DMAD_RECYCLE |
++@@ -325,17 +424,33 @@ mtk_wed_hw_init_early(struct mtk_wed_dev
++ 	      MTK_WED_WDMA_GLO_CFG_IDLE_DMAD_SUPPLY;
++ 	wed_m32(dev, MTK_WED_WDMA_GLO_CFG, mask, set);
++ 
++-	wdma_set(dev, MTK_WDMA_GLO_CFG,
++-		 MTK_WDMA_GLO_CFG_RX_INFO1_PRERES |
++-		 MTK_WDMA_GLO_CFG_RX_INFO2_PRERES |
++-		 MTK_WDMA_GLO_CFG_RX_INFO3_PRERES);
++-
++-	offset = dev->hw->index ? 0x04000400 : 0;
++-	wed_w32(dev, MTK_WED_WDMA_OFFSET0, 0x2a042a20 + offset);
++-	wed_w32(dev, MTK_WED_WDMA_OFFSET1, 0x29002800 + offset);
+++	if (dev->hw->version == 1) {
+++		u32 offset = dev->hw->index ? 0x04000400 : 0;
++ 
++-	wed_w32(dev, MTK_WED_PCIE_CFG_BASE, MTK_PCIE_BASE(dev->hw->index));
++-	wed_w32(dev, MTK_WED_WPDMA_CFG_BASE, dev->wlan.wpdma_phys);
+++		wdma_set(dev, MTK_WDMA_GLO_CFG,
+++			 MTK_WDMA_GLO_CFG_RX_INFO1_PRERES |
+++			 MTK_WDMA_GLO_CFG_RX_INFO2_PRERES |
+++			 MTK_WDMA_GLO_CFG_RX_INFO3_PRERES);
+++
+++		wed_w32(dev, MTK_WED_WDMA_OFFSET0, 0x2a042a20 + offset);
+++		wed_w32(dev, MTK_WED_WDMA_OFFSET1, 0x29002800 + offset);
+++		wed_w32(dev, MTK_WED_PCIE_CFG_BASE,
+++			MTK_PCIE_BASE(dev->hw->index));
+++	} else {
+++		wed_w32(dev, MTK_WED_WDMA_CFG_BASE, dev->hw->wdma_phy);
+++		wed_set(dev, MTK_WED_CTRL, MTK_WED_CTRL_ETH_DMAD_FMT);
+++		wed_w32(dev, MTK_WED_WDMA_OFFSET0,
+++			FIELD_PREP(MTK_WED_WDMA_OFST0_GLO_INTS,
+++				   MTK_WDMA_INT_STATUS) |
+++			FIELD_PREP(MTK_WED_WDMA_OFST0_GLO_CFG,
+++				   MTK_WDMA_GLO_CFG));
+++
+++		wed_w32(dev, MTK_WED_WDMA_OFFSET1,
+++			FIELD_PREP(MTK_WED_WDMA_OFST1_TX_CTRL,
+++				   MTK_WDMA_RING_TX(0)) |
+++			FIELD_PREP(MTK_WED_WDMA_OFST1_RX_CTRL,
+++				   MTK_WDMA_RING_RX(0)));
+++	}
++ }
++ 
++ static void
++@@ -355,37 +470,65 @@ mtk_wed_hw_init(struct mtk_wed_device *d
++ 
++ 	wed_w32(dev, MTK_WED_TX_BM_BASE, dev->buf_ring.desc_phys);
++ 
++-	wed_w32(dev, MTK_WED_TX_BM_TKID,
++-		FIELD_PREP(MTK_WED_TX_BM_TKID_START,
++-			   dev->wlan.token_start) |
++-		FIELD_PREP(MTK_WED_TX_BM_TKID_END,
++-			   dev->wlan.token_start + dev->wlan.nbuf - 1));
++-
++ 	wed_w32(dev, MTK_WED_TX_BM_BUF_LEN, MTK_WED_PKT_SIZE);
++ 
++-	wed_w32(dev, MTK_WED_TX_BM_DYN_THR,
++-		FIELD_PREP(MTK_WED_TX_BM_DYN_THR_LO, 1) |
++-		MTK_WED_TX_BM_DYN_THR_HI);
+++	if (dev->hw->version == 1) {
+++		wed_w32(dev, MTK_WED_TX_BM_TKID,
+++			FIELD_PREP(MTK_WED_TX_BM_TKID_START,
+++				   dev->wlan.token_start) |
+++			FIELD_PREP(MTK_WED_TX_BM_TKID_END,
+++				   dev->wlan.token_start +
+++				   dev->wlan.nbuf - 1));
+++		wed_w32(dev, MTK_WED_TX_BM_DYN_THR,
+++			FIELD_PREP(MTK_WED_TX_BM_DYN_THR_LO, 1) |
+++			MTK_WED_TX_BM_DYN_THR_HI);
+++	} else {
+++		wed_w32(dev, MTK_WED_TX_BM_TKID_V2,
+++			FIELD_PREP(MTK_WED_TX_BM_TKID_START,
+++				   dev->wlan.token_start) |
+++			FIELD_PREP(MTK_WED_TX_BM_TKID_END,
+++				   dev->wlan.token_start +
+++				   dev->wlan.nbuf - 1));
+++		wed_w32(dev, MTK_WED_TX_BM_DYN_THR,
+++			FIELD_PREP(MTK_WED_TX_BM_DYN_THR_LO_V2, 0) |
+++			MTK_WED_TX_BM_DYN_THR_HI_V2);
+++		wed_w32(dev, MTK_WED_TX_TKID_CTRL,
+++			MTK_WED_TX_TKID_CTRL_PAUSE |
+++			FIELD_PREP(MTK_WED_TX_TKID_CTRL_VLD_GRP_NUM,
+++				   dev->buf_ring.size / 128) |
+++			FIELD_PREP(MTK_WED_TX_TKID_CTRL_RSV_GRP_NUM,
+++				   dev->buf_ring.size / 128));
+++		wed_w32(dev, MTK_WED_TX_TKID_DYN_THR,
+++			FIELD_PREP(MTK_WED_TX_TKID_DYN_THR_LO, 0) |
+++			MTK_WED_TX_TKID_DYN_THR_HI);
+++	}
++ 
++ 	mtk_wed_reset(dev, MTK_WED_RESET_TX_BM);
++ 
++-	wed_set(dev, MTK_WED_CTRL,
++-		MTK_WED_CTRL_WED_TX_BM_EN |
++-		MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
+++	if (dev->hw->version == 1)
+++		wed_set(dev, MTK_WED_CTRL,
+++			MTK_WED_CTRL_WED_TX_BM_EN |
+++			MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
+++	else
+++		wed_clr(dev, MTK_WED_TX_TKID_CTRL, MTK_WED_TX_TKID_CTRL_PAUSE);
++ 
++ 	wed_clr(dev, MTK_WED_TX_BM_CTRL, MTK_WED_TX_BM_CTRL_PAUSE);
++ }
++ 
++ static void
++-mtk_wed_ring_reset(struct mtk_wdma_desc *desc, int size)
+++mtk_wed_ring_reset(struct mtk_wed_ring *ring, int size)
++ {
+++	void *head = (void *)ring->desc;
++ 	int i;
++ 
++ 	for (i = 0; i < size; i++) {
++-		desc[i].buf0 = 0;
++-		desc[i].ctrl = cpu_to_le32(MTK_WDMA_DESC_CTRL_DMA_DONE);
++-		desc[i].buf1 = 0;
++-		desc[i].info = 0;
+++		struct mtk_wdma_desc *desc;
+++
+++		desc = (struct mtk_wdma_desc *)(head + i * ring->desc_size);
+++		desc->buf0 = 0;
+++		desc->ctrl = cpu_to_le32(MTK_WDMA_DESC_CTRL_DMA_DONE);
+++		desc->buf1 = 0;
+++		desc->info = 0;
++ 	}
++ }
++ 
++@@ -436,12 +579,10 @@ mtk_wed_reset_dma(struct mtk_wed_device
++ 	int i;
++ 
++ 	for (i = 0; i < ARRAY_SIZE(dev->tx_ring); i++) {
++-		struct mtk_wdma_desc *desc = dev->tx_ring[i].desc;
++-
++-		if (!desc)
+++		if (!dev->tx_ring[i].desc)
++ 			continue;
++ 
++-		mtk_wed_ring_reset(desc, MTK_WED_TX_RING_SIZE);
+++		mtk_wed_ring_reset(&dev->tx_ring[i], MTK_WED_TX_RING_SIZE);
++ 	}
++ 
++ 	if (mtk_wed_poll_busy(dev))
++@@ -498,16 +639,16 @@ mtk_wed_reset_dma(struct mtk_wed_device
++ 
++ static int
++ mtk_wed_ring_alloc(struct mtk_wed_device *dev, struct mtk_wed_ring *ring,
++-		   int size)
+++		   int size, u32 desc_size)
++ {
++-	ring->desc = dma_alloc_coherent(dev->hw->dev,
++-					size * sizeof(*ring->desc),
+++	ring->desc = dma_alloc_coherent(dev->hw->dev, size * desc_size,
++ 					&ring->desc_phys, GFP_KERNEL);
++ 	if (!ring->desc)
++ 		return -ENOMEM;
++ 
+++	ring->desc_size = desc_size;
++ 	ring->size = size;
++-	mtk_wed_ring_reset(ring->desc, size);
+++	mtk_wed_ring_reset(ring, size);
++ 
++ 	return 0;
++ }
++@@ -515,9 +656,10 @@ mtk_wed_ring_alloc(struct mtk_wed_device
++ static int
++ mtk_wed_wdma_ring_setup(struct mtk_wed_device *dev, int idx, int size)
++ {
+++	u32 desc_size = sizeof(struct mtk_wdma_desc) * dev->hw->version;
++ 	struct mtk_wed_ring *wdma = &dev->tx_wdma[idx];
++ 
++-	if (mtk_wed_ring_alloc(dev, wdma, MTK_WED_WDMA_RING_SIZE))
+++	if (mtk_wed_ring_alloc(dev, wdma, MTK_WED_WDMA_RING_SIZE, desc_size))
++ 		return -ENOMEM;
++ 
++ 	wdma_w32(dev, MTK_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_BASE,
++@@ -546,16 +688,41 @@ mtk_wed_configure_irq(struct mtk_wed_dev
++ 		MTK_WED_CTRL_WED_TX_BM_EN |
++ 		MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
++ 
++-	wed_w32(dev, MTK_WED_PCIE_INT_TRIGGER,
++-		MTK_WED_PCIE_INT_TRIGGER_STATUS);
+++	if (dev->hw->version == 1) {
+++		wed_w32(dev, MTK_WED_PCIE_INT_TRIGGER,
+++			MTK_WED_PCIE_INT_TRIGGER_STATUS);
+++
+++		wed_w32(dev, MTK_WED_WPDMA_INT_TRIGGER,
+++			MTK_WED_WPDMA_INT_TRIGGER_RX_DONE |
+++			MTK_WED_WPDMA_INT_TRIGGER_TX_DONE);
++ 
++-	wed_w32(dev, MTK_WED_WPDMA_INT_TRIGGER,
++-		MTK_WED_WPDMA_INT_TRIGGER_RX_DONE |
++-		MTK_WED_WPDMA_INT_TRIGGER_TX_DONE);
+++		wed_clr(dev, MTK_WED_WDMA_INT_CTRL, wdma_mask);
+++	} else {
+++		/* initail tx interrupt trigger */
+++		wed_w32(dev, MTK_WED_WPDMA_INT_CTRL_TX,
+++			MTK_WED_WPDMA_INT_CTRL_TX0_DONE_EN |
+++			MTK_WED_WPDMA_INT_CTRL_TX0_DONE_CLR |
+++			MTK_WED_WPDMA_INT_CTRL_TX1_DONE_EN |
+++			MTK_WED_WPDMA_INT_CTRL_TX1_DONE_CLR |
+++			FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_TX0_DONE_TRIG,
+++				   dev->wlan.tx_tbit[0]) |
+++			FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_TX1_DONE_TRIG,
+++				   dev->wlan.tx_tbit[1]));
+++
+++		/* initail txfree interrupt trigger */
+++		wed_w32(dev, MTK_WED_WPDMA_INT_CTRL_TX_FREE,
+++			MTK_WED_WPDMA_INT_CTRL_TX_FREE_DONE_EN |
+++			MTK_WED_WPDMA_INT_CTRL_TX_FREE_DONE_CLR |
+++			FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_TX_FREE_DONE_TRIG,
+++				   dev->wlan.txfree_tbit));
+++
+++		wed_w32(dev, MTK_WED_WDMA_INT_CLR, wdma_mask);
+++		wed_set(dev, MTK_WED_WDMA_INT_CTRL,
+++			FIELD_PREP(MTK_WED_WDMA_INT_CTRL_POLL_SRC_SEL,
+++				   dev->wdma_idx));
+++	}
++ 
++-	/* initail wdma interrupt agent */
++ 	wed_w32(dev, MTK_WED_WDMA_INT_TRIGGER, wdma_mask);
++-	wed_clr(dev, MTK_WED_WDMA_INT_CTRL, wdma_mask);
++ 
++ 	wdma_w32(dev, MTK_WDMA_INT_MASK, wdma_mask);
++ 	wdma_w32(dev, MTK_WDMA_INT_GRP2, wdma_mask);
++@@ -580,14 +747,28 @@ mtk_wed_dma_enable(struct mtk_wed_device
++ 	wdma_set(dev, MTK_WDMA_GLO_CFG,
++ 		 MTK_WDMA_GLO_CFG_TX_DMA_EN |
++ 		 MTK_WDMA_GLO_CFG_RX_INFO1_PRERES |
++-		 MTK_WDMA_GLO_CFG_RX_INFO2_PRERES |
++-		 MTK_WDMA_GLO_CFG_RX_INFO3_PRERES);
+++		 MTK_WDMA_GLO_CFG_RX_INFO2_PRERES);
+++
+++	if (dev->hw->version == 1) {
+++		wdma_set(dev, MTK_WDMA_GLO_CFG,
+++			 MTK_WDMA_GLO_CFG_RX_INFO3_PRERES);
+++	} else {
+++		wed_set(dev, MTK_WED_WPDMA_CTRL,
+++			MTK_WED_WPDMA_CTRL_SDL1_FIXED);
+++
+++		wed_set(dev, MTK_WED_WPDMA_GLO_CFG,
+++			MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_PKT_PROC |
+++			MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_CRX_SYNC);
+++
+++		wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
+++			MTK_WED_WPDMA_GLO_CFG_TX_TKID_KEEP |
+++			MTK_WED_WPDMA_GLO_CFG_TX_DMAD_DW3_PREV);
+++	}
++ }
++ 
++ static void
++ mtk_wed_start(struct mtk_wed_device *dev, u32 irq_mask)
++ {
++-	u32 val;
++ 	int i;
++ 
++ 	for (i = 0; i < ARRAY_SIZE(dev->tx_wdma); i++)
++@@ -598,14 +779,17 @@ mtk_wed_start(struct mtk_wed_device *dev
++ 	mtk_wed_configure_irq(dev, irq_mask);
++ 
++ 	mtk_wed_set_ext_int(dev, true);
++-	val = dev->wlan.wpdma_phys |
++-	      MTK_PCIE_MIRROR_MAP_EN |
++-	      FIELD_PREP(MTK_PCIE_MIRROR_MAP_WED_ID, dev->hw->index);
++-
++-	if (dev->hw->index)
++-		val |= BIT(1);
++-	val |= BIT(0);
++-	regmap_write(dev->hw->mirror, dev->hw->index * 4, val);
+++
+++	if (dev->hw->version == 1) {
+++		u32 val = dev->wlan.wpdma_phys | MTK_PCIE_MIRROR_MAP_EN |
+++			  FIELD_PREP(MTK_PCIE_MIRROR_MAP_WED_ID,
+++				     dev->hw->index);
+++
+++		val |= BIT(0) | (BIT(1) * !!dev->hw->index);
+++		regmap_write(dev->hw->mirror, dev->hw->index * 4, val);
+++	} else {
+++		mtk_wed_set_512_support(dev, true);
+++	}
++ 
++ 	mtk_wed_dma_enable(dev);
++ 	dev->running = true;
++@@ -639,7 +823,9 @@ mtk_wed_attach(struct mtk_wed_device *de
++ 		goto out;
++ 	}
++ 
++-	dev_info(&dev->wlan.pci_dev->dev, "attaching wed device %d\n", hw->index);
+++	dev_info(&dev->wlan.pci_dev->dev,
+++		 "attaching wed device %d version %d\n",
+++		 hw->index, hw->version);
++ 
++ 	dev->hw = hw;
++ 	dev->dev = hw->dev;
++@@ -657,7 +843,9 @@ mtk_wed_attach(struct mtk_wed_device *de
++ 	}
++ 
++ 	mtk_wed_hw_init_early(dev);
++-	regmap_update_bits(hw->hifsys, HIFSYS_DMA_AG_MAP, BIT(hw->index), 0);
+++	if (hw->hifsys)
+++		regmap_update_bits(hw->hifsys, HIFSYS_DMA_AG_MAP,
+++				   BIT(hw->index), 0);
++ 
++ out:
++ 	mutex_unlock(&hw_lock);
++@@ -684,7 +872,8 @@ mtk_wed_tx_ring_setup(struct mtk_wed_dev
++ 
++ 	BUG_ON(idx >= ARRAY_SIZE(dev->tx_ring));
++ 
++-	if (mtk_wed_ring_alloc(dev, ring, MTK_WED_TX_RING_SIZE))
+++	if (mtk_wed_ring_alloc(dev, ring, MTK_WED_TX_RING_SIZE,
+++			       sizeof(*ring->desc)))
++ 		return -ENOMEM;
++ 
++ 	if (mtk_wed_wdma_ring_setup(dev, idx, MTK_WED_WDMA_RING_SIZE))
++@@ -711,21 +900,21 @@ static int
++ mtk_wed_txfree_ring_setup(struct mtk_wed_device *dev, void __iomem *regs)
++ {
++ 	struct mtk_wed_ring *ring = &dev->txfree_ring;
++-	int i;
+++	int i, index = dev->hw->version == 1;
++ 
++ 	/*
++ 	 * For txfree event handling, the same DMA ring is shared between WED
++ 	 * and WLAN. The WLAN driver accesses the ring index registers through
++ 	 * WED
++ 	 */
++-	ring->reg_base = MTK_WED_RING_RX(1);
+++	ring->reg_base = MTK_WED_RING_RX(index);
++ 	ring->wpdma = regs;
++ 
++ 	for (i = 0; i < 12; i += 4) {
++ 		u32 val = readl(regs + i);
++ 
++-		wed_w32(dev, MTK_WED_RING_RX(1) + i, val);
++-		wed_w32(dev, MTK_WED_WPDMA_RING_RX(1) + i, val);
+++		wed_w32(dev, MTK_WED_RING_RX(index) + i, val);
+++		wed_w32(dev, MTK_WED_WPDMA_RING_RX(index) + i, val);
++ 	}
++ 
++ 	return 0;
++@@ -734,11 +923,19 @@ mtk_wed_txfree_ring_setup(struct mtk_wed
++ static u32
++ mtk_wed_irq_get(struct mtk_wed_device *dev, u32 mask)
++ {
++-	u32 val;
+++	u32 val, ext_mask = MTK_WED_EXT_INT_STATUS_ERROR_MASK;
+++
+++	if (dev->hw->version == 1)
+++		ext_mask |= MTK_WED_EXT_INT_STATUS_TX_DRV_R_RESP_ERR;
+++	else
+++		ext_mask |= MTK_WED_EXT_INT_STATUS_RX_FBUF_LO_TH |
+++			    MTK_WED_EXT_INT_STATUS_RX_FBUF_HI_TH |
+++			    MTK_WED_EXT_INT_STATUS_RX_DRV_COHERENT |
+++			    MTK_WED_EXT_INT_STATUS_TX_DMA_W_RESP_ERR;
++ 
++ 	val = wed_r32(dev, MTK_WED_EXT_INT_STATUS);
++ 	wed_w32(dev, MTK_WED_EXT_INT_STATUS, val);
++-	val &= MTK_WED_EXT_INT_STATUS_ERROR_MASK;
+++	val &= ext_mask;
++ 	if (!dev->hw->num_flows)
++ 		val &= ~MTK_WED_EXT_INT_STATUS_TKID_WO_PYLD;
++ 	if (val && net_ratelimit())
++@@ -813,7 +1010,8 @@ out:
++ }
++ 
++ void mtk_wed_add_hw(struct device_node *np, struct mtk_eth *eth,
++-		    void __iomem *wdma, int index)
+++		    void __iomem *wdma, phys_addr_t wdma_phy,
+++		    int index)
++ {
++ 	static const struct mtk_wed_ops wed_ops = {
++ 		.attach = mtk_wed_attach,
++@@ -860,26 +1058,33 @@ void mtk_wed_add_hw(struct device_node *
++ 	hw = kzalloc(sizeof(*hw), GFP_KERNEL);
++ 	if (!hw)
++ 		goto unlock;
+++
++ 	hw->node = np;
++ 	hw->regs = regs;
++ 	hw->eth = eth;
++ 	hw->dev = &pdev->dev;
+++	hw->wdma_phy = wdma_phy;
++ 	hw->wdma = wdma;
++ 	hw->index = index;
++ 	hw->irq = irq;
++-	hw->mirror = syscon_regmap_lookup_by_phandle(eth_np,
++-						     "mediatek,pcie-mirror");
++-	hw->hifsys = syscon_regmap_lookup_by_phandle(eth_np,
++-						     "mediatek,hifsys");
++-	if (IS_ERR(hw->mirror) || IS_ERR(hw->hifsys)) {
++-		kfree(hw);
++-		goto unlock;
++-	}
+++	hw->version = MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2) ? 2 : 1;
++ 
++-	if (!index) {
++-		regmap_write(hw->mirror, 0, 0);
++-		regmap_write(hw->mirror, 4, 0);
+++	if (hw->version == 1) {
+++		hw->mirror = syscon_regmap_lookup_by_phandle(eth_np,
+++				"mediatek,pcie-mirror");
+++		hw->hifsys = syscon_regmap_lookup_by_phandle(eth_np,
+++				"mediatek,hifsys");
+++		if (IS_ERR(hw->mirror) || IS_ERR(hw->hifsys)) {
+++			kfree(hw);
+++			goto unlock;
+++		}
+++
+++		if (!index) {
+++			regmap_write(hw->mirror, 0, 0);
+++			regmap_write(hw->mirror, 4, 0);
+++		}
++ 	}
+++
++ 	mtk_wed_hw_add_debugfs(hw);
++ 
++ 	hw_list[index] = hw;
++--- a/drivers/net/ethernet/mediatek/mtk_wed.h
+++++ b/drivers/net/ethernet/mediatek/mtk_wed.h
++@@ -18,11 +18,13 @@ struct mtk_wed_hw {
++ 	struct regmap *hifsys;
++ 	struct device *dev;
++ 	void __iomem *wdma;
+++	phys_addr_t wdma_phy;
++ 	struct regmap *mirror;
++ 	struct dentry *debugfs_dir;
++ 	struct mtk_wed_device *wed_dev;
++ 	u32 debugfs_reg;
++ 	u32 num_flows;
+++	u8 version;
++ 	char dirname[5];
++ 	int irq;
++ 	int index;
++@@ -101,14 +103,16 @@ wpdma_txfree_w32(struct mtk_wed_device *
++ }
++ 
++ void mtk_wed_add_hw(struct device_node *np, struct mtk_eth *eth,
++-		    void __iomem *wdma, int index);
+++		    void __iomem *wdma, phys_addr_t wdma_phy,
+++		    int index);
++ void mtk_wed_exit(void);
++ int mtk_wed_flow_add(int index);
++ void mtk_wed_flow_remove(int index);
++ #else
++ static inline void
++ mtk_wed_add_hw(struct device_node *np, struct mtk_eth *eth,
++-	       void __iomem *wdma, int index)
+++	       void __iomem *wdma, phys_addr_t wdma_phy,
+++	       int index)
++ {
++ }
++ static inline void
++--- a/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
+++++ b/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
++@@ -116,6 +116,9 @@ wed_txinfo_show(struct seq_file *s, void
++ 		DUMP_WDMA(WDMA_GLO_CFG),
++ 		DUMP_WDMA_RING(WDMA_RING_RX(0)),
++ 		DUMP_WDMA_RING(WDMA_RING_RX(1)),
+++
+++		DUMP_STR("TX FREE"),
+++		DUMP_WED(WED_RX_MIB(0)),
++ 	};
++ 	struct mtk_wed_hw *hw = s->private;
++ 	struct mtk_wed_device *dev = hw->wed_dev;
++--- a/drivers/net/ethernet/mediatek/mtk_wed_regs.h
+++++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
++@@ -5,6 +5,7 @@
++ #define __MTK_WED_REGS_H
++ 
++ #define MTK_WDMA_DESC_CTRL_LEN1			GENMASK(14, 0)
+++#define MTK_WDMA_DESC_CTRL_LEN1_V2		GENMASK(13, 0)
++ #define MTK_WDMA_DESC_CTRL_LAST_SEG1		BIT(15)
++ #define MTK_WDMA_DESC_CTRL_BURST		BIT(16)
++ #define MTK_WDMA_DESC_CTRL_LEN0			GENMASK(29, 16)
++@@ -41,6 +42,7 @@ struct mtk_wdma_desc {
++ #define MTK_WED_CTRL_RESERVE_EN				BIT(12)
++ #define MTK_WED_CTRL_RESERVE_BUSY			BIT(13)
++ #define MTK_WED_CTRL_FINAL_DIDX_READ			BIT(24)
+++#define MTK_WED_CTRL_ETH_DMAD_FMT			BIT(25)
++ #define MTK_WED_CTRL_MIB_READ_CLEAR			BIT(28)
++ 
++ #define MTK_WED_EXT_INT_STATUS				0x020
++@@ -57,7 +59,8 @@ struct mtk_wdma_desc {
++ #define MTK_WED_EXT_INT_STATUS_RX_DRV_INIT_WDMA_EN	BIT(19)
++ #define MTK_WED_EXT_INT_STATUS_RX_DRV_BM_DMAD_COHERENT	BIT(20)
++ #define MTK_WED_EXT_INT_STATUS_TX_DRV_R_RESP_ERR	BIT(21)
++-#define MTK_WED_EXT_INT_STATUS_TX_DRV_W_RESP_ERR	BIT(22)
+++#define MTK_WED_EXT_INT_STATUS_TX_DMA_R_RESP_ERR	BIT(22)
+++#define MTK_WED_EXT_INT_STATUS_TX_DMA_W_RESP_ERR	BIT(23)
++ #define MTK_WED_EXT_INT_STATUS_RX_DRV_DMA_RECYCLE	BIT(24)
++ #define MTK_WED_EXT_INT_STATUS_ERROR_MASK		(MTK_WED_EXT_INT_STATUS_TF_LEN_ERR | \
++ 							 MTK_WED_EXT_INT_STATUS_TKID_WO_PYLD | \
++@@ -65,8 +68,7 @@ struct mtk_wdma_desc {
++ 							 MTK_WED_EXT_INT_STATUS_RX_DRV_R_RESP_ERR | \
++ 							 MTK_WED_EXT_INT_STATUS_RX_DRV_W_RESP_ERR | \
++ 							 MTK_WED_EXT_INT_STATUS_RX_DRV_INIT_WDMA_EN | \
++-							 MTK_WED_EXT_INT_STATUS_TX_DRV_R_RESP_ERR | \
++-							 MTK_WED_EXT_INT_STATUS_TX_DRV_W_RESP_ERR)
+++							 MTK_WED_EXT_INT_STATUS_TX_DMA_R_RESP_ERR)
++ 
++ #define MTK_WED_EXT_INT_MASK				0x028
++ 
++@@ -81,6 +83,7 @@ struct mtk_wdma_desc {
++ #define MTK_WED_TX_BM_BASE				0x084
++ 
++ #define MTK_WED_TX_BM_TKID				0x088
+++#define MTK_WED_TX_BM_TKID_V2				0x0c8
++ #define MTK_WED_TX_BM_TKID_START			GENMASK(15, 0)
++ #define MTK_WED_TX_BM_TKID_END				GENMASK(31, 16)
++ 
++@@ -94,7 +97,25 @@ struct mtk_wdma_desc {
++ 
++ #define MTK_WED_TX_BM_DYN_THR				0x0a0
++ #define MTK_WED_TX_BM_DYN_THR_LO			GENMASK(6, 0)
+++#define MTK_WED_TX_BM_DYN_THR_LO_V2			GENMASK(8, 0)
++ #define MTK_WED_TX_BM_DYN_THR_HI			GENMASK(22, 16)
+++#define MTK_WED_TX_BM_DYN_THR_HI_V2			GENMASK(24, 16)
+++
+++#define MTK_WED_TX_TKID_CTRL				0x0c0
+++#define MTK_WED_TX_TKID_CTRL_VLD_GRP_NUM		GENMASK(6, 0)
+++#define MTK_WED_TX_TKID_CTRL_RSV_GRP_NUM		GENMASK(22, 16)
+++#define MTK_WED_TX_TKID_CTRL_PAUSE			BIT(28)
+++
+++#define MTK_WED_TX_TKID_DYN_THR				0x0e0
+++#define MTK_WED_TX_TKID_DYN_THR_LO			GENMASK(6, 0)
+++#define MTK_WED_TX_TKID_DYN_THR_HI			GENMASK(22, 16)
+++
+++#define MTK_WED_TXP_DW0					0x120
+++#define MTK_WED_TXP_DW1					0x124
+++#define MTK_WED_WPDMA_WRITE_TXP				GENMASK(31, 16)
+++#define MTK_WED_TXDP_CTRL				0x130
+++#define MTK_WED_TXDP_DW9_OVERWR				BIT(9)
+++#define MTK_WED_RX_BM_TKID_MIB				0x1cc
++ 
++ #define MTK_WED_INT_STATUS				0x200
++ #define MTK_WED_INT_MASK				0x204
++@@ -125,6 +146,7 @@ struct mtk_wdma_desc {
++ #define MTK_WED_RESET_IDX_RX				GENMASK(17, 16)
++ 
++ #define MTK_WED_TX_MIB(_n)				(0x2a0 + (_n) * 4)
+++#define MTK_WED_RX_MIB(_n)				(0x2e0 + (_n) * 4)
++ 
++ #define MTK_WED_RING_TX(_n)				(0x300 + (_n) * 0x10)
++ 
++@@ -155,21 +177,62 @@ struct mtk_wdma_desc {
++ #define MTK_WED_WPDMA_GLO_CFG_BYTE_SWAP			BIT(29)
++ #define MTK_WED_WPDMA_GLO_CFG_RX_2B_OFFSET		BIT(31)
++ 
+++/* CONFIG_MEDIATEK_NETSYS_V2 */
+++#define MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_PKT_PROC	BIT(4)
+++#define MTK_WED_WPDMA_GLO_CFG_RX_DRV_R1_PKT_PROC	BIT(5)
+++#define MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_CRX_SYNC	BIT(6)
+++#define MTK_WED_WPDMA_GLO_CFG_RX_DRV_R1_CRX_SYNC	BIT(7)
+++#define MTK_WED_WPDMA_GLO_CFG_RX_DRV_EVENT_PKT_FMT_VER	GENMASK(18, 16)
+++#define MTK_WED_WPDMA_GLO_CFG_RX_DRV_UNSUPPORT_FMT	BIT(19)
+++#define MTK_WED_WPDMA_GLO_CFG_RX_DRV_UEVENT_PKT_FMT_CHK BIT(20)
+++#define MTK_WED_WPDMA_GLO_CFG_RX_DDONE2_WR		BIT(21)
+++#define MTK_WED_WPDMA_GLO_CFG_TX_TKID_KEEP		BIT(24)
+++#define MTK_WED_WPDMA_GLO_CFG_TX_DMAD_DW3_PREV		BIT(28)
+++
++ #define MTK_WED_WPDMA_RESET_IDX				0x50c
++ #define MTK_WED_WPDMA_RESET_IDX_TX			GENMASK(3, 0)
++ #define MTK_WED_WPDMA_RESET_IDX_RX			GENMASK(17, 16)
++ 
+++#define MTK_WED_WPDMA_CTRL				0x518
+++#define MTK_WED_WPDMA_CTRL_SDL1_FIXED			BIT(31)
+++
++ #define MTK_WED_WPDMA_INT_CTRL				0x520
++ #define MTK_WED_WPDMA_INT_CTRL_SUBRT_ADV		BIT(21)
++ 
++ #define MTK_WED_WPDMA_INT_MASK				0x524
++ 
+++#define MTK_WED_WPDMA_INT_CTRL_TX			0x530
+++#define MTK_WED_WPDMA_INT_CTRL_TX0_DONE_EN		BIT(0)
+++#define MTK_WED_WPDMA_INT_CTRL_TX0_DONE_CLR		BIT(1)
+++#define MTK_WED_WPDMA_INT_CTRL_TX0_DONE_TRIG		GENMASK(6, 2)
+++#define MTK_WED_WPDMA_INT_CTRL_TX1_DONE_EN		BIT(8)
+++#define MTK_WED_WPDMA_INT_CTRL_TX1_DONE_CLR		BIT(9)
+++#define MTK_WED_WPDMA_INT_CTRL_TX1_DONE_TRIG		GENMASK(14, 10)
+++
+++#define MTK_WED_WPDMA_INT_CTRL_RX			0x534
+++
+++#define MTK_WED_WPDMA_INT_CTRL_TX_FREE			0x538
+++#define MTK_WED_WPDMA_INT_CTRL_TX_FREE_DONE_EN		BIT(0)
+++#define MTK_WED_WPDMA_INT_CTRL_TX_FREE_DONE_CLR		BIT(1)
+++#define MTK_WED_WPDMA_INT_CTRL_TX_FREE_DONE_TRIG	GENMASK(6, 2)
+++
++ #define MTK_WED_PCIE_CFG_BASE				0x560
++ 
+++#define MTK_WED_PCIE_CFG_BASE				0x560
+++#define MTK_WED_PCIE_CFG_INTM				0x564
+++#define MTK_WED_PCIE_CFG_MSIS				0x568
++ #define MTK_WED_PCIE_INT_TRIGGER			0x570
++ #define MTK_WED_PCIE_INT_TRIGGER_STATUS			BIT(16)
++ 
+++#define MTK_WED_PCIE_INT_CTRL				0x57c
+++#define MTK_WED_PCIE_INT_CTRL_MSK_EN_POLA		BIT(20)
+++#define MTK_WED_PCIE_INT_CTRL_SRC_SEL			GENMASK(17, 16)
+++#define MTK_WED_PCIE_INT_CTRL_POLL_EN			GENMASK(13, 12)
+++
++ #define MTK_WED_WPDMA_CFG_BASE				0x580
+++#define MTK_WED_WPDMA_CFG_INT_MASK			0x584
+++#define MTK_WED_WPDMA_CFG_TX				0x588
+++#define MTK_WED_WPDMA_CFG_TX_FREE			0x58c
++ 
++ #define MTK_WED_WPDMA_TX_MIB(_n)			(0x5a0 + (_n) * 4)
++ #define MTK_WED_WPDMA_TX_COHERENT_MIB(_n)		(0x5d0 + (_n) * 4)
++@@ -203,15 +266,24 @@ struct mtk_wdma_desc {
++ #define MTK_WED_WDMA_RESET_IDX_RX			GENMASK(17, 16)
++ #define MTK_WED_WDMA_RESET_IDX_DRV			GENMASK(25, 24)
++ 
+++#define MTK_WED_WDMA_INT_CLR				0xa24
+++#define MTK_WED_WDMA_INT_CLR_RX_DONE			GENMASK(17, 16)
+++
++ #define MTK_WED_WDMA_INT_TRIGGER			0xa28
++ #define MTK_WED_WDMA_INT_TRIGGER_RX_DONE		GENMASK(17, 16)
++ 
++ #define MTK_WED_WDMA_INT_CTRL				0xa2c
++ #define MTK_WED_WDMA_INT_CTRL_POLL_SRC_SEL		GENMASK(17, 16)
++ 
+++#define MTK_WED_WDMA_CFG_BASE				0xaa0
++ #define MTK_WED_WDMA_OFFSET0				0xaa4
++ #define MTK_WED_WDMA_OFFSET1				0xaa8
++ 
+++#define MTK_WED_WDMA_OFST0_GLO_INTS			GENMASK(15, 0)
+++#define MTK_WED_WDMA_OFST0_GLO_CFG			GENMASK(31, 16)
+++#define MTK_WED_WDMA_OFST1_TX_CTRL			GENMASK(15, 0)
+++#define MTK_WED_WDMA_OFST1_RX_CTRL			GENMASK(31, 16)
+++
++ #define MTK_WED_WDMA_RX_MIB(_n)				(0xae0 + (_n) * 4)
++ #define MTK_WED_WDMA_RX_RECYCLE_MIB(_n)			(0xae8 + (_n) * 4)
++ #define MTK_WED_WDMA_RX_PROCESSED_MIB(_n)		(0xaf0 + (_n) * 4)
++@@ -221,6 +293,7 @@ struct mtk_wdma_desc {
++ #define MTK_WED_RING_OFS_CPU_IDX			0x08
++ #define MTK_WED_RING_OFS_DMA_IDX			0x0c
++ 
+++#define MTK_WDMA_RING_TX(_n)				(0x000 + (_n) * 0x10)
++ #define MTK_WDMA_RING_RX(_n)				(0x100 + (_n) * 0x10)
++ 
++ #define MTK_WDMA_GLO_CFG				0x204
++@@ -234,6 +307,8 @@ struct mtk_wdma_desc {
++ #define MTK_WDMA_RESET_IDX_TX				GENMASK(3, 0)
++ #define MTK_WDMA_RESET_IDX_RX				GENMASK(17, 16)
++ 
+++#define MTK_WDMA_INT_STATUS				0x220
+++
++ #define MTK_WDMA_INT_MASK				0x228
++ #define MTK_WDMA_INT_MASK_TX_DONE			GENMASK(3, 0)
++ #define MTK_WDMA_INT_MASK_RX_DONE			GENMASK(17, 16)
++--- a/include/linux/soc/mediatek/mtk_wed.h
+++++ b/include/linux/soc/mediatek/mtk_wed.h
++@@ -14,6 +14,7 @@ struct mtk_wdma_desc;
++ struct mtk_wed_ring {
++ 	struct mtk_wdma_desc *desc;
++ 	dma_addr_t desc_phys;
+++	u32 desc_size;
++ 	int size;
++ 
++ 	u32 reg_base;
++@@ -45,10 +46,17 @@ struct mtk_wed_device {
++ 		struct pci_dev *pci_dev;
++ 
++ 		u32 wpdma_phys;
+++		u32 wpdma_int;
+++		u32 wpdma_mask;
+++		u32 wpdma_tx;
+++		u32 wpdma_txfree;
++ 
++ 		u16 token_start;
++ 		unsigned int nbuf;
++ 
+++		u8 tx_tbit[MTK_WED_TX_QUEUES];
+++		u8 txfree_tbit;
+++
++ 		u32 (*init_buf)(void *ptr, dma_addr_t phys, int token_id);
++ 		int (*offload_enable)(struct mtk_wed_device *wed);
++ 		void (*offload_disable)(struct mtk_wed_device *wed);
+diff --git a/target/linux/generic/backport-5.15/722-v6.0-net-ethernet-mtk_eth_wed-add-axi-bus-support.patch b/target/linux/generic/backport-5.15/722-v6.0-net-ethernet-mtk_eth_wed-add-axi-bus-support.patch
+new file mode 100644
+index 0000000000..70b7bad31f
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/722-v6.0-net-ethernet-mtk_eth_wed-add-axi-bus-support.patch
+@@ -0,0 +1,237 @@
++From 6e1df49f330dce7c58a39d6772f1385b6887bb03 Mon Sep 17 00:00:00 2001
++Message-Id: <6e1df49f330dce7c58a39d6772f1385b6887bb03.1662990860.git.lorenzo@kernel.org>
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Thu, 8 Sep 2022 11:26:10 +0200
++Subject: [PATCH net-next] net: ethernet: mtk_eth_wed: add axi bus support
++
++Other than pcie bus, introduce support for axi bus to mtk wed driver.
++Axi bus is used to connect mt7986-wmac soc chip available on mt7986
++device.
++
++Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
++Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
++Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
++Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_wed.c      | 116 +++++++++++++------
++ drivers/net/ethernet/mediatek/mtk_wed_regs.h |   2 +
++ include/linux/soc/mediatek/mtk_wed.h         |  11 +-
++ 3 files changed, 91 insertions(+), 38 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_wed.c
+++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
++@@ -85,11 +85,31 @@ static struct mtk_wed_hw *
++ mtk_wed_assign(struct mtk_wed_device *dev)
++ {
++ 	struct mtk_wed_hw *hw;
+++	int i;
+++
+++	if (dev->wlan.bus_type == MTK_WED_BUS_PCIE) {
+++		hw = hw_list[pci_domain_nr(dev->wlan.pci_dev->bus)];
+++		if (!hw)
+++			return NULL;
+++
+++		if (!hw->wed_dev)
+++			goto out;
+++
+++		if (hw->version == 1)
+++			return NULL;
+++
+++		/* MT7986 WED devices do not have any pcie slot restrictions */
+++	}
+++	/* MT7986 PCIE or AXI */
+++	for (i = 0; i < ARRAY_SIZE(hw_list); i++) {
+++		hw = hw_list[i];
+++		if (hw && !hw->wed_dev)
+++			goto out;
+++	}
++ 
++-	hw = hw_list[pci_domain_nr(dev->wlan.pci_dev->bus)];
++-	if (!hw || hw->wed_dev)
++-		return NULL;
+++	return NULL;
++ 
+++out:
++ 	hw->wed_dev = dev;
++ 	return hw;
++ }
++@@ -322,7 +342,6 @@ mtk_wed_stop(struct mtk_wed_device *dev)
++ static void
++ mtk_wed_detach(struct mtk_wed_device *dev)
++ {
++-	struct device_node *wlan_node = dev->wlan.pci_dev->dev.of_node;
++ 	struct mtk_wed_hw *hw = dev->hw;
++ 
++ 	mutex_lock(&hw_lock);
++@@ -337,9 +356,14 @@ mtk_wed_detach(struct mtk_wed_device *de
++ 	mtk_wed_free_buffer(dev);
++ 	mtk_wed_free_tx_rings(dev);
++ 
++-	if (of_dma_is_coherent(wlan_node) && hw->hifsys)
++-		regmap_update_bits(hw->hifsys, HIFSYS_DMA_AG_MAP,
++-				   BIT(hw->index), BIT(hw->index));
+++	if (dev->wlan.bus_type == MTK_WED_BUS_PCIE) {
+++		struct device_node *wlan_node;
+++
+++		wlan_node = dev->wlan.pci_dev->dev.of_node;
+++		if (of_dma_is_coherent(wlan_node) && hw->hifsys)
+++			regmap_update_bits(hw->hifsys, HIFSYS_DMA_AG_MAP,
+++					   BIT(hw->index), BIT(hw->index));
+++	}
++ 
++ 	if (!hw_list[!hw->index]->wed_dev &&
++ 	    hw->eth->dma_dev != hw->eth->dev)
++@@ -356,40 +380,54 @@ mtk_wed_detach(struct mtk_wed_device *de
++ static void
++ mtk_wed_bus_init(struct mtk_wed_device *dev)
++ {
++-	struct device_node *np = dev->hw->eth->dev->of_node;
++-	struct regmap *regs;
++-	u32 val;
++-
++-	regs = syscon_regmap_lookup_by_phandle(np, "mediatek,wed-pcie");
++-	if (IS_ERR(regs))
++-		return;
+++	switch (dev->wlan.bus_type) {
+++	case MTK_WED_BUS_PCIE: {
+++		struct device_node *np = dev->hw->eth->dev->of_node;
+++		struct regmap *regs;
+++		u32 val;
+++
+++		regs = syscon_regmap_lookup_by_phandle(np,
+++						       "mediatek,wed-pcie");
+++		if (IS_ERR(regs))
+++			break;
++ 
++-	regmap_update_bits(regs, 0, BIT(0), BIT(0));
+++		regmap_update_bits(regs, 0, BIT(0), BIT(0));
++ 
++-	wed_w32(dev, MTK_WED_PCIE_INT_CTRL,
++-		FIELD_PREP(MTK_WED_PCIE_INT_CTRL_POLL_EN, 2));
+++		wed_w32(dev, MTK_WED_PCIE_INT_CTRL,
+++			FIELD_PREP(MTK_WED_PCIE_INT_CTRL_POLL_EN, 2));
++ 
++-	/* pcie interrupt control: pola/source selection */
++-	wed_set(dev, MTK_WED_PCIE_INT_CTRL,
++-		MTK_WED_PCIE_INT_CTRL_MSK_EN_POLA |
++-		FIELD_PREP(MTK_WED_PCIE_INT_CTRL_SRC_SEL, 1));
++-	wed_r32(dev, MTK_WED_PCIE_INT_CTRL);
++-
++-	val = wed_r32(dev, MTK_WED_PCIE_CFG_INTM);
++-	val = wed_r32(dev, MTK_WED_PCIE_CFG_BASE);
++-	wed_w32(dev, MTK_WED_PCIE_CFG_INTM, PCIE_BASE_ADDR0 | 0x180);
++-	wed_w32(dev, MTK_WED_PCIE_CFG_BASE, PCIE_BASE_ADDR0 | 0x184);
++-
++-	val = wed_r32(dev, MTK_WED_PCIE_CFG_INTM);
++-	val = wed_r32(dev, MTK_WED_PCIE_CFG_BASE);
++-
++-	/* pcie interrupt status trigger register */
++-	wed_w32(dev, MTK_WED_PCIE_INT_TRIGGER, BIT(24));
++-	wed_r32(dev, MTK_WED_PCIE_INT_TRIGGER);
++-
++-	/* pola setting */
++-	val = wed_r32(dev, MTK_WED_PCIE_INT_CTRL);
++-	wed_set(dev, MTK_WED_PCIE_INT_CTRL, MTK_WED_PCIE_INT_CTRL_MSK_EN_POLA);
+++		/* pcie interrupt control: pola/source selection */
+++		wed_set(dev, MTK_WED_PCIE_INT_CTRL,
+++			MTK_WED_PCIE_INT_CTRL_MSK_EN_POLA |
+++			FIELD_PREP(MTK_WED_PCIE_INT_CTRL_SRC_SEL, 1));
+++		wed_r32(dev, MTK_WED_PCIE_INT_CTRL);
+++
+++		val = wed_r32(dev, MTK_WED_PCIE_CFG_INTM);
+++		val = wed_r32(dev, MTK_WED_PCIE_CFG_BASE);
+++		wed_w32(dev, MTK_WED_PCIE_CFG_INTM, PCIE_BASE_ADDR0 | 0x180);
+++		wed_w32(dev, MTK_WED_PCIE_CFG_BASE, PCIE_BASE_ADDR0 | 0x184);
+++
+++		val = wed_r32(dev, MTK_WED_PCIE_CFG_INTM);
+++		val = wed_r32(dev, MTK_WED_PCIE_CFG_BASE);
+++
+++		/* pcie interrupt status trigger register */
+++		wed_w32(dev, MTK_WED_PCIE_INT_TRIGGER, BIT(24));
+++		wed_r32(dev, MTK_WED_PCIE_INT_TRIGGER);
+++
+++		/* pola setting */
+++		val = wed_r32(dev, MTK_WED_PCIE_INT_CTRL);
+++		wed_set(dev, MTK_WED_PCIE_INT_CTRL,
+++			MTK_WED_PCIE_INT_CTRL_MSK_EN_POLA);
+++		break;
+++	}
+++	case MTK_WED_BUS_AXI:
+++		wed_set(dev, MTK_WED_WPDMA_INT_CTRL,
+++			MTK_WED_WPDMA_INT_CTRL_SIG_SRC |
+++			FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_SRC_SEL, 0));
+++		break;
+++	default:
+++		break;
+++	}
++ }
++ 
++ static void
++@@ -800,12 +838,14 @@ mtk_wed_attach(struct mtk_wed_device *de
++ 	__releases(RCU)
++ {
++ 	struct mtk_wed_hw *hw;
+++	struct device *device;
++ 	int ret = 0;
++ 
++ 	RCU_LOCKDEP_WARN(!rcu_read_lock_held(),
++ 			 "mtk_wed_attach without holding the RCU read lock");
++ 
++-	if (pci_domain_nr(dev->wlan.pci_dev->bus) > 1 ||
+++	if ((dev->wlan.bus_type == MTK_WED_BUS_PCIE &&
+++	     pci_domain_nr(dev->wlan.pci_dev->bus) > 1) ||
++ 	    !try_module_get(THIS_MODULE))
++ 		ret = -ENODEV;
++ 
++@@ -823,8 +863,10 @@ mtk_wed_attach(struct mtk_wed_device *de
++ 		goto out;
++ 	}
++ 
++-	dev_info(&dev->wlan.pci_dev->dev,
++-		 "attaching wed device %d version %d\n",
+++	device = dev->wlan.bus_type == MTK_WED_BUS_PCIE
+++		? &dev->wlan.pci_dev->dev
+++		: &dev->wlan.platform_dev->dev;
+++	dev_info(device, "attaching wed device %d version %d\n",
++ 		 hw->index, hw->version);
++ 
++ 	dev->hw = hw;
++--- a/drivers/net/ethernet/mediatek/mtk_wed_regs.h
+++++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
++@@ -198,6 +198,8 @@ struct mtk_wdma_desc {
++ 
++ #define MTK_WED_WPDMA_INT_CTRL				0x520
++ #define MTK_WED_WPDMA_INT_CTRL_SUBRT_ADV		BIT(21)
+++#define MTK_WED_WPDMA_INT_CTRL_SIG_SRC			BIT(22)
+++#define MTK_WED_WPDMA_INT_CTRL_SRC_SEL			GENMASK(17, 16)
++ 
++ #define MTK_WED_WPDMA_INT_MASK				0x524
++ 
++--- a/include/linux/soc/mediatek/mtk_wed.h
+++++ b/include/linux/soc/mediatek/mtk_wed.h
++@@ -11,6 +11,11 @@
++ struct mtk_wed_hw;
++ struct mtk_wdma_desc;
++ 
+++enum mtk_wed_bus_tye {
+++	MTK_WED_BUS_PCIE,
+++	MTK_WED_BUS_AXI,
+++};
+++
++ struct mtk_wed_ring {
++ 	struct mtk_wdma_desc *desc;
++ 	dma_addr_t desc_phys;
++@@ -43,7 +48,11 @@ struct mtk_wed_device {
++ 
++ 	/* filled by driver: */
++ 	struct {
++-		struct pci_dev *pci_dev;
+++		union {
+++			struct platform_device *platform_dev;
+++			struct pci_dev *pci_dev;
+++		};
+++		enum mtk_wed_bus_tye bus_type;
++ 
++ 		u32 wpdma_phys;
++ 		u32 wpdma_int;
+diff --git a/target/linux/generic/backport-5.15/723-v6.0-net-ethernet-mtk_eth_soc-introduce-flow-offloading-s.patch b/target/linux/generic/backport-5.15/723-v6.0-net-ethernet-mtk_eth_soc-introduce-flow-offloading-s.patch
+new file mode 100644
+index 0000000000..1ecbf485f1
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/723-v6.0-net-ethernet-mtk_eth_soc-introduce-flow-offloading-s.patch
+@@ -0,0 +1,882 @@
++From 93408c858e5dc01d97c55efa721268f63fde2ae5 Mon Sep 17 00:00:00 2001
++Message-Id: <93408c858e5dc01d97c55efa721268f63fde2ae5.1662886034.git.lorenzo@kernel.org>
++In-Reply-To: <e5ecb4f619197b93fa682d722452dc8412864cdb.1662886033.git.lorenzo@kernel.org>
++References: <e5ecb4f619197b93fa682d722452dc8412864cdb.1662886033.git.lorenzo@kernel.org>
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Sat, 3 Sep 2022 18:34:09 +0200
++Subject: [PATCH net-next 4/5] net: ethernet: mtk_eth_soc: introduce flow
++ offloading support for mt7986
++
++Introduce hw flow offload support for mt7986 chipset. PPE is not enabled
++yet in mt7986 since mt76 support is not available yet.
++
++Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
++Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
++Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
++Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c   |  11 +-
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h   |  72 ++++++
++ drivers/net/ethernet/mediatek/mtk_ppe.c       | 213 +++++++++++-------
++ drivers/net/ethernet/mediatek/mtk_ppe.h       |  52 ++++-
++ .../net/ethernet/mediatek/mtk_ppe_offload.c   |  49 ++--
++ drivers/net/ethernet/mediatek/mtk_ppe_regs.h  |   8 +
++ 6 files changed, 289 insertions(+), 116 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -1858,12 +1858,14 @@ static int mtk_poll_rx(struct napi_struc
++ 		bytes += skb->len;
++ 
++ 		if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+++			reason = FIELD_GET(MTK_RXD5_PPE_CPU_REASON, trxd.rxd5);
++ 			hash = trxd.rxd5 & MTK_RXD5_FOE_ENTRY;
++ 			if (hash != MTK_RXD5_FOE_ENTRY)
++ 				skb_set_hash(skb, jhash_1word(hash, 0),
++ 					     PKT_HASH_TYPE_L4);
++ 			rxdcsum = &trxd.rxd3;
++ 		} else {
+++			reason = FIELD_GET(MTK_RXD4_PPE_CPU_REASON, trxd.rxd4);
++ 			hash = trxd.rxd4 & MTK_RXD4_FOE_ENTRY;
++ 			if (hash != MTK_RXD4_FOE_ENTRY)
++ 				skb_set_hash(skb, jhash_1word(hash, 0),
++@@ -1877,7 +1879,6 @@ static int mtk_poll_rx(struct napi_struc
++ 			skb_checksum_none_assert(skb);
++ 		skb->protocol = eth_type_trans(skb, netdev);
++ 
++-		reason = FIELD_GET(MTK_RXD4_PPE_CPU_REASON, trxd.rxd4);
++ 		if (reason == MTK_PPE_CPU_REASON_HIT_UNBIND_RATE_REACHED)
++ 			mtk_ppe_check_skb(eth->ppe[0], skb, hash);
++ 
++@@ -4181,7 +4182,7 @@ static const struct mtk_soc_data mt7621_
++ 	.required_pctl = false,
++ 	.offload_version = 2,
++ 	.hash_offset = 2,
++-	.foe_entry_size = sizeof(struct mtk_foe_entry),
+++	.foe_entry_size = sizeof(struct mtk_foe_entry) - 16,
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
++ 		.rxd_size = sizeof(struct mtk_rx_dma),
++@@ -4201,7 +4202,7 @@ static const struct mtk_soc_data mt7622_
++ 	.required_pctl = false,
++ 	.offload_version = 2,
++ 	.hash_offset = 2,
++-	.foe_entry_size = sizeof(struct mtk_foe_entry),
+++	.foe_entry_size = sizeof(struct mtk_foe_entry) - 16,
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
++ 		.rxd_size = sizeof(struct mtk_rx_dma),
++@@ -4220,7 +4221,7 @@ static const struct mtk_soc_data mt7623_
++ 	.required_pctl = true,
++ 	.offload_version = 2,
++ 	.hash_offset = 2,
++-	.foe_entry_size = sizeof(struct mtk_foe_entry),
+++	.foe_entry_size = sizeof(struct mtk_foe_entry) - 16,
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma),
++ 		.rxd_size = sizeof(struct mtk_rx_dma),
++@@ -4252,9 +4253,11 @@ static const struct mtk_soc_data mt7986_
++ 	.reg_map = &mt7986_reg_map,
++ 	.ana_rgc3 = 0x128,
++ 	.caps = MT7986_CAPS,
+++	.hw_features = MTK_HW_FEATURES,
++ 	.required_clks = MT7986_CLKS_BITMAP,
++ 	.required_pctl = false,
++ 	.hash_offset = 4,
+++	.foe_entry_size = sizeof(struct mtk_foe_entry),
++ 	.txrx = {
++ 		.txd_size = sizeof(struct mtk_tx_dma_v2),
++ 		.rxd_size = sizeof(struct mtk_rx_dma_v2),
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -1150,6 +1150,78 @@ mtk_foe_get_entry(struct mtk_ppe *ppe, u
++ 	return ppe->foe_table + hash * soc->foe_entry_size;
++ }
++ 
+++static inline u32 mtk_get_ib1_ts_mask(struct mtk_eth *eth)
+++{
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
+++		return MTK_FOE_IB1_BIND_TIMESTAMP_V2;
+++
+++	return MTK_FOE_IB1_BIND_TIMESTAMP;
+++}
+++
+++static inline u32 mtk_get_ib1_ppoe_mask(struct mtk_eth *eth)
+++{
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
+++		return MTK_FOE_IB1_BIND_PPPOE_V2;
+++
+++	return MTK_FOE_IB1_BIND_PPPOE;
+++}
+++
+++static inline u32 mtk_get_ib1_vlan_tag_mask(struct mtk_eth *eth)
+++{
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
+++		return MTK_FOE_IB1_BIND_VLAN_TAG_V2;
+++
+++	return MTK_FOE_IB1_BIND_VLAN_TAG;
+++}
+++
+++static inline u32 mtk_get_ib1_vlan_layer_mask(struct mtk_eth *eth)
+++{
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
+++		return MTK_FOE_IB1_BIND_VLAN_LAYER_V2;
+++
+++	return MTK_FOE_IB1_BIND_VLAN_LAYER;
+++}
+++
+++static inline u32 mtk_prep_ib1_vlan_layer(struct mtk_eth *eth, u32 val)
+++{
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
+++		return FIELD_PREP(MTK_FOE_IB1_BIND_VLAN_LAYER_V2, val);
+++
+++	return FIELD_PREP(MTK_FOE_IB1_BIND_VLAN_LAYER, val);
+++}
+++
+++static inline u32 mtk_get_ib1_vlan_layer(struct mtk_eth *eth, u32 val)
+++{
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
+++		return FIELD_GET(MTK_FOE_IB1_BIND_VLAN_LAYER_V2, val);
+++
+++	return FIELD_GET(MTK_FOE_IB1_BIND_VLAN_LAYER, val);
+++}
+++
+++static inline u32 mtk_get_ib1_pkt_type_mask(struct mtk_eth *eth)
+++{
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
+++		return MTK_FOE_IB1_PACKET_TYPE_V2;
+++
+++	return MTK_FOE_IB1_PACKET_TYPE;
+++}
+++
+++static inline u32 mtk_get_ib1_pkt_type(struct mtk_eth *eth, u32 val)
+++{
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
+++		return FIELD_GET(MTK_FOE_IB1_PACKET_TYPE_V2, val);
+++
+++	return FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, val);
+++}
+++
+++static inline u32 mtk_get_ib2_multicast_mask(struct mtk_eth *eth)
+++{
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
+++		return MTK_FOE_IB2_MULTICAST_V2;
+++
+++	return MTK_FOE_IB2_MULTICAST;
+++}
+++
++ /* read the hardware status register */
++ void mtk_stats_update_mac(struct mtk_mac *mac);
++ 
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
++@@ -56,7 +56,7 @@ static u32 ppe_clear(struct mtk_ppe *ppe
++ 
++ static u32 mtk_eth_timestamp(struct mtk_eth *eth)
++ {
++-	return mtk_r32(eth, 0x0010) & MTK_FOE_IB1_BIND_TIMESTAMP;
+++	return mtk_r32(eth, 0x0010) & mtk_get_ib1_ts_mask(eth);
++ }
++ 
++ static int mtk_ppe_wait_busy(struct mtk_ppe *ppe)
++@@ -93,7 +93,7 @@ static u32 mtk_ppe_hash_entry(struct mtk
++ 	u32 hv1, hv2, hv3;
++ 	u32 hash;
++ 
++-	switch (FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, e->ib1)) {
+++	switch (mtk_get_ib1_pkt_type(eth, e->ib1)) {
++ 		case MTK_PPE_PKT_TYPE_IPV4_ROUTE:
++ 		case MTK_PPE_PKT_TYPE_IPV4_HNAPT:
++ 			hv1 = e->ipv4.orig.ports;
++@@ -129,9 +129,9 @@ static u32 mtk_ppe_hash_entry(struct mtk
++ }
++ 
++ static inline struct mtk_foe_mac_info *
++-mtk_foe_entry_l2(struct mtk_foe_entry *entry)
+++mtk_foe_entry_l2(struct mtk_eth *eth, struct mtk_foe_entry *entry)
++ {
++-	int type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->ib1);
+++	int type = mtk_get_ib1_pkt_type(eth, entry->ib1);
++ 
++ 	if (type == MTK_PPE_PKT_TYPE_BRIDGE)
++ 		return &entry->bridge.l2;
++@@ -143,9 +143,9 @@ mtk_foe_entry_l2(struct mtk_foe_entry *e
++ }
++ 
++ static inline u32 *
++-mtk_foe_entry_ib2(struct mtk_foe_entry *entry)
+++mtk_foe_entry_ib2(struct mtk_eth *eth, struct mtk_foe_entry *entry)
++ {
++-	int type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->ib1);
+++	int type = mtk_get_ib1_pkt_type(eth, entry->ib1);
++ 
++ 	if (type == MTK_PPE_PKT_TYPE_BRIDGE)
++ 		return &entry->bridge.ib2;
++@@ -156,27 +156,38 @@ mtk_foe_entry_ib2(struct mtk_foe_entry *
++ 	return &entry->ipv4.ib2;
++ }
++ 
++-int mtk_foe_entry_prepare(struct mtk_foe_entry *entry, int type, int l4proto,
++-			  u8 pse_port, u8 *src_mac, u8 *dest_mac)
+++int mtk_foe_entry_prepare(struct mtk_eth *eth, struct mtk_foe_entry *entry,
+++			  int type, int l4proto, u8 pse_port, u8 *src_mac,
+++			  u8 *dest_mac)
++ {
++ 	struct mtk_foe_mac_info *l2;
++ 	u32 ports_pad, val;
++ 
++ 	memset(entry, 0, sizeof(*entry));
++ 
++-	val = FIELD_PREP(MTK_FOE_IB1_STATE, MTK_FOE_STATE_BIND) |
++-	      FIELD_PREP(MTK_FOE_IB1_PACKET_TYPE, type) |
++-	      FIELD_PREP(MTK_FOE_IB1_UDP, l4proto == IPPROTO_UDP) |
++-	      MTK_FOE_IB1_BIND_TTL |
++-	      MTK_FOE_IB1_BIND_CACHE;
++-	entry->ib1 = val;
++-
++-	val = FIELD_PREP(MTK_FOE_IB2_PORT_MG, 0x3f) |
++-	      FIELD_PREP(MTK_FOE_IB2_PORT_AG, 0x1f) |
++-	      FIELD_PREP(MTK_FOE_IB2_DEST_PORT, pse_port);
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+++		val = FIELD_PREP(MTK_FOE_IB1_STATE, MTK_FOE_STATE_BIND) |
+++		      FIELD_PREP(MTK_FOE_IB1_PACKET_TYPE_V2, type) |
+++		      FIELD_PREP(MTK_FOE_IB1_UDP, l4proto == IPPROTO_UDP) |
+++		      MTK_FOE_IB1_BIND_CACHE_V2 | MTK_FOE_IB1_BIND_TTL_V2;
+++		entry->ib1 = val;
+++
+++		val = FIELD_PREP(MTK_FOE_IB2_DEST_PORT_V2, pse_port) |
+++		      FIELD_PREP(MTK_FOE_IB2_PORT_AG_V2, 0xf);
+++	} else {
+++		val = FIELD_PREP(MTK_FOE_IB1_STATE, MTK_FOE_STATE_BIND) |
+++		      FIELD_PREP(MTK_FOE_IB1_PACKET_TYPE, type) |
+++		      FIELD_PREP(MTK_FOE_IB1_UDP, l4proto == IPPROTO_UDP) |
+++		      MTK_FOE_IB1_BIND_CACHE | MTK_FOE_IB1_BIND_TTL;
+++		entry->ib1 = val;
+++
+++		val = FIELD_PREP(MTK_FOE_IB2_DEST_PORT, pse_port) |
+++		      FIELD_PREP(MTK_FOE_IB2_PORT_MG, 0x3f) |
+++		      FIELD_PREP(MTK_FOE_IB2_PORT_AG, 0x1f);
+++	}
++ 
++ 	if (is_multicast_ether_addr(dest_mac))
++-		val |= MTK_FOE_IB2_MULTICAST;
+++		val |= mtk_get_ib2_multicast_mask(eth);
++ 
++ 	ports_pad = 0xa5a5a500 | (l4proto & 0xff);
++ 	if (type == MTK_PPE_PKT_TYPE_IPV4_ROUTE)
++@@ -210,24 +221,30 @@ int mtk_foe_entry_prepare(struct mtk_foe
++ 	return 0;
++ }
++ 
++-int mtk_foe_entry_set_pse_port(struct mtk_foe_entry *entry, u8 port)
+++int mtk_foe_entry_set_pse_port(struct mtk_eth *eth,
+++			       struct mtk_foe_entry *entry, u8 port)
++ {
++-	u32 *ib2 = mtk_foe_entry_ib2(entry);
++-	u32 val;
+++	u32 *ib2 = mtk_foe_entry_ib2(eth, entry);
+++	u32 val = *ib2;
++ 
++-	val = *ib2;
++-	val &= ~MTK_FOE_IB2_DEST_PORT;
++-	val |= FIELD_PREP(MTK_FOE_IB2_DEST_PORT, port);
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+++		val &= ~MTK_FOE_IB2_DEST_PORT_V2;
+++		val |= FIELD_PREP(MTK_FOE_IB2_DEST_PORT_V2, port);
+++	} else {
+++		val &= ~MTK_FOE_IB2_DEST_PORT;
+++		val |= FIELD_PREP(MTK_FOE_IB2_DEST_PORT, port);
+++	}
++ 	*ib2 = val;
++ 
++ 	return 0;
++ }
++ 
++-int mtk_foe_entry_set_ipv4_tuple(struct mtk_foe_entry *entry, bool egress,
+++int mtk_foe_entry_set_ipv4_tuple(struct mtk_eth *eth,
+++				 struct mtk_foe_entry *entry, bool egress,
++ 				 __be32 src_addr, __be16 src_port,
++ 				 __be32 dest_addr, __be16 dest_port)
++ {
++-	int type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->ib1);
+++	int type = mtk_get_ib1_pkt_type(eth, entry->ib1);
++ 	struct mtk_ipv4_tuple *t;
++ 
++ 	switch (type) {
++@@ -262,11 +279,12 @@ int mtk_foe_entry_set_ipv4_tuple(struct
++ 	return 0;
++ }
++ 
++-int mtk_foe_entry_set_ipv6_tuple(struct mtk_foe_entry *entry,
+++int mtk_foe_entry_set_ipv6_tuple(struct mtk_eth *eth,
+++				 struct mtk_foe_entry *entry,
++ 				 __be32 *src_addr, __be16 src_port,
++ 				 __be32 *dest_addr, __be16 dest_port)
++ {
++-	int type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->ib1);
+++	int type = mtk_get_ib1_pkt_type(eth, entry->ib1);
++ 	u32 *src, *dest;
++ 	int i;
++ 
++@@ -297,39 +315,41 @@ int mtk_foe_entry_set_ipv6_tuple(struct
++ 	return 0;
++ }
++ 
++-int mtk_foe_entry_set_dsa(struct mtk_foe_entry *entry, int port)
+++int mtk_foe_entry_set_dsa(struct mtk_eth *eth, struct mtk_foe_entry *entry,
+++			  int port)
++ {
++-	struct mtk_foe_mac_info *l2 = mtk_foe_entry_l2(entry);
+++	struct mtk_foe_mac_info *l2 = mtk_foe_entry_l2(eth, entry);
++ 
++ 	l2->etype = BIT(port);
++ 
++-	if (!(entry->ib1 & MTK_FOE_IB1_BIND_VLAN_LAYER))
++-		entry->ib1 |= FIELD_PREP(MTK_FOE_IB1_BIND_VLAN_LAYER, 1);
+++	if (!(entry->ib1 & mtk_get_ib1_vlan_layer_mask(eth)))
+++		entry->ib1 |= mtk_prep_ib1_vlan_layer(eth, 1);
++ 	else
++ 		l2->etype |= BIT(8);
++ 
++-	entry->ib1 &= ~MTK_FOE_IB1_BIND_VLAN_TAG;
+++	entry->ib1 &= ~mtk_get_ib1_vlan_tag_mask(eth);
++ 
++ 	return 0;
++ }
++ 
++-int mtk_foe_entry_set_vlan(struct mtk_foe_entry *entry, int vid)
+++int mtk_foe_entry_set_vlan(struct mtk_eth *eth, struct mtk_foe_entry *entry,
+++			   int vid)
++ {
++-	struct mtk_foe_mac_info *l2 = mtk_foe_entry_l2(entry);
+++	struct mtk_foe_mac_info *l2 = mtk_foe_entry_l2(eth, entry);
++ 
++-	switch (FIELD_GET(MTK_FOE_IB1_BIND_VLAN_LAYER, entry->ib1)) {
+++	switch (mtk_prep_ib1_vlan_layer(eth, entry->ib1)) {
++ 	case 0:
++-		entry->ib1 |= MTK_FOE_IB1_BIND_VLAN_TAG |
++-			      FIELD_PREP(MTK_FOE_IB1_BIND_VLAN_LAYER, 1);
+++		entry->ib1 |= mtk_get_ib1_vlan_tag_mask(eth) |
+++			      mtk_prep_ib1_vlan_layer(eth, 1);
++ 		l2->vlan1 = vid;
++ 		return 0;
++ 	case 1:
++-		if (!(entry->ib1 & MTK_FOE_IB1_BIND_VLAN_TAG)) {
+++		if (!(entry->ib1 & mtk_get_ib1_vlan_tag_mask(eth))) {
++ 			l2->vlan1 = vid;
++ 			l2->etype |= BIT(8);
++ 		} else {
++ 			l2->vlan2 = vid;
++-			entry->ib1 += FIELD_PREP(MTK_FOE_IB1_BIND_VLAN_LAYER, 1);
+++			entry->ib1 += mtk_prep_ib1_vlan_layer(eth, 1);
++ 		}
++ 		return 0;
++ 	default:
++@@ -337,34 +357,42 @@ int mtk_foe_entry_set_vlan(struct mtk_fo
++ 	}
++ }
++ 
++-int mtk_foe_entry_set_pppoe(struct mtk_foe_entry *entry, int sid)
+++int mtk_foe_entry_set_pppoe(struct mtk_eth *eth, struct mtk_foe_entry *entry,
+++			    int sid)
++ {
++-	struct mtk_foe_mac_info *l2 = mtk_foe_entry_l2(entry);
+++	struct mtk_foe_mac_info *l2 = mtk_foe_entry_l2(eth, entry);
++ 
++-	if (!(entry->ib1 & MTK_FOE_IB1_BIND_VLAN_LAYER) ||
++-	    (entry->ib1 & MTK_FOE_IB1_BIND_VLAN_TAG))
+++	if (!(entry->ib1 & mtk_get_ib1_vlan_layer_mask(eth)) ||
+++	    (entry->ib1 & mtk_get_ib1_vlan_tag_mask(eth)))
++ 		l2->etype = ETH_P_PPP_SES;
++ 
++-	entry->ib1 |= MTK_FOE_IB1_BIND_PPPOE;
+++	entry->ib1 |= mtk_get_ib1_ppoe_mask(eth);
++ 	l2->pppoe_id = sid;
++ 
++ 	return 0;
++ }
++ 
++-int mtk_foe_entry_set_wdma(struct mtk_foe_entry *entry, int wdma_idx, int txq,
++-			   int bss, int wcid)
+++int mtk_foe_entry_set_wdma(struct mtk_eth *eth, struct mtk_foe_entry *entry,
+++			   int wdma_idx, int txq, int bss, int wcid)
++ {
++-	struct mtk_foe_mac_info *l2 = mtk_foe_entry_l2(entry);
++-	u32 *ib2 = mtk_foe_entry_ib2(entry);
+++	struct mtk_foe_mac_info *l2 = mtk_foe_entry_l2(eth, entry);
+++	u32 *ib2 = mtk_foe_entry_ib2(eth, entry);
++ 
++-	*ib2 &= ~MTK_FOE_IB2_PORT_MG;
++-	*ib2 |= MTK_FOE_IB2_WDMA_WINFO;
++-	if (wdma_idx)
++-		*ib2 |= MTK_FOE_IB2_WDMA_DEVIDX;
++-
++-	l2->vlan2 = FIELD_PREP(MTK_FOE_VLAN2_WINFO_BSS, bss) |
++-		    FIELD_PREP(MTK_FOE_VLAN2_WINFO_WCID, wcid) |
++-		    FIELD_PREP(MTK_FOE_VLAN2_WINFO_RING, txq);
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+++		*ib2 &= ~MTK_FOE_IB2_PORT_MG_V2;
+++		*ib2 |=  FIELD_PREP(MTK_FOE_IB2_RX_IDX, txq) |
+++			 MTK_FOE_IB2_WDMA_WINFO_V2;
+++		l2->winfo = FIELD_PREP(MTK_FOE_WINFO_WCID, wcid) |
+++			    FIELD_PREP(MTK_FOE_WINFO_BSS, bss);
+++	} else {
+++		*ib2 &= ~MTK_FOE_IB2_PORT_MG;
+++		*ib2 |= MTK_FOE_IB2_WDMA_WINFO;
+++		if (wdma_idx)
+++			*ib2 |= MTK_FOE_IB2_WDMA_DEVIDX;
+++		l2->vlan2 = FIELD_PREP(MTK_FOE_VLAN2_WINFO_BSS, bss) |
+++			    FIELD_PREP(MTK_FOE_VLAN2_WINFO_WCID, wcid) |
+++			    FIELD_PREP(MTK_FOE_VLAN2_WINFO_RING, txq);
+++	}
++ 
++ 	return 0;
++ }
++@@ -376,14 +404,15 @@ static inline bool mtk_foe_entry_usable(
++ }
++ 
++ static bool
++-mtk_flow_entry_match(struct mtk_flow_entry *entry, struct mtk_foe_entry *data)
+++mtk_flow_entry_match(struct mtk_eth *eth, struct mtk_flow_entry *entry,
+++		     struct mtk_foe_entry *data)
++ {
++ 	int type, len;
++ 
++ 	if ((data->ib1 ^ entry->data.ib1) & MTK_FOE_IB1_UDP)
++ 		return false;
++ 
++-	type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->data.ib1);
+++	type = mtk_get_ib1_pkt_type(eth, entry->data.ib1);
++ 	if (type > MTK_PPE_PKT_TYPE_IPV4_DSLITE)
++ 		len = offsetof(struct mtk_foe_entry, ipv6._rsv);
++ 	else
++@@ -427,14 +456,12 @@ __mtk_foe_entry_clear(struct mtk_ppe *pp
++ 
++ static int __mtk_foe_entry_idle_time(struct mtk_ppe *ppe, u32 ib1)
++ {
++-	u16 timestamp;
++-	u16 now;
++-
++-	now = mtk_eth_timestamp(ppe->eth) & MTK_FOE_IB1_BIND_TIMESTAMP;
++-	timestamp = ib1 & MTK_FOE_IB1_BIND_TIMESTAMP;
+++	u32 ib1_ts_mask = mtk_get_ib1_ts_mask(ppe->eth);
+++	u16 now = mtk_eth_timestamp(ppe->eth);
+++	u16 timestamp = ib1 & ib1_ts_mask;
++ 
++ 	if (timestamp > now)
++-		return MTK_FOE_IB1_BIND_TIMESTAMP + 1 - timestamp + now;
+++		return ib1_ts_mask + 1 - timestamp + now;
++ 	else
++ 		return now - timestamp;
++ }
++@@ -442,6 +469,7 @@ static int __mtk_foe_entry_idle_time(str
++ static void
++ mtk_flow_entry_update_l2(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
++ {
+++	u32 ib1_ts_mask = mtk_get_ib1_ts_mask(ppe->eth);
++ 	struct mtk_flow_entry *cur;
++ 	struct mtk_foe_entry *hwe;
++ 	struct hlist_node *tmp;
++@@ -466,8 +494,8 @@ mtk_flow_entry_update_l2(struct mtk_ppe
++ 			continue;
++ 
++ 		idle = cur_idle;
++-		entry->data.ib1 &= ~MTK_FOE_IB1_BIND_TIMESTAMP;
++-		entry->data.ib1 |= hwe->ib1 & MTK_FOE_IB1_BIND_TIMESTAMP;
+++		entry->data.ib1 &= ~ib1_ts_mask;
+++		entry->data.ib1 |= hwe->ib1 & ib1_ts_mask;
++ 	}
++ }
++ 
++@@ -489,7 +517,7 @@ mtk_flow_entry_update(struct mtk_ppe *pp
++ 
++ 	hwe = mtk_foe_get_entry(ppe, entry->hash);
++ 	memcpy(&foe, hwe, ppe->eth->soc->foe_entry_size);
++-	if (!mtk_flow_entry_match(entry, &foe)) {
+++	if (!mtk_flow_entry_match(ppe->eth, entry, &foe)) {
++ 		entry->hash = 0xffff;
++ 		goto out;
++ 	}
++@@ -504,16 +532,22 @@ static void
++ __mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_foe_entry *entry,
++ 		       u16 hash)
++ {
+++	struct mtk_eth *eth = ppe->eth;
+++	u16 timestamp = mtk_eth_timestamp(eth);
++ 	struct mtk_foe_entry *hwe;
++-	u16 timestamp;
++ 
++-	timestamp = mtk_eth_timestamp(ppe->eth);
++-	timestamp &= MTK_FOE_IB1_BIND_TIMESTAMP;
++-	entry->ib1 &= ~MTK_FOE_IB1_BIND_TIMESTAMP;
++-	entry->ib1 |= FIELD_PREP(MTK_FOE_IB1_BIND_TIMESTAMP, timestamp);
+++	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+++		entry->ib1 &= ~MTK_FOE_IB1_BIND_TIMESTAMP_V2;
+++		entry->ib1 |= FIELD_PREP(MTK_FOE_IB1_BIND_TIMESTAMP_V2,
+++					 timestamp);
+++	} else {
+++		entry->ib1 &= ~MTK_FOE_IB1_BIND_TIMESTAMP;
+++		entry->ib1 |= FIELD_PREP(MTK_FOE_IB1_BIND_TIMESTAMP,
+++					 timestamp);
+++	}
++ 
++ 	hwe = mtk_foe_get_entry(ppe, hash);
++-	memcpy(&hwe->data, &entry->data, ppe->eth->soc->foe_entry_size);
+++	memcpy(&hwe->data, &entry->data, eth->soc->foe_entry_size);
++ 	wmb();
++ 	hwe->ib1 = entry->ib1;
++ 
++@@ -540,8 +574,8 @@ mtk_foe_entry_commit_l2(struct mtk_ppe *
++ 
++ int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
++ {
++-	int type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->data.ib1);
++ 	const struct mtk_soc_data *soc = ppe->eth->soc;
+++	int type = mtk_get_ib1_pkt_type(ppe->eth, entry->data.ib1);
++ 	u32 hash;
++ 
++ 	if (type == MTK_PPE_PKT_TYPE_BRIDGE)
++@@ -564,7 +598,7 @@ mtk_foe_entry_commit_subflow(struct mtk_
++ 	struct mtk_flow_entry *flow_info;
++ 	struct mtk_foe_entry foe = {}, *hwe;
++ 	struct mtk_foe_mac_info *l2;
++-	u32 ib1_mask = MTK_FOE_IB1_PACKET_TYPE | MTK_FOE_IB1_UDP;
+++	u32 ib1_mask = mtk_get_ib1_pkt_type_mask(ppe->eth) | MTK_FOE_IB1_UDP;
++ 	int type;
++ 
++ 	flow_info = kzalloc(offsetof(struct mtk_flow_entry, l2_data.end),
++@@ -584,16 +618,16 @@ mtk_foe_entry_commit_subflow(struct mtk_
++ 	foe.ib1 &= ib1_mask;
++ 	foe.ib1 |= entry->data.ib1 & ~ib1_mask;
++ 
++-	l2 = mtk_foe_entry_l2(&foe);
+++	l2 = mtk_foe_entry_l2(ppe->eth, &foe);
++ 	memcpy(l2, &entry->data.bridge.l2, sizeof(*l2));
++ 
++-	type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, foe.ib1);
+++	type = mtk_get_ib1_pkt_type(ppe->eth, foe.ib1);
++ 	if (type == MTK_PPE_PKT_TYPE_IPV4_HNAPT)
++ 		memcpy(&foe.ipv4.new, &foe.ipv4.orig, sizeof(foe.ipv4.new));
++ 	else if (type >= MTK_PPE_PKT_TYPE_IPV6_ROUTE_3T && l2->etype == ETH_P_IP)
++ 		l2->etype = ETH_P_IPV6;
++ 
++-	*mtk_foe_entry_ib2(&foe) = entry->data.bridge.ib2;
+++	*mtk_foe_entry_ib2(ppe->eth, &foe) = entry->data.bridge.ib2;
++ 
++ 	__mtk_foe_entry_commit(ppe, &foe, hash);
++ }
++@@ -626,7 +660,7 @@ void __mtk_ppe_check_skb(struct mtk_ppe
++ 			continue;
++ 		}
++ 
++-		if (found || !mtk_flow_entry_match(entry, hwe)) {
+++		if (found || !mtk_flow_entry_match(ppe->eth, entry, hwe)) {
++ 			if (entry->hash != 0xffff)
++ 				entry->hash = 0xffff;
++ 			continue;
++@@ -771,6 +805,8 @@ void mtk_ppe_start(struct mtk_ppe *ppe)
++ 			 MTK_PPE_SCAN_MODE_KEEPALIVE_AGE) |
++ 	      FIELD_PREP(MTK_PPE_TB_CFG_ENTRY_NUM,
++ 			 MTK_PPE_ENTRIES_SHIFT);
+++	if (MTK_HAS_CAPS(ppe->eth->soc->caps, MTK_NETSYS_V2))
+++		val |= MTK_PPE_TB_CFG_INFO_SEL;
++ 	ppe_w32(ppe, MTK_PPE_TB_CFG, val);
++ 
++ 	ppe_w32(ppe, MTK_PPE_IP_PROTO_CHK,
++@@ -778,15 +814,21 @@ void mtk_ppe_start(struct mtk_ppe *ppe)
++ 
++ 	mtk_ppe_cache_enable(ppe, true);
++ 
++-	val = MTK_PPE_FLOW_CFG_IP4_TCP_FRAG |
++-	      MTK_PPE_FLOW_CFG_IP4_UDP_FRAG |
++-	      MTK_PPE_FLOW_CFG_IP6_3T_ROUTE |
+++	val = MTK_PPE_FLOW_CFG_IP6_3T_ROUTE |
++ 	      MTK_PPE_FLOW_CFG_IP6_5T_ROUTE |
++ 	      MTK_PPE_FLOW_CFG_IP6_6RD |
++ 	      MTK_PPE_FLOW_CFG_IP4_NAT |
++ 	      MTK_PPE_FLOW_CFG_IP4_NAPT |
++ 	      MTK_PPE_FLOW_CFG_IP4_DSLITE |
++ 	      MTK_PPE_FLOW_CFG_IP4_NAT_FRAG;
+++	if (MTK_HAS_CAPS(ppe->eth->soc->caps, MTK_NETSYS_V2))
+++		val |= MTK_PPE_MD_TOAP_BYP_CRSN0 |
+++		       MTK_PPE_MD_TOAP_BYP_CRSN1 |
+++		       MTK_PPE_MD_TOAP_BYP_CRSN2 |
+++		       MTK_PPE_FLOW_CFG_IP4_HASH_GRE_KEY;
+++	else
+++		val |= MTK_PPE_FLOW_CFG_IP4_TCP_FRAG |
+++		       MTK_PPE_FLOW_CFG_IP4_UDP_FRAG;
++ 	ppe_w32(ppe, MTK_PPE_FLOW_CFG, val);
++ 
++ 	val = FIELD_PREP(MTK_PPE_UNBIND_AGE_MIN_PACKETS, 1000) |
++@@ -820,6 +862,11 @@ void mtk_ppe_start(struct mtk_ppe *ppe)
++ 	ppe_w32(ppe, MTK_PPE_GLO_CFG, val);
++ 
++ 	ppe_w32(ppe, MTK_PPE_DEFAULT_CPU_PORT, 0);
+++
+++	if (MTK_HAS_CAPS(ppe->eth->soc->caps, MTK_NETSYS_V2)) {
+++		ppe_w32(ppe, MTK_PPE_DEFAULT_CPU_PORT1, 0xcb777);
+++		ppe_w32(ppe, MTK_PPE_SBW_CTRL, 0x7f);
+++	}
++ }
++ 
++ int mtk_ppe_stop(struct mtk_ppe *ppe)
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
++@@ -32,6 +32,15 @@
++ #define MTK_FOE_IB1_UDP			BIT(30)
++ #define MTK_FOE_IB1_STATIC		BIT(31)
++ 
+++/* CONFIG_MEDIATEK_NETSYS_V2 */
+++#define MTK_FOE_IB1_BIND_TIMESTAMP_V2	GENMASK(7, 0)
+++#define MTK_FOE_IB1_BIND_VLAN_LAYER_V2	GENMASK(16, 14)
+++#define MTK_FOE_IB1_BIND_PPPOE_V2	BIT(17)
+++#define MTK_FOE_IB1_BIND_VLAN_TAG_V2	BIT(18)
+++#define MTK_FOE_IB1_BIND_CACHE_V2	BIT(20)
+++#define MTK_FOE_IB1_BIND_TTL_V2		BIT(22)
+++#define MTK_FOE_IB1_PACKET_TYPE_V2	GENMASK(27, 23)
+++
++ enum {
++ 	MTK_PPE_PKT_TYPE_IPV4_HNAPT = 0,
++ 	MTK_PPE_PKT_TYPE_IPV4_ROUTE = 1,
++@@ -53,14 +62,25 @@ enum {
++ 
++ #define MTK_FOE_IB2_PORT_MG		GENMASK(17, 12)
++ 
+++#define MTK_FOE_IB2_RX_IDX		GENMASK(18, 17)
++ #define MTK_FOE_IB2_PORT_AG		GENMASK(23, 18)
++ 
++ #define MTK_FOE_IB2_DSCP		GENMASK(31, 24)
++ 
+++/* CONFIG_MEDIATEK_NETSYS_V2 */
+++#define MTK_FOE_IB2_PORT_MG_V2		BIT(7)
+++#define MTK_FOE_IB2_DEST_PORT_V2	GENMASK(12, 9)
+++#define MTK_FOE_IB2_MULTICAST_V2	BIT(13)
+++#define MTK_FOE_IB2_WDMA_WINFO_V2	BIT(19)
+++#define MTK_FOE_IB2_PORT_AG_V2		GENMASK(23, 20)
+++
++ #define MTK_FOE_VLAN2_WINFO_BSS		GENMASK(5, 0)
++ #define MTK_FOE_VLAN2_WINFO_WCID	GENMASK(13, 6)
++ #define MTK_FOE_VLAN2_WINFO_RING	GENMASK(15, 14)
++ 
+++#define MTK_FOE_WINFO_BSS		GENMASK(5, 0)
+++#define MTK_FOE_WINFO_WCID		GENMASK(15, 6)
+++
++ enum {
++ 	MTK_FOE_STATE_INVALID,
++ 	MTK_FOE_STATE_UNBIND,
++@@ -81,6 +101,9 @@ struct mtk_foe_mac_info {
++ 
++ 	u16 pppoe_id;
++ 	u16 src_mac_lo;
+++
+++	u16 minfo;
+++	u16 winfo;
++ };
++ 
++ /* software-only entry type */
++@@ -198,7 +221,7 @@ struct mtk_foe_entry {
++ 		struct mtk_foe_ipv4_dslite dslite;
++ 		struct mtk_foe_ipv6 ipv6;
++ 		struct mtk_foe_ipv6_6rd ipv6_6rd;
++-		u32 data[19];
+++		u32 data[23];
++ 	};
++ };
++ 
++@@ -306,20 +329,27 @@ mtk_ppe_check_skb(struct mtk_ppe *ppe, s
++ 	__mtk_ppe_check_skb(ppe, skb, hash);
++ }
++ 
++-int mtk_foe_entry_prepare(struct mtk_foe_entry *entry, int type, int l4proto,
++-			  u8 pse_port, u8 *src_mac, u8 *dest_mac);
++-int mtk_foe_entry_set_pse_port(struct mtk_foe_entry *entry, u8 port);
++-int mtk_foe_entry_set_ipv4_tuple(struct mtk_foe_entry *entry, bool orig,
+++int mtk_foe_entry_prepare(struct mtk_eth *eth, struct mtk_foe_entry *entry,
+++			  int type, int l4proto, u8 pse_port, u8 *src_mac,
+++			  u8 *dest_mac);
+++int mtk_foe_entry_set_pse_port(struct mtk_eth *eth,
+++			       struct mtk_foe_entry *entry, u8 port);
+++int mtk_foe_entry_set_ipv4_tuple(struct mtk_eth *eth,
+++				 struct mtk_foe_entry *entry, bool orig,
++ 				 __be32 src_addr, __be16 src_port,
++ 				 __be32 dest_addr, __be16 dest_port);
++-int mtk_foe_entry_set_ipv6_tuple(struct mtk_foe_entry *entry,
+++int mtk_foe_entry_set_ipv6_tuple(struct mtk_eth *eth,
+++				 struct mtk_foe_entry *entry,
++ 				 __be32 *src_addr, __be16 src_port,
++ 				 __be32 *dest_addr, __be16 dest_port);
++-int mtk_foe_entry_set_dsa(struct mtk_foe_entry *entry, int port);
++-int mtk_foe_entry_set_vlan(struct mtk_foe_entry *entry, int vid);
++-int mtk_foe_entry_set_pppoe(struct mtk_foe_entry *entry, int sid);
++-int mtk_foe_entry_set_wdma(struct mtk_foe_entry *entry, int wdma_idx, int txq,
++-			   int bss, int wcid);
+++int mtk_foe_entry_set_dsa(struct mtk_eth *eth, struct mtk_foe_entry *entry,
+++			  int port);
+++int mtk_foe_entry_set_vlan(struct mtk_eth *eth, struct mtk_foe_entry *entry,
+++			   int vid);
+++int mtk_foe_entry_set_pppoe(struct mtk_eth *eth, struct mtk_foe_entry *entry,
+++			    int sid);
+++int mtk_foe_entry_set_wdma(struct mtk_eth *eth, struct mtk_foe_entry *entry,
+++			   int wdma_idx, int txq, int bss, int wcid);
++ int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
++ void mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
++ int mtk_foe_entry_idle_time(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
++--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++@@ -52,18 +52,19 @@ static const struct rhashtable_params mt
++ };
++ 
++ static int
++-mtk_flow_set_ipv4_addr(struct mtk_foe_entry *foe, struct mtk_flow_data *data,
++-		       bool egress)
+++mtk_flow_set_ipv4_addr(struct mtk_eth *eth, struct mtk_foe_entry *foe,
+++		       struct mtk_flow_data *data, bool egress)
++ {
++-	return mtk_foe_entry_set_ipv4_tuple(foe, egress,
+++	return mtk_foe_entry_set_ipv4_tuple(eth, foe, egress,
++ 					    data->v4.src_addr, data->src_port,
++ 					    data->v4.dst_addr, data->dst_port);
++ }
++ 
++ static int
++-mtk_flow_set_ipv6_addr(struct mtk_foe_entry *foe, struct mtk_flow_data *data)
+++mtk_flow_set_ipv6_addr(struct mtk_eth *eth, struct mtk_foe_entry *foe,
+++		       struct mtk_flow_data *data)
++ {
++-	return mtk_foe_entry_set_ipv6_tuple(foe,
+++	return mtk_foe_entry_set_ipv6_tuple(eth, foe,
++ 					    data->v6.src_addr.s6_addr32, data->src_port,
++ 					    data->v6.dst_addr.s6_addr32, data->dst_port);
++ }
++@@ -190,16 +191,29 @@ mtk_flow_set_output_device(struct mtk_et
++ 	int pse_port, dsa_port;
++ 
++ 	if (mtk_flow_get_wdma_info(dev, dest_mac, &info) == 0) {
++-		mtk_foe_entry_set_wdma(foe, info.wdma_idx, info.queue, info.bss,
++-				       info.wcid);
++-		pse_port = 3;
+++		mtk_foe_entry_set_wdma(eth, foe, info.wdma_idx, info.queue,
+++				       info.bss, info.wcid);
+++		if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+++			switch (info.wdma_idx) {
+++			case 0:
+++				pse_port = 8;
+++				break;
+++			case 1:
+++				pse_port = 9;
+++				break;
+++			default:
+++				return -EINVAL;
+++			}
+++		} else {
+++			pse_port = 3;
+++		}
++ 		*wed_index = info.wdma_idx;
++ 		goto out;
++ 	}
++ 
++ 	dsa_port = mtk_flow_get_dsa_port(&dev);
++ 	if (dsa_port >= 0)
++-		mtk_foe_entry_set_dsa(foe, dsa_port);
+++		mtk_foe_entry_set_dsa(eth, foe, dsa_port);
++ 
++ 	if (dev == eth->netdev[0])
++ 		pse_port = 1;
++@@ -209,7 +223,7 @@ mtk_flow_set_output_device(struct mtk_et
++ 		return -EOPNOTSUPP;
++ 
++ out:
++-	mtk_foe_entry_set_pse_port(foe, pse_port);
+++	mtk_foe_entry_set_pse_port(eth, foe, pse_port);
++ 
++ 	return 0;
++ }
++@@ -333,9 +347,8 @@ mtk_flow_offload_replace(struct mtk_eth
++ 	    !is_valid_ether_addr(data.eth.h_dest))
++ 		return -EINVAL;
++ 
++-	err = mtk_foe_entry_prepare(&foe, offload_type, l4proto, 0,
++-				    data.eth.h_source,
++-				    data.eth.h_dest);
+++	err = mtk_foe_entry_prepare(eth, &foe, offload_type, l4proto, 0,
+++				    data.eth.h_source, data.eth.h_dest);
++ 	if (err)
++ 		return err;
++ 
++@@ -360,7 +373,7 @@ mtk_flow_offload_replace(struct mtk_eth
++ 		data.v4.src_addr = addrs.key->src;
++ 		data.v4.dst_addr = addrs.key->dst;
++ 
++-		mtk_flow_set_ipv4_addr(&foe, &data, false);
+++		mtk_flow_set_ipv4_addr(eth, &foe, &data, false);
++ 	}
++ 
++ 	if (addr_type == FLOW_DISSECTOR_KEY_IPV6_ADDRS) {
++@@ -371,7 +384,7 @@ mtk_flow_offload_replace(struct mtk_eth
++ 		data.v6.src_addr = addrs.key->src;
++ 		data.v6.dst_addr = addrs.key->dst;
++ 
++-		mtk_flow_set_ipv6_addr(&foe, &data);
+++		mtk_flow_set_ipv6_addr(eth, &foe, &data);
++ 	}
++ 
++ 	flow_action_for_each(i, act, &rule->action) {
++@@ -401,7 +414,7 @@ mtk_flow_offload_replace(struct mtk_eth
++ 	}
++ 
++ 	if (addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS) {
++-		err = mtk_flow_set_ipv4_addr(&foe, &data, true);
+++		err = mtk_flow_set_ipv4_addr(eth, &foe, &data, true);
++ 		if (err)
++ 			return err;
++ 	}
++@@ -413,10 +426,10 @@ mtk_flow_offload_replace(struct mtk_eth
++ 		if (data.vlan.proto != htons(ETH_P_8021Q))
++ 			return -EOPNOTSUPP;
++ 
++-		mtk_foe_entry_set_vlan(&foe, data.vlan.id);
+++		mtk_foe_entry_set_vlan(eth, &foe, data.vlan.id);
++ 	}
++ 	if (data.pppoe.num == 1)
++-		mtk_foe_entry_set_pppoe(&foe, data.pppoe.sid);
+++		mtk_foe_entry_set_pppoe(eth, &foe, data.pppoe.sid);
++ 
++ 	err = mtk_flow_set_output_device(eth, &foe, odev, data.eth.h_dest,
++ 					 &wed_index);
++--- a/drivers/net/ethernet/mediatek/mtk_ppe_regs.h
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe_regs.h
++@@ -21,6 +21,9 @@
++ #define MTK_PPE_GLO_CFG_BUSY			BIT(31)
++ 
++ #define MTK_PPE_FLOW_CFG			0x204
+++#define MTK_PPE_MD_TOAP_BYP_CRSN0		BIT(1)
+++#define MTK_PPE_MD_TOAP_BYP_CRSN1		BIT(2)
+++#define MTK_PPE_MD_TOAP_BYP_CRSN2		BIT(3)
++ #define MTK_PPE_FLOW_CFG_IP4_TCP_FRAG		BIT(6)
++ #define MTK_PPE_FLOW_CFG_IP4_UDP_FRAG		BIT(7)
++ #define MTK_PPE_FLOW_CFG_IP6_3T_ROUTE		BIT(8)
++@@ -54,6 +57,7 @@
++ #define MTK_PPE_TB_CFG_HASH_MODE		GENMASK(15, 14)
++ #define MTK_PPE_TB_CFG_SCAN_MODE		GENMASK(17, 16)
++ #define MTK_PPE_TB_CFG_HASH_DEBUG		GENMASK(19, 18)
+++#define MTK_PPE_TB_CFG_INFO_SEL			BIT(20)
++ 
++ enum {
++ 	MTK_PPE_SCAN_MODE_DISABLED,
++@@ -112,6 +116,8 @@ enum {
++ #define MTK_PPE_DEFAULT_CPU_PORT		0x248
++ #define MTK_PPE_DEFAULT_CPU_PORT_MASK(_n)	(GENMASK(2, 0) << ((_n) * 4))
++ 
+++#define MTK_PPE_DEFAULT_CPU_PORT1		0x24c
+++
++ #define MTK_PPE_MTU_DROP			0x308
++ 
++ #define MTK_PPE_VLAN_MTU0			0x30c
++@@ -141,4 +147,6 @@ enum {
++ #define MTK_PPE_MIB_CACHE_CTL_EN		BIT(0)
++ #define MTK_PPE_MIB_CACHE_CTL_FLUSH		BIT(2)
++ 
+++#define MTK_PPE_SBW_CTRL			0x374
+++
++ #endif
+diff --git a/target/linux/generic/backport-5.15/724-v6.0-net-ethernet-mtk_eth_soc-enable-flow-offloading-supp.patch b/target/linux/generic/backport-5.15/724-v6.0-net-ethernet-mtk_eth_soc-enable-flow-offloading-supp.patch
+new file mode 100644
+index 0000000000..037e9d4443
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/724-v6.0-net-ethernet-mtk_eth_soc-enable-flow-offloading-supp.patch
+@@ -0,0 +1,26 @@
++From b94b02a270471337bef73c44fa3493a521e31a61 Mon Sep 17 00:00:00 2001
++Message-Id: <b94b02a270471337bef73c44fa3493a521e31a61.1662886034.git.lorenzo@kernel.org>
++In-Reply-To: <e5ecb4f619197b93fa682d722452dc8412864cdb.1662886033.git.lorenzo@kernel.org>
++References: <e5ecb4f619197b93fa682d722452dc8412864cdb.1662886033.git.lorenzo@kernel.org>
++From: Lorenzo Bianconi <lorenzo@kernel.org>
++Date: Mon, 5 Sep 2022 13:56:13 +0200
++Subject: [PATCH net-next 5/5] net: ethernet: mtk_eth_soc: enable flow
++ offloading support for mt7986
++
++Enable hw packet engine and wireless packet dispatcher for mt7986
++
++Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 1 +
++ 1 file changed, 1 insertion(+)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -4256,6 +4256,7 @@ static const struct mtk_soc_data mt7986_
++ 	.hw_features = MTK_HW_FEATURES,
++ 	.required_clks = MT7986_CLKS_BITMAP,
++ 	.required_pctl = false,
+++	.offload_version = 2,
++ 	.hash_offset = 4,
++ 	.foe_entry_size = sizeof(struct mtk_foe_entry),
++ 	.txrx = {
+diff --git a/target/linux/generic/backport-5.15/724-v6.0-net-ethernet-mtk_eth_soc-fix-wrong-use-of-new-helper.patch b/target/linux/generic/backport-5.15/724-v6.0-net-ethernet-mtk_eth_soc-fix-wrong-use-of-new-helper.patch
+new file mode 100644
+index 0000000000..c4bd29365c
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/724-v6.0-net-ethernet-mtk_eth_soc-fix-wrong-use-of-new-helper.patch
+@@ -0,0 +1,46 @@
++From 40350ce3ae8701146aafd79c5f7b5582d9955e58 Mon Sep 17 00:00:00 2001
++From: Daniel Golle <daniel@makrotopia.org>
++Date: Sun, 25 Sep 2022 15:12:35 +0100
++Subject: [PATCH 1/2] net: ethernet: mtk_eth_soc: fix wrong use of new helper
++ function
++To: linux-mediatek@lists.infradead.org,
++    netdev@vger.kernel.org,
++    Lorenzo Bianconi <lorenzo@kernel.org>
++Cc: Sujuan Chen <sujuan.chen@mediatek.com>,
++    Bo Jiao <Bo.Jiao@mediatek.com>,
++    Felix Fietkau <nbd@nbd.name>,
++    John Crispin <john@phrozen.org>,
++    Sean Wang <sean.wang@mediatek.com>,
++    Mark Lee <Mark-MC.Lee@mediatek.com>,
++    David S. Miller <davem@davemloft.net>,
++    Eric Dumazet <edumazet@google.com>,
++    Jakub Kicinski <kuba@kernel.org>,
++    Paolo Abeni <pabeni@redhat.com>,
++    Matthias Brugger <matthias.bgg@gmail.com>,
++    Chen Minqiang <ptpt52@gmail.com>
++
++In function mtk_foe_entry_set_vlan() the call to field accessor macro
++FIELD_GET(MTK_FOE_IB1_BIND_VLAN_LAYER, entry->ib1)
++has been wrongly replaced by
++mtk_prep_ib1_vlan_layer(eth, entry->ib1)
++
++Use correct helper function mtk_get_ib1_vlan_layer instead.
++
++Reported-by: Chen Minqiang <ptpt52@gmail.com>
++Fixes: 03a3180e5c09e1 ("net: ethernet: mtk_eth_soc: introduce flow offloading support for mt7986")
++Signed-off-by: Daniel Golle <daniel@makrotopia.org>
++---
++ drivers/net/ethernet/mediatek/mtk_ppe.c | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
++@@ -337,7 +337,7 @@ int mtk_foe_entry_set_vlan(struct mtk_et
++ {
++ 	struct mtk_foe_mac_info *l2 = mtk_foe_entry_l2(eth, entry);
++ 
++-	switch (mtk_prep_ib1_vlan_layer(eth, entry->ib1)) {
+++	switch (mtk_get_ib1_vlan_layer(eth, entry->ib1)) {
++ 	case 0:
++ 		entry->ib1 |= mtk_get_ib1_vlan_tag_mask(eth) |
++ 			      mtk_prep_ib1_vlan_layer(eth, 1);
+diff --git a/target/linux/generic/backport-5.15/725-v6.0-net-ethernet-mtk_eth_soc-fix-usage-of-foe_entry_size.patch b/target/linux/generic/backport-5.15/725-v6.0-net-ethernet-mtk_eth_soc-fix-usage-of-foe_entry_size.patch
+new file mode 100644
+index 0000000000..bb02f401a2
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/725-v6.0-net-ethernet-mtk_eth_soc-fix-usage-of-foe_entry_size.patch
+@@ -0,0 +1,49 @@
++From fcf14c2c5deae8f8c3d25530bab10856f63f8a63 Mon Sep 17 00:00:00 2001
++From: Daniel Golle <daniel@makrotopia.org>
++Date: Sun, 25 Sep 2022 15:18:54 +0100
++Subject: [PATCH 2/2] net: ethernet: mtk_eth_soc: fix usage of foe_entry_size
++To: linux-mediatek@lists.infradead.org,
++    netdev@vger.kernel.org,
++    Lorenzo Bianconi <lorenzo@kernel.org>
++Cc: Sujuan Chen <sujuan.chen@mediatek.com>,
++    Bo Jiao <Bo.Jiao@mediatek.com>,
++    Felix Fietkau <nbd@nbd.name>,
++    John Crispin <john@phrozen.org>,
++    Sean Wang <sean.wang@mediatek.com>,
++    Mark Lee <Mark-MC.Lee@mediatek.com>,
++    David S. Miller <davem@davemloft.net>,
++    Eric Dumazet <edumazet@google.com>,
++    Jakub Kicinski <kuba@kernel.org>,
++    Paolo Abeni <pabeni@redhat.com>,
++    Matthias Brugger <matthias.bgg@gmail.com>,
++    Chen Minqiang <ptpt52@gmail.com>
++
++As sizeof(hwe->data) can now longer be used as the actual size depends
++on foe_entry_size, in commit 9d8cb4c096ab02
++("net: ethernet: mtk_eth_soc: add foe_entry_size to mtk_eth_soc") the
++use of sizeof(hwe->data) is hence replaced.
++However, replacing it with ppe->eth->soc->foe_entry_size is wrong as
++foe_entry_size represents the size of the whole descriptor and not just
++the 'data' field.
++Fix this by subtracing the size of the only other field in the struct
++'ib1', so we actually end up with the correct size to be copied to the
++data field.
++
++Reported-by: Chen Minqiang <ptpt52@gmail.com>
++Fixes: 9d8cb4c096ab02 ("net: ethernet: mtk_eth_soc: add foe_entry_size to mtk_eth_soc")
++Signed-off-by: Daniel Golle <daniel@makrotopia.org>
++---
++ drivers/net/ethernet/mediatek/mtk_ppe.c | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
++@@ -547,7 +547,7 @@ __mtk_foe_entry_commit(struct mtk_ppe *p
++ 	}
++ 
++ 	hwe = mtk_foe_get_entry(ppe, hash);
++-	memcpy(&hwe->data, &entry->data, eth->soc->foe_entry_size);
+++	memcpy(&hwe->data, &entry->data, eth->soc->foe_entry_size - sizeof(hwe->ib1));
++ 	wmb();
++ 	hwe->ib1 = entry->ib1;
++ 
+diff --git a/target/linux/generic/backport-5.15/726-v6.0-net-ethernet-mtk_eth_soc-fix-mask-of-RX_DMA_GET_SPOR.patch b/target/linux/generic/backport-5.15/726-v6.0-net-ethernet-mtk_eth_soc-fix-mask-of-RX_DMA_GET_SPOR.patch
+new file mode 100644
+index 0000000000..27c719b663
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/726-v6.0-net-ethernet-mtk_eth_soc-fix-mask-of-RX_DMA_GET_SPOR.patch
+@@ -0,0 +1,32 @@
++From c9da02bfb1112461e048d3b736afb1873f6f4ccf Mon Sep 17 00:00:00 2001
++From: Daniel Golle <daniel@makrotopia.org>
++Date: Tue, 27 Sep 2022 16:30:02 +0100
++Subject: [PATCH 1/1] net: ethernet: mtk_eth_soc: fix mask of
++ RX_DMA_GET_SPORT{,_V2}
++
++The bitmasks applied in RX_DMA_GET_SPORT and RX_DMA_GET_SPORT_V2 macros
++were swapped. Fix that.
++
++Reported-by: Chen Minqiang <ptpt52@gmail.com>
++Fixes: 160d3a9b192985 ("net: ethernet: mtk_eth_soc: introduce MTK_NETSYS_V2 support")
++Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
++Signed-off-by: Daniel Golle <daniel@makrotopia.org>
++Link: https://lore.kernel.org/r/YzMW+mg9UsaCdKRQ@makrotopia.org
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 4 ++--
++ 1 file changed, 2 insertions(+), 2 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++@@ -315,8 +315,8 @@
++ #define MTK_RXD5_PPE_CPU_REASON	GENMASK(22, 18)
++ #define MTK_RXD5_SRC_PORT	GENMASK(29, 26)
++ 
++-#define RX_DMA_GET_SPORT(x)	(((x) >> 19) & 0xf)
++-#define RX_DMA_GET_SPORT_V2(x)	(((x) >> 26) & 0x7)
+++#define RX_DMA_GET_SPORT(x)	(((x) >> 19) & 0x7)
+++#define RX_DMA_GET_SPORT_V2(x)	(((x) >> 26) & 0xf)
++ 
++ /* PDMA V2 descriptor rxd3 */
++ #define RX_DMA_VTAG_V2		BIT(0)
+diff --git a/target/linux/generic/backport-5.15/727-v6.1-net-ethernet-mtk_eth_soc-fix-state-in-__mtk_foe_entr.patch b/target/linux/generic/backport-5.15/727-v6.1-net-ethernet-mtk_eth_soc-fix-state-in-__mtk_foe_entr.patch
+new file mode 100644
+index 0000000000..11465c1c5b
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/727-v6.1-net-ethernet-mtk_eth_soc-fix-state-in-__mtk_foe_entr.patch
+@@ -0,0 +1,37 @@
++From ae3ed15da5889263de372ff9df2e83e16acca4cb Mon Sep 17 00:00:00 2001
++From: Daniel Golle <daniel@makrotopia.org>
++Date: Fri, 30 Sep 2022 01:56:53 +0100
++Subject: [PATCH 1/1] net: ethernet: mtk_eth_soc: fix state in
++ __mtk_foe_entry_clear
++
++Setting ib1 state to MTK_FOE_STATE_UNBIND in __mtk_foe_entry_clear
++routine as done by commit 0e80707d94e4c8 ("net: ethernet: mtk_eth_soc:
++fix typo in __mtk_foe_entry_clear") breaks flow offloading, at least
++on older MTK_NETSYS_V1 SoCs, OpenWrt users have confirmed the bug on
++MT7622 and MT7621 systems.
++Felix Fietkau suggested to use MTK_FOE_STATE_INVALID instead which
++works well on both, MTK_NETSYS_V1 and MTK_NETSYS_V2.
++
++Tested on MT7622 (Linksys E8450) and MT7986 (BananaPi BPI-R3).
++
++Suggested-by: Felix Fietkau <nbd@nbd.name>
++Fixes: 0e80707d94e4c8 ("net: ethernet: mtk_eth_soc: fix typo in __mtk_foe_entry_clear")
++Fixes: 33fc42de33278b ("net: ethernet: mtk_eth_soc: support creating mac address based offload entries")
++Signed-off-by: Daniel Golle <daniel@makrotopia.org>
++Link: https://lore.kernel.org/r/YzY+1Yg0FBXcnrtc@makrotopia.org
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/ethernet/mediatek/mtk_ppe.c | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
+++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
++@@ -442,7 +442,7 @@ __mtk_foe_entry_clear(struct mtk_ppe *pp
++ 		struct mtk_foe_entry *hwe = mtk_foe_get_entry(ppe, entry->hash);
++ 
++ 		hwe->ib1 &= ~MTK_FOE_IB1_STATE;
++-		hwe->ib1 |= FIELD_PREP(MTK_FOE_IB1_STATE, MTK_FOE_STATE_UNBIND);
+++		hwe->ib1 |= FIELD_PREP(MTK_FOE_IB1_STATE, MTK_FOE_STATE_INVALID);
++ 		dma_wmb();
++ 	}
++ 	entry->hash = 0xffff;
+diff --git a/target/linux/generic/backport-5.15/728-v6.1-01-net-ethernet-mtk_eth_soc-fix-possible-memory-leak-in.patch b/target/linux/generic/backport-5.15/728-v6.1-01-net-ethernet-mtk_eth_soc-fix-possible-memory-leak-in.patch
+new file mode 100644
+index 0000000000..c419fc8130
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/728-v6.1-01-net-ethernet-mtk_eth_soc-fix-possible-memory-leak-in.patch
+@@ -0,0 +1,73 @@
++From b3d0d98179d62f9d55635a600679c4fa362baf8d Mon Sep 17 00:00:00 2001
++From: Yang Yingliang <yangyingliang@huawei.com>
++Date: Mon, 17 Oct 2022 11:51:54 +0800
++Subject: [PATCH 1/3] net: ethernet: mtk_eth_soc: fix possible memory leak in
++ mtk_probe()
++
++If mtk_wed_add_hw() has been called, mtk_wed_exit() needs be called
++in error path or removing module to free the memory allocated in
++mtk_wed_add_hw().
++
++Fixes: 804775dfc288 ("net: ethernet: mtk_eth_soc: add support for Wireless Ethernet Dispatch (WED)")
++Signed-off-by: Yang Yingliang <yangyingliang@huawei.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 17 ++++++++++++-----
++ 1 file changed, 12 insertions(+), 5 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++@@ -4008,19 +4008,23 @@ static int mtk_probe(struct platform_dev
++ 			eth->irq[i] = platform_get_irq(pdev, i);
++ 		if (eth->irq[i] < 0) {
++ 			dev_err(&pdev->dev, "no IRQ%d resource found\n", i);
++-			return -ENXIO;
+++			err = -ENXIO;
+++			goto err_wed_exit;
++ 		}
++ 	}
++ 	for (i = 0; i < ARRAY_SIZE(eth->clks); i++) {
++ 		eth->clks[i] = devm_clk_get(eth->dev,
++ 					    mtk_clks_source_name[i]);
++ 		if (IS_ERR(eth->clks[i])) {
++-			if (PTR_ERR(eth->clks[i]) == -EPROBE_DEFER)
++-				return -EPROBE_DEFER;
+++			if (PTR_ERR(eth->clks[i]) == -EPROBE_DEFER) {
+++				err = -EPROBE_DEFER;
+++				goto err_wed_exit;
+++			}
++ 			if (eth->soc->required_clks & BIT(i)) {
++ 				dev_err(&pdev->dev, "clock %s not found\n",
++ 					mtk_clks_source_name[i]);
++-				return -EINVAL;
+++				err = -EINVAL;
+++				goto err_wed_exit;
++ 			}
++ 			eth->clks[i] = NULL;
++ 		}
++@@ -4031,7 +4035,7 @@ static int mtk_probe(struct platform_dev
++ 
++ 	err = mtk_hw_init(eth);
++ 	if (err)
++-		return err;
+++		goto err_wed_exit;
++ 
++ 	eth->hwlro = MTK_HAS_CAPS(eth->soc->caps, MTK_HWLRO);
++ 
++@@ -4129,6 +4133,8 @@ err_free_dev:
++ 	mtk_free_dev(eth);
++ err_deinit_hw:
++ 	mtk_hw_deinit(eth);
+++err_wed_exit:
+++	mtk_wed_exit();
++ 
++ 	return err;
++ }
++@@ -4148,6 +4154,7 @@ static int mtk_remove(struct platform_de
++ 		phylink_disconnect_phy(mac->phylink);
++ 	}
++ 
+++	mtk_wed_exit();
++ 	mtk_hw_deinit(eth);
++ 
++ 	netif_napi_del(&eth->tx_napi);
+diff --git a/target/linux/generic/backport-5.15/728-v6.1-02-net-ethernet-mtk_eth_wed-add-missing-put_device-in-m.patch b/target/linux/generic/backport-5.15/728-v6.1-02-net-ethernet-mtk_eth_wed-add-missing-put_device-in-m.patch
+new file mode 100644
+index 0000000000..ef5374dcc5
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/728-v6.1-02-net-ethernet-mtk_eth_wed-add-missing-put_device-in-m.patch
+@@ -0,0 +1,47 @@
++From 9d4f20a476ca57e4c9246eb1fa2a61bea2354720 Mon Sep 17 00:00:00 2001
++From: Yang Yingliang <yangyingliang@huawei.com>
++Date: Mon, 17 Oct 2022 11:51:55 +0800
++Subject: [PATCH 2/3] net: ethernet: mtk_eth_wed: add missing put_device() in
++ mtk_wed_add_hw()
++
++After calling get_device() in mtk_wed_add_hw(), in error path, put_device()
++needs be called.
++
++Fixes: 804775dfc288 ("net: ethernet: mtk_eth_soc: add support for Wireless Ethernet Dispatch (WED)")
++Signed-off-by: Yang Yingliang <yangyingliang@huawei.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/mediatek/mtk_wed.c | 10 ++++++++--
++ 1 file changed, 8 insertions(+), 2 deletions(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_wed.c
+++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
++@@ -1084,11 +1084,11 @@ void mtk_wed_add_hw(struct device_node *
++ 	get_device(&pdev->dev);
++ 	irq = platform_get_irq(pdev, 0);
++ 	if (irq < 0)
++-		return;
+++		goto err_put_device;
++ 
++ 	regs = syscon_regmap_lookup_by_phandle(np, NULL);
++ 	if (IS_ERR(regs))
++-		return;
+++		goto err_put_device;
++ 
++ 	rcu_assign_pointer(mtk_soc_wed_ops, &wed_ops);
++ 
++@@ -1131,8 +1131,14 @@ void mtk_wed_add_hw(struct device_node *
++ 
++ 	hw_list[index] = hw;
++ 
+++	mutex_unlock(&hw_lock);
+++
+++	return;
+++
++ unlock:
++ 	mutex_unlock(&hw_lock);
+++err_put_device:
+++	put_device(&pdev->dev);
++ }
++ 
++ void mtk_wed_exit(void)
+diff --git a/target/linux/generic/backport-5.15/728-v6.1-03-net-ethernet-mtk_eth_wed-add-missing-of_node_put.patch b/target/linux/generic/backport-5.15/728-v6.1-03-net-ethernet-mtk_eth_wed-add-missing-of_node_put.patch
+new file mode 100644
+index 0000000000..0a452d4a7d
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/728-v6.1-03-net-ethernet-mtk_eth_wed-add-missing-of_node_put.patch
+@@ -0,0 +1,43 @@
++From e0bb4659e235770e6f53b3692e958591f49448f5 Mon Sep 17 00:00:00 2001
++From: Yang Yingliang <yangyingliang@huawei.com>
++Date: Mon, 17 Oct 2022 11:51:56 +0800
++Subject: [PATCH 3/3] net: ethernet: mtk_eth_wed: add missing of_node_put()
++
++The device_node pointer returned by of_parse_phandle() with refcount
++incremented, when finish using it, the refcount need be decreased.
++
++Fixes: 804775dfc288 ("net: ethernet: mtk_eth_soc: add support for Wireless Ethernet Dispatch (WED)")
++Signed-off-by: Yang Yingliang <yangyingliang@huawei.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/mediatek/mtk_wed.c | 5 ++++-
++ 1 file changed, 4 insertions(+), 1 deletion(-)
++
++--- a/drivers/net/ethernet/mediatek/mtk_wed.c
+++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
++@@ -1079,7 +1079,7 @@ void mtk_wed_add_hw(struct device_node *
++ 
++ 	pdev = of_find_device_by_node(np);
++ 	if (!pdev)
++-		return;
+++		goto err_of_node_put;
++ 
++ 	get_device(&pdev->dev);
++ 	irq = platform_get_irq(pdev, 0);
++@@ -1139,6 +1139,8 @@ unlock:
++ 	mutex_unlock(&hw_lock);
++ err_put_device:
++ 	put_device(&pdev->dev);
+++err_of_node_put:
+++	of_node_put(np);
++ }
++ 
++ void mtk_wed_exit(void)
++@@ -1159,6 +1161,7 @@ void mtk_wed_exit(void)
++ 		hw_list[i] = NULL;
++ 		debugfs_remove(hw->debugfs_dir);
++ 		put_device(hw->dev);
+++		of_node_put(hw->node);
++ 		kfree(hw);
++ 	}
++ }
+diff --git a/target/linux/generic/backport-5.15/734-v5.16-0001-net-bgmac-improve-handling-PHY.patch b/target/linux/generic/backport-5.15/734-v5.16-0001-net-bgmac-improve-handling-PHY.patch
+new file mode 100644
+index 0000000000..6788a2ec35
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/734-v5.16-0001-net-bgmac-improve-handling-PHY.patch
+@@ -0,0 +1,84 @@
++From b5375509184dc23d2b7fa0c5ed8763899ccc9674 Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Sat, 2 Oct 2021 19:58:11 +0200
++Subject: [PATCH] net: bgmac: improve handling PHY
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++1. Use info from DT if available
++
++It allows describing for example a fixed link. It's more accurate than
++just guessing there may be one (depending on a chipset).
++
++2. Verify PHY ID before trying to connect PHY
++
++PHY addr 0x1e (30) is special in Broadcom routers and means a switch
++connected as MDIO devices instead of a real PHY. Don't try connecting to
++it.
++
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/broadcom/bgmac-bcma.c | 33 ++++++++++++++--------
++ 1 file changed, 21 insertions(+), 12 deletions(-)
++
++--- a/drivers/net/ethernet/broadcom/bgmac-bcma.c
+++++ b/drivers/net/ethernet/broadcom/bgmac-bcma.c
++@@ -11,6 +11,7 @@
++ #include <linux/bcma/bcma.h>
++ #include <linux/brcmphy.h>
++ #include <linux/etherdevice.h>
+++#include <linux/of_mdio.h>
++ #include <linux/of_net.h>
++ #include "bgmac.h"
++ 
++@@ -86,17 +87,28 @@ static int bcma_phy_connect(struct bgmac
++ 	struct phy_device *phy_dev;
++ 	char bus_id[MII_BUS_ID_SIZE + 3];
++ 
+++	/* DT info should be the most accurate */
+++	phy_dev = of_phy_get_and_connect(bgmac->net_dev, bgmac->dev->of_node,
+++					 bgmac_adjust_link);
+++	if (phy_dev)
+++		return 0;
+++
++ 	/* Connect to the PHY */
++-	snprintf(bus_id, sizeof(bus_id), PHY_ID_FMT, bgmac->mii_bus->id,
++-		 bgmac->phyaddr);
++-	phy_dev = phy_connect(bgmac->net_dev, bus_id, bgmac_adjust_link,
++-			      PHY_INTERFACE_MODE_MII);
++-	if (IS_ERR(phy_dev)) {
++-		dev_err(bgmac->dev, "PHY connection failed\n");
++-		return PTR_ERR(phy_dev);
+++	if (bgmac->mii_bus && bgmac->phyaddr != BGMAC_PHY_NOREGS) {
+++		snprintf(bus_id, sizeof(bus_id), PHY_ID_FMT, bgmac->mii_bus->id,
+++			 bgmac->phyaddr);
+++		phy_dev = phy_connect(bgmac->net_dev, bus_id, bgmac_adjust_link,
+++				      PHY_INTERFACE_MODE_MII);
+++		if (IS_ERR(phy_dev)) {
+++			dev_err(bgmac->dev, "PHY connection failed\n");
+++			return PTR_ERR(phy_dev);
+++		}
+++
+++		return 0;
++ 	}
++ 
++-	return 0;
+++	/* Assume a fixed link to the switch port */
+++	return bgmac_phy_connect_direct(bgmac);
++ }
++ 
++ static const struct bcma_device_id bgmac_bcma_tbl[] = {
++@@ -297,10 +309,7 @@ static int bgmac_probe(struct bcma_devic
++ 	bgmac->cco_ctl_maskset = bcma_bgmac_cco_ctl_maskset;
++ 	bgmac->get_bus_clock = bcma_bgmac_get_bus_clock;
++ 	bgmac->cmn_maskset32 = bcma_bgmac_cmn_maskset32;
++-	if (bgmac->mii_bus)
++-		bgmac->phy_connect = bcma_phy_connect;
++-	else
++-		bgmac->phy_connect = bgmac_phy_connect_direct;
+++	bgmac->phy_connect = bcma_phy_connect;
++ 
++ 	err = bgmac_enet_probe(bgmac);
++ 	if (err)
+diff --git a/target/linux/generic/backport-5.15/734-v5.16-0002-net-bgmac-support-MDIO-described-in-DT.patch b/target/linux/generic/backport-5.15/734-v5.16-0002-net-bgmac-support-MDIO-described-in-DT.patch
+new file mode 100644
+index 0000000000..f134828273
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/734-v5.16-0002-net-bgmac-support-MDIO-described-in-DT.patch
+@@ -0,0 +1,54 @@
++From 45c9d966688e7fad7f24bfc450547d91e4304d0b Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Sat, 2 Oct 2021 19:58:12 +0200
++Subject: [PATCH] net: bgmac: support MDIO described in DT
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++Check ethernet controller DT node for "mdio" subnode and use it with
++of_mdiobus_register() when present. That allows specifying MDIO and its
++PHY devices in a standard DT based way.
++
++This is required for BCM53573 SoC support. That family is sometimes
++called Northstar (by marketing?) but is quite different from it. It uses
++different CPU(s) and many different hw blocks.
++
++One of shared blocks in BCM53573 is Ethernet controller. Switch however
++is not SRAB accessible (as it Northstar) but is MDIO attached.
++
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/ethernet/broadcom/bgmac-bcma-mdio.c | 6 +++++-
++ 1 file changed, 5 insertions(+), 1 deletion(-)
++
++--- a/drivers/net/ethernet/broadcom/bgmac-bcma-mdio.c
+++++ b/drivers/net/ethernet/broadcom/bgmac-bcma-mdio.c
++@@ -10,6 +10,7 @@
++ 
++ #include <linux/bcma/bcma.h>
++ #include <linux/brcmphy.h>
+++#include <linux/of_mdio.h>
++ #include "bgmac.h"
++ 
++ static bool bcma_mdio_wait_value(struct bcma_device *core, u16 reg, u32 mask,
++@@ -211,6 +212,7 @@ struct mii_bus *bcma_mdio_mii_register(s
++ {
++ 	struct bcma_device *core = bgmac->bcma.core;
++ 	struct mii_bus *mii_bus;
+++	struct device_node *np;
++ 	int err;
++ 
++ 	mii_bus = mdiobus_alloc();
++@@ -229,7 +231,9 @@ struct mii_bus *bcma_mdio_mii_register(s
++ 	mii_bus->parent = &core->dev;
++ 	mii_bus->phy_mask = ~(1 << bgmac->phyaddr);
++ 
++-	err = mdiobus_register(mii_bus);
+++	np = of_get_child_by_name(core->dev.of_node, "mdio");
+++
+++	err = of_mdiobus_register(mii_bus, np);
++ 	if (err) {
++ 		dev_err(&core->dev, "Registration of mii bus failed\n");
++ 		goto err_free_bus;
+diff --git a/target/linux/generic/backport-5.15/742-v5.16-net-phy-at803x-add-support-for-qca-8327-internal-phy.patch b/target/linux/generic/backport-5.15/742-v5.16-net-phy-at803x-add-support-for-qca-8327-internal-phy.patch
+new file mode 100644
+index 0000000000..8f000ba918
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/742-v5.16-net-phy-at803x-add-support-for-qca-8327-internal-phy.patch
+@@ -0,0 +1,48 @@
++From 0ccf8511182436183c031e8a2f740ae91a02c625 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Tue, 14 Sep 2021 14:33:45 +0200
++Subject: net: phy: at803x: add support for qca 8327 internal phy
++
++Add support for qca8327 internal phy needed for correct init of the
++switch port. It does use the same qca8337 function and reg just with a
++different id.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Tested-by: Rosen Penev <rosenp@gmail.com>
++Tested-by: Andrew Lunn <andrew@lunn.ch>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/phy/at803x.c | 15 +++++++++++++++
++ 1 file changed, 15 insertions(+)
++
++--- a/drivers/net/phy/at803x.c
+++++ b/drivers/net/phy/at803x.c
++@@ -1412,6 +1412,19 @@ static struct phy_driver at803x_driver[]
++ 	.get_sset_count = at803x_get_sset_count,
++ 	.get_strings = at803x_get_strings,
++ 	.get_stats = at803x_get_stats,
+++}, {
+++	/* QCA8327 */
+++	.phy_id = QCA8327_PHY_ID,
+++	.phy_id_mask = QCA8K_PHY_ID_MASK,
+++	.name = "QCA PHY 8327",
+++	/* PHY_GBIT_FEATURES */
+++	.probe = at803x_probe,
+++	.flags = PHY_IS_INTERNAL,
+++	.config_init = qca83xx_config_init,
+++	.soft_reset = genphy_soft_reset,
+++	.get_sset_count = at803x_get_sset_count,
+++	.get_strings = at803x_get_strings,
+++	.get_stats = at803x_get_stats,
++ }, };
++ 
++ module_phy_driver(at803x_driver);
++@@ -1422,6 +1435,8 @@ static struct mdio_device_id __maybe_unu
++ 	{ PHY_ID_MATCH_EXACT(ATH8032_PHY_ID) },
++ 	{ PHY_ID_MATCH_EXACT(ATH8035_PHY_ID) },
++ 	{ PHY_ID_MATCH_EXACT(ATH9331_PHY_ID) },
+++	{ PHY_ID_MATCH_EXACT(QCA8337_PHY_ID) },
+++	{ PHY_ID_MATCH_EXACT(QCA8327_PHY_ID) },
++ 	{ }
++ };
++ 
+diff --git a/target/linux/generic/backport-5.15/743-v5.16-0001-net-dsa-b53-Include-all-ports-in-enabled_ports.patch b/target/linux/generic/backport-5.15/743-v5.16-0001-net-dsa-b53-Include-all-ports-in-enabled_ports.patch
+new file mode 100644
+index 0000000000..dc149a742b
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/743-v5.16-0001-net-dsa-b53-Include-all-ports-in-enabled_ports.patch
+@@ -0,0 +1,131 @@
++From 983d96a9116a328668601555d96736261d33170c Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Thu, 16 Sep 2021 14:03:51 +0200
++Subject: [PATCH] net: dsa: b53: Include all ports in "enabled_ports"
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++Make "enabled_ports" bitfield contain all available switch ports
++including a CPU port. This way there is no need for fixup during
++initialization.
++
++For BCM53010, BCM53018 and BCM53019 include also other available ports.
++
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Tested-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/dsa/b53/b53_common.c | 23 +++++++++++------------
++ 1 file changed, 11 insertions(+), 12 deletions(-)
++
++--- a/drivers/net/dsa/b53/b53_common.c
+++++ b/drivers/net/dsa/b53/b53_common.c
++@@ -2300,7 +2300,7 @@ static const struct b53_chip_data b53_sw
++ 		.chip_id = BCM5325_DEVICE_ID,
++ 		.dev_name = "BCM5325",
++ 		.vlans = 16,
++-		.enabled_ports = 0x1f,
+++		.enabled_ports = 0x3f,
++ 		.arl_bins = 2,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 5,
++@@ -2311,7 +2311,7 @@ static const struct b53_chip_data b53_sw
++ 		.chip_id = BCM5365_DEVICE_ID,
++ 		.dev_name = "BCM5365",
++ 		.vlans = 256,
++-		.enabled_ports = 0x1f,
+++		.enabled_ports = 0x3f,
++ 		.arl_bins = 2,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 5,
++@@ -2322,7 +2322,7 @@ static const struct b53_chip_data b53_sw
++ 		.chip_id = BCM5389_DEVICE_ID,
++ 		.dev_name = "BCM5389",
++ 		.vlans = 4096,
++-		.enabled_ports = 0x1f,
+++		.enabled_ports = 0x11f,
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++@@ -2336,7 +2336,7 @@ static const struct b53_chip_data b53_sw
++ 		.chip_id = BCM5395_DEVICE_ID,
++ 		.dev_name = "BCM5395",
++ 		.vlans = 4096,
++-		.enabled_ports = 0x1f,
+++		.enabled_ports = 0x11f,
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++@@ -2350,7 +2350,7 @@ static const struct b53_chip_data b53_sw
++ 		.chip_id = BCM5397_DEVICE_ID,
++ 		.dev_name = "BCM5397",
++ 		.vlans = 4096,
++-		.enabled_ports = 0x1f,
+++		.enabled_ports = 0x11f,
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++@@ -2364,7 +2364,7 @@ static const struct b53_chip_data b53_sw
++ 		.chip_id = BCM5398_DEVICE_ID,
++ 		.dev_name = "BCM5398",
++ 		.vlans = 4096,
++-		.enabled_ports = 0x7f,
+++		.enabled_ports = 0x17f,
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++@@ -2378,7 +2378,7 @@ static const struct b53_chip_data b53_sw
++ 		.chip_id = BCM53115_DEVICE_ID,
++ 		.dev_name = "BCM53115",
++ 		.vlans = 4096,
++-		.enabled_ports = 0x1f,
+++		.enabled_ports = 0x11f,
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.vta_regs = B53_VTA_REGS,
++@@ -2392,7 +2392,7 @@ static const struct b53_chip_data b53_sw
++ 		.chip_id = BCM53125_DEVICE_ID,
++ 		.dev_name = "BCM53125",
++ 		.vlans = 4096,
++-		.enabled_ports = 0xff,
+++		.enabled_ports = 0x1ff,
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++@@ -2434,7 +2434,7 @@ static const struct b53_chip_data b53_sw
++ 		.chip_id = BCM53010_DEVICE_ID,
++ 		.dev_name = "BCM53010",
++ 		.vlans = 4096,
++-		.enabled_ports = 0x1f,
+++		.enabled_ports = 0x1bf,
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++@@ -2476,7 +2476,7 @@ static const struct b53_chip_data b53_sw
++ 		.chip_id = BCM53018_DEVICE_ID,
++ 		.dev_name = "BCM53018",
++ 		.vlans = 4096,
++-		.enabled_ports = 0x1f,
+++		.enabled_ports = 0x1bf,
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++@@ -2490,7 +2490,7 @@ static const struct b53_chip_data b53_sw
++ 		.chip_id = BCM53019_DEVICE_ID,
++ 		.dev_name = "BCM53019",
++ 		.vlans = 4096,
++-		.enabled_ports = 0x1f,
+++		.enabled_ports = 0x1bf,
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++@@ -2632,7 +2632,6 @@ static int b53_switch_init(struct b53_de
++ 			dev->cpu_port = 5;
++ 	}
++ 
++-	dev->enabled_ports |= BIT(dev->cpu_port);
++ 	dev->num_ports = fls(dev->enabled_ports);
++ 
++ 	dev->ds->num_ports = min_t(unsigned int, dev->num_ports, DSA_MAX_PORTS);
+diff --git a/target/linux/generic/backport-5.15/743-v5.16-0002-net-dsa-b53-Drop-BCM5301x-workaround-for-a-wrong-CPU.patch b/target/linux/generic/backport-5.15/743-v5.16-0002-net-dsa-b53-Drop-BCM5301x-workaround-for-a-wrong-CPU.patch
+new file mode 100644
+index 0000000000..23805a9027
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/743-v5.16-0002-net-dsa-b53-Drop-BCM5301x-workaround-for-a-wrong-CPU.patch
+@@ -0,0 +1,42 @@
++From b290c6384afabbca5ae6e2af72fb1b2bc37922be Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Thu, 16 Sep 2021 14:03:52 +0200
++Subject: [PATCH] net: dsa: b53: Drop BCM5301x workaround for a wrong CPU/IMP
++ port
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++On BCM5301x port 8 requires a fixed link when used.
++
++Years ago when b53 was an OpenWrt downstream driver (with configuration
++based on sometimes bugged NVRAM) there was a need for a fixup. In case
++of forcing fixed link for (incorrectly specified) port 5 the code had to
++actually setup port 8 link.
++
++For upstream b53 driver with setup based on DT there is no need for that
++workaround. In DT we have and require correct ports setup.
++
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Tested-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/dsa/b53/b53_common.c | 6 ------
++ 1 file changed, 6 deletions(-)
++
++--- a/drivers/net/dsa/b53/b53_common.c
+++++ b/drivers/net/dsa/b53/b53_common.c
++@@ -1291,12 +1291,6 @@ static void b53_adjust_link(struct dsa_s
++ 				return;
++ 			}
++ 		}
++-	} else if (is5301x(dev)) {
++-		if (port != dev->cpu_port) {
++-			b53_force_port_config(dev, dev->cpu_port, 2000,
++-					      DUPLEX_FULL, true, true);
++-			b53_force_link(dev, dev->cpu_port, 1);
++-		}
++ 	}
++ 
++ 	/* Re-negotiate EEE if it was enabled already */
+diff --git a/target/linux/generic/backport-5.15/743-v5.16-0003-net-dsa-b53-Improve-flow-control-setup-on-BCM5301x.patch b/target/linux/generic/backport-5.15/743-v5.16-0003-net-dsa-b53-Improve-flow-control-setup-on-BCM5301x.patch
+new file mode 100644
+index 0000000000..941fa23eb4
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/743-v5.16-0003-net-dsa-b53-Improve-flow-control-setup-on-BCM5301x.patch
+@@ -0,0 +1,32 @@
++From 3ff26b29230c54fea2353b63124c589b61953e14 Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Thu, 16 Sep 2021 14:03:53 +0200
++Subject: [PATCH] net: dsa: b53: Improve flow control setup on BCM5301x
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++According to the Broadcom's reference driver flow control needs to be
++enabled for any CPU switch port (5, 7 or 8 - depending on which one is
++used). Current code makes it work only for the port 5. Use
++dsa_is_cpu_port() which solved that problem.
++
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Tested-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/dsa/b53/b53_common.c | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
++
++--- a/drivers/net/dsa/b53/b53_common.c
+++++ b/drivers/net/dsa/b53/b53_common.c
++@@ -1222,7 +1222,7 @@ static void b53_adjust_link(struct dsa_s
++ 		return;
++ 
++ 	/* Enable flow control on BCM5301x's CPU port */
++-	if (is5301x(dev) && port == dev->cpu_port)
+++	if (is5301x(dev) && dsa_is_cpu_port(ds, port))
++ 		tx_pause = rx_pause = true;
++ 
++ 	if (phydev->pause) {
+diff --git a/target/linux/generic/backport-5.15/743-v5.16-0004-net-dsa-b53-Drop-unused-cpu_port-field.patch b/target/linux/generic/backport-5.15/743-v5.16-0004-net-dsa-b53-Drop-unused-cpu_port-field.patch
+new file mode 100644
+index 0000000000..07d0ec03cf
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/743-v5.16-0004-net-dsa-b53-Drop-unused-cpu_port-field.patch
+@@ -0,0 +1,205 @@
++From 7d5af56418d7d01e43247a33b6fe6492ea871923 Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Thu, 16 Sep 2021 14:03:54 +0200
++Subject: [PATCH] net: dsa: b53: Drop unused "cpu_port" field
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++It's set but never used anymore.
++
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Tested-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/dsa/b53/b53_common.c | 28 ----------------------------
++ drivers/net/dsa/b53/b53_priv.h   |  1 -
++ 2 files changed, 29 deletions(-)
++
++--- a/drivers/net/dsa/b53/b53_common.c
+++++ b/drivers/net/dsa/b53/b53_common.c
++@@ -2298,7 +2298,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_bins = 2,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 5,
++-		.cpu_port = B53_CPU_PORT_25,
++ 		.duplex_reg = B53_DUPLEX_STAT_FE,
++ 	},
++ 	{
++@@ -2309,7 +2308,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_bins = 2,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 5,
++-		.cpu_port = B53_CPU_PORT_25,
++ 		.duplex_reg = B53_DUPLEX_STAT_FE,
++ 	},
++ 	{
++@@ -2320,7 +2318,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++-		.cpu_port = B53_CPU_PORT,
++ 		.vta_regs = B53_VTA_REGS,
++ 		.duplex_reg = B53_DUPLEX_STAT_GE,
++ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
++@@ -2334,7 +2331,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++-		.cpu_port = B53_CPU_PORT,
++ 		.vta_regs = B53_VTA_REGS,
++ 		.duplex_reg = B53_DUPLEX_STAT_GE,
++ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
++@@ -2348,7 +2344,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++-		.cpu_port = B53_CPU_PORT,
++ 		.vta_regs = B53_VTA_REGS_9798,
++ 		.duplex_reg = B53_DUPLEX_STAT_GE,
++ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
++@@ -2362,7 +2357,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++-		.cpu_port = B53_CPU_PORT,
++ 		.vta_regs = B53_VTA_REGS_9798,
++ 		.duplex_reg = B53_DUPLEX_STAT_GE,
++ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
++@@ -2377,7 +2371,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_buckets = 1024,
++ 		.vta_regs = B53_VTA_REGS,
++ 		.imp_port = 8,
++-		.cpu_port = B53_CPU_PORT,
++ 		.duplex_reg = B53_DUPLEX_STAT_GE,
++ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
++ 		.jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++@@ -2390,7 +2383,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++-		.cpu_port = B53_CPU_PORT,
++ 		.vta_regs = B53_VTA_REGS,
++ 		.duplex_reg = B53_DUPLEX_STAT_GE,
++ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
++@@ -2404,7 +2396,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++-		.cpu_port = B53_CPU_PORT,
++ 		.vta_regs = B53_VTA_REGS,
++ 		.duplex_reg = B53_DUPLEX_STAT_GE,
++ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
++@@ -2418,7 +2409,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++-		.cpu_port = B53_CPU_PORT,
++ 		.vta_regs = B53_VTA_REGS_63XX,
++ 		.duplex_reg = B53_DUPLEX_STAT_63XX,
++ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK_63XX,
++@@ -2432,7 +2422,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++-		.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */
++ 		.vta_regs = B53_VTA_REGS,
++ 		.duplex_reg = B53_DUPLEX_STAT_GE,
++ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
++@@ -2446,7 +2435,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++-		.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */
++ 		.vta_regs = B53_VTA_REGS,
++ 		.duplex_reg = B53_DUPLEX_STAT_GE,
++ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
++@@ -2460,7 +2448,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++-		.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */
++ 		.vta_regs = B53_VTA_REGS,
++ 		.duplex_reg = B53_DUPLEX_STAT_GE,
++ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
++@@ -2474,7 +2461,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++-		.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */
++ 		.vta_regs = B53_VTA_REGS,
++ 		.duplex_reg = B53_DUPLEX_STAT_GE,
++ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
++@@ -2488,7 +2474,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++-		.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */
++ 		.vta_regs = B53_VTA_REGS,
++ 		.duplex_reg = B53_DUPLEX_STAT_GE,
++ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
++@@ -2502,7 +2487,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++-		.cpu_port = B53_CPU_PORT,
++ 		.vta_regs = B53_VTA_REGS,
++ 		.duplex_reg = B53_DUPLEX_STAT_GE,
++ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
++@@ -2516,7 +2500,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++-		.cpu_port = B53_CPU_PORT,
++ 		.vta_regs = B53_VTA_REGS,
++ 		.duplex_reg = B53_DUPLEX_STAT_GE,
++ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
++@@ -2545,7 +2528,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_bins = 4,
++ 		.arl_buckets = 1024,
++ 		.imp_port = 8,
++-		.cpu_port = B53_CPU_PORT,
++ 		.vta_regs = B53_VTA_REGS,
++ 		.duplex_reg = B53_DUPLEX_STAT_GE,
++ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
++@@ -2559,7 +2541,6 @@ static const struct b53_chip_data b53_sw
++ 		.arl_bins = 4,
++ 		.arl_buckets = 256,
++ 		.imp_port = 8,
++-		.cpu_port = B53_CPU_PORT,
++ 		.vta_regs = B53_VTA_REGS,
++ 		.duplex_reg = B53_DUPLEX_STAT_GE,
++ 		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
++@@ -2585,7 +2566,6 @@ static int b53_switch_init(struct b53_de
++ 			dev->vta_regs[2] = chip->vta_regs[2];
++ 			dev->jumbo_pm_reg = chip->jumbo_pm_reg;
++ 			dev->imp_port = chip->imp_port;
++-			dev->cpu_port = chip->cpu_port;
++ 			dev->num_vlans = chip->vlans;
++ 			dev->num_arl_bins = chip->arl_bins;
++ 			dev->num_arl_buckets = chip->arl_buckets;
++@@ -2617,13 +2597,6 @@ static int b53_switch_init(struct b53_de
++ 			break;
++ #endif
++ 		}
++-	} else if (dev->chip_id == BCM53115_DEVICE_ID) {
++-		u64 strap_value;
++-
++-		b53_read48(dev, B53_STAT_PAGE, B53_STRAP_VALUE, &strap_value);
++-		/* use second IMP port if GMII is enabled */
++-		if (strap_value & SV_GMII_CTRL_115)
++-			dev->cpu_port = 5;
++ 	}
++ 
++ 	dev->num_ports = fls(dev->enabled_ports);
++--- a/drivers/net/dsa/b53/b53_priv.h
+++++ b/drivers/net/dsa/b53/b53_priv.h
++@@ -124,7 +124,6 @@ struct b53_device {
++ 	/* used ports mask */
++ 	u16 enabled_ports;
++ 	unsigned int imp_port;
++-	unsigned int cpu_port;
++ 
++ 	/* connect specific data */
++ 	u8 current_page;
+diff --git a/target/linux/generic/backport-5.15/745-v5.16-01-net-phy-at803x-add-support-for-qca-8327-A-variant.patch b/target/linux/generic/backport-5.15/745-v5.16-01-net-phy-at803x-add-support-for-qca-8327-A-variant.patch
+new file mode 100644
+index 0000000000..99d91dfa76
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/745-v5.16-01-net-phy-at803x-add-support-for-qca-8327-A-variant.patch
+@@ -0,0 +1,65 @@
++From b4df02b562f4aa14ff6811f30e1b4d2159585c59 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Sun, 19 Sep 2021 18:28:15 +0200
++Subject: net: phy: at803x: add support for qca 8327 A variant internal phy
++
++For qca8327 internal phy there are 2 different switch variant with 2
++different phy id. Add this missing variant so the internal phy can be
++correctly identified and fixed.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Andrew Lunn <andrew@lunn.ch>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/phy/at803x.c | 25 ++++++++++++++++++++-----
++ 1 file changed, 20 insertions(+), 5 deletions(-)
++
++--- a/drivers/net/phy/at803x.c
+++++ b/drivers/net/phy/at803x.c
++@@ -150,7 +150,8 @@
++ #define ATH8035_PHY_ID				0x004dd072
++ #define AT8030_PHY_ID_MASK			0xffffffef
++ 
++-#define QCA8327_PHY_ID				0x004dd034
+++#define QCA8327_A_PHY_ID			0x004dd033
+++#define QCA8327_B_PHY_ID			0x004dd034
++ #define QCA8337_PHY_ID				0x004dd036
++ #define QCA8K_PHY_ID_MASK			0xffffffff
++ 
++@@ -1413,10 +1414,23 @@ static struct phy_driver at803x_driver[]
++ 	.get_strings = at803x_get_strings,
++ 	.get_stats = at803x_get_stats,
++ }, {
++-	/* QCA8327 */
++-	.phy_id = QCA8327_PHY_ID,
+++	/* QCA8327-A from switch QCA8327-AL1A */
+++	.phy_id = QCA8327_A_PHY_ID,
++ 	.phy_id_mask = QCA8K_PHY_ID_MASK,
++-	.name = "QCA PHY 8327",
+++	.name = "QCA PHY 8327-A",
+++	/* PHY_GBIT_FEATURES */
+++	.probe = at803x_probe,
+++	.flags = PHY_IS_INTERNAL,
+++	.config_init = qca83xx_config_init,
+++	.soft_reset = genphy_soft_reset,
+++	.get_sset_count = at803x_get_sset_count,
+++	.get_strings = at803x_get_strings,
+++	.get_stats = at803x_get_stats,
+++}, {
+++	/* QCA8327-B from switch QCA8327-BL1A */
+++	.phy_id = QCA8327_B_PHY_ID,
+++	.phy_id_mask = QCA8K_PHY_ID_MASK,
+++	.name = "QCA PHY 8327-B",
++ 	/* PHY_GBIT_FEATURES */
++ 	.probe = at803x_probe,
++ 	.flags = PHY_IS_INTERNAL,
++@@ -1436,7 +1450,8 @@ static struct mdio_device_id __maybe_unu
++ 	{ PHY_ID_MATCH_EXACT(ATH8035_PHY_ID) },
++ 	{ PHY_ID_MATCH_EXACT(ATH9331_PHY_ID) },
++ 	{ PHY_ID_MATCH_EXACT(QCA8337_PHY_ID) },
++-	{ PHY_ID_MATCH_EXACT(QCA8327_PHY_ID) },
+++	{ PHY_ID_MATCH_EXACT(QCA8327_A_PHY_ID) },
+++	{ PHY_ID_MATCH_EXACT(QCA8327_B_PHY_ID) },
++ 	{ }
++ };
++ 
+diff --git a/target/linux/generic/backport-5.15/745-v5.16-02-net-phy-at803x-add-resume-suspend-function-to-qca83x.patch b/target/linux/generic/backport-5.15/745-v5.16-02-net-phy-at803x-add-resume-suspend-function-to-qca83x.patch
+new file mode 100644
+index 0000000000..cd83fac83c
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/745-v5.16-02-net-phy-at803x-add-resume-suspend-function-to-qca83x.patch
+@@ -0,0 +1,45 @@
++From 15b9df4ece17d084f14eb0ca1cf05f2ad497e425 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Sun, 19 Sep 2021 18:28:16 +0200
++Subject: net: phy: at803x: add resume/suspend function to qca83xx phy
++
++Add resume/suspend function to qca83xx internal phy.
++We can't use the at803x generic function as the documentation lacks of
++any support for WoL regs.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Andrew Lunn <andrew@lunn.ch>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/phy/at803x.c | 6 ++++++
++ 1 file changed, 6 insertions(+)
++
++--- a/drivers/net/phy/at803x.c
+++++ b/drivers/net/phy/at803x.c
++@@ -1413,6 +1413,8 @@ static struct phy_driver at803x_driver[]
++ 	.get_sset_count = at803x_get_sset_count,
++ 	.get_strings = at803x_get_strings,
++ 	.get_stats = at803x_get_stats,
+++	.suspend		= genphy_suspend,
+++	.resume			= genphy_resume,
++ }, {
++ 	/* QCA8327-A from switch QCA8327-AL1A */
++ 	.phy_id = QCA8327_A_PHY_ID,
++@@ -1426,6 +1428,8 @@ static struct phy_driver at803x_driver[]
++ 	.get_sset_count = at803x_get_sset_count,
++ 	.get_strings = at803x_get_strings,
++ 	.get_stats = at803x_get_stats,
+++	.suspend		= genphy_suspend,
+++	.resume			= genphy_resume,
++ }, {
++ 	/* QCA8327-B from switch QCA8327-BL1A */
++ 	.phy_id = QCA8327_B_PHY_ID,
++@@ -1439,6 +1443,8 @@ static struct phy_driver at803x_driver[]
++ 	.get_sset_count = at803x_get_sset_count,
++ 	.get_strings = at803x_get_strings,
++ 	.get_stats = at803x_get_stats,
+++	.suspend		= genphy_suspend,
+++	.resume			= genphy_resume,
++ }, };
++ 
++ module_phy_driver(at803x_driver);
+diff --git a/target/linux/generic/backport-5.15/745-v5.16-03-net-phy-at803x-fix-spacing-and-improve-name-for-83xx.patch b/target/linux/generic/backport-5.15/745-v5.16-03-net-phy-at803x-fix-spacing-and-improve-name-for-83xx.patch
+new file mode 100644
+index 0000000000..586d8953b2
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/745-v5.16-03-net-phy-at803x-fix-spacing-and-improve-name-for-83xx.patch
+@@ -0,0 +1,95 @@
++From d44fd8604a4ab92119adb35f05fd87612af722b5 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Sun, 19 Sep 2021 18:28:17 +0200
++Subject: net: phy: at803x: fix spacing and improve name for 83xx phy
++
++Fix spacing and improve name for 83xx phy following other phy in the
++same driver.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Andrew Lunn <andrew@lunn.ch>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/phy/at803x.c | 60 ++++++++++++++++++++++++------------------------
++ 1 file changed, 30 insertions(+), 30 deletions(-)
++
++--- a/drivers/net/phy/at803x.c
+++++ b/drivers/net/phy/at803x.c
++@@ -1402,47 +1402,47 @@ static struct phy_driver at803x_driver[]
++ 	.config_aneg		= at803x_config_aneg,
++ }, {
++ 	/* QCA8337 */
++-	.phy_id = QCA8337_PHY_ID,
++-	.phy_id_mask = QCA8K_PHY_ID_MASK,
++-	.name = "QCA PHY 8337",
+++	.phy_id			= QCA8337_PHY_ID,
+++	.phy_id_mask		= QCA8K_PHY_ID_MASK,
+++	.name			= "Qualcomm Atheros 8337 internal PHY",
++ 	/* PHY_GBIT_FEATURES */
++-	.probe = at803x_probe,
++-	.flags = PHY_IS_INTERNAL,
++-	.config_init = qca83xx_config_init,
++-	.soft_reset = genphy_soft_reset,
++-	.get_sset_count = at803x_get_sset_count,
++-	.get_strings = at803x_get_strings,
++-	.get_stats = at803x_get_stats,
+++	.probe			= at803x_probe,
+++	.flags			= PHY_IS_INTERNAL,
+++	.config_init		= qca83xx_config_init,
+++	.soft_reset		= genphy_soft_reset,
+++	.get_sset_count		= at803x_get_sset_count,
+++	.get_strings		= at803x_get_strings,
+++	.get_stats		= at803x_get_stats,
++ 	.suspend		= genphy_suspend,
++ 	.resume			= genphy_resume,
++ }, {
++ 	/* QCA8327-A from switch QCA8327-AL1A */
++-	.phy_id = QCA8327_A_PHY_ID,
++-	.phy_id_mask = QCA8K_PHY_ID_MASK,
++-	.name = "QCA PHY 8327-A",
+++	.phy_id			= QCA8327_A_PHY_ID,
+++	.phy_id_mask		= QCA8K_PHY_ID_MASK,
+++	.name			= "Qualcomm Atheros 8327-A internal PHY",
++ 	/* PHY_GBIT_FEATURES */
++-	.probe = at803x_probe,
++-	.flags = PHY_IS_INTERNAL,
++-	.config_init = qca83xx_config_init,
++-	.soft_reset = genphy_soft_reset,
++-	.get_sset_count = at803x_get_sset_count,
++-	.get_strings = at803x_get_strings,
++-	.get_stats = at803x_get_stats,
+++	.probe			= at803x_probe,
+++	.flags			= PHY_IS_INTERNAL,
+++	.config_init		= qca83xx_config_init,
+++	.soft_reset		= genphy_soft_reset,
+++	.get_sset_count		= at803x_get_sset_count,
+++	.get_strings		= at803x_get_strings,
+++	.get_stats		= at803x_get_stats,
++ 	.suspend		= genphy_suspend,
++ 	.resume			= genphy_resume,
++ }, {
++ 	/* QCA8327-B from switch QCA8327-BL1A */
++-	.phy_id = QCA8327_B_PHY_ID,
++-	.phy_id_mask = QCA8K_PHY_ID_MASK,
++-	.name = "QCA PHY 8327-B",
+++	.phy_id			= QCA8327_B_PHY_ID,
+++	.phy_id_mask		= QCA8K_PHY_ID_MASK,
+++	.name			= "Qualcomm Atheros 8327-B internal PHY",
++ 	/* PHY_GBIT_FEATURES */
++-	.probe = at803x_probe,
++-	.flags = PHY_IS_INTERNAL,
++-	.config_init = qca83xx_config_init,
++-	.soft_reset = genphy_soft_reset,
++-	.get_sset_count = at803x_get_sset_count,
++-	.get_strings = at803x_get_strings,
++-	.get_stats = at803x_get_stats,
+++	.probe			= at803x_probe,
+++	.flags			= PHY_IS_INTERNAL,
+++	.config_init		= qca83xx_config_init,
+++	.soft_reset		= genphy_soft_reset,
+++	.get_sset_count		= at803x_get_sset_count,
+++	.get_strings		= at803x_get_strings,
+++	.get_stats		= at803x_get_stats,
++ 	.suspend		= genphy_suspend,
++ 	.resume			= genphy_resume,
++ }, };
+diff --git a/target/linux/generic/backport-5.15/746-v5.16-01-net-phy-at803x-fix-resume-for-QCA8327-phy.patch b/target/linux/generic/backport-5.15/746-v5.16-01-net-phy-at803x-fix-resume-for-QCA8327-phy.patch
+new file mode 100644
+index 0000000000..09797ae83b
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/746-v5.16-01-net-phy-at803x-fix-resume-for-QCA8327-phy.patch
+@@ -0,0 +1,131 @@
++From ba3c01ee02ed0d821c9f241f179bbc9457542b8f Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Sun, 10 Oct 2021 00:46:15 +0200
++Subject: net: phy: at803x: fix resume for QCA8327 phy
++
++From Documentation phy resume triggers phy reset and restart
++auto-negotiation. Add a dedicated function to wait reset to finish as
++it was notice a regression where port sometime are not reliable after a
++suspend/resume session. The reset wait logic is copied from phy_poll_reset.
++Add dedicated suspend function to use genphy_suspend only with QCA8337
++phy and set only additional debug settings for QCA8327. With more test
++it was reported that QCA8327 doesn't proprely support this mode and
++using this cause the unreliability of the switch ports, especially the
++malfunction of the port0.
++
++Fixes: 15b9df4ece17 ("net: phy: at803x: add resume/suspend function to qca83xx phy")
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/phy/at803x.c | 69 +++++++++++++++++++++++++++++++++++++++++++-----
++ 1 file changed, 63 insertions(+), 6 deletions(-)
++
++--- a/drivers/net/phy/at803x.c
+++++ b/drivers/net/phy/at803x.c
++@@ -92,9 +92,14 @@
++ #define AT803X_DEBUG_REG_5			0x05
++ #define AT803X_DEBUG_TX_CLK_DLY_EN		BIT(8)
++ 
+++#define AT803X_DEBUG_REG_HIB_CTRL		0x0b
+++#define   AT803X_DEBUG_HIB_CTRL_SEL_RST_80U	BIT(10)
+++#define   AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE	BIT(13)
+++
++ #define AT803X_DEBUG_REG_3C			0x3C
++ 
++ #define AT803X_DEBUG_REG_3D			0x3D
+++#define   AT803X_DEBUG_GATE_CLK_IN1000		BIT(6)
++ 
++ #define AT803X_DEBUG_REG_1F			0x1F
++ #define AT803X_DEBUG_PLL_ON			BIT(2)
++@@ -1304,6 +1309,58 @@ static int qca83xx_config_init(struct ph
++ 	return 0;
++ }
++ 
+++static int qca83xx_resume(struct phy_device *phydev)
+++{
+++	int ret, val;
+++
+++	/* Skip reset if not suspended */
+++	if (!phydev->suspended)
+++		return 0;
+++
+++	/* Reinit the port, reset values set by suspend */
+++	qca83xx_config_init(phydev);
+++
+++	/* Reset the port on port resume */
+++	phy_set_bits(phydev, MII_BMCR, BMCR_RESET | BMCR_ANENABLE);
+++
+++	/* On resume from suspend the switch execute a reset and
+++	 * restart auto-negotiation. Wait for reset to complete.
+++	 */
+++	ret = phy_read_poll_timeout(phydev, MII_BMCR, val, !(val & BMCR_RESET),
+++				    50000, 600000, true);
+++	if (ret)
+++		return ret;
+++
+++	msleep(1);
+++
+++	return 0;
+++}
+++
+++static int qca83xx_suspend(struct phy_device *phydev)
+++{
+++	u16 mask = 0;
+++
+++	/* Only QCA8337 support actual suspend.
+++	 * QCA8327 cause port unreliability when phy suspend
+++	 * is set.
+++	 */
+++	if (phydev->drv->phy_id == QCA8337_PHY_ID) {
+++		genphy_suspend(phydev);
+++	} else {
+++		mask |= ~(BMCR_SPEED1000 | BMCR_FULLDPLX);
+++		phy_modify(phydev, MII_BMCR, mask, 0);
+++	}
+++
+++	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_3D,
+++			      AT803X_DEBUG_GATE_CLK_IN1000, 0);
+++
+++	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_HIB_CTRL,
+++			      AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE |
+++			      AT803X_DEBUG_HIB_CTRL_SEL_RST_80U, 0);
+++
+++	return 0;
+++}
+++
++ static struct phy_driver at803x_driver[] = {
++ {
++ 	/* Qualcomm Atheros AR8035 */
++@@ -1413,8 +1470,8 @@ static struct phy_driver at803x_driver[]
++ 	.get_sset_count		= at803x_get_sset_count,
++ 	.get_strings		= at803x_get_strings,
++ 	.get_stats		= at803x_get_stats,
++-	.suspend		= genphy_suspend,
++-	.resume			= genphy_resume,
+++	.suspend		= qca83xx_suspend,
+++	.resume			= qca83xx_resume,
++ }, {
++ 	/* QCA8327-A from switch QCA8327-AL1A */
++ 	.phy_id			= QCA8327_A_PHY_ID,
++@@ -1428,8 +1485,8 @@ static struct phy_driver at803x_driver[]
++ 	.get_sset_count		= at803x_get_sset_count,
++ 	.get_strings		= at803x_get_strings,
++ 	.get_stats		= at803x_get_stats,
++-	.suspend		= genphy_suspend,
++-	.resume			= genphy_resume,
+++	.suspend		= qca83xx_suspend,
+++	.resume			= qca83xx_resume,
++ }, {
++ 	/* QCA8327-B from switch QCA8327-BL1A */
++ 	.phy_id			= QCA8327_B_PHY_ID,
++@@ -1443,8 +1500,8 @@ static struct phy_driver at803x_driver[]
++ 	.get_sset_count		= at803x_get_sset_count,
++ 	.get_strings		= at803x_get_strings,
++ 	.get_stats		= at803x_get_stats,
++-	.suspend		= genphy_suspend,
++-	.resume			= genphy_resume,
+++	.suspend		= qca83xx_suspend,
+++	.resume			= qca83xx_resume,
++ }, };
++ 
++ module_phy_driver(at803x_driver);
+diff --git a/target/linux/generic/backport-5.15/746-v5.16-02-net-phy-at803x-add-DAC-amplitude-fix-for-8327-phy.patch b/target/linux/generic/backport-5.15/746-v5.16-02-net-phy-at803x-add-DAC-amplitude-fix-for-8327-phy.patch
+new file mode 100644
+index 0000000000..c504c37c84
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/746-v5.16-02-net-phy-at803x-add-DAC-amplitude-fix-for-8327-phy.patch
+@@ -0,0 +1,91 @@
++From 1ca8311949aec5c9447645731ef1c6bc5bd71350 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Sun, 10 Oct 2021 00:46:16 +0200
++Subject: net: phy: at803x: add DAC amplitude fix for 8327 phy
++
++QCA8327 internal phy require DAC amplitude adjustement set to +6% with
++100m speed. Also add additional define to report a change of the same
++reg in QCA8337. (different scope it does set 1000m voltage)
++Add link_change_notify function to set the proper amplitude adjustement
++on PHY_RUNNING state and disable on any other state.
++
++Fixes: b4df02b562f4 ("net: phy: at803x: add support for qca 8327 A variant internal phy")
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/phy/at803x.c | 33 +++++++++++++++++++++++++++++++++
++ 1 file changed, 33 insertions(+)
++
++--- a/drivers/net/phy/at803x.c
+++++ b/drivers/net/phy/at803x.c
++@@ -87,6 +87,8 @@
++ #define AT803X_PSSR_MR_AN_COMPLETE		0x0200
++ 
++ #define AT803X_DEBUG_REG_0			0x00
+++#define QCA8327_DEBUG_MANU_CTRL_EN		BIT(2)
+++#define QCA8337_DEBUG_MANU_CTRL_EN		GENMASK(3, 2)
++ #define AT803X_DEBUG_RX_CLK_DLY_EN		BIT(15)
++ 
++ #define AT803X_DEBUG_REG_5			0x05
++@@ -1306,9 +1308,37 @@ static int qca83xx_config_init(struct ph
++ 		break;
++ 	}
++ 
+++	/* QCA8327 require DAC amplitude adjustment for 100m set to +6%.
+++	 * Disable on init and enable only with 100m speed following
+++	 * qca original source code.
+++	 */
+++	if (phydev->drv->phy_id == QCA8327_A_PHY_ID ||
+++	    phydev->drv->phy_id == QCA8327_B_PHY_ID)
+++		at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0,
+++				      QCA8327_DEBUG_MANU_CTRL_EN, 0);
+++
++ 	return 0;
++ }
++ 
+++static void qca83xx_link_change_notify(struct phy_device *phydev)
+++{
+++	/* QCA8337 doesn't require DAC Amplitude adjustement */
+++	if (phydev->drv->phy_id == QCA8337_PHY_ID)
+++		return;
+++
+++	/* Set DAC Amplitude adjustment to +6% for 100m on link running */
+++	if (phydev->state == PHY_RUNNING) {
+++		if (phydev->speed == SPEED_100)
+++			at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0,
+++					      QCA8327_DEBUG_MANU_CTRL_EN,
+++					      QCA8327_DEBUG_MANU_CTRL_EN);
+++	} else {
+++		/* Reset DAC Amplitude adjustment */
+++		at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0,
+++				      QCA8327_DEBUG_MANU_CTRL_EN, 0);
+++	}
+++}
+++
++ static int qca83xx_resume(struct phy_device *phydev)
++ {
++ 	int ret, val;
++@@ -1463,6 +1493,7 @@ static struct phy_driver at803x_driver[]
++ 	.phy_id_mask		= QCA8K_PHY_ID_MASK,
++ 	.name			= "Qualcomm Atheros 8337 internal PHY",
++ 	/* PHY_GBIT_FEATURES */
+++	.link_change_notify	= qca83xx_link_change_notify,
++ 	.probe			= at803x_probe,
++ 	.flags			= PHY_IS_INTERNAL,
++ 	.config_init		= qca83xx_config_init,
++@@ -1478,6 +1509,7 @@ static struct phy_driver at803x_driver[]
++ 	.phy_id_mask		= QCA8K_PHY_ID_MASK,
++ 	.name			= "Qualcomm Atheros 8327-A internal PHY",
++ 	/* PHY_GBIT_FEATURES */
+++	.link_change_notify	= qca83xx_link_change_notify,
++ 	.probe			= at803x_probe,
++ 	.flags			= PHY_IS_INTERNAL,
++ 	.config_init		= qca83xx_config_init,
++@@ -1493,6 +1525,7 @@ static struct phy_driver at803x_driver[]
++ 	.phy_id_mask		= QCA8K_PHY_ID_MASK,
++ 	.name			= "Qualcomm Atheros 8327-B internal PHY",
++ 	/* PHY_GBIT_FEATURES */
+++	.link_change_notify	= qca83xx_link_change_notify,
++ 	.probe			= at803x_probe,
++ 	.flags			= PHY_IS_INTERNAL,
++ 	.config_init		= qca83xx_config_init,
+diff --git a/target/linux/generic/backport-5.15/746-v5.16-03-net-phy-at803x-enable-prefer-master-for-83xx-interna.patch b/target/linux/generic/backport-5.15/746-v5.16-03-net-phy-at803x-enable-prefer-master-for-83xx-interna.patch
+new file mode 100644
+index 0000000000..9f880593f1
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/746-v5.16-03-net-phy-at803x-enable-prefer-master-for-83xx-interna.patch
+@@ -0,0 +1,27 @@
++From 9d1c29b4028557a496be9c5eb2b4b86063700636 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Sun, 10 Oct 2021 00:46:17 +0200
++Subject: net: phy: at803x: enable prefer master for 83xx internal phy
++
++From original QCA source code the port was set to prefer master as port
++type in 1000BASE-T mode. Apply the same settings also here.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Andrew Lunn <andrew@lunn.ch>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/phy/at803x.c | 3 +++
++ 1 file changed, 3 insertions(+)
++
++--- a/drivers/net/phy/at803x.c
+++++ b/drivers/net/phy/at803x.c
++@@ -1317,6 +1317,9 @@ static int qca83xx_config_init(struct ph
++ 		at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0,
++ 				      QCA8327_DEBUG_MANU_CTRL_EN, 0);
++ 
+++	/* Following original QCA sourcecode set port to prefer master */
+++	phy_set_bits(phydev, MII_CTRL1000, CTL1000_PREFER_MASTER);
+++
++ 	return 0;
++ }
++ 
+diff --git a/target/linux/generic/backport-5.15/746-v5.16-04-net-phy-at803x-better-describe-debug-regs.patch b/target/linux/generic/backport-5.15/746-v5.16-04-net-phy-at803x-better-describe-debug-regs.patch
+new file mode 100644
+index 0000000000..89e9b3f662
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/746-v5.16-04-net-phy-at803x-better-describe-debug-regs.patch
+@@ -0,0 +1,127 @@
++From 67999555ff42e91de7654488d9a7735bd9e84555 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Sun, 10 Oct 2021 00:46:18 +0200
++Subject: net: phy: at803x: better describe debug regs
++
++Give a name to known debug regs from Documentation instead of using
++unknown hex values.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Andrew Lunn <andrew@lunn.ch>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/phy/at803x.c | 30 +++++++++++++++---------------
++ 1 file changed, 15 insertions(+), 15 deletions(-)
++
++--- a/drivers/net/phy/at803x.c
+++++ b/drivers/net/phy/at803x.c
++@@ -86,12 +86,12 @@
++ #define AT803X_PSSR				0x11	/*PHY-Specific Status Register*/
++ #define AT803X_PSSR_MR_AN_COMPLETE		0x0200
++ 
++-#define AT803X_DEBUG_REG_0			0x00
+++#define AT803X_DEBUG_ANALOG_TEST_CTRL		0x00
++ #define QCA8327_DEBUG_MANU_CTRL_EN		BIT(2)
++ #define QCA8337_DEBUG_MANU_CTRL_EN		GENMASK(3, 2)
++ #define AT803X_DEBUG_RX_CLK_DLY_EN		BIT(15)
++ 
++-#define AT803X_DEBUG_REG_5			0x05
+++#define AT803X_DEBUG_SYSTEM_CTRL_MODE		0x05
++ #define AT803X_DEBUG_TX_CLK_DLY_EN		BIT(8)
++ 
++ #define AT803X_DEBUG_REG_HIB_CTRL		0x0b
++@@ -100,7 +100,7 @@
++ 
++ #define AT803X_DEBUG_REG_3C			0x3C
++ 
++-#define AT803X_DEBUG_REG_3D			0x3D
+++#define AT803X_DEBUG_REG_GREEN			0x3D
++ #define   AT803X_DEBUG_GATE_CLK_IN1000		BIT(6)
++ 
++ #define AT803X_DEBUG_REG_1F			0x1F
++@@ -284,25 +284,25 @@ static int at803x_read_page(struct phy_d
++ 
++ static int at803x_enable_rx_delay(struct phy_device *phydev)
++ {
++-	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0, 0,
+++	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, 0,
++ 				     AT803X_DEBUG_RX_CLK_DLY_EN);
++ }
++ 
++ static int at803x_enable_tx_delay(struct phy_device *phydev)
++ {
++-	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_5, 0,
+++	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE, 0,
++ 				     AT803X_DEBUG_TX_CLK_DLY_EN);
++ }
++ 
++ static int at803x_disable_rx_delay(struct phy_device *phydev)
++ {
++-	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0,
+++	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
++ 				     AT803X_DEBUG_RX_CLK_DLY_EN, 0);
++ }
++ 
++ static int at803x_disable_tx_delay(struct phy_device *phydev)
++ {
++-	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_5,
+++	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE,
++ 				     AT803X_DEBUG_TX_CLK_DLY_EN, 0);
++ }
++ 
++@@ -1292,9 +1292,9 @@ static int qca83xx_config_init(struct ph
++ 	switch (switch_revision) {
++ 	case 1:
++ 		/* For 100M waveform */
++-		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_0, 0x02ea);
+++		at803x_debug_reg_write(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, 0x02ea);
++ 		/* Turn on Gigabit clock */
++-		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_3D, 0x68a0);
+++		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x68a0);
++ 		break;
++ 
++ 	case 2:
++@@ -1302,8 +1302,8 @@ static int qca83xx_config_init(struct ph
++ 		fallthrough;
++ 	case 4:
++ 		phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_AZ_DEBUG, 0x803f);
++-		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_3D, 0x6860);
++-		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_5, 0x2c46);
+++		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x6860);
+++		at803x_debug_reg_write(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE, 0x2c46);
++ 		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_3C, 0x6000);
++ 		break;
++ 	}
++@@ -1314,7 +1314,7 @@ static int qca83xx_config_init(struct ph
++ 	 */
++ 	if (phydev->drv->phy_id == QCA8327_A_PHY_ID ||
++ 	    phydev->drv->phy_id == QCA8327_B_PHY_ID)
++-		at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0,
+++		at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
++ 				      QCA8327_DEBUG_MANU_CTRL_EN, 0);
++ 
++ 	/* Following original QCA sourcecode set port to prefer master */
++@@ -1332,12 +1332,12 @@ static void qca83xx_link_change_notify(s
++ 	/* Set DAC Amplitude adjustment to +6% for 100m on link running */
++ 	if (phydev->state == PHY_RUNNING) {
++ 		if (phydev->speed == SPEED_100)
++-			at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0,
+++			at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
++ 					      QCA8327_DEBUG_MANU_CTRL_EN,
++ 					      QCA8327_DEBUG_MANU_CTRL_EN);
++ 	} else {
++ 		/* Reset DAC Amplitude adjustment */
++-		at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0,
+++		at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
++ 				      QCA8327_DEBUG_MANU_CTRL_EN, 0);
++ 	}
++ }
++@@ -1384,7 +1384,7 @@ static int qca83xx_suspend(struct phy_de
++ 		phy_modify(phydev, MII_BMCR, mask, 0);
++ 	}
++ 
++-	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_3D,
+++	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_GREEN,
++ 			      AT803X_DEBUG_GATE_CLK_IN1000, 0);
++ 
++ 	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_HIB_CTRL,
+diff --git a/target/linux/generic/backport-5.15/747-v5.16-01-dsa-qca8k-add-mac-power-sel-support.patch b/target/linux/generic/backport-5.15/747-v5.16-01-dsa-qca8k-add-mac-power-sel-support.patch
+new file mode 100644
+index 0000000000..c8d424de38
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/747-v5.16-01-dsa-qca8k-add-mac-power-sel-support.patch
+@@ -0,0 +1,80 @@
++From d8b6f5bae6d3b648a67b6958cb98e4e97256d652 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Thu, 14 Oct 2021 00:39:06 +0200
++Subject: dsa: qca8k: add mac_power_sel support
++
++Add missing mac power sel support needed for ipq8064/5 SoC that require
++1.8v for the internal regulator port instead of the default 1.5v.
++If other device needs this, consider adding a dedicated binding to
++support this.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 31 +++++++++++++++++++++++++++++++
++ drivers/net/dsa/qca8k.h |  5 +++++
++ 2 files changed, 36 insertions(+)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -951,6 +951,33 @@ qca8k_setup_of_rgmii_delay(struct qca8k_
++ }
++ 
++ static int
+++qca8k_setup_mac_pwr_sel(struct qca8k_priv *priv)
+++{
+++	u32 mask = 0;
+++	int ret = 0;
+++
+++	/* SoC specific settings for ipq8064.
+++	 * If more device require this consider adding
+++	 * a dedicated binding.
+++	 */
+++	if (of_machine_is_compatible("qcom,ipq8064"))
+++		mask |= QCA8K_MAC_PWR_RGMII0_1_8V;
+++
+++	/* SoC specific settings for ipq8065 */
+++	if (of_machine_is_compatible("qcom,ipq8065"))
+++		mask |= QCA8K_MAC_PWR_RGMII1_1_8V;
+++
+++	if (mask) {
+++		ret = qca8k_rmw(priv, QCA8K_REG_MAC_PWR_SEL,
+++				QCA8K_MAC_PWR_RGMII0_1_8V |
+++				QCA8K_MAC_PWR_RGMII1_1_8V,
+++				mask);
+++	}
+++
+++	return ret;
+++}
+++
+++static int
++ qca8k_setup(struct dsa_switch *ds)
++ {
++ 	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++@@ -979,6 +1006,10 @@ qca8k_setup(struct dsa_switch *ds)
++ 	if (ret)
++ 		return ret;
++ 
+++	ret = qca8k_setup_mac_pwr_sel(priv);
+++	if (ret)
+++		return ret;
+++
++ 	/* Enable CPU Port */
++ 	ret = qca8k_reg_set(priv, QCA8K_REG_GLOBAL_FW_CTRL0,
++ 			    QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -100,6 +100,11 @@
++ #define   QCA8K_SGMII_MODE_CTRL_PHY			(1 << 22)
++ #define   QCA8K_SGMII_MODE_CTRL_MAC			(2 << 22)
++ 
+++/* MAC_PWR_SEL registers */
+++#define QCA8K_REG_MAC_PWR_SEL				0x0e4
+++#define   QCA8K_MAC_PWR_RGMII1_1_8V			BIT(18)
+++#define   QCA8K_MAC_PWR_RGMII0_1_8V			BIT(19)
+++
++ /* EEE control registers */
++ #define QCA8K_REG_EEE_CTRL				0x100
++ #define  QCA8K_REG_EEE_CTRL_LPI_EN(_i)			((_i + 1) * 2)
+diff --git a/target/linux/generic/backport-5.15/747-v5.16-02-dt-bindings-net-dsa-qca8k-Add-SGMII-clock-phase-prop.patch b/target/linux/generic/backport-5.15/747-v5.16-02-dt-bindings-net-dsa-qca8k-Add-SGMII-clock-phase-prop.patch
+new file mode 100644
+index 0000000000..bd768ec27d
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/747-v5.16-02-dt-bindings-net-dsa-qca8k-Add-SGMII-clock-phase-prop.patch
+@@ -0,0 +1,30 @@
++From fdbf35df9c091db9c46e57e9938e3f7a4f603a7c Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Thu, 14 Oct 2021 00:39:07 +0200
++Subject: dt-bindings: net: dsa: qca8k: Add SGMII clock phase properties
++
++Add names and descriptions of additional PORT0_PAD_CTRL properties.
++qca,sgmii-(rx|tx)clk-falling-edge are for setting the respective clock
++phase to failling edge.
++
++Co-developed-by: Matthew Hagan <mnhagan88@gmail.com>
++Signed-off-by: Matthew Hagan <mnhagan88@gmail.com>
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ Documentation/devicetree/bindings/net/dsa/qca8k.txt | 4 ++++
++ 1 file changed, 4 insertions(+)
++
++--- a/Documentation/devicetree/bindings/net/dsa/qca8k.txt
+++++ b/Documentation/devicetree/bindings/net/dsa/qca8k.txt
++@@ -37,6 +37,10 @@ A CPU port node has the following option
++                           managed entity. See
++                           Documentation/devicetree/bindings/net/fixed-link.txt
++                           for details.
+++- qca,sgmii-rxclk-falling-edge: Set the receive clock phase to falling edge.
+++                                Mostly used in qca8327 with CPU port 0 set to
+++                                sgmii.
+++- qca,sgmii-txclk-falling-edge: Set the transmit clock phase to falling edge.
++ 
++ For QCA8K the 'fixed-link' sub-node supports only the following properties:
++ 
+diff --git a/target/linux/generic/backport-5.15/747-v5.16-03-net-dsa-qca8k-add-support-for-sgmii-falling-edge.patch b/target/linux/generic/backport-5.15/747-v5.16-03-net-dsa-qca8k-add-support-for-sgmii-falling-edge.patch
+new file mode 100644
+index 0000000000..e464452d82
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/747-v5.16-03-net-dsa-qca8k-add-support-for-sgmii-falling-edge.patch
+@@ -0,0 +1,127 @@
++From 6c43809bf1bee76c434e365a26546a92a5fbec14 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Thu, 14 Oct 2021 00:39:08 +0200
++Subject: net: dsa: qca8k: add support for sgmii falling edge
++
++Add support for this in the qca8k driver. Also add support for SGMII
++rx/tx clock falling edge. This is only present for pad0, pad5 and
++pad6 have these bit reserved from Documentation. Add a comment that this
++is hardcoded to PAD0 as qca8327/28/34/37 have an unique sgmii line and
++setting falling in port0 applies to both configuration with sgmii used
++for port0 or port6.
++
++Co-developed-by: Matthew Hagan <mnhagan88@gmail.com>
++Signed-off-by: Matthew Hagan <mnhagan88@gmail.com>
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++
++ drivers/net/dsa/qca8k.h |  4 ++++
++ 2 files changed, 67 insertions(+)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -978,6 +978,42 @@ qca8k_setup_mac_pwr_sel(struct qca8k_pri
++ }
++ 
++ static int
+++qca8k_parse_port_config(struct qca8k_priv *priv)
+++{
+++	struct device_node *port_dn;
+++	phy_interface_t mode;
+++	struct dsa_port *dp;
+++	int port, ret;
+++
+++	/* We have 2 CPU port. Check them */
+++	for (port = 0; port < QCA8K_NUM_PORTS; port++) {
+++		/* Skip every other port */
+++		if (port != 0 && port != 6)
+++			continue;
+++
+++		dp = dsa_to_port(priv->ds, port);
+++		port_dn = dp->dn;
+++
+++		if (!of_device_is_available(port_dn))
+++			continue;
+++
+++		ret = of_get_phy_mode(port_dn, &mode);
+++		if (ret)
+++			continue;
+++
+++		if (mode == PHY_INTERFACE_MODE_SGMII) {
+++			if (of_property_read_bool(port_dn, "qca,sgmii-txclk-falling-edge"))
+++				priv->sgmii_tx_clk_falling_edge = true;
+++
+++			if (of_property_read_bool(port_dn, "qca,sgmii-rxclk-falling-edge"))
+++				priv->sgmii_rx_clk_falling_edge = true;
+++		}
+++	}
+++
+++	return 0;
+++}
+++
+++static int
++ qca8k_setup(struct dsa_switch *ds)
++ {
++ 	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++@@ -990,6 +1026,11 @@ qca8k_setup(struct dsa_switch *ds)
++ 		return -EINVAL;
++ 	}
++ 
+++	/* Parse CPU port config to be later used in phy_link mac_config */
+++	ret = qca8k_parse_port_config(priv);
+++	if (ret)
+++		return ret;
+++
++ 	mutex_init(&priv->reg_mutex);
++ 
++ 	/* Start by setting up the register mapping */
++@@ -1274,6 +1315,28 @@ qca8k_phylink_mac_config(struct dsa_swit
++ 		}
++ 
++ 		qca8k_write(priv, QCA8K_REG_SGMII_CTRL, val);
+++
+++		/* For qca8327/qca8328/qca8334/qca8338 sgmii is unique and
+++		 * falling edge is set writing in the PORT0 PAD reg
+++		 */
+++		if (priv->switch_id == QCA8K_ID_QCA8327 ||
+++		    priv->switch_id == QCA8K_ID_QCA8337)
+++			reg = QCA8K_REG_PORT0_PAD_CTRL;
+++
+++		val = 0;
+++
+++		/* SGMII Clock phase configuration */
+++		if (priv->sgmii_rx_clk_falling_edge)
+++			val |= QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE;
+++
+++		if (priv->sgmii_tx_clk_falling_edge)
+++			val |= QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE;
+++
+++		if (val)
+++			ret = qca8k_rmw(priv, reg,
+++					QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE |
+++					QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE,
+++					val);
++ 		break;
++ 	default:
++ 		dev_err(ds->dev, "xMII mode %s not supported for port %d\n",
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -35,6 +35,8 @@
++ #define   QCA8K_MASK_CTRL_DEVICE_ID_MASK		GENMASK(15, 8)
++ #define   QCA8K_MASK_CTRL_DEVICE_ID(x)			((x) >> 8)
++ #define QCA8K_REG_PORT0_PAD_CTRL			0x004
+++#define   QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE	BIT(19)
+++#define   QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE	BIT(18)
++ #define QCA8K_REG_PORT5_PAD_CTRL			0x008
++ #define QCA8K_REG_PORT6_PAD_CTRL			0x00c
++ #define   QCA8K_PORT_PAD_RGMII_EN			BIT(26)
++@@ -260,6 +262,8 @@ struct qca8k_priv {
++ 	u8 switch_revision;
++ 	u8 rgmii_tx_delay;
++ 	u8 rgmii_rx_delay;
+++	bool sgmii_rx_clk_falling_edge;
+++	bool sgmii_tx_clk_falling_edge;
++ 	bool legacy_phy_port_mapping;
++ 	struct regmap *regmap;
++ 	struct mii_bus *bus;
+diff --git a/target/linux/generic/backport-5.15/747-v5.16-04-dt-bindings-net-dsa-qca8k-Document-support-for-CPU-p.patch b/target/linux/generic/backport-5.15/747-v5.16-04-dt-bindings-net-dsa-qca8k-Document-support-for-CPU-p.patch
+new file mode 100644
+index 0000000000..606ac0af3d
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/747-v5.16-04-dt-bindings-net-dsa-qca8k-Document-support-for-CPU-p.patch
+@@ -0,0 +1,29 @@
++From 731d613338ec6de482053ffa3f71be2325b0f8eb Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Thu, 14 Oct 2021 00:39:09 +0200
++Subject: dt-bindings: net: dsa: qca8k: Document support for CPU port 6
++
++The switch now support CPU port to be set 6 instead of be hardcoded to
++0. Document support for it and describe logic selection.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ Documentation/devicetree/bindings/net/dsa/qca8k.txt | 6 +++++-
++ 1 file changed, 5 insertions(+), 1 deletion(-)
++
++--- a/Documentation/devicetree/bindings/net/dsa/qca8k.txt
+++++ b/Documentation/devicetree/bindings/net/dsa/qca8k.txt
++@@ -29,7 +29,11 @@ the mdio MASTER is used as communication
++ Don't use mixed external and internal mdio-bus configurations, as this is
++ not supported by the hardware.
++ 
++-The CPU port of this switch is always port 0.
+++This switch support 2 CPU port. Normally and advised configuration is with
+++CPU port set to port 0. It is also possible to set the CPU port to port 6
+++if the device requires it. The driver will configure the switch to the defined
+++port. With both CPU port declared the first CPU port is selected as primary
+++and the secondary CPU ignored.
++ 
++ A CPU port node has the following optional node:
++ 
+diff --git a/target/linux/generic/backport-5.15/747-v5.16-05-net-dsa-qca8k-add-support-for-cpu-port-6.patch b/target/linux/generic/backport-5.15/747-v5.16-05-net-dsa-qca8k-add-support-for-cpu-port-6.patch
+new file mode 100644
+index 0000000000..320db8fa9f
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/747-v5.16-05-net-dsa-qca8k-add-support-for-cpu-port-6.patch
+@@ -0,0 +1,153 @@
++From 3fcf734aa482487df83cf8f18608438fcf59127f Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Thu, 14 Oct 2021 00:39:10 +0200
++Subject: net: dsa: qca8k: add support for cpu port 6
++
++Currently CPU port is always hardcoded to port 0. This switch have 2 CPU
++ports. The original intention of this driver seems to be use the
++mac06_exchange bit to swap MAC0 with MAC6 in the strange configuration
++where device have connected only the CPU port 6. To skip the
++introduction of a new binding, rework the driver to address the
++secondary CPU port as primary and drop any reference of hardcoded port.
++With configuration of mac06 exchange, just skip the definition of port0
++and define the CPU port as a secondary. The driver will autoconfigure
++the switch to use that as the primary CPU port.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 51 ++++++++++++++++++++++++++++++++++---------------
++ drivers/net/dsa/qca8k.h |  2 --
++ 2 files changed, 36 insertions(+), 17 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -977,6 +977,22 @@ qca8k_setup_mac_pwr_sel(struct qca8k_pri
++ 	return ret;
++ }
++ 
+++static int qca8k_find_cpu_port(struct dsa_switch *ds)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++
+++	/* Find the connected cpu port. Valid port are 0 or 6 */
+++	if (dsa_is_cpu_port(ds, 0))
+++		return 0;
+++
+++	dev_dbg(priv->dev, "port 0 is not the CPU port. Checking port 6");
+++
+++	if (dsa_is_cpu_port(ds, 6))
+++		return 6;
+++
+++	return -EINVAL;
+++}
+++
++ static int
++ qca8k_parse_port_config(struct qca8k_priv *priv)
++ {
++@@ -1017,13 +1033,13 @@ static int
++ qca8k_setup(struct dsa_switch *ds)
++ {
++ 	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	int ret, i;
+++	int cpu_port, ret, i;
++ 	u32 mask;
++ 
++-	/* Make sure that port 0 is the cpu port */
++-	if (!dsa_is_cpu_port(ds, 0)) {
++-		dev_err(priv->dev, "port 0 is not the CPU port");
++-		return -EINVAL;
+++	cpu_port = qca8k_find_cpu_port(ds);
+++	if (cpu_port < 0) {
+++		dev_err(priv->dev, "No cpu port configured in both cpu port0 and port6");
+++		return cpu_port;
++ 	}
++ 
++ 	/* Parse CPU port config to be later used in phy_link mac_config */
++@@ -1065,7 +1081,7 @@ qca8k_setup(struct dsa_switch *ds)
++ 		dev_warn(priv->dev, "mib init failed");
++ 
++ 	/* Enable QCA header mode on the cpu port */
++-	ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(QCA8K_CPU_PORT),
+++	ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(cpu_port),
++ 			  QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_TX_S |
++ 			  QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_RX_S);
++ 	if (ret) {
++@@ -1087,10 +1103,10 @@ qca8k_setup(struct dsa_switch *ds)
++ 
++ 	/* Forward all unknown frames to CPU port for Linux processing */
++ 	ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1,
++-			  BIT(0) << QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S |
++-			  BIT(0) << QCA8K_GLOBAL_FW_CTRL1_BC_DP_S |
++-			  BIT(0) << QCA8K_GLOBAL_FW_CTRL1_MC_DP_S |
++-			  BIT(0) << QCA8K_GLOBAL_FW_CTRL1_UC_DP_S);
+++			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S |
+++			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_BC_DP_S |
+++			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_MC_DP_S |
+++			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_UC_DP_S);
++ 	if (ret)
++ 		return ret;
++ 
++@@ -1098,7 +1114,7 @@ qca8k_setup(struct dsa_switch *ds)
++ 	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++ 		/* CPU port gets connected to all user ports of the switch */
++ 		if (dsa_is_cpu_port(ds, i)) {
++-			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(QCA8K_CPU_PORT),
+++			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(cpu_port),
++ 					QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds));
++ 			if (ret)
++ 				return ret;
++@@ -1110,7 +1126,7 @@ qca8k_setup(struct dsa_switch *ds)
++ 
++ 			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
++ 					QCA8K_PORT_LOOKUP_MEMBER,
++-					BIT(QCA8K_CPU_PORT));
+++					BIT(cpu_port));
++ 			if (ret)
++ 				return ret;
++ 
++@@ -1616,9 +1632,12 @@ static int
++ qca8k_port_bridge_join(struct dsa_switch *ds, int port, struct net_device *br)
++ {
++ 	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	int port_mask = BIT(QCA8K_CPU_PORT);
+++	int port_mask, cpu_port;
++ 	int i, ret;
++ 
+++	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
+++	port_mask = BIT(cpu_port);
+++
++ 	for (i = 1; i < QCA8K_NUM_PORTS; i++) {
++ 		if (dsa_to_port(ds, i)->bridge_dev != br)
++ 			continue;
++@@ -1645,7 +1664,9 @@ static void
++ qca8k_port_bridge_leave(struct dsa_switch *ds, int port, struct net_device *br)
++ {
++ 	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	int i;
+++	int cpu_port, i;
+++
+++	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
++ 
++ 	for (i = 1; i < QCA8K_NUM_PORTS; i++) {
++ 		if (dsa_to_port(ds, i)->bridge_dev != br)
++@@ -1662,7 +1683,7 @@ qca8k_port_bridge_leave(struct dsa_switc
++ 	 * this port
++ 	 */
++ 	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++-		  QCA8K_PORT_LOOKUP_MEMBER, BIT(QCA8K_CPU_PORT));
+++		  QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port));
++ }
++ 
++ static int
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -24,8 +24,6 @@
++ 
++ #define QCA8K_NUM_FDB_RECORDS				2048
++ 
++-#define QCA8K_CPU_PORT					0
++-
++ #define QCA8K_PORT_VID_DEF				1
++ 
++ /* Global control registers */
+diff --git a/target/linux/generic/backport-5.15/747-v5.16-06-net-dsa-qca8k-rework-rgmii-delay-logic-and-scan-for-.patch b/target/linux/generic/backport-5.15/747-v5.16-06-net-dsa-qca8k-rework-rgmii-delay-logic-and-scan-for-.patch
+new file mode 100644
+index 0000000000..de201764f9
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/747-v5.16-06-net-dsa-qca8k-rework-rgmii-delay-logic-and-scan-for-.patch
+@@ -0,0 +1,295 @@
++From 5654ec78dd7e64b1e04777b24007344329e6a63b Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Thu, 14 Oct 2021 00:39:11 +0200
++Subject: net: dsa: qca8k: rework rgmii delay logic and scan for cpu port 6
++
++Future proof commit. This switch have 2 CPU ports and one valid
++configuration is first CPU port set to sgmii and second CPU port set to
++rgmii-id. The current implementation detects delay only for CPU port
++zero set to rgmii and doesn't count any delay set in a secondary CPU
++port. Drop the current delay scan function and move it to the sgmii
++parser function to generalize and implicitly add support for secondary
++CPU port set to rgmii-id. Introduce new logic where delay is enabled
++also with internal delay binding declared and rgmii set as PHY mode.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 165 ++++++++++++++++++++++++------------------------
++ drivers/net/dsa/qca8k.h |  10 ++-
++ 2 files changed, 89 insertions(+), 86 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -889,68 +889,6 @@ qca8k_setup_mdio_bus(struct qca8k_priv *
++ }
++ 
++ static int
++-qca8k_setup_of_rgmii_delay(struct qca8k_priv *priv)
++-{
++-	struct device_node *port_dn;
++-	phy_interface_t mode;
++-	struct dsa_port *dp;
++-	u32 val;
++-
++-	/* CPU port is already checked */
++-	dp = dsa_to_port(priv->ds, 0);
++-
++-	port_dn = dp->dn;
++-
++-	/* Check if port 0 is set to the correct type */
++-	of_get_phy_mode(port_dn, &mode);
++-	if (mode != PHY_INTERFACE_MODE_RGMII_ID &&
++-	    mode != PHY_INTERFACE_MODE_RGMII_RXID &&
++-	    mode != PHY_INTERFACE_MODE_RGMII_TXID) {
++-		return 0;
++-	}
++-
++-	switch (mode) {
++-	case PHY_INTERFACE_MODE_RGMII_ID:
++-	case PHY_INTERFACE_MODE_RGMII_RXID:
++-		if (of_property_read_u32(port_dn, "rx-internal-delay-ps", &val))
++-			val = 2;
++-		else
++-			/* Switch regs accept value in ns, convert ps to ns */
++-			val = val / 1000;
++-
++-		if (val > QCA8K_MAX_DELAY) {
++-			dev_err(priv->dev, "rgmii rx delay is limited to a max value of 3ns, setting to the max value");
++-			val = 3;
++-		}
++-
++-		priv->rgmii_rx_delay = val;
++-		/* Stop here if we need to check only for rx delay */
++-		if (mode != PHY_INTERFACE_MODE_RGMII_ID)
++-			break;
++-
++-		fallthrough;
++-	case PHY_INTERFACE_MODE_RGMII_TXID:
++-		if (of_property_read_u32(port_dn, "tx-internal-delay-ps", &val))
++-			val = 1;
++-		else
++-			/* Switch regs accept value in ns, convert ps to ns */
++-			val = val / 1000;
++-
++-		if (val > QCA8K_MAX_DELAY) {
++-			dev_err(priv->dev, "rgmii tx delay is limited to a max value of 3ns, setting to the max value");
++-			val = 3;
++-		}
++-
++-		priv->rgmii_tx_delay = val;
++-		break;
++-	default:
++-		return 0;
++-	}
++-
++-	return 0;
++-}
++-
++-static int
++ qca8k_setup_mac_pwr_sel(struct qca8k_priv *priv)
++ {
++ 	u32 mask = 0;
++@@ -996,19 +934,21 @@ static int qca8k_find_cpu_port(struct ds
++ static int
++ qca8k_parse_port_config(struct qca8k_priv *priv)
++ {
+++	int port, cpu_port_index = 0, ret;
++ 	struct device_node *port_dn;
++ 	phy_interface_t mode;
++ 	struct dsa_port *dp;
++-	int port, ret;
+++	u32 delay;
++ 
++ 	/* We have 2 CPU port. Check them */
++-	for (port = 0; port < QCA8K_NUM_PORTS; port++) {
+++	for (port = 0; port < QCA8K_NUM_PORTS && cpu_port_index < QCA8K_NUM_CPU_PORTS; port++) {
++ 		/* Skip every other port */
++ 		if (port != 0 && port != 6)
++ 			continue;
++ 
++ 		dp = dsa_to_port(priv->ds, port);
++ 		port_dn = dp->dn;
+++		cpu_port_index++;
++ 
++ 		if (!of_device_is_available(port_dn))
++ 			continue;
++@@ -1017,12 +957,54 @@ qca8k_parse_port_config(struct qca8k_pri
++ 		if (ret)
++ 			continue;
++ 
++-		if (mode == PHY_INTERFACE_MODE_SGMII) {
+++		switch (mode) {
+++		case PHY_INTERFACE_MODE_RGMII:
+++		case PHY_INTERFACE_MODE_RGMII_ID:
+++		case PHY_INTERFACE_MODE_RGMII_TXID:
+++		case PHY_INTERFACE_MODE_RGMII_RXID:
+++			delay = 0;
+++
+++			if (!of_property_read_u32(port_dn, "tx-internal-delay-ps", &delay))
+++				/* Switch regs accept value in ns, convert ps to ns */
+++				delay = delay / 1000;
+++			else if (mode == PHY_INTERFACE_MODE_RGMII_ID ||
+++				 mode == PHY_INTERFACE_MODE_RGMII_TXID)
+++				delay = 1;
+++
+++			if (delay > QCA8K_MAX_DELAY) {
+++				dev_err(priv->dev, "rgmii tx delay is limited to a max value of 3ns, setting to the max value");
+++				delay = 3;
+++			}
+++
+++			priv->rgmii_tx_delay[cpu_port_index] = delay;
+++
+++			delay = 0;
+++
+++			if (!of_property_read_u32(port_dn, "rx-internal-delay-ps", &delay))
+++				/* Switch regs accept value in ns, convert ps to ns */
+++				delay = delay / 1000;
+++			else if (mode == PHY_INTERFACE_MODE_RGMII_ID ||
+++				 mode == PHY_INTERFACE_MODE_RGMII_RXID)
+++				delay = 2;
+++
+++			if (delay > QCA8K_MAX_DELAY) {
+++				dev_err(priv->dev, "rgmii rx delay is limited to a max value of 3ns, setting to the max value");
+++				delay = 3;
+++			}
+++
+++			priv->rgmii_rx_delay[cpu_port_index] = delay;
+++
+++			break;
+++		case PHY_INTERFACE_MODE_SGMII:
++ 			if (of_property_read_bool(port_dn, "qca,sgmii-txclk-falling-edge"))
++ 				priv->sgmii_tx_clk_falling_edge = true;
++ 
++ 			if (of_property_read_bool(port_dn, "qca,sgmii-rxclk-falling-edge"))
++ 				priv->sgmii_rx_clk_falling_edge = true;
+++
+++			break;
+++		default:
+++			continue;
++ 		}
++ 	}
++ 
++@@ -1059,10 +1041,6 @@ qca8k_setup(struct dsa_switch *ds)
++ 	if (ret)
++ 		return ret;
++ 
++-	ret = qca8k_setup_of_rgmii_delay(priv);
++-	if (ret)
++-		return ret;
++-
++ 	ret = qca8k_setup_mac_pwr_sel(priv);
++ 	if (ret)
++ 		return ret;
++@@ -1229,8 +1207,8 @@ qca8k_phylink_mac_config(struct dsa_swit
++ 			 const struct phylink_link_state *state)
++ {
++ 	struct qca8k_priv *priv = ds->priv;
++-	u32 reg, val;
++-	int ret;
+++	int cpu_port_index, ret;
+++	u32 reg, val, delay;
++ 
++ 	switch (port) {
++ 	case 0: /* 1st CPU port */
++@@ -1242,6 +1220,7 @@ qca8k_phylink_mac_config(struct dsa_swit
++ 			return;
++ 
++ 		reg = QCA8K_REG_PORT0_PAD_CTRL;
+++		cpu_port_index = QCA8K_CPU_PORT0;
++ 		break;
++ 	case 1:
++ 	case 2:
++@@ -1260,6 +1239,7 @@ qca8k_phylink_mac_config(struct dsa_swit
++ 			return;
++ 
++ 		reg = QCA8K_REG_PORT6_PAD_CTRL;
+++		cpu_port_index = QCA8K_CPU_PORT6;
++ 		break;
++ 	default:
++ 		dev_err(ds->dev, "%s: unsupported port: %i\n", __func__, port);
++@@ -1274,23 +1254,40 @@ qca8k_phylink_mac_config(struct dsa_swit
++ 
++ 	switch (state->interface) {
++ 	case PHY_INTERFACE_MODE_RGMII:
++-		/* RGMII mode means no delay so don't enable the delay */
++-		qca8k_write(priv, reg, QCA8K_PORT_PAD_RGMII_EN);
++-		break;
++ 	case PHY_INTERFACE_MODE_RGMII_ID:
++ 	case PHY_INTERFACE_MODE_RGMII_TXID:
++ 	case PHY_INTERFACE_MODE_RGMII_RXID:
++-		/* RGMII_ID needs internal delay. This is enabled through
++-		 * PORT5_PAD_CTRL for all ports, rather than individual port
++-		 * registers
+++		val = QCA8K_PORT_PAD_RGMII_EN;
+++
+++		/* Delay can be declared in 3 different way.
+++		 * Mode to rgmii and internal-delay standard binding defined
+++		 * rgmii-id or rgmii-tx/rx phy mode set.
+++		 * The parse logic set a delay different than 0 only when one
+++		 * of the 3 different way is used. In all other case delay is
+++		 * not enabled. With ID or TX/RXID delay is enabled and set
+++		 * to the default and recommended value.
+++		 */
+++		if (priv->rgmii_tx_delay[cpu_port_index]) {
+++			delay = priv->rgmii_tx_delay[cpu_port_index];
+++
+++			val |= QCA8K_PORT_PAD_RGMII_TX_DELAY(delay) |
+++			       QCA8K_PORT_PAD_RGMII_TX_DELAY_EN;
+++		}
+++
+++		if (priv->rgmii_rx_delay[cpu_port_index]) {
+++			delay = priv->rgmii_rx_delay[cpu_port_index];
+++
+++			val |= QCA8K_PORT_PAD_RGMII_RX_DELAY(delay) |
+++			       QCA8K_PORT_PAD_RGMII_RX_DELAY_EN;
+++		}
+++
+++		/* Set RGMII delay based on the selected values */
+++		qca8k_write(priv, reg, val);
+++
+++		/* QCA8337 requires to set rgmii rx delay for all ports.
+++		 * This is enabled through PORT5_PAD_CTRL for all ports,
+++		 * rather than individual port registers.
++ 		 */
++-		qca8k_write(priv, reg,
++-			    QCA8K_PORT_PAD_RGMII_EN |
++-			    QCA8K_PORT_PAD_RGMII_TX_DELAY(priv->rgmii_tx_delay) |
++-			    QCA8K_PORT_PAD_RGMII_RX_DELAY(priv->rgmii_rx_delay) |
++-			    QCA8K_PORT_PAD_RGMII_TX_DELAY_EN |
++-			    QCA8K_PORT_PAD_RGMII_RX_DELAY_EN);
++-		/* QCA8337 requires to set rgmii rx delay */
++ 		if (priv->switch_id == QCA8K_ID_QCA8337)
++ 			qca8k_write(priv, QCA8K_REG_PORT5_PAD_CTRL,
++ 				    QCA8K_PORT_PAD_RGMII_RX_DELAY_EN);
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -13,6 +13,7 @@
++ #include <linux/gpio.h>
++ 
++ #define QCA8K_NUM_PORTS					7
+++#define QCA8K_NUM_CPU_PORTS				2
++ #define QCA8K_MAX_MTU					9000
++ 
++ #define PHY_ID_QCA8327					0x004dd034
++@@ -255,13 +256,18 @@ struct qca8k_match_data {
++ 	u8 id;
++ };
++ 
+++enum {
+++	QCA8K_CPU_PORT0,
+++	QCA8K_CPU_PORT6,
+++};
+++
++ struct qca8k_priv {
++ 	u8 switch_id;
++ 	u8 switch_revision;
++-	u8 rgmii_tx_delay;
++-	u8 rgmii_rx_delay;
++ 	bool sgmii_rx_clk_falling_edge;
++ 	bool sgmii_tx_clk_falling_edge;
+++	u8 rgmii_rx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
+++	u8 rgmii_tx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
++ 	bool legacy_phy_port_mapping;
++ 	struct regmap *regmap;
++ 	struct mii_bus *bus;
+diff --git a/target/linux/generic/backport-5.15/747-v5.16-07-dt-bindings-net-dsa-qca8k-Document-qca-sgmii-enable-.patch b/target/linux/generic/backport-5.15/747-v5.16-07-dt-bindings-net-dsa-qca8k-Document-qca-sgmii-enable-.patch
+new file mode 100644
+index 0000000000..8abd264e79
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/747-v5.16-07-dt-bindings-net-dsa-qca8k-Document-qca-sgmii-enable-.patch
+@@ -0,0 +1,33 @@
++From 13ad5ccc093ff448b99ac7e138e91e78796adb48 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Thu, 14 Oct 2021 00:39:12 +0200
++Subject: dt-bindings: net: dsa: qca8k: Document qca,sgmii-enable-pll
++
++Document qca,sgmii-enable-pll binding used in the CPU nodes to
++enable SGMII PLL on MAC config.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ Documentation/devicetree/bindings/net/dsa/qca8k.txt | 10 ++++++++++
++ 1 file changed, 10 insertions(+)
++
++--- a/Documentation/devicetree/bindings/net/dsa/qca8k.txt
+++++ b/Documentation/devicetree/bindings/net/dsa/qca8k.txt
++@@ -45,6 +45,16 @@ A CPU port node has the following option
++                                 Mostly used in qca8327 with CPU port 0 set to
++                                 sgmii.
++ - qca,sgmii-txclk-falling-edge: Set the transmit clock phase to falling edge.
+++- qca,sgmii-enable-pll  : For SGMII CPU port, explicitly enable PLL, TX and RX
+++                          chain along with Signal Detection.
+++                          This should NOT be enabled for qca8327. If enabled with
+++                          qca8327 the sgmii port won't correctly init and an err
+++                          is printed.
+++                          This can be required for qca8337 switch with revision 2.
+++                          A warning is displayed when used with revision greater
+++                          2.
+++                          With CPU port set to sgmii and qca8337 it is advised
+++                          to set this unless a communication problem is observed.
++ 
++ For QCA8K the 'fixed-link' sub-node supports only the following properties:
++ 
+diff --git a/target/linux/generic/backport-5.15/747-v5.16-08-net-dsa-qca8k-add-explicit-SGMII-PLL-enable.patch b/target/linux/generic/backport-5.15/747-v5.16-08-net-dsa-qca8k-add-explicit-SGMII-PLL-enable.patch
+new file mode 100644
+index 0000000000..2b5a84a1b0
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/747-v5.16-08-net-dsa-qca8k-add-explicit-SGMII-PLL-enable.patch
+@@ -0,0 +1,65 @@
++From bbc4799e8bb6c397e3b3fec13de68e179f5db9ff Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Thu, 14 Oct 2021 00:39:13 +0200
++Subject: net: dsa: qca8k: add explicit SGMII PLL enable
++
++Support enabling PLL on the SGMII CPU port. Some device require this
++special configuration or no traffic is transmitted and the switch
++doesn't work at all. A dedicated binding is added to the CPU node
++port to apply the correct reg on mac config.
++Fail to correctly configure sgmii with qca8327 switch and warn if pll is
++used on qca8337 with a revision greater than 1.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 19 +++++++++++++++++--
++ drivers/net/dsa/qca8k.h |  1 +
++ 2 files changed, 18 insertions(+), 2 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -1002,6 +1002,18 @@ qca8k_parse_port_config(struct qca8k_pri
++ 			if (of_property_read_bool(port_dn, "qca,sgmii-rxclk-falling-edge"))
++ 				priv->sgmii_rx_clk_falling_edge = true;
++ 
+++			if (of_property_read_bool(port_dn, "qca,sgmii-enable-pll")) {
+++				priv->sgmii_enable_pll = true;
+++
+++				if (priv->switch_id == QCA8K_ID_QCA8327) {
+++					dev_err(priv->dev, "SGMII PLL should NOT be enabled for qca8327. Aborting enabling");
+++					priv->sgmii_enable_pll = false;
+++				}
+++
+++				if (priv->switch_revision < 2)
+++					dev_warn(priv->dev, "SGMII PLL should NOT be enabled for qca8337 with revision 2 or more.");
+++			}
+++
++ 			break;
++ 		default:
++ 			continue;
++@@ -1312,8 +1324,11 @@ qca8k_phylink_mac_config(struct dsa_swit
++ 		if (ret)
++ 			return;
++ 
++-		val |= QCA8K_SGMII_EN_PLL | QCA8K_SGMII_EN_RX |
++-			QCA8K_SGMII_EN_TX | QCA8K_SGMII_EN_SD;
+++		val |= QCA8K_SGMII_EN_SD;
+++
+++		if (priv->sgmii_enable_pll)
+++			val |= QCA8K_SGMII_EN_PLL | QCA8K_SGMII_EN_RX |
+++			       QCA8K_SGMII_EN_TX;
++ 
++ 		if (dsa_is_cpu_port(ds, port)) {
++ 			/* CPU port, we're talking to the CPU MAC, be a PHY */
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -266,6 +266,7 @@ struct qca8k_priv {
++ 	u8 switch_revision;
++ 	bool sgmii_rx_clk_falling_edge;
++ 	bool sgmii_tx_clk_falling_edge;
+++	bool sgmii_enable_pll;
++ 	u8 rgmii_rx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
++ 	u8 rgmii_tx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
++ 	bool legacy_phy_port_mapping;
+diff --git a/target/linux/generic/backport-5.15/747-v5.16-09-dt-bindings-net-dsa-qca8k-Document-qca-led-open-drai.patch b/target/linux/generic/backport-5.15/747-v5.16-09-dt-bindings-net-dsa-qca8k-Document-qca-led-open-drai.patch
+new file mode 100644
+index 0000000000..38dc954e8c
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/747-v5.16-09-dt-bindings-net-dsa-qca8k-Document-qca-led-open-drai.patch
+@@ -0,0 +1,37 @@
++From 924087c5c3d41553700b0eb83ca2a53b91643dca Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Thu, 14 Oct 2021 00:39:14 +0200
++Subject: dt-bindings: net: dsa: qca8k: Document qca,led-open-drain binding
++
++Document new binding qca,ignore-power-on-sel used to ignore
++power on strapping and use sw regs instead.
++Document qca,led-open.drain to set led to open drain mode, the
++qca,ignore-power-on-sel is mandatory with this enabled or an error will
++be reported.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ Documentation/devicetree/bindings/net/dsa/qca8k.txt | 11 +++++++++++
++ 1 file changed, 11 insertions(+)
++
++--- a/Documentation/devicetree/bindings/net/dsa/qca8k.txt
+++++ b/Documentation/devicetree/bindings/net/dsa/qca8k.txt
++@@ -13,6 +13,17 @@ Required properties:
++ Optional properties:
++ 
++ - reset-gpios: GPIO to be used to reset the whole device
+++- qca,ignore-power-on-sel: Ignore power on pin strapping to configure led open
+++                           drain or eeprom presence. This is needed for broken
+++                           devices that have wrong configuration or when the oem
+++                           decided to not use pin strapping and fallback to sw
+++                           regs.
+++- qca,led-open-drain: Set leds to open-drain mode. This requires the
+++                      qca,ignore-power-on-sel to be set or the driver will fail
+++                      to probe. This is needed if the oem doesn't use pin
+++                      strapping to set this mode and prefers to set it using sw
+++                      regs. The pin strapping related to led open drain mode is
+++                      the pin B68 for QCA832x and B49 for QCA833x
++ 
++ Subnodes:
++ 
+diff --git a/target/linux/generic/backport-5.15/747-v5.16-10-net-dsa-qca8k-add-support-for-pws-config-reg.patch b/target/linux/generic/backport-5.15/747-v5.16-10-net-dsa-qca8k-add-support-for-pws-config-reg.patch
+new file mode 100644
+index 0000000000..aa5d92a4fd
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/747-v5.16-10-net-dsa-qca8k-add-support-for-pws-config-reg.patch
+@@ -0,0 +1,92 @@
++From 362bb238d8bf1470424214a8a5968d9c6cce68fa Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Thu, 14 Oct 2021 00:39:15 +0200
++Subject: net: dsa: qca8k: add support for pws config reg
++
++Some qca8327 switch require to force the ignore of power on sel
++strapping. Some switch require to set the led open drain mode in regs
++instead of using strapping. While most of the device implements this
++using the correct way using pin strapping, there are still some broken
++device that require to be set using sw regs.
++Introduce a new binding and support these special configuration.
++As led open drain require to ignore pin strapping to work, the probe
++fails with EINVAL error with incorrect configuration.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 39 +++++++++++++++++++++++++++++++++++++++
++ drivers/net/dsa/qca8k.h |  6 ++++++
++ 2 files changed, 45 insertions(+)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -932,6 +932,41 @@ static int qca8k_find_cpu_port(struct ds
++ }
++ 
++ static int
+++qca8k_setup_of_pws_reg(struct qca8k_priv *priv)
+++{
+++	struct device_node *node = priv->dev->of_node;
+++	u32 val = 0;
+++	int ret;
+++
+++	/* QCA8327 require to set to the correct mode.
+++	 * His bigger brother QCA8328 have the 172 pin layout.
+++	 * Should be applied by default but we set this just to make sure.
+++	 */
+++	if (priv->switch_id == QCA8K_ID_QCA8327) {
+++		ret = qca8k_rmw(priv, QCA8K_REG_PWS, QCA8327_PWS_PACKAGE148_EN,
+++				QCA8327_PWS_PACKAGE148_EN);
+++		if (ret)
+++			return ret;
+++	}
+++
+++	if (of_property_read_bool(node, "qca,ignore-power-on-sel"))
+++		val |= QCA8K_PWS_POWER_ON_SEL;
+++
+++	if (of_property_read_bool(node, "qca,led-open-drain")) {
+++		if (!(val & QCA8K_PWS_POWER_ON_SEL)) {
+++			dev_err(priv->dev, "qca,led-open-drain require qca,ignore-power-on-sel to be set.");
+++			return -EINVAL;
+++		}
+++
+++		val |= QCA8K_PWS_LED_OPEN_EN_CSR;
+++	}
+++
+++	return qca8k_rmw(priv, QCA8K_REG_PWS,
+++			QCA8K_PWS_LED_OPEN_EN_CSR | QCA8K_PWS_POWER_ON_SEL,
+++			val);
+++}
+++
+++static int
++ qca8k_parse_port_config(struct qca8k_priv *priv)
++ {
++ 	int port, cpu_port_index = 0, ret;
++@@ -1053,6 +1088,10 @@ qca8k_setup(struct dsa_switch *ds)
++ 	if (ret)
++ 		return ret;
++ 
+++	ret = qca8k_setup_of_pws_reg(priv);
+++	if (ret)
+++		return ret;
+++
++ 	ret = qca8k_setup_mac_pwr_sel(priv);
++ 	if (ret)
++ 		return ret;
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -46,6 +46,12 @@
++ #define   QCA8K_MAX_DELAY				3
++ #define   QCA8K_PORT_PAD_SGMII_EN			BIT(7)
++ #define QCA8K_REG_PWS					0x010
+++#define   QCA8K_PWS_POWER_ON_SEL			BIT(31)
+++/* This reg is only valid for QCA832x and toggle the package
+++ * type from 176 pin (by default) to 148 pin used on QCA8327
+++ */
+++#define   QCA8327_PWS_PACKAGE148_EN			BIT(30)
+++#define   QCA8K_PWS_LED_OPEN_EN_CSR			BIT(24)
++ #define   QCA8K_PWS_SERDES_AEN_DIS			BIT(7)
++ #define QCA8K_REG_MODULE_EN				0x030
++ #define   QCA8K_MODULE_EN_MIB				BIT(0)
+diff --git a/target/linux/generic/backport-5.15/747-v5.16-11-dt-bindings-net-dsa-qca8k-document-support-for-qca83.patch b/target/linux/generic/backport-5.15/747-v5.16-11-dt-bindings-net-dsa-qca8k-document-support-for-qca83.patch
+new file mode 100644
+index 0000000000..1bfb00c5b2
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/747-v5.16-11-dt-bindings-net-dsa-qca8k-document-support-for-qca83.patch
+@@ -0,0 +1,32 @@
++From ed7988d77fbfb79366b68f9e7fa60a6080da23d4 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Thu, 14 Oct 2021 00:39:16 +0200
++Subject: dt-bindings: net: dsa: qca8k: document support for qca8328
++
++QCA8328 is the bigger brother of qca8327. Document the new compatible
++binding and add some information to understand the various switch
++compatible.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ Documentation/devicetree/bindings/net/dsa/qca8k.txt | 7 ++++---
++ 1 file changed, 4 insertions(+), 3 deletions(-)
++
++--- a/Documentation/devicetree/bindings/net/dsa/qca8k.txt
+++++ b/Documentation/devicetree/bindings/net/dsa/qca8k.txt
++@@ -3,9 +3,10 @@
++ Required properties:
++ 
++ - compatible: should be one of:
++-    "qca,qca8327"
++-    "qca,qca8334"
++-    "qca,qca8337"
+++    "qca,qca8328": referenced as AR8328(N)-AK1(A/B) QFN 176 pin package
+++    "qca,qca8327": referenced as AR8327(N)-AL1A DR-QFN 148 pin package
+++    "qca,qca8334": referenced as QCA8334-AL3C QFN 88 pin package
+++    "qca,qca8337": referenced as QCA8337N-AL3(B/C) DR-QFN 148 pin package
++ 
++ - #size-cells: must be 0
++ - #address-cells: must be 1
+diff --git a/target/linux/generic/backport-5.15/747-v5.16-12-net-dsa-qca8k-add-support-for-QCA8328.patch b/target/linux/generic/backport-5.15/747-v5.16-12-net-dsa-qca8k-add-support-for-QCA8328.patch
+new file mode 100644
+index 0000000000..b300621e63
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/747-v5.16-12-net-dsa-qca8k-add-support-for-QCA8328.patch
+@@ -0,0 +1,78 @@
++From f477d1c8bdbef4f400718238e350f16f521d2a3e Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Thu, 14 Oct 2021 00:39:17 +0200
++Subject: net: dsa: qca8k: add support for QCA8328
++
++QCA8328 switch is the bigger brother of the qca8327. Same regs different
++chip. Change the function to set the correct pin layout and introduce a
++new match_data to differentiate the 2 switch as they have the same ID
++and their internal PHY have the same ID.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 19 ++++++++++++++++---
++ drivers/net/dsa/qca8k.h |  1 +
++ 2 files changed, 17 insertions(+), 3 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -935,6 +935,7 @@ static int
++ qca8k_setup_of_pws_reg(struct qca8k_priv *priv)
++ {
++ 	struct device_node *node = priv->dev->of_node;
+++	const struct qca8k_match_data *data;
++ 	u32 val = 0;
++ 	int ret;
++ 
++@@ -943,8 +944,14 @@ qca8k_setup_of_pws_reg(struct qca8k_priv
++ 	 * Should be applied by default but we set this just to make sure.
++ 	 */
++ 	if (priv->switch_id == QCA8K_ID_QCA8327) {
+++		data = of_device_get_match_data(priv->dev);
+++
+++		/* Set the correct package of 148 pin for QCA8327 */
+++		if (data->reduced_package)
+++			val |= QCA8327_PWS_PACKAGE148_EN;
+++
++ 		ret = qca8k_rmw(priv, QCA8K_REG_PWS, QCA8327_PWS_PACKAGE148_EN,
++-				QCA8327_PWS_PACKAGE148_EN);
+++				val);
++ 		if (ret)
++ 			return ret;
++ 	}
++@@ -2124,7 +2131,12 @@ static int qca8k_resume(struct device *d
++ static SIMPLE_DEV_PM_OPS(qca8k_pm_ops,
++ 			 qca8k_suspend, qca8k_resume);
++ 
++-static const struct qca8k_match_data qca832x = {
+++static const struct qca8k_match_data qca8327 = {
+++	.id = QCA8K_ID_QCA8327,
+++	.reduced_package = true,
+++};
+++
+++static const struct qca8k_match_data qca8328 = {
++ 	.id = QCA8K_ID_QCA8327,
++ };
++ 
++@@ -2133,7 +2145,8 @@ static const struct qca8k_match_data qca
++ };
++ 
++ static const struct of_device_id qca8k_of_match[] = {
++-	{ .compatible = "qca,qca8327", .data = &qca832x },
+++	{ .compatible = "qca,qca8327", .data = &qca8327 },
+++	{ .compatible = "qca,qca8328", .data = &qca8328 },
++ 	{ .compatible = "qca,qca8334", .data = &qca833x },
++ 	{ .compatible = "qca,qca8337", .data = &qca833x },
++ 	{ /* sentinel */ },
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -260,6 +260,7 @@ struct ar8xxx_port_status {
++ 
++ struct qca8k_match_data {
++ 	u8 id;
+++	bool reduced_package;
++ };
++ 
++ enum {
+diff --git a/target/linux/generic/backport-5.15/747-v5.16-13-net-dsa-qca8k-set-internal-delay-also-for-sgmii.patch b/target/linux/generic/backport-5.15/747-v5.16-13-net-dsa-qca8k-set-internal-delay-also-for-sgmii.patch
+new file mode 100644
+index 0000000000..27f94dca02
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/747-v5.16-13-net-dsa-qca8k-set-internal-delay-also-for-sgmii.patch
+@@ -0,0 +1,159 @@
++From cef08115846e581f80ff99abf7bf218da1840616 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Thu, 14 Oct 2021 00:39:18 +0200
++Subject: net: dsa: qca8k: set internal delay also for sgmii
++
++QCA original code report port instability and sa that SGMII also require
++to set internal delay. Generalize the rgmii delay function and apply the
++advised value if they are not defined in DT.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 88 +++++++++++++++++++++++++++++++++----------------
++ drivers/net/dsa/qca8k.h |  2 ++
++ 2 files changed, 62 insertions(+), 28 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -1004,6 +1004,7 @@ qca8k_parse_port_config(struct qca8k_pri
++ 		case PHY_INTERFACE_MODE_RGMII_ID:
++ 		case PHY_INTERFACE_MODE_RGMII_TXID:
++ 		case PHY_INTERFACE_MODE_RGMII_RXID:
+++		case PHY_INTERFACE_MODE_SGMII:
++ 			delay = 0;
++ 
++ 			if (!of_property_read_u32(port_dn, "tx-internal-delay-ps", &delay))
++@@ -1036,8 +1037,13 @@ qca8k_parse_port_config(struct qca8k_pri
++ 
++ 			priv->rgmii_rx_delay[cpu_port_index] = delay;
++ 
++-			break;
++-		case PHY_INTERFACE_MODE_SGMII:
+++			/* Skip sgmii parsing for rgmii* mode */
+++			if (mode == PHY_INTERFACE_MODE_RGMII ||
+++			    mode == PHY_INTERFACE_MODE_RGMII_ID ||
+++			    mode == PHY_INTERFACE_MODE_RGMII_TXID ||
+++			    mode == PHY_INTERFACE_MODE_RGMII_RXID)
+++				break;
+++
++ 			if (of_property_read_bool(port_dn, "qca,sgmii-txclk-falling-edge"))
++ 				priv->sgmii_tx_clk_falling_edge = true;
++ 
++@@ -1261,12 +1267,53 @@ qca8k_setup(struct dsa_switch *ds)
++ }
++ 
++ static void
+++qca8k_mac_config_setup_internal_delay(struct qca8k_priv *priv, int cpu_port_index,
+++				      u32 reg)
+++{
+++	u32 delay, val = 0;
+++	int ret;
+++
+++	/* Delay can be declared in 3 different way.
+++	 * Mode to rgmii and internal-delay standard binding defined
+++	 * rgmii-id or rgmii-tx/rx phy mode set.
+++	 * The parse logic set a delay different than 0 only when one
+++	 * of the 3 different way is used. In all other case delay is
+++	 * not enabled. With ID or TX/RXID delay is enabled and set
+++	 * to the default and recommended value.
+++	 */
+++	if (priv->rgmii_tx_delay[cpu_port_index]) {
+++		delay = priv->rgmii_tx_delay[cpu_port_index];
+++
+++		val |= QCA8K_PORT_PAD_RGMII_TX_DELAY(delay) |
+++			QCA8K_PORT_PAD_RGMII_TX_DELAY_EN;
+++	}
+++
+++	if (priv->rgmii_rx_delay[cpu_port_index]) {
+++		delay = priv->rgmii_rx_delay[cpu_port_index];
+++
+++		val |= QCA8K_PORT_PAD_RGMII_RX_DELAY(delay) |
+++			QCA8K_PORT_PAD_RGMII_RX_DELAY_EN;
+++	}
+++
+++	/* Set RGMII delay based on the selected values */
+++	ret = qca8k_rmw(priv, reg,
+++			QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK |
+++			QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK |
+++			QCA8K_PORT_PAD_RGMII_TX_DELAY_EN |
+++			QCA8K_PORT_PAD_RGMII_RX_DELAY_EN,
+++			val);
+++	if (ret)
+++		dev_err(priv->dev, "Failed to set internal delay for CPU port%d",
+++			cpu_port_index == QCA8K_CPU_PORT0 ? 0 : 6);
+++}
+++
+++static void
++ qca8k_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode,
++ 			 const struct phylink_link_state *state)
++ {
++ 	struct qca8k_priv *priv = ds->priv;
++ 	int cpu_port_index, ret;
++-	u32 reg, val, delay;
+++	u32 reg, val;
++ 
++ 	switch (port) {
++ 	case 0: /* 1st CPU port */
++@@ -1315,32 +1362,10 @@ qca8k_phylink_mac_config(struct dsa_swit
++ 	case PHY_INTERFACE_MODE_RGMII_ID:
++ 	case PHY_INTERFACE_MODE_RGMII_TXID:
++ 	case PHY_INTERFACE_MODE_RGMII_RXID:
++-		val = QCA8K_PORT_PAD_RGMII_EN;
++-
++-		/* Delay can be declared in 3 different way.
++-		 * Mode to rgmii and internal-delay standard binding defined
++-		 * rgmii-id or rgmii-tx/rx phy mode set.
++-		 * The parse logic set a delay different than 0 only when one
++-		 * of the 3 different way is used. In all other case delay is
++-		 * not enabled. With ID or TX/RXID delay is enabled and set
++-		 * to the default and recommended value.
++-		 */
++-		if (priv->rgmii_tx_delay[cpu_port_index]) {
++-			delay = priv->rgmii_tx_delay[cpu_port_index];
++-
++-			val |= QCA8K_PORT_PAD_RGMII_TX_DELAY(delay) |
++-			       QCA8K_PORT_PAD_RGMII_TX_DELAY_EN;
++-		}
++-
++-		if (priv->rgmii_rx_delay[cpu_port_index]) {
++-			delay = priv->rgmii_rx_delay[cpu_port_index];
++-
++-			val |= QCA8K_PORT_PAD_RGMII_RX_DELAY(delay) |
++-			       QCA8K_PORT_PAD_RGMII_RX_DELAY_EN;
++-		}
+++		qca8k_write(priv, reg, QCA8K_PORT_PAD_RGMII_EN);
++ 
++-		/* Set RGMII delay based on the selected values */
++-		qca8k_write(priv, reg, val);
+++		/* Configure rgmii delay */
+++		qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg);
++ 
++ 		/* QCA8337 requires to set rgmii rx delay for all ports.
++ 		 * This is enabled through PORT5_PAD_CTRL for all ports,
++@@ -1411,6 +1436,13 @@ qca8k_phylink_mac_config(struct dsa_swit
++ 					QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE |
++ 					QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE,
++ 					val);
+++
+++		/* From original code is reported port instability as SGMII also
+++		 * require delay set. Apply advised values here or take them from DT.
+++		 */
+++		if (state->interface == PHY_INTERFACE_MODE_SGMII)
+++			qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg);
+++
++ 		break;
++ 	default:
++ 		dev_err(ds->dev, "xMII mode %s not supported for port %d\n",
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -39,7 +39,9 @@
++ #define QCA8K_REG_PORT5_PAD_CTRL			0x008
++ #define QCA8K_REG_PORT6_PAD_CTRL			0x00c
++ #define   QCA8K_PORT_PAD_RGMII_EN			BIT(26)
+++#define   QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK		GENMASK(23, 22)
++ #define   QCA8K_PORT_PAD_RGMII_TX_DELAY(x)		((x) << 22)
+++#define   QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK		GENMASK(21, 20)
++ #define   QCA8K_PORT_PAD_RGMII_RX_DELAY(x)		((x) << 20)
++ #define	  QCA8K_PORT_PAD_RGMII_TX_DELAY_EN		BIT(25)
++ #define   QCA8K_PORT_PAD_RGMII_RX_DELAY_EN		BIT(24)
+diff --git a/target/linux/generic/backport-5.15/747-v5.16-14-net-dsa-qca8k-move-port-config-to-dedicated-struct.patch b/target/linux/generic/backport-5.15/747-v5.16-14-net-dsa-qca8k-move-port-config-to-dedicated-struct.patch
+new file mode 100644
+index 0000000000..b991798c87
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/747-v5.16-14-net-dsa-qca8k-move-port-config-to-dedicated-struct.patch
+@@ -0,0 +1,124 @@
++From fd0bb28c547f7c8affb1691128cece38f5b626a1 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Thu, 14 Oct 2021 00:39:19 +0200
++Subject: net: dsa: qca8k: move port config to dedicated struct
++
++Move ports related config to dedicated struct to keep things organized.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 26 +++++++++++++-------------
++ drivers/net/dsa/qca8k.h | 10 +++++++---
++ 2 files changed, 20 insertions(+), 16 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -1019,7 +1019,7 @@ qca8k_parse_port_config(struct qca8k_pri
++ 				delay = 3;
++ 			}
++ 
++-			priv->rgmii_tx_delay[cpu_port_index] = delay;
+++			priv->ports_config.rgmii_tx_delay[cpu_port_index] = delay;
++ 
++ 			delay = 0;
++ 
++@@ -1035,7 +1035,7 @@ qca8k_parse_port_config(struct qca8k_pri
++ 				delay = 3;
++ 			}
++ 
++-			priv->rgmii_rx_delay[cpu_port_index] = delay;
+++			priv->ports_config.rgmii_rx_delay[cpu_port_index] = delay;
++ 
++ 			/* Skip sgmii parsing for rgmii* mode */
++ 			if (mode == PHY_INTERFACE_MODE_RGMII ||
++@@ -1045,17 +1045,17 @@ qca8k_parse_port_config(struct qca8k_pri
++ 				break;
++ 
++ 			if (of_property_read_bool(port_dn, "qca,sgmii-txclk-falling-edge"))
++-				priv->sgmii_tx_clk_falling_edge = true;
+++				priv->ports_config.sgmii_tx_clk_falling_edge = true;
++ 
++ 			if (of_property_read_bool(port_dn, "qca,sgmii-rxclk-falling-edge"))
++-				priv->sgmii_rx_clk_falling_edge = true;
+++				priv->ports_config.sgmii_rx_clk_falling_edge = true;
++ 
++ 			if (of_property_read_bool(port_dn, "qca,sgmii-enable-pll")) {
++-				priv->sgmii_enable_pll = true;
+++				priv->ports_config.sgmii_enable_pll = true;
++ 
++ 				if (priv->switch_id == QCA8K_ID_QCA8327) {
++ 					dev_err(priv->dev, "SGMII PLL should NOT be enabled for qca8327. Aborting enabling");
++-					priv->sgmii_enable_pll = false;
+++					priv->ports_config.sgmii_enable_pll = false;
++ 				}
++ 
++ 				if (priv->switch_revision < 2)
++@@ -1281,15 +1281,15 @@ qca8k_mac_config_setup_internal_delay(st
++ 	 * not enabled. With ID or TX/RXID delay is enabled and set
++ 	 * to the default and recommended value.
++ 	 */
++-	if (priv->rgmii_tx_delay[cpu_port_index]) {
++-		delay = priv->rgmii_tx_delay[cpu_port_index];
+++	if (priv->ports_config.rgmii_tx_delay[cpu_port_index]) {
+++		delay = priv->ports_config.rgmii_tx_delay[cpu_port_index];
++ 
++ 		val |= QCA8K_PORT_PAD_RGMII_TX_DELAY(delay) |
++ 			QCA8K_PORT_PAD_RGMII_TX_DELAY_EN;
++ 	}
++ 
++-	if (priv->rgmii_rx_delay[cpu_port_index]) {
++-		delay = priv->rgmii_rx_delay[cpu_port_index];
+++	if (priv->ports_config.rgmii_rx_delay[cpu_port_index]) {
+++		delay = priv->ports_config.rgmii_rx_delay[cpu_port_index];
++ 
++ 		val |= QCA8K_PORT_PAD_RGMII_RX_DELAY(delay) |
++ 			QCA8K_PORT_PAD_RGMII_RX_DELAY_EN;
++@@ -1397,7 +1397,7 @@ qca8k_phylink_mac_config(struct dsa_swit
++ 
++ 		val |= QCA8K_SGMII_EN_SD;
++ 
++-		if (priv->sgmii_enable_pll)
+++		if (priv->ports_config.sgmii_enable_pll)
++ 			val |= QCA8K_SGMII_EN_PLL | QCA8K_SGMII_EN_RX |
++ 			       QCA8K_SGMII_EN_TX;
++ 
++@@ -1425,10 +1425,10 @@ qca8k_phylink_mac_config(struct dsa_swit
++ 		val = 0;
++ 
++ 		/* SGMII Clock phase configuration */
++-		if (priv->sgmii_rx_clk_falling_edge)
+++		if (priv->ports_config.sgmii_rx_clk_falling_edge)
++ 			val |= QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE;
++ 
++-		if (priv->sgmii_tx_clk_falling_edge)
+++		if (priv->ports_config.sgmii_tx_clk_falling_edge)
++ 			val |= QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE;
++ 
++ 		if (val)
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -270,15 +270,19 @@ enum {
++ 	QCA8K_CPU_PORT6,
++ };
++ 
++-struct qca8k_priv {
++-	u8 switch_id;
++-	u8 switch_revision;
+++struct qca8k_ports_config {
++ 	bool sgmii_rx_clk_falling_edge;
++ 	bool sgmii_tx_clk_falling_edge;
++ 	bool sgmii_enable_pll;
++ 	u8 rgmii_rx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
++ 	u8 rgmii_tx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
+++};
+++
+++struct qca8k_priv {
+++	u8 switch_id;
+++	u8 switch_revision;
++ 	bool legacy_phy_port_mapping;
+++	struct qca8k_ports_config ports_config;
++ 	struct regmap *regmap;
++ 	struct mii_bus *bus;
++ 	struct ar8xxx_port_status port_sts[QCA8K_NUM_PORTS];
+diff --git a/target/linux/generic/backport-5.15/747-v5.16-15-dt-bindings-net-ipq8064-mdio-fix-warning-with-new-qc.patch b/target/linux/generic/backport-5.15/747-v5.16-15-dt-bindings-net-ipq8064-mdio-fix-warning-with-new-qc.patch
+new file mode 100644
+index 0000000000..f7cb514176
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/747-v5.16-15-dt-bindings-net-ipq8064-mdio-fix-warning-with-new-qc.patch
+@@ -0,0 +1,26 @@
++From e52073a8e3086046a098b8a7cbeb282ff0cdb424 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Thu, 14 Oct 2021 00:39:20 +0200
++Subject: dt-bindings: net: ipq8064-mdio: fix warning with new qca8k switch
++
++Fix warning now that we have qca8k switch Documentation using yaml.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ Documentation/devicetree/bindings/net/qcom,ipq8064-mdio.yaml | 5 ++++-
++ 1 file changed, 4 insertions(+), 1 deletion(-)
++
++--- a/Documentation/devicetree/bindings/net/qcom,ipq8064-mdio.yaml
+++++ b/Documentation/devicetree/bindings/net/qcom,ipq8064-mdio.yaml
++@@ -51,6 +51,9 @@ examples:
++         switch@10 {
++             compatible = "qca,qca8337";
++             reg = <0x10>;
++-            /* ... */
+++
+++            ports {
+++              /* ... */
+++            };
++         };
++     };
+diff --git a/target/linux/generic/backport-5.15/747-v5.16-16-dt-bindings-net-dsa-qca8k-convert-to-YAML-schema.patch b/target/linux/generic/backport-5.15/747-v5.16-16-dt-bindings-net-dsa-qca8k-convert-to-YAML-schema.patch
+new file mode 100644
+index 0000000000..b9bce97dd3
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/747-v5.16-16-dt-bindings-net-dsa-qca8k-convert-to-YAML-schema.patch
+@@ -0,0 +1,631 @@
++From d291fbb8245d5ba04979fed85575860a5cea7196 Mon Sep 17 00:00:00 2001
++From: Matthew Hagan <mnhagan88@gmail.com>
++Date: Thu, 14 Oct 2021 00:39:21 +0200
++Subject: dt-bindings: net: dsa: qca8k: convert to YAML schema
++
++Convert the qca8k bindings to YAML format.
++
++Signed-off-by: Matthew Hagan <mnhagan88@gmail.com>
++Co-developed-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ .../devicetree/bindings/net/dsa/qca8k.txt          | 245 --------------
++ .../devicetree/bindings/net/dsa/qca8k.yaml         | 362 +++++++++++++++++++++
++ 2 files changed, 362 insertions(+), 245 deletions(-)
++ delete mode 100644 Documentation/devicetree/bindings/net/dsa/qca8k.txt
++ create mode 100644 Documentation/devicetree/bindings/net/dsa/qca8k.yaml
++
++--- a/Documentation/devicetree/bindings/net/dsa/qca8k.txt
+++++ /dev/null
++@@ -1,245 +0,0 @@
++-* Qualcomm Atheros QCA8xxx switch family
++-
++-Required properties:
++-
++-- compatible: should be one of:
++-    "qca,qca8328": referenced as AR8328(N)-AK1(A/B) QFN 176 pin package
++-    "qca,qca8327": referenced as AR8327(N)-AL1A DR-QFN 148 pin package
++-    "qca,qca8334": referenced as QCA8334-AL3C QFN 88 pin package
++-    "qca,qca8337": referenced as QCA8337N-AL3(B/C) DR-QFN 148 pin package
++-
++-- #size-cells: must be 0
++-- #address-cells: must be 1
++-
++-Optional properties:
++-
++-- reset-gpios: GPIO to be used to reset the whole device
++-- qca,ignore-power-on-sel: Ignore power on pin strapping to configure led open
++-                           drain or eeprom presence. This is needed for broken
++-                           devices that have wrong configuration or when the oem
++-                           decided to not use pin strapping and fallback to sw
++-                           regs.
++-- qca,led-open-drain: Set leds to open-drain mode. This requires the
++-                      qca,ignore-power-on-sel to be set or the driver will fail
++-                      to probe. This is needed if the oem doesn't use pin
++-                      strapping to set this mode and prefers to set it using sw
++-                      regs. The pin strapping related to led open drain mode is
++-                      the pin B68 for QCA832x and B49 for QCA833x
++-
++-Subnodes:
++-
++-The integrated switch subnode should be specified according to the binding
++-described in dsa/dsa.txt. If the QCA8K switch is connect to a SoC's external
++-mdio-bus each subnode describing a port needs to have a valid phandle
++-referencing the internal PHY it is connected to. This is because there's no
++-N:N mapping of port and PHY id.
++-To declare the internal mdio-bus configuration, declare a mdio node in the
++-switch node and declare the phandle for the port referencing the internal
++-PHY is connected to. In this config a internal mdio-bus is registered and
++-the mdio MASTER is used as communication.
++-
++-Don't use mixed external and internal mdio-bus configurations, as this is
++-not supported by the hardware.
++-
++-This switch support 2 CPU port. Normally and advised configuration is with
++-CPU port set to port 0. It is also possible to set the CPU port to port 6
++-if the device requires it. The driver will configure the switch to the defined
++-port. With both CPU port declared the first CPU port is selected as primary
++-and the secondary CPU ignored.
++-
++-A CPU port node has the following optional node:
++-
++-- fixed-link            : Fixed-link subnode describing a link to a non-MDIO
++-                          managed entity. See
++-                          Documentation/devicetree/bindings/net/fixed-link.txt
++-                          for details.
++-- qca,sgmii-rxclk-falling-edge: Set the receive clock phase to falling edge.
++-                                Mostly used in qca8327 with CPU port 0 set to
++-                                sgmii.
++-- qca,sgmii-txclk-falling-edge: Set the transmit clock phase to falling edge.
++-- qca,sgmii-enable-pll  : For SGMII CPU port, explicitly enable PLL, TX and RX
++-                          chain along with Signal Detection.
++-                          This should NOT be enabled for qca8327. If enabled with
++-                          qca8327 the sgmii port won't correctly init and an err
++-                          is printed.
++-                          This can be required for qca8337 switch with revision 2.
++-                          A warning is displayed when used with revision greater
++-                          2.
++-                          With CPU port set to sgmii and qca8337 it is advised
++-                          to set this unless a communication problem is observed.
++-
++-For QCA8K the 'fixed-link' sub-node supports only the following properties:
++-
++-- 'speed' (integer, mandatory), to indicate the link speed. Accepted
++-  values are 10, 100 and 1000
++-- 'full-duplex' (boolean, optional), to indicate that full duplex is
++-  used. When absent, half duplex is assumed.
++-
++-Examples:
++-
++-for the external mdio-bus configuration:
++-
++-	&mdio0 {
++-		phy_port1: phy@0 {
++-			reg = <0>;
++-		};
++-
++-		phy_port2: phy@1 {
++-			reg = <1>;
++-		};
++-
++-		phy_port3: phy@2 {
++-			reg = <2>;
++-		};
++-
++-		phy_port4: phy@3 {
++-			reg = <3>;
++-		};
++-
++-		phy_port5: phy@4 {
++-			reg = <4>;
++-		};
++-
++-		switch@10 {
++-			compatible = "qca,qca8337";
++-			#address-cells = <1>;
++-			#size-cells = <0>;
++-
++-			reset-gpios = <&gpio 42 GPIO_ACTIVE_LOW>;
++-			reg = <0x10>;
++-
++-			ports {
++-				#address-cells = <1>;
++-				#size-cells = <0>;
++-				port@0 {
++-					reg = <0>;
++-					label = "cpu";
++-					ethernet = <&gmac1>;
++-					phy-mode = "rgmii";
++-					fixed-link {
++-						speed = 1000;
++-						full-duplex;
++-					};
++-				};
++-
++-				port@1 {
++-					reg = <1>;
++-					label = "lan1";
++-					phy-handle = <&phy_port1>;
++-				};
++-
++-				port@2 {
++-					reg = <2>;
++-					label = "lan2";
++-					phy-handle = <&phy_port2>;
++-				};
++-
++-				port@3 {
++-					reg = <3>;
++-					label = "lan3";
++-					phy-handle = <&phy_port3>;
++-				};
++-
++-				port@4 {
++-					reg = <4>;
++-					label = "lan4";
++-					phy-handle = <&phy_port4>;
++-				};
++-
++-				port@5 {
++-					reg = <5>;
++-					label = "wan";
++-					phy-handle = <&phy_port5>;
++-				};
++-			};
++-		};
++-	};
++-
++-for the internal master mdio-bus configuration:
++-
++-	&mdio0 {
++-		switch@10 {
++-			compatible = "qca,qca8337";
++-			#address-cells = <1>;
++-			#size-cells = <0>;
++-
++-			reset-gpios = <&gpio 42 GPIO_ACTIVE_LOW>;
++-			reg = <0x10>;
++-
++-			ports {
++-				#address-cells = <1>;
++-				#size-cells = <0>;
++-
++-				port@0 {
++-					reg = <0>;
++-					label = "cpu";
++-					ethernet = <&gmac1>;
++-					phy-mode = "rgmii";
++-					fixed-link {
++-						speed = 1000;
++-						full-duplex;
++-					};
++-				};
++-
++-				port@1 {
++-					reg = <1>;
++-					label = "lan1";
++-					phy-mode = "internal";
++-					phy-handle = <&phy_port1>;
++-				};
++-
++-				port@2 {
++-					reg = <2>;
++-					label = "lan2";
++-					phy-mode = "internal";
++-					phy-handle = <&phy_port2>;
++-				};
++-
++-				port@3 {
++-					reg = <3>;
++-					label = "lan3";
++-					phy-mode = "internal";
++-					phy-handle = <&phy_port3>;
++-				};
++-
++-				port@4 {
++-					reg = <4>;
++-					label = "lan4";
++-					phy-mode = "internal";
++-					phy-handle = <&phy_port4>;
++-				};
++-
++-				port@5 {
++-					reg = <5>;
++-					label = "wan";
++-					phy-mode = "internal";
++-					phy-handle = <&phy_port5>;
++-				};
++-			};
++-
++-			mdio {
++-				#address-cells = <1>;
++-				#size-cells = <0>;
++-
++-				phy_port1: phy@0 {
++-					reg = <0>;
++-				};
++-
++-				phy_port2: phy@1 {
++-					reg = <1>;
++-				};
++-
++-				phy_port3: phy@2 {
++-					reg = <2>;
++-				};
++-
++-				phy_port4: phy@3 {
++-					reg = <3>;
++-				};
++-
++-				phy_port5: phy@4 {
++-					reg = <4>;
++-				};
++-			};
++-		};
++-	};
++--- /dev/null
+++++ b/Documentation/devicetree/bindings/net/dsa/qca8k.yaml
++@@ -0,0 +1,362 @@
+++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+++%YAML 1.2
+++---
+++$id: http://devicetree.org/schemas/net/dsa/qca8k.yaml#
+++$schema: http://devicetree.org/meta-schemas/core.yaml#
+++
+++title: Qualcomm Atheros QCA83xx switch family
+++
+++maintainers:
+++  - John Crispin <john@phrozen.org>
+++
+++description:
+++  If the QCA8K switch is connect to an SoC's external mdio-bus, each subnode
+++  describing a port needs to have a valid phandle referencing the internal PHY
+++  it is connected to. This is because there is no N:N mapping of port and PHY
+++  ID. To declare the internal mdio-bus configuration, declare an MDIO node in
+++  the switch node and declare the phandle for the port, referencing the internal
+++  PHY it is connected to. In this config, an internal mdio-bus is registered and
+++  the MDIO master is used for communication. Mixed external and internal
+++  mdio-bus configurations are not supported by the hardware.
+++
+++properties:
+++  compatible:
+++    oneOf:
+++      - enum:
+++          - qca,qca8327
+++          - qca,qca8328
+++          - qca,qca8334
+++          - qca,qca8337
+++    description: |
+++      qca,qca8328: referenced as AR8328(N)-AK1(A/B) QFN 176 pin package
+++      qca,qca8327: referenced as AR8327(N)-AL1A DR-QFN 148 pin package
+++      qca,qca8334: referenced as QCA8334-AL3C QFN 88 pin package
+++      qca,qca8337: referenced as QCA8337N-AL3(B/C) DR-QFN 148 pin package
+++
+++  reg:
+++    maxItems: 1
+++
+++  reset-gpios:
+++    description:
+++      GPIO to be used to reset the whole device
+++    maxItems: 1
+++
+++  qca,ignore-power-on-sel:
+++    $ref: /schemas/types.yaml#/definitions/flag
+++    description:
+++      Ignore power-on pin strapping to configure LED open-drain or EEPROM
+++      presence. This is needed for devices with incorrect configuration or when
+++      the OEM has decided not to use pin strapping and falls back to SW regs.
+++
+++  qca,led-open-drain:
+++    $ref: /schemas/types.yaml#/definitions/flag
+++    description:
+++      Set LEDs to open-drain mode. This requires the qca,ignore-power-on-sel to
+++      be set, otherwise the driver will fail at probe. This is required if the
+++      OEM does not use pin strapping to set this mode and prefers to set it
+++      using SW regs. The pin strappings related to LED open-drain mode are
+++      B68 on the QCA832x and B49 on the QCA833x.
+++
+++  mdio:
+++    type: object
+++    description: Qca8k switch have an internal mdio to access switch port.
+++                 If this is not present, the legacy mapping is used and the
+++                 internal mdio access is used.
+++                 With the legacy mapping the reg corresponding to the internal
+++                 mdio is the switch reg with an offset of -1.
+++
+++    properties:
+++      '#address-cells':
+++        const: 1
+++      '#size-cells':
+++        const: 0
+++
+++    patternProperties:
+++      "^(ethernet-)?phy@[0-4]$":
+++        type: object
+++
+++        allOf:
+++          - $ref: "http://devicetree.org/schemas/net/mdio.yaml#"
+++
+++        properties:
+++          reg:
+++            maxItems: 1
+++
+++        required:
+++          - reg
+++
+++patternProperties:
+++  "^(ethernet-)?ports$":
+++    type: object
+++    properties:
+++      '#address-cells':
+++        const: 1
+++      '#size-cells':
+++        const: 0
+++
+++    patternProperties:
+++      "^(ethernet-)?port@[0-6]$":
+++        type: object
+++        description: Ethernet switch ports
+++
+++        properties:
+++          reg:
+++            description: Port number
+++
+++          label:
+++            description:
+++              Describes the label associated with this port, which will become
+++              the netdev name
+++            $ref: /schemas/types.yaml#/definitions/string
+++
+++          link:
+++            description:
+++              Should be a list of phandles to other switch's DSA port. This
+++              port is used as the outgoing port towards the phandle ports. The
+++              full routing information must be given, not just the one hop
+++              routes to neighbouring switches
+++            $ref: /schemas/types.yaml#/definitions/phandle-array
+++
+++          ethernet:
+++            description:
+++              Should be a phandle to a valid Ethernet device node.  This host
+++              device is what the switch port is connected to
+++            $ref: /schemas/types.yaml#/definitions/phandle
+++
+++          phy-handle: true
+++
+++          phy-mode: true
+++
+++          fixed-link: true
+++
+++          mac-address: true
+++
+++          sfp: true
+++
+++          qca,sgmii-rxclk-falling-edge:
+++            $ref: /schemas/types.yaml#/definitions/flag
+++            description:
+++              Set the receive clock phase to falling edge. Mostly commonly used on
+++              the QCA8327 with CPU port 0 set to SGMII.
+++
+++          qca,sgmii-txclk-falling-edge:
+++            $ref: /schemas/types.yaml#/definitions/flag
+++            description:
+++              Set the transmit clock phase to falling edge.
+++
+++          qca,sgmii-enable-pll:
+++            $ref: /schemas/types.yaml#/definitions/flag
+++            description:
+++              For SGMII CPU port, explicitly enable PLL, TX and RX chain along with
+++              Signal Detection. On the QCA8327 this should not be enabled, otherwise
+++              the SGMII port will not initialize. When used on the QCA8337, revision 3
+++              or greater, a warning will be displayed. When the CPU port is set to
+++              SGMII on the QCA8337, it is advised to set this unless a communication
+++              issue is observed.
+++
+++        required:
+++          - reg
+++
+++        additionalProperties: false
+++
+++oneOf:
+++  - required:
+++      - ports
+++  - required:
+++      - ethernet-ports
+++
+++required:
+++  - compatible
+++  - reg
+++
+++additionalProperties: true
+++
+++examples:
+++  - |
+++    #include <dt-bindings/gpio/gpio.h>
+++
+++    mdio {
+++        #address-cells = <1>;
+++        #size-cells = <0>;
+++
+++        external_phy_port1: ethernet-phy@0 {
+++            reg = <0>;
+++        };
+++
+++        external_phy_port2: ethernet-phy@1 {
+++            reg = <1>;
+++        };
+++
+++        external_phy_port3: ethernet-phy@2 {
+++            reg = <2>;
+++        };
+++
+++        external_phy_port4: ethernet-phy@3 {
+++            reg = <3>;
+++        };
+++
+++        external_phy_port5: ethernet-phy@4 {
+++            reg = <4>;
+++        };
+++
+++        switch@10 {
+++            compatible = "qca,qca8337";
+++            #address-cells = <1>;
+++            #size-cells = <0>;
+++            reset-gpios = <&gpio 42 GPIO_ACTIVE_LOW>;
+++            reg = <0x10>;
+++
+++            ports {
+++                #address-cells = <1>;
+++                #size-cells = <0>;
+++
+++                port@0 {
+++                    reg = <0>;
+++                    label = "cpu";
+++                    ethernet = <&gmac1>;
+++                    phy-mode = "rgmii";
+++
+++                    fixed-link {
+++                        speed = <1000>;
+++                        full-duplex;
+++                    };
+++                };
+++
+++                port@1 {
+++                    reg = <1>;
+++                    label = "lan1";
+++                    phy-handle = <&external_phy_port1>;
+++                };
+++
+++                port@2 {
+++                    reg = <2>;
+++                    label = "lan2";
+++                    phy-handle = <&external_phy_port2>;
+++                };
+++
+++                port@3 {
+++                    reg = <3>;
+++                    label = "lan3";
+++                    phy-handle = <&external_phy_port3>;
+++                };
+++
+++                port@4 {
+++                    reg = <4>;
+++                    label = "lan4";
+++                    phy-handle = <&external_phy_port4>;
+++                };
+++
+++                port@5 {
+++                    reg = <5>;
+++                    label = "wan";
+++                    phy-handle = <&external_phy_port5>;
+++                };
+++            };
+++        };
+++    };
+++  - |
+++    #include <dt-bindings/gpio/gpio.h>
+++
+++    mdio {
+++        #address-cells = <1>;
+++        #size-cells = <0>;
+++
+++        switch@10 {
+++            compatible = "qca,qca8337";
+++            #address-cells = <1>;
+++            #size-cells = <0>;
+++            reset-gpios = <&gpio 42 GPIO_ACTIVE_LOW>;
+++            reg = <0x10>;
+++
+++            ports {
+++                #address-cells = <1>;
+++                #size-cells = <0>;
+++
+++                port@0 {
+++                    reg = <0>;
+++                    label = "cpu";
+++                    ethernet = <&gmac1>;
+++                    phy-mode = "rgmii";
+++
+++                    fixed-link {
+++                        speed = <1000>;
+++                        full-duplex;
+++                    };
+++                };
+++
+++                port@1 {
+++                    reg = <1>;
+++                    label = "lan1";
+++                    phy-mode = "internal";
+++                    phy-handle = <&internal_phy_port1>;
+++                };
+++
+++                port@2 {
+++                    reg = <2>;
+++                    label = "lan2";
+++                    phy-mode = "internal";
+++                    phy-handle = <&internal_phy_port2>;
+++                };
+++
+++                port@3 {
+++                    reg = <3>;
+++                    label = "lan3";
+++                    phy-mode = "internal";
+++                    phy-handle = <&internal_phy_port3>;
+++                };
+++
+++                port@4 {
+++                    reg = <4>;
+++                    label = "lan4";
+++                    phy-mode = "internal";
+++                    phy-handle = <&internal_phy_port4>;
+++                };
+++
+++                port@5 {
+++                    reg = <5>;
+++                    label = "wan";
+++                    phy-mode = "internal";
+++                    phy-handle = <&internal_phy_port5>;
+++                };
+++
+++                port@6 {
+++                    reg = <0>;
+++                    label = "cpu";
+++                    ethernet = <&gmac1>;
+++                    phy-mode = "sgmii";
+++
+++                    qca,sgmii-rxclk-falling-edge;
+++
+++                    fixed-link {
+++                        speed = <1000>;
+++                        full-duplex;
+++                    };
+++                };
+++            };
+++
+++            mdio {
+++                #address-cells = <1>;
+++                #size-cells = <0>;
+++
+++                internal_phy_port1: ethernet-phy@0 {
+++                    reg = <0>;
+++                };
+++
+++                internal_phy_port2: ethernet-phy@1 {
+++                    reg = <1>;
+++                };
+++
+++                internal_phy_port3: ethernet-phy@2 {
+++                    reg = <2>;
+++                };
+++
+++                internal_phy_port4: ethernet-phy@3 {
+++                    reg = <3>;
+++                };
+++
+++                internal_phy_port5: ethernet-phy@4 {
+++                    reg = <4>;
+++                };
+++            };
+++        };
+++    };
+diff --git a/target/linux/generic/backport-5.15/748-v5.16-net-dsa-qca8k-fix-delay-applied-to-wrong-cpu-in-parse-p.patch b/target/linux/generic/backport-5.15/748-v5.16-net-dsa-qca8k-fix-delay-applied-to-wrong-cpu-in-parse-p.patch
+new file mode 100644
+index 0000000000..a510cfdc18
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/748-v5.16-net-dsa-qca8k-fix-delay-applied-to-wrong-cpu-in-parse-p.patch
+@@ -0,0 +1,28 @@
++From 06dd34a628ae5b6a839b757e746de165d6789ca8 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Sun, 17 Oct 2021 16:56:46 +0200
++Subject: net: dsa: qca8k: fix delay applied to wrong cpu in parse_port_config
++
++Fix delay settings applied to wrong cpu in parse_port_config. The delay
++values is set to the wrong index as the cpu_port_index is incremented
++too early. Start the cpu_port_index to -1 so the correct value is
++applied to address also the case with invalid phy mode and not available
++port.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -976,7 +976,7 @@ qca8k_setup_of_pws_reg(struct qca8k_priv
++ static int
++ qca8k_parse_port_config(struct qca8k_priv *priv)
++ {
++-	int port, cpu_port_index = 0, ret;
+++	int port, cpu_port_index = -1, ret;
++ 	struct device_node *port_dn;
++ 	phy_interface_t mode;
++ 	struct dsa_port *dp;
+diff --git a/target/linux/generic/backport-5.15/749-v5.16-net-dsa-qca8k-tidy-for-loop-in-setup-and-add-cpu-port-c.patch b/target/linux/generic/backport-5.15/749-v5.16-net-dsa-qca8k-tidy-for-loop-in-setup-and-add-cpu-port-c.patch
+new file mode 100644
+index 0000000000..71fa3022d5
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/749-v5.16-net-dsa-qca8k-tidy-for-loop-in-setup-and-add-cpu-port-c.patch
+@@ -0,0 +1,151 @@
++From 040e926f5813a5f4cc18dbff7c942d1e52f368f2 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Tue, 19 Oct 2021 02:08:50 +0200
++Subject: net: dsa: qca8k: tidy for loop in setup and add cpu port check
++
++Tidy and organize qca8k setup function from multiple for loop.
++Change for loop in bridge leave/join to scan all port and skip cpu port.
++No functional change intended.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 74 +++++++++++++++++++++++++++++--------------------
++ 1 file changed, 44 insertions(+), 30 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -1122,28 +1122,34 @@ qca8k_setup(struct dsa_switch *ds)
++ 	if (ret)
++ 		dev_warn(priv->dev, "mib init failed");
++ 
++-	/* Enable QCA header mode on the cpu port */
++-	ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(cpu_port),
++-			  QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_TX_S |
++-			  QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_RX_S);
++-	if (ret) {
++-		dev_err(priv->dev, "failed enabling QCA header mode");
++-		return ret;
++-	}
++-
++-	/* Disable forwarding by default on all ports */
+++	/* Initial setup of all ports */
++ 	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+++		/* Disable forwarding by default on all ports */
++ 		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
++ 				QCA8K_PORT_LOOKUP_MEMBER, 0);
++ 		if (ret)
++ 			return ret;
++-	}
++ 
++-	/* Disable MAC by default on all ports */
++-	for (i = 1; i < QCA8K_NUM_PORTS; i++)
++-		qca8k_port_set_status(priv, i, 0);
+++		/* Enable QCA header mode on all cpu ports */
+++		if (dsa_is_cpu_port(ds, i)) {
+++			ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(i),
+++					  QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_TX_S |
+++					  QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_RX_S);
+++			if (ret) {
+++				dev_err(priv->dev, "failed enabling QCA header mode");
+++				return ret;
+++			}
+++		}
+++
+++		/* Disable MAC by default on all user ports */
+++		if (dsa_is_user_port(ds, i))
+++			qca8k_port_set_status(priv, i, 0);
+++	}
++ 
++-	/* Forward all unknown frames to CPU port for Linux processing */
+++	/* Forward all unknown frames to CPU port for Linux processing
+++	 * Notice that in multi-cpu config only one port should be set
+++	 * for igmp, unknown, multicast and broadcast packet
+++	 */
++ 	ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1,
++ 			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S |
++ 			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_BC_DP_S |
++@@ -1152,11 +1158,13 @@ qca8k_setup(struct dsa_switch *ds)
++ 	if (ret)
++ 		return ret;
++ 
++-	/* Setup connection between CPU port & user ports */
+++	/* Setup connection between CPU port & user ports
+++	 * Configure specific switch configuration for ports
+++	 */
++ 	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++ 		/* CPU port gets connected to all user ports of the switch */
++ 		if (dsa_is_cpu_port(ds, i)) {
++-			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(cpu_port),
+++			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
++ 					QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds));
++ 			if (ret)
++ 				return ret;
++@@ -1193,16 +1201,14 @@ qca8k_setup(struct dsa_switch *ds)
++ 			if (ret)
++ 				return ret;
++ 		}
++-	}
++ 
++-	/* The port 5 of the qca8337 have some problem in flood condition. The
++-	 * original legacy driver had some specific buffer and priority settings
++-	 * for the different port suggested by the QCA switch team. Add this
++-	 * missing settings to improve switch stability under load condition.
++-	 * This problem is limited to qca8337 and other qca8k switch are not affected.
++-	 */
++-	if (priv->switch_id == QCA8K_ID_QCA8337) {
++-		for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+++		/* The port 5 of the qca8337 have some problem in flood condition. The
+++		 * original legacy driver had some specific buffer and priority settings
+++		 * for the different port suggested by the QCA switch team. Add this
+++		 * missing settings to improve switch stability under load condition.
+++		 * This problem is limited to qca8337 and other qca8k switch are not affected.
+++		 */
+++		if (priv->switch_id == QCA8K_ID_QCA8337) {
++ 			switch (i) {
++ 			/* The 2 CPU port and port 5 requires some different
++ 			 * priority than any other ports.
++@@ -1238,6 +1244,12 @@ qca8k_setup(struct dsa_switch *ds)
++ 				  QCA8K_PORT_HOL_CTRL1_WRED_EN,
++ 				  mask);
++ 		}
+++
+++		/* Set initial MTU for every port.
+++		 * We have only have a general MTU setting. So track
+++		 * every port and set the max across all port.
+++		 */
+++		priv->port_mtu[i] = ETH_FRAME_LEN + ETH_FCS_LEN;
++ 	}
++ 
++ 	/* Special GLOBAL_FC_THRESH value are needed for ar8327 switch */
++@@ -1251,8 +1263,6 @@ qca8k_setup(struct dsa_switch *ds)
++ 	}
++ 
++ 	/* Setup our port MTUs to match power on defaults */
++-	for (i = 0; i < QCA8K_NUM_PORTS; i++)
++-		priv->port_mtu[i] = ETH_FRAME_LEN + ETH_FCS_LEN;
++ 	ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, ETH_FRAME_LEN + ETH_FCS_LEN);
++ 	if (ret)
++ 		dev_warn(priv->dev, "failed setting MTU settings");
++@@ -1728,7 +1738,9 @@ qca8k_port_bridge_join(struct dsa_switch
++ 	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
++ 	port_mask = BIT(cpu_port);
++ 
++-	for (i = 1; i < QCA8K_NUM_PORTS; i++) {
+++	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+++		if (dsa_is_cpu_port(ds, i))
+++			continue;
++ 		if (dsa_to_port(ds, i)->bridge_dev != br)
++ 			continue;
++ 		/* Add this port to the portvlan mask of the other ports
++@@ -1758,7 +1770,9 @@ qca8k_port_bridge_leave(struct dsa_switc
++ 
++ 	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
++ 
++-	for (i = 1; i < QCA8K_NUM_PORTS; i++) {
+++	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+++		if (dsa_is_cpu_port(ds, i))
+++			continue;
++ 		if (dsa_to_port(ds, i)->bridge_dev != br)
++ 			continue;
++ 		/* Remove this port to the portvlan mask of the other ports
+diff --git a/target/linux/generic/backport-5.15/750-v5.16-net-dsa-qca8k-make-sure-pad0-mac06-exchange-is-disabled.patch b/target/linux/generic/backport-5.15/750-v5.16-net-dsa-qca8k-make-sure-pad0-mac06-exchange-is-disabled.patch
+new file mode 100644
+index 0000000000..4a61703c52
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/750-v5.16-net-dsa-qca8k-make-sure-pad0-mac06-exchange-is-disabled.patch
+@@ -0,0 +1,47 @@
++From 5f15d392dcb4aa250a63d6f2c5adfc26c0aedc78 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Tue, 2 Nov 2021 19:30:41 +0100
++Subject: net: dsa: qca8k: make sure PAD0 MAC06 exchange is disabled
++
++Some device set MAC06 exchange in the bootloader. This cause some
++problem as we don't support this strange mode and we just set the port6
++as the primary CPU port. With MAC06 exchange, PAD0 reg configure port6
++instead of port0. Add an extra check and explicitly disable MAC06 exchange
++to correctly configure the port PAD config.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Fixes: 3fcf734aa482 ("net: dsa: qca8k: add support for cpu port 6")
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 8 ++++++++
++ drivers/net/dsa/qca8k.h | 1 +
++ 2 files changed, 9 insertions(+)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -1109,6 +1109,14 @@ qca8k_setup(struct dsa_switch *ds)
++ 	if (ret)
++ 		return ret;
++ 
+++	/* Make sure MAC06 is disabled */
+++	ret = qca8k_reg_clear(priv, QCA8K_REG_PORT0_PAD_CTRL,
+++			      QCA8K_PORT0_PAD_MAC06_EXCHANGE_EN);
+++	if (ret) {
+++		dev_err(priv->dev, "failed disabling MAC06 exchange");
+++		return ret;
+++	}
+++
++ 	/* Enable CPU Port */
++ 	ret = qca8k_reg_set(priv, QCA8K_REG_GLOBAL_FW_CTRL0,
++ 			    QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -34,6 +34,7 @@
++ #define   QCA8K_MASK_CTRL_DEVICE_ID_MASK		GENMASK(15, 8)
++ #define   QCA8K_MASK_CTRL_DEVICE_ID(x)			((x) >> 8)
++ #define QCA8K_REG_PORT0_PAD_CTRL			0x004
+++#define   QCA8K_PORT0_PAD_MAC06_EXCHANGE_EN		BIT(31)
++ #define   QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE	BIT(19)
++ #define   QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE	BIT(18)
++ #define QCA8K_REG_PORT5_PAD_CTRL			0x008
+diff --git a/target/linux/generic/backport-5.15/751-v5.16-net-dsa-qca8k-fix-internal-delay-applied-to-the-wrong-PAD.patch b/target/linux/generic/backport-5.15/751-v5.16-net-dsa-qca8k-fix-internal-delay-applied-to-the-wrong-PAD.patch
+new file mode 100644
+index 0000000000..df9518d86c
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/751-v5.16-net-dsa-qca8k-fix-internal-delay-applied-to-the-wrong-PAD.patch
+@@ -0,0 +1,48 @@
++From 3b00a07c2443745d62babfe08dbb2ad8e649526e Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Fri, 19 Nov 2021 03:03:49 +0100
++Subject: [PATCH] net: dsa: qca8k: fix internal delay applied to the wrong PAD
++ config
++
++With SGMII phy the internal delay is always applied to the PAD0 config.
++This is caused by the falling edge configuration that hardcode the reg
++to PAD0 (as the falling edge bits are present only in PAD0 reg)
++Move the delay configuration before the reg overwrite to correctly apply
++the delay.
++
++Fixes: cef08115846e ("net: dsa: qca8k: set internal delay also for sgmii")
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 12 ++++++------
++ 1 file changed, 6 insertions(+), 6 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -1433,6 +1433,12 @@ qca8k_phylink_mac_config(struct dsa_swit
++ 
++ 		qca8k_write(priv, QCA8K_REG_SGMII_CTRL, val);
++ 
+++		/* From original code is reported port instability as SGMII also
+++		 * require delay set. Apply advised values here or take them from DT.
+++		 */
+++		if (state->interface == PHY_INTERFACE_MODE_SGMII)
+++			qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg);
+++
++ 		/* For qca8327/qca8328/qca8334/qca8338 sgmii is unique and
++ 		 * falling edge is set writing in the PORT0 PAD reg
++ 		 */
++@@ -1455,12 +1461,6 @@ qca8k_phylink_mac_config(struct dsa_swit
++ 					QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE,
++ 					val);
++ 
++-		/* From original code is reported port instability as SGMII also
++-		 * require delay set. Apply advised values here or take them from DT.
++-		 */
++-		if (state->interface == PHY_INTERFACE_MODE_SGMII)
++-			qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg);
++-
++ 		break;
++ 	default:
++ 		dev_err(ds->dev, "xMII mode %s not supported for port %d\n",
+diff --git a/target/linux/generic/backport-5.15/752-v5.16-net-dsa-qca8k-fix-MTU-calculation.patch b/target/linux/generic/backport-5.15/752-v5.16-net-dsa-qca8k-fix-MTU-calculation.patch
+new file mode 100644
+index 0000000000..7348d93ec4
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/752-v5.16-net-dsa-qca8k-fix-MTU-calculation.patch
+@@ -0,0 +1,46 @@
++From 65258b9d8cde45689bdc86ca39b50f01f983733b Mon Sep 17 00:00:00 2001
++From: Robert Marko <robert.marko@sartura.hr>
++Date: Fri, 19 Nov 2021 03:03:50 +0100
++Subject: [PATCH] net: dsa: qca8k: fix MTU calculation
++
++qca8k has a global MTU, so its tracking the MTU per port to make sure
++that the largest MTU gets applied.
++Since it uses the frame size instead of MTU the driver MTU change function
++will then add the size of Ethernet header and checksum on top of MTU.
++
++The driver currently populates the per port MTU size as Ethernet frame
++length + checksum which equals 1518.
++
++The issue is that then MTU change function will go through all of the
++ports, find the largest MTU and apply the Ethernet header + checksum on
++top of it again, so for a desired MTU of 1500 you will end up with 1536.
++
++This is obviously incorrect, so to correct it populate the per port struct
++MTU with just the MTU and not include the Ethernet header + checksum size
++as those will be added by the MTU change function.
++
++Fixes: f58d2598cf70 ("net: dsa: qca8k: implement the port MTU callbacks")
++Signed-off-by: Robert Marko <robert.marko@sartura.hr>
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 6 +++++-
++ 1 file changed, 5 insertions(+), 1 deletion(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -1256,8 +1256,12 @@ qca8k_setup(struct dsa_switch *ds)
++ 		/* Set initial MTU for every port.
++ 		 * We have only have a general MTU setting. So track
++ 		 * every port and set the max across all port.
+++		 * Set per port MTU to 1500 as the MTU change function
+++		 * will add the overhead and if its set to 1518 then it
+++		 * will apply the overhead again and we will end up with
+++		 * MTU of 1536 instead of 1518
++ 		 */
++-		priv->port_mtu[i] = ETH_FRAME_LEN + ETH_FCS_LEN;
+++		priv->port_mtu[i] = ETH_DATA_LEN;
++ 	}
++ 
++ 	/* Special GLOBAL_FC_THRESH value are needed for ar8327 switch */
+diff --git a/target/linux/generic/backport-5.15/753-v5.17-net-next-net-dsa-qca8k-remove-redundant-check-in-parse_port_config.patch b/target/linux/generic/backport-5.15/753-v5.17-net-next-net-dsa-qca8k-remove-redundant-check-in-parse_port_config.patch
+new file mode 100644
+index 0000000000..f477b1b929
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/753-v5.17-net-next-net-dsa-qca8k-remove-redundant-check-in-parse_port_config.patch
+@@ -0,0 +1,29 @@
++From b9133f3ef5a2659730cf47a74bd0a9259f1cf8ff Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Mon, 22 Nov 2021 16:23:40 +0100
++Subject: net: dsa: qca8k: remove redundant check in parse_port_config
++
++The very next check for port 0 and 6 already makes sure we don't go out
++of bounds with the ports_config delay table.
++Remove the redundant check.
++
++Reported-by: kernel test robot <lkp@intel.com>
++Reported-by: Dan Carpenter <dan.carpenter@oracle.com>
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -983,7 +983,7 @@ qca8k_parse_port_config(struct qca8k_pri
++ 	u32 delay;
++ 
++ 	/* We have 2 CPU port. Check them */
++-	for (port = 0; port < QCA8K_NUM_PORTS && cpu_port_index < QCA8K_NUM_CPU_PORTS; port++) {
+++	for (port = 0; port < QCA8K_NUM_PORTS; port++) {
++ 		/* Skip every other port */
++ 		if (port != 0 && port != 6)
++ 			continue;
+diff --git a/target/linux/generic/backport-5.15/754-v5.17-net-next-net-dsa-qca8k-convert-to-GENMASK_FIELD_PREP_FIELD_GET.patch b/target/linux/generic/backport-5.15/754-v5.17-net-next-net-dsa-qca8k-convert-to-GENMASK_FIELD_PREP_FIELD_GET.patch
+new file mode 100644
+index 0000000000..2cea88089d
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/754-v5.17-net-next-net-dsa-qca8k-convert-to-GENMASK_FIELD_PREP_FIELD_GET.patch
+@@ -0,0 +1,507 @@
++From 90ae68bfc2ffcb54a4ba4f64edbeb84a80cbb57c Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Mon, 22 Nov 2021 16:23:41 +0100
++Subject: net: dsa: qca8k: convert to GENMASK/FIELD_PREP/FIELD_GET
++
++Convert and try to standardize bit fields using
++GENMASK/FIELD_PREP/FIELD_GET macros. Rework some logic to support the
++standard macro and tidy things up. No functional change intended.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c |  98 +++++++++++++++----------------
++ drivers/net/dsa/qca8k.h | 153 ++++++++++++++++++++++++++----------------------
++ 2 files changed, 130 insertions(+), 121 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -9,6 +9,7 @@
++ #include <linux/module.h>
++ #include <linux/phy.h>
++ #include <linux/netdevice.h>
+++#include <linux/bitfield.h>
++ #include <net/dsa.h>
++ #include <linux/of_net.h>
++ #include <linux/of_mdio.h>
++@@ -319,18 +320,18 @@ qca8k_fdb_read(struct qca8k_priv *priv,
++ 	}
++ 
++ 	/* vid - 83:72 */
++-	fdb->vid = (reg[2] >> QCA8K_ATU_VID_S) & QCA8K_ATU_VID_M;
+++	fdb->vid = FIELD_GET(QCA8K_ATU_VID_MASK, reg[2]);
++ 	/* aging - 67:64 */
++-	fdb->aging = reg[2] & QCA8K_ATU_STATUS_M;
+++	fdb->aging = FIELD_GET(QCA8K_ATU_STATUS_MASK, reg[2]);
++ 	/* portmask - 54:48 */
++-	fdb->port_mask = (reg[1] >> QCA8K_ATU_PORT_S) & QCA8K_ATU_PORT_M;
+++	fdb->port_mask = FIELD_GET(QCA8K_ATU_PORT_MASK, reg[1]);
++ 	/* mac - 47:0 */
++-	fdb->mac[0] = (reg[1] >> QCA8K_ATU_ADDR0_S) & 0xff;
++-	fdb->mac[1] = reg[1] & 0xff;
++-	fdb->mac[2] = (reg[0] >> QCA8K_ATU_ADDR2_S) & 0xff;
++-	fdb->mac[3] = (reg[0] >> QCA8K_ATU_ADDR3_S) & 0xff;
++-	fdb->mac[4] = (reg[0] >> QCA8K_ATU_ADDR4_S) & 0xff;
++-	fdb->mac[5] = reg[0] & 0xff;
+++	fdb->mac[0] = FIELD_GET(QCA8K_ATU_ADDR0_MASK, reg[1]);
+++	fdb->mac[1] = FIELD_GET(QCA8K_ATU_ADDR1_MASK, reg[1]);
+++	fdb->mac[2] = FIELD_GET(QCA8K_ATU_ADDR2_MASK, reg[0]);
+++	fdb->mac[3] = FIELD_GET(QCA8K_ATU_ADDR3_MASK, reg[0]);
+++	fdb->mac[4] = FIELD_GET(QCA8K_ATU_ADDR4_MASK, reg[0]);
+++	fdb->mac[5] = FIELD_GET(QCA8K_ATU_ADDR5_MASK, reg[0]);
++ 
++ 	return 0;
++ }
++@@ -343,18 +344,18 @@ qca8k_fdb_write(struct qca8k_priv *priv,
++ 	int i;
++ 
++ 	/* vid - 83:72 */
++-	reg[2] = (vid & QCA8K_ATU_VID_M) << QCA8K_ATU_VID_S;
+++	reg[2] = FIELD_PREP(QCA8K_ATU_VID_MASK, vid);
++ 	/* aging - 67:64 */
++-	reg[2] |= aging & QCA8K_ATU_STATUS_M;
+++	reg[2] |= FIELD_PREP(QCA8K_ATU_STATUS_MASK, aging);
++ 	/* portmask - 54:48 */
++-	reg[1] = (port_mask & QCA8K_ATU_PORT_M) << QCA8K_ATU_PORT_S;
+++	reg[1] = FIELD_PREP(QCA8K_ATU_PORT_MASK, port_mask);
++ 	/* mac - 47:0 */
++-	reg[1] |= mac[0] << QCA8K_ATU_ADDR0_S;
++-	reg[1] |= mac[1];
++-	reg[0] |= mac[2] << QCA8K_ATU_ADDR2_S;
++-	reg[0] |= mac[3] << QCA8K_ATU_ADDR3_S;
++-	reg[0] |= mac[4] << QCA8K_ATU_ADDR4_S;
++-	reg[0] |= mac[5];
+++	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR0_MASK, mac[0]);
+++	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR1_MASK, mac[1]);
+++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR2_MASK, mac[2]);
+++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR3_MASK, mac[3]);
+++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR4_MASK, mac[4]);
+++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR5_MASK, mac[5]);
++ 
++ 	/* load the array into the ARL table */
++ 	for (i = 0; i < 3; i++)
++@@ -372,7 +373,7 @@ qca8k_fdb_access(struct qca8k_priv *priv
++ 	reg |= cmd;
++ 	if (port >= 0) {
++ 		reg |= QCA8K_ATU_FUNC_PORT_EN;
++-		reg |= (port & QCA8K_ATU_FUNC_PORT_M) << QCA8K_ATU_FUNC_PORT_S;
+++		reg |= FIELD_PREP(QCA8K_ATU_FUNC_PORT_MASK, port);
++ 	}
++ 
++ 	/* Write the function register triggering the table access */
++@@ -454,7 +455,7 @@ qca8k_vlan_access(struct qca8k_priv *pri
++ 	/* Set the command and VLAN index */
++ 	reg = QCA8K_VTU_FUNC1_BUSY;
++ 	reg |= cmd;
++-	reg |= vid << QCA8K_VTU_FUNC1_VID_S;
+++	reg |= FIELD_PREP(QCA8K_VTU_FUNC1_VID_MASK, vid);
++ 
++ 	/* Write the function register triggering the table access */
++ 	ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC1, reg);
++@@ -500,13 +501,11 @@ qca8k_vlan_add(struct qca8k_priv *priv,
++ 	if (ret < 0)
++ 		goto out;
++ 	reg |= QCA8K_VTU_FUNC0_VALID | QCA8K_VTU_FUNC0_IVL_EN;
++-	reg &= ~(QCA8K_VTU_FUNC0_EG_MODE_MASK << QCA8K_VTU_FUNC0_EG_MODE_S(port));
+++	reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port);
++ 	if (untagged)
++-		reg |= QCA8K_VTU_FUNC0_EG_MODE_UNTAG <<
++-				QCA8K_VTU_FUNC0_EG_MODE_S(port);
+++		reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_UNTAG(port);
++ 	else
++-		reg |= QCA8K_VTU_FUNC0_EG_MODE_TAG <<
++-				QCA8K_VTU_FUNC0_EG_MODE_S(port);
+++		reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_TAG(port);
++ 
++ 	ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
++ 	if (ret)
++@@ -534,15 +533,13 @@ qca8k_vlan_del(struct qca8k_priv *priv,
++ 	ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, &reg);
++ 	if (ret < 0)
++ 		goto out;
++-	reg &= ~(3 << QCA8K_VTU_FUNC0_EG_MODE_S(port));
++-	reg |= QCA8K_VTU_FUNC0_EG_MODE_NOT <<
++-			QCA8K_VTU_FUNC0_EG_MODE_S(port);
+++	reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port);
+++	reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(port);
++ 
++ 	/* Check if we're the last member to be removed */
++ 	del = true;
++ 	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++-		mask = QCA8K_VTU_FUNC0_EG_MODE_NOT;
++-		mask <<= QCA8K_VTU_FUNC0_EG_MODE_S(i);
+++		mask = QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(i);
++ 
++ 		if ((reg & mask) != mask) {
++ 			del = false;
++@@ -1014,7 +1011,7 @@ qca8k_parse_port_config(struct qca8k_pri
++ 				 mode == PHY_INTERFACE_MODE_RGMII_TXID)
++ 				delay = 1;
++ 
++-			if (delay > QCA8K_MAX_DELAY) {
+++			if (!FIELD_FIT(QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK, delay)) {
++ 				dev_err(priv->dev, "rgmii tx delay is limited to a max value of 3ns, setting to the max value");
++ 				delay = 3;
++ 			}
++@@ -1030,7 +1027,7 @@ qca8k_parse_port_config(struct qca8k_pri
++ 				 mode == PHY_INTERFACE_MODE_RGMII_RXID)
++ 				delay = 2;
++ 
++-			if (delay > QCA8K_MAX_DELAY) {
+++			if (!FIELD_FIT(QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK, delay)) {
++ 				dev_err(priv->dev, "rgmii rx delay is limited to a max value of 3ns, setting to the max value");
++ 				delay = 3;
++ 			}
++@@ -1141,8 +1138,8 @@ qca8k_setup(struct dsa_switch *ds)
++ 		/* Enable QCA header mode on all cpu ports */
++ 		if (dsa_is_cpu_port(ds, i)) {
++ 			ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(i),
++-					  QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_TX_S |
++-					  QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_RX_S);
+++					  FIELD_PREP(QCA8K_PORT_HDR_CTRL_TX_MASK, QCA8K_PORT_HDR_CTRL_ALL) |
+++					  FIELD_PREP(QCA8K_PORT_HDR_CTRL_RX_MASK, QCA8K_PORT_HDR_CTRL_ALL));
++ 			if (ret) {
++ 				dev_err(priv->dev, "failed enabling QCA header mode");
++ 				return ret;
++@@ -1159,10 +1156,10 @@ qca8k_setup(struct dsa_switch *ds)
++ 	 * for igmp, unknown, multicast and broadcast packet
++ 	 */
++ 	ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1,
++-			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S |
++-			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_BC_DP_S |
++-			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_MC_DP_S |
++-			  BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_UC_DP_S);
+++			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK, BIT(cpu_port)) |
+++			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK, BIT(cpu_port)) |
+++			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_MC_DP_MASK, BIT(cpu_port)) |
+++			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_UC_DP_MASK, BIT(cpu_port)));
++ 	if (ret)
++ 		return ret;
++ 
++@@ -1180,8 +1177,6 @@ qca8k_setup(struct dsa_switch *ds)
++ 
++ 		/* Individual user ports get connected to CPU port only */
++ 		if (dsa_is_user_port(ds, i)) {
++-			int shift = 16 * (i % 2);
++-
++ 			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
++ 					QCA8K_PORT_LOOKUP_MEMBER,
++ 					BIT(cpu_port));
++@@ -1198,8 +1193,8 @@ qca8k_setup(struct dsa_switch *ds)
++ 			 * default egress vid
++ 			 */
++ 			ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(i),
++-					0xfff << shift,
++-					QCA8K_PORT_VID_DEF << shift);
+++					QCA8K_EGREES_VLAN_PORT_MASK(i),
+++					QCA8K_EGREES_VLAN_PORT(i, QCA8K_PORT_VID_DEF));
++ 			if (ret)
++ 				return ret;
++ 
++@@ -1246,7 +1241,7 @@ qca8k_setup(struct dsa_switch *ds)
++ 			QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
++ 			QCA8K_PORT_HOL_CTRL1_WRED_EN;
++ 			qca8k_rmw(priv, QCA8K_REG_PORT_HOL_CTRL1(i),
++-				  QCA8K_PORT_HOL_CTRL1_ING_BUF |
+++				  QCA8K_PORT_HOL_CTRL1_ING_BUF_MASK |
++ 				  QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
++ 				  QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
++ 				  QCA8K_PORT_HOL_CTRL1_WRED_EN,
++@@ -1269,8 +1264,8 @@ qca8k_setup(struct dsa_switch *ds)
++ 		mask = QCA8K_GLOBAL_FC_GOL_XON_THRES(288) |
++ 		       QCA8K_GLOBAL_FC_GOL_XOFF_THRES(496);
++ 		qca8k_rmw(priv, QCA8K_REG_GLOBAL_FC_THRESH,
++-			  QCA8K_GLOBAL_FC_GOL_XON_THRES_S |
++-			  QCA8K_GLOBAL_FC_GOL_XOFF_THRES_S,
+++			  QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK |
+++			  QCA8K_GLOBAL_FC_GOL_XOFF_THRES_MASK,
++ 			  mask);
++ 	}
++ 
++@@ -1935,11 +1930,11 @@ qca8k_port_vlan_filtering(struct dsa_swi
++ 
++ 	if (vlan_filtering) {
++ 		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++-				QCA8K_PORT_LOOKUP_VLAN_MODE,
+++				QCA8K_PORT_LOOKUP_VLAN_MODE_MASK,
++ 				QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE);
++ 	} else {
++ 		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++-				QCA8K_PORT_LOOKUP_VLAN_MODE,
+++				QCA8K_PORT_LOOKUP_VLAN_MODE_MASK,
++ 				QCA8K_PORT_LOOKUP_VLAN_MODE_NONE);
++ 	}
++ 
++@@ -1963,10 +1958,9 @@ qca8k_port_vlan_add(struct dsa_switch *d
++ 	}
++ 
++ 	if (pvid) {
++-		int shift = 16 * (port % 2);
++-
++ 		ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port),
++-				0xfff << shift, vlan->vid << shift);
+++				QCA8K_EGREES_VLAN_PORT_MASK(port),
+++				QCA8K_EGREES_VLAN_PORT(port, vlan->vid));
++ 		if (ret)
++ 			return ret;
++ 
++@@ -2060,7 +2054,7 @@ static int qca8k_read_switch_id(struct q
++ 	if (ret < 0)
++ 		return -ENODEV;
++ 
++-	id = QCA8K_MASK_CTRL_DEVICE_ID(val & QCA8K_MASK_CTRL_DEVICE_ID_MASK);
+++	id = QCA8K_MASK_CTRL_DEVICE_ID(val);
++ 	if (id != data->id) {
++ 		dev_err(priv->dev, "Switch id detected %x but expected %x", id, data->id);
++ 		return -ENODEV;
++@@ -2069,7 +2063,7 @@ static int qca8k_read_switch_id(struct q
++ 	priv->switch_id = id;
++ 
++ 	/* Save revision to communicate to the internal PHY driver */
++-	priv->switch_revision = (val & QCA8K_MASK_CTRL_REV_ID_MASK);
+++	priv->switch_revision = QCA8K_MASK_CTRL_REV_ID(val);
++ 
++ 	return 0;
++ }
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -30,9 +30,9 @@
++ /* Global control registers */
++ #define QCA8K_REG_MASK_CTRL				0x000
++ #define   QCA8K_MASK_CTRL_REV_ID_MASK			GENMASK(7, 0)
++-#define   QCA8K_MASK_CTRL_REV_ID(x)			((x) >> 0)
+++#define   QCA8K_MASK_CTRL_REV_ID(x)			FIELD_GET(QCA8K_MASK_CTRL_REV_ID_MASK, x)
++ #define   QCA8K_MASK_CTRL_DEVICE_ID_MASK		GENMASK(15, 8)
++-#define   QCA8K_MASK_CTRL_DEVICE_ID(x)			((x) >> 8)
+++#define   QCA8K_MASK_CTRL_DEVICE_ID(x)			FIELD_GET(QCA8K_MASK_CTRL_DEVICE_ID_MASK, x)
++ #define QCA8K_REG_PORT0_PAD_CTRL			0x004
++ #define   QCA8K_PORT0_PAD_MAC06_EXCHANGE_EN		BIT(31)
++ #define   QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE	BIT(19)
++@@ -41,12 +41,11 @@
++ #define QCA8K_REG_PORT6_PAD_CTRL			0x00c
++ #define   QCA8K_PORT_PAD_RGMII_EN			BIT(26)
++ #define   QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK		GENMASK(23, 22)
++-#define   QCA8K_PORT_PAD_RGMII_TX_DELAY(x)		((x) << 22)
+++#define   QCA8K_PORT_PAD_RGMII_TX_DELAY(x)		FIELD_PREP(QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK, x)
++ #define   QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK		GENMASK(21, 20)
++-#define   QCA8K_PORT_PAD_RGMII_RX_DELAY(x)		((x) << 20)
+++#define   QCA8K_PORT_PAD_RGMII_RX_DELAY(x)		FIELD_PREP(QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK, x)
++ #define	  QCA8K_PORT_PAD_RGMII_TX_DELAY_EN		BIT(25)
++ #define   QCA8K_PORT_PAD_RGMII_RX_DELAY_EN		BIT(24)
++-#define   QCA8K_MAX_DELAY				3
++ #define   QCA8K_PORT_PAD_SGMII_EN			BIT(7)
++ #define QCA8K_REG_PWS					0x010
++ #define   QCA8K_PWS_POWER_ON_SEL			BIT(31)
++@@ -68,10 +67,12 @@
++ #define   QCA8K_MDIO_MASTER_READ			BIT(27)
++ #define   QCA8K_MDIO_MASTER_WRITE			0
++ #define   QCA8K_MDIO_MASTER_SUP_PRE			BIT(26)
++-#define   QCA8K_MDIO_MASTER_PHY_ADDR(x)			((x) << 21)
++-#define   QCA8K_MDIO_MASTER_REG_ADDR(x)			((x) << 16)
++-#define   QCA8K_MDIO_MASTER_DATA(x)			(x)
+++#define   QCA8K_MDIO_MASTER_PHY_ADDR_MASK		GENMASK(25, 21)
+++#define   QCA8K_MDIO_MASTER_PHY_ADDR(x)			FIELD_PREP(QCA8K_MDIO_MASTER_PHY_ADDR_MASK, x)
+++#define   QCA8K_MDIO_MASTER_REG_ADDR_MASK		GENMASK(20, 16)
+++#define   QCA8K_MDIO_MASTER_REG_ADDR(x)			FIELD_PREP(QCA8K_MDIO_MASTER_REG_ADDR_MASK, x)
++ #define   QCA8K_MDIO_MASTER_DATA_MASK			GENMASK(15, 0)
+++#define   QCA8K_MDIO_MASTER_DATA(x)			FIELD_PREP(QCA8K_MDIO_MASTER_DATA_MASK, x)
++ #define   QCA8K_MDIO_MASTER_MAX_PORTS			5
++ #define   QCA8K_MDIO_MASTER_MAX_REG			32
++ #define QCA8K_GOL_MAC_ADDR0				0x60
++@@ -93,9 +94,7 @@
++ #define   QCA8K_PORT_STATUS_FLOW_AUTO			BIT(12)
++ #define QCA8K_REG_PORT_HDR_CTRL(_i)			(0x9c + (_i * 4))
++ #define   QCA8K_PORT_HDR_CTRL_RX_MASK			GENMASK(3, 2)
++-#define   QCA8K_PORT_HDR_CTRL_RX_S			2
++ #define   QCA8K_PORT_HDR_CTRL_TX_MASK			GENMASK(1, 0)
++-#define   QCA8K_PORT_HDR_CTRL_TX_S			0
++ #define   QCA8K_PORT_HDR_CTRL_ALL			2
++ #define   QCA8K_PORT_HDR_CTRL_MGMT			1
++ #define   QCA8K_PORT_HDR_CTRL_NONE			0
++@@ -105,10 +104,11 @@
++ #define   QCA8K_SGMII_EN_TX				BIT(3)
++ #define   QCA8K_SGMII_EN_SD				BIT(4)
++ #define   QCA8K_SGMII_CLK125M_DELAY			BIT(7)
++-#define   QCA8K_SGMII_MODE_CTRL_MASK			(BIT(22) | BIT(23))
++-#define   QCA8K_SGMII_MODE_CTRL_BASEX			(0 << 22)
++-#define   QCA8K_SGMII_MODE_CTRL_PHY			(1 << 22)
++-#define   QCA8K_SGMII_MODE_CTRL_MAC			(2 << 22)
+++#define   QCA8K_SGMII_MODE_CTRL_MASK			GENMASK(23, 22)
+++#define   QCA8K_SGMII_MODE_CTRL(x)			FIELD_PREP(QCA8K_SGMII_MODE_CTRL_MASK, x)
+++#define   QCA8K_SGMII_MODE_CTRL_BASEX			QCA8K_SGMII_MODE_CTRL(0x0)
+++#define   QCA8K_SGMII_MODE_CTRL_PHY			QCA8K_SGMII_MODE_CTRL(0x1)
+++#define   QCA8K_SGMII_MODE_CTRL_MAC			QCA8K_SGMII_MODE_CTRL(0x2)
++ 
++ /* MAC_PWR_SEL registers */
++ #define QCA8K_REG_MAC_PWR_SEL				0x0e4
++@@ -121,100 +121,115 @@
++ 
++ /* ACL registers */
++ #define QCA8K_REG_PORT_VLAN_CTRL0(_i)			(0x420 + (_i * 8))
++-#define   QCA8K_PORT_VLAN_CVID(x)			(x << 16)
++-#define   QCA8K_PORT_VLAN_SVID(x)			x
+++#define   QCA8K_PORT_VLAN_CVID_MASK			GENMASK(27, 16)
+++#define   QCA8K_PORT_VLAN_CVID(x)			FIELD_PREP(QCA8K_PORT_VLAN_CVID_MASK, x)
+++#define   QCA8K_PORT_VLAN_SVID_MASK			GENMASK(11, 0)
+++#define   QCA8K_PORT_VLAN_SVID(x)			FIELD_PREP(QCA8K_PORT_VLAN_SVID_MASK, x)
++ #define QCA8K_REG_PORT_VLAN_CTRL1(_i)			(0x424 + (_i * 8))
++ #define QCA8K_REG_IPV4_PRI_BASE_ADDR			0x470
++ #define QCA8K_REG_IPV4_PRI_ADDR_MASK			0x474
++ 
++ /* Lookup registers */
++ #define QCA8K_REG_ATU_DATA0				0x600
++-#define   QCA8K_ATU_ADDR2_S				24
++-#define   QCA8K_ATU_ADDR3_S				16
++-#define   QCA8K_ATU_ADDR4_S				8
+++#define   QCA8K_ATU_ADDR2_MASK				GENMASK(31, 24)
+++#define   QCA8K_ATU_ADDR3_MASK				GENMASK(23, 16)
+++#define   QCA8K_ATU_ADDR4_MASK				GENMASK(15, 8)
+++#define   QCA8K_ATU_ADDR5_MASK				GENMASK(7, 0)
++ #define QCA8K_REG_ATU_DATA1				0x604
++-#define   QCA8K_ATU_PORT_M				0x7f
++-#define   QCA8K_ATU_PORT_S				16
++-#define   QCA8K_ATU_ADDR0_S				8
+++#define   QCA8K_ATU_PORT_MASK				GENMASK(22, 16)
+++#define   QCA8K_ATU_ADDR0_MASK				GENMASK(15, 8)
+++#define   QCA8K_ATU_ADDR1_MASK				GENMASK(7, 0)
++ #define QCA8K_REG_ATU_DATA2				0x608
++-#define   QCA8K_ATU_VID_M				0xfff
++-#define   QCA8K_ATU_VID_S				8
++-#define   QCA8K_ATU_STATUS_M				0xf
+++#define   QCA8K_ATU_VID_MASK				GENMASK(19, 8)
+++#define   QCA8K_ATU_STATUS_MASK				GENMASK(3, 0)
++ #define   QCA8K_ATU_STATUS_STATIC			0xf
++ #define QCA8K_REG_ATU_FUNC				0x60c
++ #define   QCA8K_ATU_FUNC_BUSY				BIT(31)
++ #define   QCA8K_ATU_FUNC_PORT_EN			BIT(14)
++ #define   QCA8K_ATU_FUNC_MULTI_EN			BIT(13)
++ #define   QCA8K_ATU_FUNC_FULL				BIT(12)
++-#define   QCA8K_ATU_FUNC_PORT_M				0xf
++-#define   QCA8K_ATU_FUNC_PORT_S				8
+++#define   QCA8K_ATU_FUNC_PORT_MASK			GENMASK(11, 8)
++ #define QCA8K_REG_VTU_FUNC0				0x610
++ #define   QCA8K_VTU_FUNC0_VALID				BIT(20)
++ #define   QCA8K_VTU_FUNC0_IVL_EN			BIT(19)
++-#define   QCA8K_VTU_FUNC0_EG_MODE_S(_i)			(4 + (_i) * 2)
++-#define   QCA8K_VTU_FUNC0_EG_MODE_MASK			3
++-#define   QCA8K_VTU_FUNC0_EG_MODE_UNMOD			0
++-#define   QCA8K_VTU_FUNC0_EG_MODE_UNTAG			1
++-#define   QCA8K_VTU_FUNC0_EG_MODE_TAG			2
++-#define   QCA8K_VTU_FUNC0_EG_MODE_NOT			3
+++/*        QCA8K_VTU_FUNC0_EG_MODE_MASK			GENMASK(17, 4)
+++ *          It does contain VLAN_MODE for each port [5:4] for port0,
+++ *          [7:6] for port1 ... [17:16] for port6. Use virtual port
+++ *          define to handle this.
+++ */
+++#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i)	(4 + (_i) * 2)
+++#define   QCA8K_VTU_FUNC0_EG_MODE_MASK			GENMASK(1, 0)
+++#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(_i)		(GENMASK(1, 0) << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
+++#define   QCA8K_VTU_FUNC0_EG_MODE_UNMOD			FIELD_PREP(QCA8K_VTU_FUNC0_EG_MODE_MASK, 0x0)
+++#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_UNMOD(_i)	(QCA8K_VTU_FUNC0_EG_MODE_UNMOD << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
+++#define   QCA8K_VTU_FUNC0_EG_MODE_UNTAG			FIELD_PREP(QCA8K_VTU_FUNC0_EG_MODE_MASK, 0x1)
+++#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_UNTAG(_i)	(QCA8K_VTU_FUNC0_EG_MODE_UNTAG << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
+++#define   QCA8K_VTU_FUNC0_EG_MODE_TAG			FIELD_PREP(QCA8K_VTU_FUNC0_EG_MODE_MASK, 0x2)
+++#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_TAG(_i)		(QCA8K_VTU_FUNC0_EG_MODE_TAG << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
+++#define   QCA8K_VTU_FUNC0_EG_MODE_NOT			FIELD_PREP(QCA8K_VTU_FUNC0_EG_MODE_MASK, 0x3)
+++#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(_i)		(QCA8K_VTU_FUNC0_EG_MODE_NOT << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
++ #define QCA8K_REG_VTU_FUNC1				0x614
++ #define   QCA8K_VTU_FUNC1_BUSY				BIT(31)
++-#define   QCA8K_VTU_FUNC1_VID_S				16
+++#define   QCA8K_VTU_FUNC1_VID_MASK			GENMASK(27, 16)
++ #define   QCA8K_VTU_FUNC1_FULL				BIT(4)
++ #define QCA8K_REG_GLOBAL_FW_CTRL0			0x620
++ #define   QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN		BIT(10)
++ #define QCA8K_REG_GLOBAL_FW_CTRL1			0x624
++-#define   QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S		24
++-#define   QCA8K_GLOBAL_FW_CTRL1_BC_DP_S			16
++-#define   QCA8K_GLOBAL_FW_CTRL1_MC_DP_S			8
++-#define   QCA8K_GLOBAL_FW_CTRL1_UC_DP_S			0
+++#define   QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK		GENMASK(30, 24)
+++#define   QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK		GENMASK(22, 16)
+++#define   QCA8K_GLOBAL_FW_CTRL1_MC_DP_MASK		GENMASK(14, 8)
+++#define   QCA8K_GLOBAL_FW_CTRL1_UC_DP_MASK		GENMASK(6, 0)
++ #define QCA8K_PORT_LOOKUP_CTRL(_i)			(0x660 + (_i) * 0xc)
++ #define   QCA8K_PORT_LOOKUP_MEMBER			GENMASK(6, 0)
++-#define   QCA8K_PORT_LOOKUP_VLAN_MODE			GENMASK(9, 8)
++-#define   QCA8K_PORT_LOOKUP_VLAN_MODE_NONE		(0 << 8)
++-#define   QCA8K_PORT_LOOKUP_VLAN_MODE_FALLBACK		(1 << 8)
++-#define   QCA8K_PORT_LOOKUP_VLAN_MODE_CHECK		(2 << 8)
++-#define   QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE		(3 << 8)
+++#define   QCA8K_PORT_LOOKUP_VLAN_MODE_MASK		GENMASK(9, 8)
+++#define   QCA8K_PORT_LOOKUP_VLAN_MODE(x)		FIELD_PREP(QCA8K_PORT_LOOKUP_VLAN_MODE_MASK, x)
+++#define   QCA8K_PORT_LOOKUP_VLAN_MODE_NONE		QCA8K_PORT_LOOKUP_VLAN_MODE(0x0)
+++#define   QCA8K_PORT_LOOKUP_VLAN_MODE_FALLBACK		QCA8K_PORT_LOOKUP_VLAN_MODE(0x1)
+++#define   QCA8K_PORT_LOOKUP_VLAN_MODE_CHECK		QCA8K_PORT_LOOKUP_VLAN_MODE(0x2)
+++#define   QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE		QCA8K_PORT_LOOKUP_VLAN_MODE(0x3)
++ #define   QCA8K_PORT_LOOKUP_STATE_MASK			GENMASK(18, 16)
++-#define   QCA8K_PORT_LOOKUP_STATE_DISABLED		(0 << 16)
++-#define   QCA8K_PORT_LOOKUP_STATE_BLOCKING		(1 << 16)
++-#define   QCA8K_PORT_LOOKUP_STATE_LISTENING		(2 << 16)
++-#define   QCA8K_PORT_LOOKUP_STATE_LEARNING		(3 << 16)
++-#define   QCA8K_PORT_LOOKUP_STATE_FORWARD		(4 << 16)
++-#define   QCA8K_PORT_LOOKUP_STATE			GENMASK(18, 16)
+++#define   QCA8K_PORT_LOOKUP_STATE(x)			FIELD_PREP(QCA8K_PORT_LOOKUP_STATE_MASK, x)
+++#define   QCA8K_PORT_LOOKUP_STATE_DISABLED		QCA8K_PORT_LOOKUP_STATE(0x0)
+++#define   QCA8K_PORT_LOOKUP_STATE_BLOCKING		QCA8K_PORT_LOOKUP_STATE(0x1)
+++#define   QCA8K_PORT_LOOKUP_STATE_LISTENING		QCA8K_PORT_LOOKUP_STATE(0x2)
+++#define   QCA8K_PORT_LOOKUP_STATE_LEARNING		QCA8K_PORT_LOOKUP_STATE(0x3)
+++#define   QCA8K_PORT_LOOKUP_STATE_FORWARD		QCA8K_PORT_LOOKUP_STATE(0x4)
++ #define   QCA8K_PORT_LOOKUP_LEARN			BIT(20)
++ 
++ #define QCA8K_REG_GLOBAL_FC_THRESH			0x800
++-#define   QCA8K_GLOBAL_FC_GOL_XON_THRES(x)		((x) << 16)
++-#define   QCA8K_GLOBAL_FC_GOL_XON_THRES_S		GENMASK(24, 16)
++-#define   QCA8K_GLOBAL_FC_GOL_XOFF_THRES(x)		((x) << 0)
++-#define   QCA8K_GLOBAL_FC_GOL_XOFF_THRES_S		GENMASK(8, 0)
+++#define   QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK		GENMASK(24, 16)
+++#define   QCA8K_GLOBAL_FC_GOL_XON_THRES(x)		FIELD_PREP(QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK, x)
+++#define   QCA8K_GLOBAL_FC_GOL_XOFF_THRES_MASK		GENMASK(8, 0)
+++#define   QCA8K_GLOBAL_FC_GOL_XOFF_THRES(x)		FIELD_PREP(QCA8K_GLOBAL_FC_GOL_XOFF_THRES_MASK, x)
++ 
++ #define QCA8K_REG_PORT_HOL_CTRL0(_i)			(0x970 + (_i) * 0x8)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI0_BUF		GENMASK(3, 0)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI0(x)		((x) << 0)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI1_BUF		GENMASK(7, 4)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI1(x)		((x) << 4)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI2_BUF		GENMASK(11, 8)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI2(x)		((x) << 8)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI3_BUF		GENMASK(15, 12)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI3(x)		((x) << 12)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI4_BUF		GENMASK(19, 16)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI4(x)		((x) << 16)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI5_BUF		GENMASK(23, 20)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI5(x)		((x) << 20)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PORT_BUF		GENMASK(29, 24)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PORT(x)		((x) << 24)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI0_BUF_MASK		GENMASK(3, 0)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI0(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI0_BUF_MASK, x)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI1_BUF_MASK		GENMASK(7, 4)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI1(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI1_BUF_MASK, x)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI2_BUF_MASK		GENMASK(11, 8)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI2(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI2_BUF_MASK, x)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI3_BUF_MASK		GENMASK(15, 12)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI3(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI3_BUF_MASK, x)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI4_BUF_MASK		GENMASK(19, 16)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI4(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI4_BUF_MASK, x)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI5_BUF_MASK		GENMASK(23, 20)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI5(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI5_BUF_MASK, x)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PORT_BUF_MASK		GENMASK(29, 24)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PORT(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PORT_BUF_MASK, x)
++ 
++ #define QCA8K_REG_PORT_HOL_CTRL1(_i)			(0x974 + (_i) * 0x8)
++-#define   QCA8K_PORT_HOL_CTRL1_ING_BUF			GENMASK(3, 0)
++-#define   QCA8K_PORT_HOL_CTRL1_ING(x)			((x) << 0)
+++#define   QCA8K_PORT_HOL_CTRL1_ING_BUF_MASK		GENMASK(3, 0)
+++#define   QCA8K_PORT_HOL_CTRL1_ING(x)			FIELD_PREP(QCA8K_PORT_HOL_CTRL1_ING_BUF_MASK, x)
++ #define   QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN		BIT(6)
++ #define   QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN		BIT(7)
++ #define   QCA8K_PORT_HOL_CTRL1_WRED_EN			BIT(8)
++ #define   QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN		BIT(16)
++ 
++ /* Pkt edit registers */
+++#define QCA8K_EGREES_VLAN_PORT_SHIFT(_i)		(16 * ((_i) % 2))
+++#define QCA8K_EGREES_VLAN_PORT_MASK(_i)			(GENMASK(11, 0) << QCA8K_EGREES_VLAN_PORT_SHIFT(_i))
+++#define QCA8K_EGREES_VLAN_PORT(_i, x)			((x) << QCA8K_EGREES_VLAN_PORT_SHIFT(_i))
++ #define QCA8K_EGRESS_VLAN(x)				(0x0c70 + (4 * (x / 2)))
++ 
++ /* L3 registers */
+diff --git a/target/linux/generic/backport-5.15/755-v5.17-net-next-net-dsa-qca8k-remove-extra-mutex_init-in-qca8k_setup.patch b/target/linux/generic/backport-5.15/755-v5.17-net-next-net-dsa-qca8k-remove-extra-mutex_init-in-qca8k_setup.patch
+new file mode 100644
+index 0000000000..8c39b8ea29
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/755-v5.17-net-next-net-dsa-qca8k-remove-extra-mutex_init-in-qca8k_setup.patch
+@@ -0,0 +1,25 @@
++From 994c28b6f971fa5db8ae977daea37eee87d93d51 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Mon, 22 Nov 2021 16:23:42 +0100
++Subject: net: dsa: qca8k: remove extra mutex_init in qca8k_setup
++
++Mutex is already init in sw_probe. Remove the extra init in qca8k_setup.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 2 --
++ 1 file changed, 2 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -1086,8 +1086,6 @@ qca8k_setup(struct dsa_switch *ds)
++ 	if (ret)
++ 		return ret;
++ 
++-	mutex_init(&priv->reg_mutex);
++-
++ 	/* Start by setting up the register mapping */
++ 	priv->regmap = devm_regmap_init(ds->dev, NULL, priv,
++ 					&qca8k_regmap_config);
+diff --git a/target/linux/generic/backport-5.15/756-v5.17-net-next-net-dsa-qca8k-move-regmap-init-in-probe-and-set-it.patch b/target/linux/generic/backport-5.15/756-v5.17-net-next-net-dsa-qca8k-move-regmap-init-in-probe-and-set-it.patch
+new file mode 100644
+index 0000000000..44d938c53e
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/756-v5.17-net-next-net-dsa-qca8k-move-regmap-init-in-probe-and-set-it.patch
+@@ -0,0 +1,46 @@
++From 36b8af12f424e7a7f60a935c60a0fd4aa0822378 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Mon, 22 Nov 2021 16:23:43 +0100
++Subject: net: dsa: qca8k: move regmap init in probe and set it mandatory
++
++In preparation for regmap conversion, move regmap init in the probe
++function and make it mandatory as any read/write/rmw operation will be
++converted to regmap API.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 14 ++++++++------
++ 1 file changed, 8 insertions(+), 6 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -1086,12 +1086,6 @@ qca8k_setup(struct dsa_switch *ds)
++ 	if (ret)
++ 		return ret;
++ 
++-	/* Start by setting up the register mapping */
++-	priv->regmap = devm_regmap_init(ds->dev, NULL, priv,
++-					&qca8k_regmap_config);
++-	if (IS_ERR(priv->regmap))
++-		dev_warn(priv->dev, "regmap initialization failed");
++-
++ 	ret = qca8k_setup_mdio_bus(priv);
++ 	if (ret)
++ 		return ret;
++@@ -2096,6 +2090,14 @@ qca8k_sw_probe(struct mdio_device *mdiod
++ 		gpiod_set_value_cansleep(priv->reset_gpio, 0);
++ 	}
++ 
+++	/* Start by setting up the register mapping */
+++	priv->regmap = devm_regmap_init(&mdiodev->dev, NULL, priv,
+++					&qca8k_regmap_config);
+++	if (IS_ERR(priv->regmap)) {
+++		dev_err(priv->dev, "regmap initialization failed");
+++		return PTR_ERR(priv->regmap);
+++	}
+++
++ 	/* Check the detected switch id */
++ 	ret = qca8k_read_switch_id(priv);
++ 	if (ret)
+diff --git a/target/linux/generic/backport-5.15/757-v5.17-net-next-net-dsa-qca8k-initial-conversion-to-regmap-heper.patch b/target/linux/generic/backport-5.15/757-v5.17-net-next-net-dsa-qca8k-initial-conversion-to-regmap-heper.patch
+new file mode 100644
+index 0000000000..4ca9c8ba41
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/757-v5.17-net-next-net-dsa-qca8k-initial-conversion-to-regmap-heper.patch
+@@ -0,0 +1,249 @@
++From 8b5f3f29a81a71934d004e21a1292c1148b05926 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Mon, 22 Nov 2021 16:23:44 +0100
++Subject: net: dsa: qca8k: initial conversion to regmap helper
++
++Convert any qca8k set/clear/pool to regmap helper and add
++missing config to regmap_config struct.
++Read/write/rmw operation are reworked to use the regmap helper
++internally to keep the delta of this patch low. These additional
++function will then be dropped when the code split will be proposed.
++
++Ipq40xx SoC have the internal switch based on the qca8k regmap but use
++mmio for read/write/rmw operation instead of mdio.
++In preparation for the support of this internal switch, convert the
++driver to regmap API to later split the driver to common and specific
++code. The overhead introduced by the use of regamp API is marginal as the
++internal mdio will bypass it by using its direct access and regmap will be
++used only by configuration functions or fdb access.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 107 +++++++++++++++++++++---------------------------
++ 1 file changed, 47 insertions(+), 60 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -10,6 +10,7 @@
++ #include <linux/phy.h>
++ #include <linux/netdevice.h>
++ #include <linux/bitfield.h>
+++#include <linux/regmap.h>
++ #include <net/dsa.h>
++ #include <linux/of_net.h>
++ #include <linux/of_mdio.h>
++@@ -152,6 +153,25 @@ qca8k_set_page(struct mii_bus *bus, u16
++ static int
++ qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val)
++ {
+++	return regmap_read(priv->regmap, reg, val);
+++}
+++
+++static int
+++qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val)
+++{
+++	return regmap_write(priv->regmap, reg, val);
+++}
+++
+++static int
+++qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
+++{
+++	return regmap_update_bits(priv->regmap, reg, mask, write_val);
+++}
+++
+++static int
+++qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
++ 	struct mii_bus *bus = priv->bus;
++ 	u16 r1, r2, page;
++ 	int ret;
++@@ -172,8 +192,9 @@ exit:
++ }
++ 
++ static int
++-qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val)
+++qca8k_regmap_write(void *ctx, uint32_t reg, uint32_t val)
++ {
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
++ 	struct mii_bus *bus = priv->bus;
++ 	u16 r1, r2, page;
++ 	int ret;
++@@ -194,8 +215,9 @@ exit:
++ }
++ 
++ static int
++-qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
+++qca8k_regmap_update_bits(void *ctx, uint32_t reg, uint32_t mask, uint32_t write_val)
++ {
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
++ 	struct mii_bus *bus = priv->bus;
++ 	u16 r1, r2, page;
++ 	u32 val;
++@@ -223,34 +245,6 @@ exit:
++ 	return ret;
++ }
++ 
++-static int
++-qca8k_reg_set(struct qca8k_priv *priv, u32 reg, u32 val)
++-{
++-	return qca8k_rmw(priv, reg, 0, val);
++-}
++-
++-static int
++-qca8k_reg_clear(struct qca8k_priv *priv, u32 reg, u32 val)
++-{
++-	return qca8k_rmw(priv, reg, val, 0);
++-}
++-
++-static int
++-qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
++-
++-	return qca8k_read(priv, reg, val);
++-}
++-
++-static int
++-qca8k_regmap_write(void *ctx, uint32_t reg, uint32_t val)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
++-
++-	return qca8k_write(priv, reg, val);
++-}
++-
++ static const struct regmap_range qca8k_readable_ranges[] = {
++ 	regmap_reg_range(0x0000, 0x00e4), /* Global control */
++ 	regmap_reg_range(0x0100, 0x0168), /* EEE control */
++@@ -282,26 +276,19 @@ static struct regmap_config qca8k_regmap
++ 	.max_register = 0x16ac, /* end MIB - Port6 range */
++ 	.reg_read = qca8k_regmap_read,
++ 	.reg_write = qca8k_regmap_write,
+++	.reg_update_bits = qca8k_regmap_update_bits,
++ 	.rd_table = &qca8k_readable_table,
+++	.disable_locking = true, /* Locking is handled by qca8k read/write */
+++	.cache_type = REGCACHE_NONE, /* Explicitly disable CACHE */
++ };
++ 
++ static int
++ qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask)
++ {
++-	int ret, ret1;
++ 	u32 val;
++ 
++-	ret = read_poll_timeout(qca8k_read, ret1, !(val & mask),
++-				0, QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
++-				priv, reg, &val);
++-
++-	/* Check if qca8k_read has failed for a different reason
++-	 * before returning -ETIMEDOUT
++-	 */
++-	if (ret < 0 && ret1 < 0)
++-		return ret1;
++-
++-	return ret;
+++	return regmap_read_poll_timeout(priv->regmap, reg, val, !(val & mask), 0,
+++				       QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC);
++ }
++ 
++ static int
++@@ -568,7 +555,7 @@ qca8k_mib_init(struct qca8k_priv *priv)
++ 	int ret;
++ 
++ 	mutex_lock(&priv->reg_mutex);
++-	ret = qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_FLUSH | QCA8K_MIB_BUSY);
+++	ret = regmap_set_bits(priv->regmap, QCA8K_REG_MIB, QCA8K_MIB_FLUSH | QCA8K_MIB_BUSY);
++ 	if (ret)
++ 		goto exit;
++ 
++@@ -576,7 +563,7 @@ qca8k_mib_init(struct qca8k_priv *priv)
++ 	if (ret)
++ 		goto exit;
++ 
++-	ret = qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP);
+++	ret = regmap_set_bits(priv->regmap, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP);
++ 	if (ret)
++ 		goto exit;
++ 
++@@ -597,9 +584,9 @@ qca8k_port_set_status(struct qca8k_priv
++ 		mask |= QCA8K_PORT_STATUS_LINK_AUTO;
++ 
++ 	if (enable)
++-		qca8k_reg_set(priv, QCA8K_REG_PORT_STATUS(port), mask);
+++		regmap_set_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
++ 	else
++-		qca8k_reg_clear(priv, QCA8K_REG_PORT_STATUS(port), mask);
+++		regmap_clear_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
++ }
++ 
++ static u32
++@@ -861,8 +848,8 @@ qca8k_setup_mdio_bus(struct qca8k_priv *
++ 		 * a dt-overlay and driver reload changed the configuration
++ 		 */
++ 
++-		return qca8k_reg_clear(priv, QCA8K_MDIO_MASTER_CTRL,
++-				       QCA8K_MDIO_MASTER_EN);
+++		return regmap_clear_bits(priv->regmap, QCA8K_MDIO_MASTER_CTRL,
+++					 QCA8K_MDIO_MASTER_EN);
++ 	}
++ 
++ 	/* Check if the devicetree declare the port:phy mapping */
++@@ -1099,16 +1086,16 @@ qca8k_setup(struct dsa_switch *ds)
++ 		return ret;
++ 
++ 	/* Make sure MAC06 is disabled */
++-	ret = qca8k_reg_clear(priv, QCA8K_REG_PORT0_PAD_CTRL,
++-			      QCA8K_PORT0_PAD_MAC06_EXCHANGE_EN);
+++	ret = regmap_clear_bits(priv->regmap, QCA8K_REG_PORT0_PAD_CTRL,
+++				QCA8K_PORT0_PAD_MAC06_EXCHANGE_EN);
++ 	if (ret) {
++ 		dev_err(priv->dev, "failed disabling MAC06 exchange");
++ 		return ret;
++ 	}
++ 
++ 	/* Enable CPU Port */
++-	ret = qca8k_reg_set(priv, QCA8K_REG_GLOBAL_FW_CTRL0,
++-			    QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
+++	ret = regmap_set_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
+++			      QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
++ 	if (ret) {
++ 		dev_err(priv->dev, "failed enabling CPU port");
++ 		return ret;
++@@ -1176,8 +1163,8 @@ qca8k_setup(struct dsa_switch *ds)
++ 				return ret;
++ 
++ 			/* Enable ARP Auto-learning by default */
++-			ret = qca8k_reg_set(priv, QCA8K_PORT_LOOKUP_CTRL(i),
++-					    QCA8K_PORT_LOOKUP_LEARN);
+++			ret = regmap_set_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(i),
+++					      QCA8K_PORT_LOOKUP_LEARN);
++ 			if (ret)
++ 				return ret;
++ 
++@@ -1745,9 +1732,9 @@ qca8k_port_bridge_join(struct dsa_switch
++ 		/* Add this port to the portvlan mask of the other ports
++ 		 * in the bridge
++ 		 */
++-		ret = qca8k_reg_set(priv,
++-				    QCA8K_PORT_LOOKUP_CTRL(i),
++-				    BIT(port));
+++		ret = regmap_set_bits(priv->regmap,
+++				      QCA8K_PORT_LOOKUP_CTRL(i),
+++				      BIT(port));
++ 		if (ret)
++ 			return ret;
++ 		if (i != port)
++@@ -1777,9 +1764,9 @@ qca8k_port_bridge_leave(struct dsa_switc
++ 		/* Remove this port to the portvlan mask of the other ports
++ 		 * in the bridge
++ 		 */
++-		qca8k_reg_clear(priv,
++-				QCA8K_PORT_LOOKUP_CTRL(i),
++-				BIT(port));
+++		regmap_clear_bits(priv->regmap,
+++				  QCA8K_PORT_LOOKUP_CTRL(i),
+++				  BIT(port));
++ 	}
++ 
++ 	/* Set the cpu port to be the only one in the portvlan mask of
+diff --git a/target/linux/generic/backport-5.15/758-v5.17-net-next-net-dsa-qca8k-add-additional-MIB-counter-and-.patch b/target/linux/generic/backport-5.15/758-v5.17-net-next-net-dsa-qca8k-add-additional-MIB-counter-and-.patch
+new file mode 100644
+index 0000000000..c8c050933b
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/758-v5.17-net-next-net-dsa-qca8k-add-additional-MIB-counter-and-.patch
+@@ -0,0 +1,120 @@
++From c126f118b330ccf0db0dda4a4bd6c729865a205f Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Mon, 22 Nov 2021 16:23:45 +0100
++Subject: net: dsa: qca8k: add additional MIB counter and make it dynamic
++
++We are currently missing 2 additionals MIB counter present in QCA833x
++switch.
++QC832x switch have 39 MIB counter and QCA833X have 41 MIB counter.
++Add the additional MIB counter and rework the MIB function to print the
++correct supported counter from the match_data struct.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 23 ++++++++++++++++++++---
++ drivers/net/dsa/qca8k.h |  4 ++++
++ 2 files changed, 24 insertions(+), 3 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -70,6 +70,8 @@ static const struct qca8k_mib_desc ar832
++ 	MIB_DESC(1, 0x9c, "TxExcDefer"),
++ 	MIB_DESC(1, 0xa0, "TxDefer"),
++ 	MIB_DESC(1, 0xa4, "TxLateCol"),
+++	MIB_DESC(1, 0xa8, "RXUnicast"),
+++	MIB_DESC(1, 0xac, "TXUnicast"),
++ };
++ 
++ /* The 32bit switch registers are accessed indirectly. To achieve this we need
++@@ -1605,12 +1607,16 @@ qca8k_phylink_mac_link_up(struct dsa_swi
++ static void
++ qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data)
++ {
+++	const struct qca8k_match_data *match_data;
+++	struct qca8k_priv *priv = ds->priv;
++ 	int i;
++ 
++ 	if (stringset != ETH_SS_STATS)
++ 		return;
++ 
++-	for (i = 0; i < ARRAY_SIZE(ar8327_mib); i++)
+++	match_data = of_device_get_match_data(priv->dev);
+++
+++	for (i = 0; i < match_data->mib_count; i++)
++ 		strncpy(data + i * ETH_GSTRING_LEN, ar8327_mib[i].name,
++ 			ETH_GSTRING_LEN);
++ }
++@@ -1620,12 +1626,15 @@ qca8k_get_ethtool_stats(struct dsa_switc
++ 			uint64_t *data)
++ {
++ 	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	const struct qca8k_match_data *match_data;
++ 	const struct qca8k_mib_desc *mib;
++ 	u32 reg, i, val;
++ 	u32 hi = 0;
++ 	int ret;
++ 
++-	for (i = 0; i < ARRAY_SIZE(ar8327_mib); i++) {
+++	match_data = of_device_get_match_data(priv->dev);
+++
+++	for (i = 0; i < match_data->mib_count; i++) {
++ 		mib = &ar8327_mib[i];
++ 		reg = QCA8K_PORT_MIB_COUNTER(port) + mib->offset;
++ 
++@@ -1648,10 +1657,15 @@ qca8k_get_ethtool_stats(struct dsa_switc
++ static int
++ qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset)
++ {
+++	const struct qca8k_match_data *match_data;
+++	struct qca8k_priv *priv = ds->priv;
+++
++ 	if (sset != ETH_SS_STATS)
++ 		return 0;
++ 
++-	return ARRAY_SIZE(ar8327_mib);
+++	match_data = of_device_get_match_data(priv->dev);
+++
+++	return match_data->mib_count;
++ }
++ 
++ static int
++@@ -2173,14 +2187,17 @@ static SIMPLE_DEV_PM_OPS(qca8k_pm_ops,
++ static const struct qca8k_match_data qca8327 = {
++ 	.id = QCA8K_ID_QCA8327,
++ 	.reduced_package = true,
+++	.mib_count = QCA8K_QCA832X_MIB_COUNT,
++ };
++ 
++ static const struct qca8k_match_data qca8328 = {
++ 	.id = QCA8K_ID_QCA8327,
+++	.mib_count = QCA8K_QCA832X_MIB_COUNT,
++ };
++ 
++ static const struct qca8k_match_data qca833x = {
++ 	.id = QCA8K_ID_QCA8337,
+++	.mib_count = QCA8K_QCA833X_MIB_COUNT,
++ };
++ 
++ static const struct of_device_id qca8k_of_match[] = {
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -21,6 +21,9 @@
++ #define PHY_ID_QCA8337					0x004dd036
++ #define QCA8K_ID_QCA8337				0x13
++ 
+++#define QCA8K_QCA832X_MIB_COUNT				39
+++#define QCA8K_QCA833X_MIB_COUNT				41
+++
++ #define QCA8K_BUSY_WAIT_TIMEOUT				2000
++ 
++ #define QCA8K_NUM_FDB_RECORDS				2048
++@@ -279,6 +282,7 @@ struct ar8xxx_port_status {
++ struct qca8k_match_data {
++ 	u8 id;
++ 	bool reduced_package;
+++	u8 mib_count;
++ };
++ 
++ enum {
+diff --git a/target/linux/generic/backport-5.15/759-v5.17-net-next-net-dsa-qca8k-add-support-for-port-fast-aging.patch b/target/linux/generic/backport-5.15/759-v5.17-net-next-net-dsa-qca8k-add-support-for-port-fast-aging.patch
+new file mode 100644
+index 0000000000..8ad7ab472d
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/759-v5.17-net-next-net-dsa-qca8k-add-support-for-port-fast-aging.patch
+@@ -0,0 +1,53 @@
++From 4592538bfb0d5d3c3c8a1d7071724d081412ac91 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Mon, 22 Nov 2021 16:23:46 +0100
++Subject: net: dsa: qca8k: add support for port fast aging
++
++The switch supports fast aging by flushing any rule in the ARL
++table for a specific port.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 11 +++++++++++
++ drivers/net/dsa/qca8k.h |  1 +
++ 2 files changed, 12 insertions(+)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -1790,6 +1790,16 @@ qca8k_port_bridge_leave(struct dsa_switc
++ 		  QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port));
++ }
++ 
+++static void
+++qca8k_port_fast_age(struct dsa_switch *ds, int port)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++
+++	mutex_lock(&priv->reg_mutex);
+++	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port);
+++	mutex_unlock(&priv->reg_mutex);
+++}
+++
++ static int
++ qca8k_port_enable(struct dsa_switch *ds, int port,
++ 		  struct phy_device *phy)
++@@ -2017,6 +2027,7 @@ static const struct dsa_switch_ops qca8k
++ 	.port_stp_state_set	= qca8k_port_stp_state_set,
++ 	.port_bridge_join	= qca8k_port_bridge_join,
++ 	.port_bridge_leave	= qca8k_port_bridge_leave,
+++	.port_fast_age		= qca8k_port_fast_age,
++ 	.port_fdb_add		= qca8k_port_fdb_add,
++ 	.port_fdb_del		= qca8k_port_fdb_del,
++ 	.port_fdb_dump		= qca8k_port_fdb_dump,
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -262,6 +262,7 @@ enum qca8k_fdb_cmd {
++ 	QCA8K_FDB_FLUSH	= 1,
++ 	QCA8K_FDB_LOAD = 2,
++ 	QCA8K_FDB_PURGE = 3,
+++	QCA8K_FDB_FLUSH_PORT = 5,
++ 	QCA8K_FDB_NEXT = 6,
++ 	QCA8K_FDB_SEARCH = 7,
++ };
+diff --git a/target/linux/generic/backport-5.15/760-v5.17-net-next-net-dsa-qca8k-add-set_ageing_time-support.patch b/target/linux/generic/backport-5.15/760-v5.17-net-next-net-dsa-qca8k-add-set_ageing_time-support.patch
+new file mode 100644
+index 0000000000..659e482405
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/760-v5.17-net-next-net-dsa-qca8k-add-set_ageing_time-support.patch
+@@ -0,0 +1,78 @@
++From 6a3bdc5209f45d2af83aa92433ab6e5cf2297aa4 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Mon, 22 Nov 2021 16:23:47 +0100
++Subject: net: dsa: qca8k: add set_ageing_time support
++
++qca8k support setting ageing time in step of 7s. Add support for it and
++set the max value accepted of 7645m.
++Documentation talks about support for 10000m but that values doesn't
++make sense as the value doesn't match the max value in the reg.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 25 +++++++++++++++++++++++++
++ drivers/net/dsa/qca8k.h |  3 +++
++ 2 files changed, 28 insertions(+)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -1261,6 +1261,10 @@ qca8k_setup(struct dsa_switch *ds)
++ 	/* We don't have interrupts for link changes, so we need to poll */
++ 	ds->pcs_poll = true;
++ 
+++	/* Set min a max ageing value supported */
+++	ds->ageing_time_min = 7000;
+++	ds->ageing_time_max = 458745000;
+++
++ 	return 0;
++ }
++ 
++@@ -1801,6 +1805,26 @@ qca8k_port_fast_age(struct dsa_switch *d
++ }
++ 
++ static int
+++qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	unsigned int secs = msecs / 1000;
+++	u32 val;
+++
+++	/* AGE_TIME reg is set in 7s step */
+++	val = secs / 7;
+++
+++	/* Handle case with 0 as val to NOT disable
+++	 * learning
+++	 */
+++	if (!val)
+++		val = 1;
+++
+++	return regmap_update_bits(priv->regmap, QCA8K_REG_ATU_CTRL, QCA8K_ATU_AGE_TIME_MASK,
+++				  QCA8K_ATU_AGE_TIME(val));
+++}
+++
+++static int
++ qca8k_port_enable(struct dsa_switch *ds, int port,
++ 		  struct phy_device *phy)
++ {
++@@ -2018,6 +2042,7 @@ static const struct dsa_switch_ops qca8k
++ 	.get_strings		= qca8k_get_strings,
++ 	.get_ethtool_stats	= qca8k_get_ethtool_stats,
++ 	.get_sset_count		= qca8k_get_sset_count,
+++	.set_ageing_time	= qca8k_set_ageing_time,
++ 	.get_mac_eee		= qca8k_get_mac_eee,
++ 	.set_mac_eee		= qca8k_set_mac_eee,
++ 	.port_enable		= qca8k_port_enable,
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -175,6 +175,9 @@
++ #define   QCA8K_VTU_FUNC1_BUSY				BIT(31)
++ #define   QCA8K_VTU_FUNC1_VID_MASK			GENMASK(27, 16)
++ #define   QCA8K_VTU_FUNC1_FULL				BIT(4)
+++#define QCA8K_REG_ATU_CTRL				0x618
+++#define   QCA8K_ATU_AGE_TIME_MASK			GENMASK(15, 0)
+++#define   QCA8K_ATU_AGE_TIME(x)				FIELD_PREP(QCA8K_ATU_AGE_TIME_MASK, (x))
++ #define QCA8K_REG_GLOBAL_FW_CTRL0			0x620
++ #define   QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN		BIT(10)
++ #define QCA8K_REG_GLOBAL_FW_CTRL1			0x624
+diff --git a/target/linux/generic/backport-5.15/761-v5.17-net-next-net-dsa-qca8k-add-support-for-mdb_add-del.patch b/target/linux/generic/backport-5.15/761-v5.17-net-next-net-dsa-qca8k-add-support-for-mdb_add-del.patch
+new file mode 100644
+index 0000000000..8b97939ecb
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/761-v5.17-net-next-net-dsa-qca8k-add-support-for-mdb_add-del.patch
+@@ -0,0 +1,142 @@
++From ba8f870dfa635113ce6e8095a5eb1835ecde2e9e Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Mon, 22 Nov 2021 16:23:48 +0100
++Subject: net: dsa: qca8k: add support for mdb_add/del
++
++Add support for mdb add/del function. The ARL table is used to insert
++the rule. The rule will be searched, deleted and reinserted with the
++port mask updated. The function will check if the rule has to be updated
++or insert directly with no deletion of the old rule.
++If every port is removed from the port mask, the rule is removed.
++The rule is set STATIC in the ARL table (aka it doesn't age) to not be
++flushed by fast age function.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++
++ 1 file changed, 99 insertions(+)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -436,6 +436,81 @@ qca8k_fdb_flush(struct qca8k_priv *priv)
++ }
++ 
++ static int
+++qca8k_fdb_search_and_insert(struct qca8k_priv *priv, u8 port_mask,
+++			    const u8 *mac, u16 vid)
+++{
+++	struct qca8k_fdb fdb = { 0 };
+++	int ret;
+++
+++	mutex_lock(&priv->reg_mutex);
+++
+++	qca8k_fdb_write(priv, vid, 0, mac, 0);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
+++	if (ret < 0)
+++		goto exit;
+++
+++	ret = qca8k_fdb_read(priv, &fdb);
+++	if (ret < 0)
+++		goto exit;
+++
+++	/* Rule exist. Delete first */
+++	if (!fdb.aging) {
+++		ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
+++		if (ret)
+++			goto exit;
+++	}
+++
+++	/* Add port to fdb portmask */
+++	fdb.port_mask |= port_mask;
+++
+++	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
+++
+++exit:
+++	mutex_unlock(&priv->reg_mutex);
+++	return ret;
+++}
+++
+++static int
+++qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask,
+++			 const u8 *mac, u16 vid)
+++{
+++	struct qca8k_fdb fdb = { 0 };
+++	int ret;
+++
+++	mutex_lock(&priv->reg_mutex);
+++
+++	qca8k_fdb_write(priv, vid, 0, mac, 0);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
+++	if (ret < 0)
+++		goto exit;
+++
+++	/* Rule doesn't exist. Why delete? */
+++	if (!fdb.aging) {
+++		ret = -EINVAL;
+++		goto exit;
+++	}
+++
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
+++	if (ret)
+++		goto exit;
+++
+++	/* Only port in the rule is this port. Don't re insert */
+++	if (fdb.port_mask == port_mask)
+++		goto exit;
+++
+++	/* Remove port from port mask */
+++	fdb.port_mask &= ~port_mask;
+++
+++	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
+++
+++exit:
+++	mutex_unlock(&priv->reg_mutex);
+++	return ret;
+++}
+++
+++static int
++ qca8k_vlan_access(struct qca8k_priv *priv, enum qca8k_vlan_cmd cmd, u16 vid)
++ {
++ 	u32 reg;
++@@ -1949,6 +2024,28 @@ qca8k_port_fdb_dump(struct dsa_switch *d
++ }
++ 
++ static int
+++qca8k_port_mdb_add(struct dsa_switch *ds, int port,
+++		   const struct switchdev_obj_port_mdb *mdb)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	const u8 *addr = mdb->addr;
+++	u16 vid = mdb->vid;
+++
+++	return qca8k_fdb_search_and_insert(priv, BIT(port), addr, vid);
+++}
+++
+++static int
+++qca8k_port_mdb_del(struct dsa_switch *ds, int port,
+++		   const struct switchdev_obj_port_mdb *mdb)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	const u8 *addr = mdb->addr;
+++	u16 vid = mdb->vid;
+++
+++	return qca8k_fdb_search_and_del(priv, BIT(port), addr, vid);
+++}
+++
+++static int
++ qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
++ 			  struct netlink_ext_ack *extack)
++ {
++@@ -2056,6 +2153,8 @@ static const struct dsa_switch_ops qca8k
++ 	.port_fdb_add		= qca8k_port_fdb_add,
++ 	.port_fdb_del		= qca8k_port_fdb_del,
++ 	.port_fdb_dump		= qca8k_port_fdb_dump,
+++	.port_mdb_add		= qca8k_port_mdb_add,
+++	.port_mdb_del		= qca8k_port_mdb_del,
++ 	.port_vlan_filtering	= qca8k_port_vlan_filtering,
++ 	.port_vlan_add		= qca8k_port_vlan_add,
++ 	.port_vlan_del		= qca8k_port_vlan_del,
+diff --git a/target/linux/generic/backport-5.15/762-v5.17-net-next-net-dsa-qca8k-add-support-for-mirror-mode.patch b/target/linux/generic/backport-5.15/762-v5.17-net-next-net-dsa-qca8k-add-support-for-mirror-mode.patch
+new file mode 100644
+index 0000000000..dc5a22935f
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/762-v5.17-net-next-net-dsa-qca8k-add-support-for-mirror-mode.patch
+@@ -0,0 +1,155 @@
++From 2c1bdbc7e7560d7de754cad277d968d56bb1899e Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Tue, 23 Nov 2021 03:59:10 +0100
++Subject: net: dsa: qca8k: add support for mirror mode
++
++The switch supports mirror mode. Only one port can set as mirror port and
++every other port can set to both ingress and egress mode. The mirror
++port is disabled and reverted to normal operation once every port is
++removed from sending packet to it.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++++
++ drivers/net/dsa/qca8k.h |  4 +++
++ 2 files changed, 99 insertions(+)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -2046,6 +2046,99 @@ qca8k_port_mdb_del(struct dsa_switch *ds
++ }
++ 
++ static int
+++qca8k_port_mirror_add(struct dsa_switch *ds, int port,
+++		      struct dsa_mall_mirror_tc_entry *mirror,
+++		      bool ingress)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int monitor_port, ret;
+++	u32 reg, val;
+++
+++	/* Check for existent entry */
+++	if ((ingress ? priv->mirror_rx : priv->mirror_tx) & BIT(port))
+++		return -EEXIST;
+++
+++	ret = regmap_read(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0, &val);
+++	if (ret)
+++		return ret;
+++
+++	/* QCA83xx can have only one port set to mirror mode.
+++	 * Check that the correct port is requested and return error otherwise.
+++	 * When no mirror port is set, the values is set to 0xF
+++	 */
+++	monitor_port = FIELD_GET(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
+++	if (monitor_port != 0xF && monitor_port != mirror->to_local_port)
+++		return -EEXIST;
+++
+++	/* Set the monitor port */
+++	val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM,
+++			 mirror->to_local_port);
+++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
+++				 QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
+++	if (ret)
+++		return ret;
+++
+++	if (ingress) {
+++		reg = QCA8K_PORT_LOOKUP_CTRL(port);
+++		val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN;
+++	} else {
+++		reg = QCA8K_REG_PORT_HOL_CTRL1(port);
+++		val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN;
+++	}
+++
+++	ret = regmap_update_bits(priv->regmap, reg, val, val);
+++	if (ret)
+++		return ret;
+++
+++	/* Track mirror port for tx and rx to decide when the
+++	 * mirror port has to be disabled.
+++	 */
+++	if (ingress)
+++		priv->mirror_rx |= BIT(port);
+++	else
+++		priv->mirror_tx |= BIT(port);
+++
+++	return 0;
+++}
+++
+++static void
+++qca8k_port_mirror_del(struct dsa_switch *ds, int port,
+++		      struct dsa_mall_mirror_tc_entry *mirror)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	u32 reg, val;
+++	int ret;
+++
+++	if (mirror->ingress) {
+++		reg = QCA8K_PORT_LOOKUP_CTRL(port);
+++		val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN;
+++	} else {
+++		reg = QCA8K_REG_PORT_HOL_CTRL1(port);
+++		val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN;
+++	}
+++
+++	ret = regmap_clear_bits(priv->regmap, reg, val);
+++	if (ret)
+++		goto err;
+++
+++	if (mirror->ingress)
+++		priv->mirror_rx &= ~BIT(port);
+++	else
+++		priv->mirror_tx &= ~BIT(port);
+++
+++	/* No port set to send packet to mirror port. Disable mirror port */
+++	if (!priv->mirror_rx && !priv->mirror_tx) {
+++		val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, 0xF);
+++		ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
+++					 QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
+++		if (ret)
+++			goto err;
+++	}
+++err:
+++	dev_err(priv->dev, "Failed to del mirror port from %d", port);
+++}
+++
+++static int
++ qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
++ 			  struct netlink_ext_ack *extack)
++ {
++@@ -2155,6 +2248,8 @@ static const struct dsa_switch_ops qca8k
++ 	.port_fdb_dump		= qca8k_port_fdb_dump,
++ 	.port_mdb_add		= qca8k_port_mdb_add,
++ 	.port_mdb_del		= qca8k_port_mdb_del,
+++	.port_mirror_add	= qca8k_port_mirror_add,
+++	.port_mirror_del	= qca8k_port_mirror_del,
++ 	.port_vlan_filtering	= qca8k_port_vlan_filtering,
++ 	.port_vlan_add		= qca8k_port_vlan_add,
++ 	.port_vlan_del		= qca8k_port_vlan_del,
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -180,6 +180,7 @@
++ #define   QCA8K_ATU_AGE_TIME(x)				FIELD_PREP(QCA8K_ATU_AGE_TIME_MASK, (x))
++ #define QCA8K_REG_GLOBAL_FW_CTRL0			0x620
++ #define   QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN		BIT(10)
+++#define   QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM		GENMASK(7, 4)
++ #define QCA8K_REG_GLOBAL_FW_CTRL1			0x624
++ #define   QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK		GENMASK(30, 24)
++ #define   QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK		GENMASK(22, 16)
++@@ -201,6 +202,7 @@
++ #define   QCA8K_PORT_LOOKUP_STATE_LEARNING		QCA8K_PORT_LOOKUP_STATE(0x3)
++ #define   QCA8K_PORT_LOOKUP_STATE_FORWARD		QCA8K_PORT_LOOKUP_STATE(0x4)
++ #define   QCA8K_PORT_LOOKUP_LEARN			BIT(20)
+++#define   QCA8K_PORT_LOOKUP_ING_MIRROR_EN		BIT(25)
++ 
++ #define QCA8K_REG_GLOBAL_FC_THRESH			0x800
++ #define   QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK		GENMASK(24, 16)
++@@ -305,6 +307,8 @@ struct qca8k_ports_config {
++ struct qca8k_priv {
++ 	u8 switch_id;
++ 	u8 switch_revision;
+++	u8 mirror_rx;
+++	u8 mirror_tx;
++ 	bool legacy_phy_port_mapping;
++ 	struct qca8k_ports_config ports_config;
++ 	struct regmap *regmap;
+diff --git a/target/linux/generic/backport-5.15/763-v5.17-net-next-net-dsa-qca8k-add-LAG-support.patch b/target/linux/generic/backport-5.15/763-v5.17-net-next-net-dsa-qca8k-add-LAG-support.patch
+new file mode 100644
+index 0000000000..b53f1288d5
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/763-v5.17-net-next-net-dsa-qca8k-add-LAG-support.patch
+@@ -0,0 +1,288 @@
++From def975307c01191b6f0170048c3724b0ed3348af Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Tue, 23 Nov 2021 03:59:11 +0100
++Subject: net: dsa: qca8k: add LAG support
++
++Add LAG support to this switch. In Documentation this is described as
++trunk mode. A max of 4 LAGs are supported and each can support up to 4
++port. The current tx mode supported is Hash mode with both L2 and L2+3
++mode.
++When no port are present in the trunk, the trunk is disabled in the
++switch.
++When a port is disconnected, the traffic is redirected to the other
++available port.
++The hash mode is global and each LAG require to have the same hash mode
++set. To change the hash mode when multiple LAG are configured, it's
++required to remove each LAG and set the desired hash mode to the last.
++An error is printed when it's asked to set a not supported hadh mode.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 177 ++++++++++++++++++++++++++++++++++++++++++++++++
++ drivers/net/dsa/qca8k.h |  33 +++++++++
++ 2 files changed, 210 insertions(+)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -1340,6 +1340,9 @@ qca8k_setup(struct dsa_switch *ds)
++ 	ds->ageing_time_min = 7000;
++ 	ds->ageing_time_max = 458745000;
++ 
+++	/* Set max number of LAGs supported */
+++	ds->num_lag_ids = QCA8K_NUM_LAGS;
+++
++ 	return 0;
++ }
++ 
++@@ -2226,6 +2229,178 @@ qca8k_get_tag_protocol(struct dsa_switch
++ 	return DSA_TAG_PROTO_QCA;
++ }
++ 
+++static bool
+++qca8k_lag_can_offload(struct dsa_switch *ds,
+++		      struct net_device *lag,
+++		      struct netdev_lag_upper_info *info)
+++{
+++	struct dsa_port *dp;
+++	int id, members = 0;
+++
+++	id = dsa_lag_id(ds->dst, lag);
+++	if (id < 0 || id >= ds->num_lag_ids)
+++		return false;
+++
+++	dsa_lag_foreach_port(dp, ds->dst, lag)
+++		/* Includes the port joining the LAG */
+++		members++;
+++
+++	if (members > QCA8K_NUM_PORTS_FOR_LAG)
+++		return false;
+++
+++	if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH)
+++		return false;
+++
+++	if (info->hash_type != NETDEV_LAG_HASH_L2 ||
+++	    info->hash_type != NETDEV_LAG_HASH_L23)
+++		return false;
+++
+++	return true;
+++}
+++
+++static int
+++qca8k_lag_setup_hash(struct dsa_switch *ds,
+++		     struct net_device *lag,
+++		     struct netdev_lag_upper_info *info)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	bool unique_lag = true;
+++	int i, id;
+++	u32 hash;
+++
+++	id = dsa_lag_id(ds->dst, lag);
+++
+++	switch (info->hash_type) {
+++	case NETDEV_LAG_HASH_L23:
+++		hash |= QCA8K_TRUNK_HASH_SIP_EN;
+++		hash |= QCA8K_TRUNK_HASH_DIP_EN;
+++		fallthrough;
+++	case NETDEV_LAG_HASH_L2:
+++		hash |= QCA8K_TRUNK_HASH_SA_EN;
+++		hash |= QCA8K_TRUNK_HASH_DA_EN;
+++		break;
+++	default: /* We should NEVER reach this */
+++		return -EOPNOTSUPP;
+++	}
+++
+++	/* Check if we are the unique configured LAG */
+++	dsa_lags_foreach_id(i, ds->dst)
+++		if (i != id && dsa_lag_dev(ds->dst, i)) {
+++			unique_lag = false;
+++			break;
+++		}
+++
+++	/* Hash Mode is global. Make sure the same Hash Mode
+++	 * is set to all the 4 possible lag.
+++	 * If we are the unique LAG we can set whatever hash
+++	 * mode we want.
+++	 * To change hash mode it's needed to remove all LAG
+++	 * and change the mode with the latest.
+++	 */
+++	if (unique_lag) {
+++		priv->lag_hash_mode = hash;
+++	} else if (priv->lag_hash_mode != hash) {
+++		netdev_err(lag, "Error: Mismateched Hash Mode across different lag is not supported\n");
+++		return -EOPNOTSUPP;
+++	}
+++
+++	return regmap_update_bits(priv->regmap, QCA8K_TRUNK_HASH_EN_CTRL,
+++				  QCA8K_TRUNK_HASH_MASK, hash);
+++}
+++
+++static int
+++qca8k_lag_refresh_portmap(struct dsa_switch *ds, int port,
+++			  struct net_device *lag, bool delete)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int ret, id, i;
+++	u32 val;
+++
+++	id = dsa_lag_id(ds->dst, lag);
+++
+++	/* Read current port member */
+++	ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0, &val);
+++	if (ret)
+++		return ret;
+++
+++	/* Shift val to the correct trunk */
+++	val >>= QCA8K_REG_GOL_TRUNK_SHIFT(id);
+++	val &= QCA8K_REG_GOL_TRUNK_MEMBER_MASK;
+++	if (delete)
+++		val &= ~BIT(port);
+++	else
+++		val |= BIT(port);
+++
+++	/* Update port member. With empty portmap disable trunk */
+++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0,
+++				 QCA8K_REG_GOL_TRUNK_MEMBER(id) |
+++				 QCA8K_REG_GOL_TRUNK_EN(id),
+++				 !val << QCA8K_REG_GOL_TRUNK_SHIFT(id) |
+++				 val << QCA8K_REG_GOL_TRUNK_SHIFT(id));
+++
+++	/* Search empty member if adding or port on deleting */
+++	for (i = 0; i < QCA8K_NUM_PORTS_FOR_LAG; i++) {
+++		ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id), &val);
+++		if (ret)
+++			return ret;
+++
+++		val >>= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i);
+++		val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_MASK;
+++
+++		if (delete) {
+++			/* If port flagged to be disabled assume this member is
+++			 * empty
+++			 */
+++			if (val != QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
+++				continue;
+++
+++			val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK;
+++			if (val != port)
+++				continue;
+++		} else {
+++			/* If port flagged to be enabled assume this member is
+++			 * already set
+++			 */
+++			if (val == QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
+++				continue;
+++		}
+++
+++		/* We have found the member to add/remove */
+++		break;
+++	}
+++
+++	/* Set port in the correct port mask or disable port if in delete mode */
+++	return regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id),
+++				  QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN(id, i) |
+++				  QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT(id, i),
+++				  !delete << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i) |
+++				  port << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i));
+++}
+++
+++static int
+++qca8k_port_lag_join(struct dsa_switch *ds, int port,
+++		    struct net_device *lag,
+++		    struct netdev_lag_upper_info *info)
+++{
+++	int ret;
+++
+++	if (!qca8k_lag_can_offload(ds, lag, info))
+++		return -EOPNOTSUPP;
+++
+++	ret = qca8k_lag_setup_hash(ds, lag, info);
+++	if (ret)
+++		return ret;
+++
+++	return qca8k_lag_refresh_portmap(ds, port, lag, false);
+++}
+++
+++static int
+++qca8k_port_lag_leave(struct dsa_switch *ds, int port,
+++		     struct net_device *lag)
+++{
+++	return qca8k_lag_refresh_portmap(ds, port, lag, true);
+++}
+++
++ static const struct dsa_switch_ops qca8k_switch_ops = {
++ 	.get_tag_protocol	= qca8k_get_tag_protocol,
++ 	.setup			= qca8k_setup,
++@@ -2259,6 +2434,8 @@ static const struct dsa_switch_ops qca8k
++ 	.phylink_mac_link_down	= qca8k_phylink_mac_link_down,
++ 	.phylink_mac_link_up	= qca8k_phylink_mac_link_up,
++ 	.get_phy_flags		= qca8k_get_phy_flags,
+++	.port_lag_join		= qca8k_port_lag_join,
+++	.port_lag_leave		= qca8k_port_lag_leave,
++ };
++ 
++ static int qca8k_read_switch_id(struct qca8k_priv *priv)
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -15,6 +15,8 @@
++ #define QCA8K_NUM_PORTS					7
++ #define QCA8K_NUM_CPU_PORTS				2
++ #define QCA8K_MAX_MTU					9000
+++#define QCA8K_NUM_LAGS					4
+++#define QCA8K_NUM_PORTS_FOR_LAG				4
++ 
++ #define PHY_ID_QCA8327					0x004dd034
++ #define QCA8K_ID_QCA8327				0x12
++@@ -122,6 +124,14 @@
++ #define QCA8K_REG_EEE_CTRL				0x100
++ #define  QCA8K_REG_EEE_CTRL_LPI_EN(_i)			((_i + 1) * 2)
++ 
+++/* TRUNK_HASH_EN registers */
+++#define QCA8K_TRUNK_HASH_EN_CTRL			0x270
+++#define   QCA8K_TRUNK_HASH_SIP_EN			BIT(3)
+++#define   QCA8K_TRUNK_HASH_DIP_EN			BIT(2)
+++#define   QCA8K_TRUNK_HASH_SA_EN			BIT(1)
+++#define   QCA8K_TRUNK_HASH_DA_EN			BIT(0)
+++#define   QCA8K_TRUNK_HASH_MASK				GENMASK(3, 0)
+++
++ /* ACL registers */
++ #define QCA8K_REG_PORT_VLAN_CTRL0(_i)			(0x420 + (_i * 8))
++ #define   QCA8K_PORT_VLAN_CVID_MASK			GENMASK(27, 16)
++@@ -204,6 +214,28 @@
++ #define   QCA8K_PORT_LOOKUP_LEARN			BIT(20)
++ #define   QCA8K_PORT_LOOKUP_ING_MIRROR_EN		BIT(25)
++ 
+++#define QCA8K_REG_GOL_TRUNK_CTRL0			0x700
+++/* 4 max trunk first
+++ * first 6 bit for member bitmap
+++ * 7th bit is to enable trunk port
+++ */
+++#define QCA8K_REG_GOL_TRUNK_SHIFT(_i)			((_i) * 8)
+++#define QCA8K_REG_GOL_TRUNK_EN_MASK			BIT(7)
+++#define QCA8K_REG_GOL_TRUNK_EN(_i)			(QCA8K_REG_GOL_TRUNK_EN_MASK << QCA8K_REG_GOL_TRUNK_SHIFT(_i))
+++#define QCA8K_REG_GOL_TRUNK_MEMBER_MASK			GENMASK(6, 0)
+++#define QCA8K_REG_GOL_TRUNK_MEMBER(_i)			(QCA8K_REG_GOL_TRUNK_MEMBER_MASK << QCA8K_REG_GOL_TRUNK_SHIFT(_i))
+++/* 0x704 for TRUNK 0-1 --- 0x708 for TRUNK 2-3 */
+++#define QCA8K_REG_GOL_TRUNK_CTRL(_i)			(0x704 + (((_i) / 2) * 4))
+++#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_MASK		GENMASK(3, 0)
+++#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK		BIT(3)
+++#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK		GENMASK(2, 0)
+++#define QCA8K_REG_GOL_TRUNK_ID_SHIFT(_i)		(((_i) / 2) * 16)
+++#define QCA8K_REG_GOL_MEM_ID_SHIFT(_i)			((_i) * 4)
+++/* Complex shift: FIRST shift for port THEN shift for trunk */
+++#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(_i, _j)	(QCA8K_REG_GOL_MEM_ID_SHIFT(_j) + QCA8K_REG_GOL_TRUNK_ID_SHIFT(_i))
+++#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN(_i, _j)	(QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(_i, _j))
+++#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT(_i, _j)	(QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(_i, _j))
+++
++ #define QCA8K_REG_GLOBAL_FC_THRESH			0x800
++ #define   QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK		GENMASK(24, 16)
++ #define   QCA8K_GLOBAL_FC_GOL_XON_THRES(x)		FIELD_PREP(QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK, x)
++@@ -309,6 +341,7 @@ struct qca8k_priv {
++ 	u8 switch_revision;
++ 	u8 mirror_rx;
++ 	u8 mirror_tx;
+++	u8 lag_hash_mode;
++ 	bool legacy_phy_port_mapping;
++ 	struct qca8k_ports_config ports_config;
++ 	struct regmap *regmap;
+diff --git a/target/linux/generic/backport-5.15/764-v5.17-net-next-net-dsa-qca8k-fix-warning-in-LAG-feature.patch b/target/linux/generic/backport-5.15/764-v5.17-net-next-net-dsa-qca8k-fix-warning-in-LAG-feature.patch
+new file mode 100644
+index 0000000000..7d811be11c
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/764-v5.17-net-next-net-dsa-qca8k-fix-warning-in-LAG-feature.patch
+@@ -0,0 +1,40 @@
++From 0898ca67b86e14207d4feb3f3fea8b87cec5aab1 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Tue, 23 Nov 2021 16:44:46 +0100
++Subject: net: dsa: qca8k: fix warning in LAG feature
++
++Fix warning reported by bot.
++Make sure hash is init to 0 and fix wrong logic for hash_type in
++qca8k_lag_can_offload.
++
++Reported-by: kernel test robot <lkp@intel.com>
++Fixes: def975307c01 ("net: dsa: qca8k: add LAG support")
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Link: https://lore.kernel.org/r/20211123154446.31019-1-ansuelsmth@gmail.com
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/dsa/qca8k.c | 4 ++--
++ 1 file changed, 2 insertions(+), 2 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -2251,7 +2251,7 @@ qca8k_lag_can_offload(struct dsa_switch
++ 	if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH)
++ 		return false;
++ 
++-	if (info->hash_type != NETDEV_LAG_HASH_L2 ||
+++	if (info->hash_type != NETDEV_LAG_HASH_L2 &&
++ 	    info->hash_type != NETDEV_LAG_HASH_L23)
++ 		return false;
++ 
++@@ -2265,8 +2265,8 @@ qca8k_lag_setup_hash(struct dsa_switch *
++ {
++ 	struct qca8k_priv *priv = ds->priv;
++ 	bool unique_lag = true;
+++	u32 hash = 0;
++ 	int i, id;
++-	u32 hash;
++ 
++ 	id = dsa_lag_id(ds->dst, lag);
++ 
+diff --git a/target/linux/generic/backport-5.15/765-v5.17-01-net-next-net-dsa-reorder-PHY-initialization-with-MTU-setup-in.patch b/target/linux/generic/backport-5.15/765-v5.17-01-net-next-net-dsa-reorder-PHY-initialization-with-MTU-setup-in.patch
+new file mode 100644
+index 0000000000..77cf63b809
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/765-v5.17-01-net-next-net-dsa-reorder-PHY-initialization-with-MTU-setup-in.patch
+@@ -0,0 +1,52 @@
++From 904e112ad431492b34f235f59738e8312802bbf9 Mon Sep 17 00:00:00 2001
++From: Vladimir Oltean <vladimir.oltean@nxp.com>
++Date: Thu, 6 Jan 2022 01:11:12 +0200
++Subject: [PATCH 1/6] net: dsa: reorder PHY initialization with MTU setup in
++ slave.c
++
++In dsa_slave_create() there are 2 sections that take rtnl_lock():
++MTU change and netdev registration. They are separated by PHY
++initialization.
++
++There isn't any strict ordering requirement except for the fact that
++netdev registration should be last. Therefore, we can perform the MTU
++change a bit later, after the PHY setup. A future change will then be
++able to merge the two rtnl_lock sections into one.
++
++Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ net/dsa/slave.c | 14 +++++++-------
++ 1 file changed, 7 insertions(+), 7 deletions(-)
++
++--- a/net/dsa/slave.c
+++++ b/net/dsa/slave.c
++@@ -1977,13 +1977,6 @@ int dsa_slave_create(struct dsa_port *po
++ 	port->slave = slave_dev;
++ 	dsa_slave_setup_tagger(slave_dev);
++ 
++-	rtnl_lock();
++-	ret = dsa_slave_change_mtu(slave_dev, ETH_DATA_LEN);
++-	rtnl_unlock();
++-	if (ret && ret != -EOPNOTSUPP)
++-		dev_warn(ds->dev, "nonfatal error %d setting MTU to %d on port %d\n",
++-			 ret, ETH_DATA_LEN, port->index);
++-
++ 	netif_carrier_off(slave_dev);
++ 
++ 	ret = dsa_slave_phy_setup(slave_dev);
++@@ -1995,6 +1988,13 @@ int dsa_slave_create(struct dsa_port *po
++ 	}
++ 
++ 	rtnl_lock();
+++	ret = dsa_slave_change_mtu(slave_dev, ETH_DATA_LEN);
+++	rtnl_unlock();
+++	if (ret && ret != -EOPNOTSUPP)
+++		dev_warn(ds->dev, "nonfatal error %d setting MTU to %d on port %d\n",
+++			 ret, ETH_DATA_LEN, port->index);
+++
+++	rtnl_lock();
++ 
++ 	ret = register_netdevice(slave_dev);
++ 	if (ret) {
+diff --git a/target/linux/generic/backport-5.15/765-v5.17-02-net-next-net-dsa-merge-rtnl_lock-sections-in-dsa_slave_create.patch b/target/linux/generic/backport-5.15/765-v5.17-02-net-next-net-dsa-merge-rtnl_lock-sections-in-dsa_slave_create.patch
+new file mode 100644
+index 0000000000..50aa5d8f0d
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/765-v5.17-02-net-next-net-dsa-merge-rtnl_lock-sections-in-dsa_slave_create.patch
+@@ -0,0 +1,34 @@
++From e31dbd3b6aba585231cd84a87adeb22e7c6a8c19 Mon Sep 17 00:00:00 2001
++From: Vladimir Oltean <vladimir.oltean@nxp.com>
++Date: Thu, 6 Jan 2022 01:11:13 +0200
++Subject: [PATCH 2/6] net: dsa: merge rtnl_lock sections in dsa_slave_create
++
++Currently dsa_slave_create() has two sequences of rtnl_lock/rtnl_unlock
++in a row. Remove the rtnl_unlock() and rtnl_lock() in between, such that
++the operation can execute slighly faster.
++
++Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ net/dsa/slave.c | 4 +---
++ 1 file changed, 1 insertion(+), 3 deletions(-)
++
++--- a/net/dsa/slave.c
+++++ b/net/dsa/slave.c
++@@ -1988,14 +1988,12 @@ int dsa_slave_create(struct dsa_port *po
++ 	}
++ 
++ 	rtnl_lock();
+++
++ 	ret = dsa_slave_change_mtu(slave_dev, ETH_DATA_LEN);
++-	rtnl_unlock();
++ 	if (ret && ret != -EOPNOTSUPP)
++ 		dev_warn(ds->dev, "nonfatal error %d setting MTU to %d on port %d\n",
++ 			 ret, ETH_DATA_LEN, port->index);
++ 
++-	rtnl_lock();
++-
++ 	ret = register_netdevice(slave_dev);
++ 	if (ret) {
++ 		netdev_err(master, "error %d registering interface %s\n",
+diff --git a/target/linux/generic/backport-5.15/765-v5.17-03-net-next-net-dsa-stop-updating-master-MTU-from-master.c.patch b/target/linux/generic/backport-5.15/765-v5.17-03-net-next-net-dsa-stop-updating-master-MTU-from-master.c.patch
+new file mode 100644
+index 0000000000..6c7aad6928
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/765-v5.17-03-net-next-net-dsa-stop-updating-master-MTU-from-master.c.patch
+@@ -0,0 +1,91 @@
++From a1ff94c2973c43bc1e2677ac63ebb15b1d1ff846 Mon Sep 17 00:00:00 2001
++From: Vladimir Oltean <vladimir.oltean@nxp.com>
++Date: Thu, 6 Jan 2022 01:11:14 +0200
++Subject: [PATCH 3/6] net: dsa: stop updating master MTU from master.c
++
++At present there are two paths for changing the MTU of the DSA master.
++
++The first is:
++
++dsa_tree_setup
++-> dsa_tree_setup_ports
++   -> dsa_port_setup
++      -> dsa_slave_create
++         -> dsa_slave_change_mtu
++            -> dev_set_mtu(master)
++
++The second is:
++
++dsa_tree_setup
++-> dsa_tree_setup_master
++   -> dsa_master_setup
++      -> dev_set_mtu(dev)
++
++So the dev_set_mtu() call from dsa_master_setup() has been effectively
++superseded by the dsa_slave_change_mtu(slave_dev, ETH_DATA_LEN) that is
++done from dsa_slave_create() for each user port. The later function also
++updates the master MTU according to the largest user port MTU from the
++tree. Therefore, updating the master MTU through a separate code path
++isn't needed.
++
++Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ net/dsa/master.c | 25 +------------------------
++ 1 file changed, 1 insertion(+), 24 deletions(-)
++
++--- a/net/dsa/master.c
+++++ b/net/dsa/master.c
++@@ -329,28 +329,13 @@ static const struct attribute_group dsa_
++ 	.attrs	= dsa_slave_attrs,
++ };
++ 
++-static void dsa_master_reset_mtu(struct net_device *dev)
++-{
++-	int err;
++-
++-	rtnl_lock();
++-	err = dev_set_mtu(dev, ETH_DATA_LEN);
++-	if (err)
++-		netdev_dbg(dev,
++-			   "Unable to reset MTU to exclude DSA overheads\n");
++-	rtnl_unlock();
++-}
++-
++ static struct lock_class_key dsa_master_addr_list_lock_key;
++ 
++ int dsa_master_setup(struct net_device *dev, struct dsa_port *cpu_dp)
++ {
++-	const struct dsa_device_ops *tag_ops = cpu_dp->tag_ops;
++ 	struct dsa_switch *ds = cpu_dp->ds;
++ 	struct device_link *consumer_link;
++-	int mtu, ret;
++-
++-	mtu = ETH_DATA_LEN + dsa_tag_protocol_overhead(tag_ops);
+++	int ret;
++ 
++ 	/* The DSA master must use SET_NETDEV_DEV for this to work. */
++ 	consumer_link = device_link_add(ds->dev, dev->dev.parent,
++@@ -360,13 +345,6 @@ int dsa_master_setup(struct net_device *
++ 			   "Failed to create a device link to DSA switch %s\n",
++ 			   dev_name(ds->dev));
++ 
++-	rtnl_lock();
++-	ret = dev_set_mtu(dev, mtu);
++-	rtnl_unlock();
++-	if (ret)
++-		netdev_warn(dev, "error %d setting MTU to %d to include DSA overhead\n",
++-			    ret, mtu);
++-
++ 	/* If we use a tagging format that doesn't have an ethertype
++ 	 * field, make sure that all packets from this point on get
++ 	 * sent to the tag format's receive function.
++@@ -404,7 +382,6 @@ void dsa_master_teardown(struct net_devi
++ 	sysfs_remove_group(&dev->dev.kobj, &dsa_group);
++ 	dsa_netdev_ops_set(dev, NULL);
++ 	dsa_master_ethtool_teardown(dev);
++-	dsa_master_reset_mtu(dev);
++ 	dsa_master_set_promiscuity(dev, -1);
++ 
++ 	dev->dsa_ptr = NULL;
+diff --git a/target/linux/generic/backport-5.15/765-v5.17-04-net-next-net-dsa-hold-rtnl_mutex-when-calling-dsa_master_-set.patch b/target/linux/generic/backport-5.15/765-v5.17-04-net-next-net-dsa-hold-rtnl_mutex-when-calling-dsa_master_-set.patch
+new file mode 100644
+index 0000000000..e331226fc4
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/765-v5.17-04-net-next-net-dsa-hold-rtnl_mutex-when-calling-dsa_master_-set.patch
+@@ -0,0 +1,78 @@
++From c146f9bc195a9dc3ad7fd000a14540e7c9df952d Mon Sep 17 00:00:00 2001
++From: Vladimir Oltean <vladimir.oltean@nxp.com>
++Date: Thu, 6 Jan 2022 01:11:15 +0200
++Subject: [PATCH 4/6] net: dsa: hold rtnl_mutex when calling
++ dsa_master_{setup,teardown}
++
++DSA needs to simulate master tracking events when a binding is first
++with a DSA master established and torn down, in order to give drivers
++the simplifying guarantee that ->master_state_change calls are made
++only when the master's readiness state to pass traffic changes.
++master_state_change() provide a operational bool that DSA driver can use
++to understand if DSA master is operational or not.
++To avoid races, we need to block the reception of
++NETDEV_UP/NETDEV_CHANGE/NETDEV_GOING_DOWN events in the netdev notifier
++chain while we are changing the master's dev->dsa_ptr (this changes what
++netdev_uses_dsa(dev) reports).
++
++The dsa_master_setup() and dsa_master_teardown() functions optionally
++require the rtnl_mutex to be held, if the tagger needs the master to be
++promiscuous, these functions call dev_set_promiscuity(). Move the
++rtnl_lock() from that function and make it top-level.
++
++Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ net/dsa/dsa2.c   | 8 ++++++++
++ net/dsa/master.c | 4 ++--
++ 2 files changed, 10 insertions(+), 2 deletions(-)
++
++--- a/net/dsa/dsa2.c
+++++ b/net/dsa/dsa2.c
++@@ -1034,6 +1034,8 @@ static int dsa_tree_setup_master(struct
++ 	struct dsa_port *dp;
++ 	int err;
++ 
+++	rtnl_lock();
+++
++ 	list_for_each_entry(dp, &dst->ports, list) {
++ 		if (dsa_port_is_cpu(dp)) {
++ 			err = dsa_master_setup(dp->master, dp);
++@@ -1042,6 +1044,8 @@ static int dsa_tree_setup_master(struct
++ 		}
++ 	}
++ 
+++	rtnl_unlock();
+++
++ 	return 0;
++ }
++ 
++@@ -1049,9 +1053,13 @@ static void dsa_tree_teardown_master(str
++ {
++ 	struct dsa_port *dp;
++ 
+++	rtnl_lock();
+++
++ 	list_for_each_entry(dp, &dst->ports, list)
++ 		if (dsa_port_is_cpu(dp))
++ 			dsa_master_teardown(dp->master);
+++
+++	rtnl_unlock();
++ }
++ 
++ static int dsa_tree_setup_lags(struct dsa_switch_tree *dst)
++--- a/net/dsa/master.c
+++++ b/net/dsa/master.c
++@@ -266,9 +266,9 @@ static void dsa_master_set_promiscuity(s
++ 	if (!ops->promisc_on_master)
++ 		return;
++ 
++-	rtnl_lock();
+++	ASSERT_RTNL();
+++
++ 	dev_set_promiscuity(dev, inc);
++-	rtnl_unlock();
++ }
++ 
++ static ssize_t tagging_show(struct device *d, struct device_attribute *attr,
+diff --git a/target/linux/generic/backport-5.15/765-v5.17-05-net-next-net-dsa-first-set-up-shared-ports-then-non-shared-po.patch b/target/linux/generic/backport-5.15/765-v5.17-05-net-next-net-dsa-first-set-up-shared-ports-then-non-shared-po.patch
+new file mode 100644
+index 0000000000..e6472c61da
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/765-v5.17-05-net-next-net-dsa-first-set-up-shared-ports-then-non-shared-po.patch
+@@ -0,0 +1,118 @@
++From 1e3f407f3cacc5dcfe27166c412ed9bc263d82bf Mon Sep 17 00:00:00 2001
++From: Vladimir Oltean <vladimir.oltean@nxp.com>
++Date: Thu, 6 Jan 2022 01:11:16 +0200
++Subject: [PATCH 5/6] net: dsa: first set up shared ports, then non-shared
++ ports
++
++After commit a57d8c217aad ("net: dsa: flush switchdev workqueue before
++tearing down CPU/DSA ports"), the port setup and teardown procedure
++became asymmetric.
++
++The fact of the matter is that user ports need the shared ports to be up
++before they can be used for CPU-initiated termination. And since we
++register net devices for the user ports, those won't be functional until
++we also call the setup for the shared (CPU, DSA) ports. But we may do
++that later, depending on the port numbering scheme of the hardware we
++are dealing with.
++
++It just makes sense that all shared ports are brought up before any user
++port is. I can't pinpoint any issue due to the current behavior, but
++let's change it nonetheless, for consistency's sake.
++
++Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ net/dsa/dsa2.c | 50 +++++++++++++++++++++++++++++++++++++-------------
++ 1 file changed, 37 insertions(+), 13 deletions(-)
++
++--- a/net/dsa/dsa2.c
+++++ b/net/dsa/dsa2.c
++@@ -999,23 +999,28 @@ static void dsa_tree_teardown_switches(s
++ 		dsa_switch_teardown(dp->ds);
++ }
++ 
++-static int dsa_tree_setup_switches(struct dsa_switch_tree *dst)
+++/* Bring shared ports up first, then non-shared ports */
+++static int dsa_tree_setup_ports(struct dsa_switch_tree *dst)
++ {
++ 	struct dsa_port *dp;
++-	int err;
+++	int err = 0;
++ 
++ 	list_for_each_entry(dp, &dst->ports, list) {
++-		err = dsa_switch_setup(dp->ds);
++-		if (err)
++-			goto teardown;
+++		if (dsa_port_is_dsa(dp) || dsa_port_is_cpu(dp)) {
+++			err = dsa_port_setup(dp);
+++			if (err)
+++				goto teardown;
+++		}
++ 	}
++ 
++ 	list_for_each_entry(dp, &dst->ports, list) {
++-		err = dsa_port_setup(dp);
++-		if (err) {
++-			err = dsa_port_reinit_as_unused(dp);
++-			if (err)
++-				goto teardown;
+++		if (dsa_port_is_user(dp) || dsa_port_is_unused(dp)) {
+++			err = dsa_port_setup(dp);
+++			if (err) {
+++				err = dsa_port_reinit_as_unused(dp);
+++				if (err)
+++					goto teardown;
+++			}
++ 		}
++ 	}
++ 
++@@ -1024,7 +1029,21 @@ static int dsa_tree_setup_switches(struc
++ teardown:
++ 	dsa_tree_teardown_ports(dst);
++ 
++-	dsa_tree_teardown_switches(dst);
+++	return err;
+++}
+++
+++static int dsa_tree_setup_switches(struct dsa_switch_tree *dst)
+++{
+++	struct dsa_port *dp;
+++	int err = 0;
+++
+++	list_for_each_entry(dp, &dst->ports, list) {
+++		err = dsa_switch_setup(dp->ds);
+++		if (err) {
+++			dsa_tree_teardown_switches(dst);
+++			break;
+++		}
+++	}
++ 
++ 	return err;
++ }
++@@ -1111,10 +1130,14 @@ static int dsa_tree_setup(struct dsa_swi
++ 	if (err)
++ 		goto teardown_cpu_ports;
++ 
++-	err = dsa_tree_setup_master(dst);
+++	err = dsa_tree_setup_ports(dst);
++ 	if (err)
++ 		goto teardown_switches;
++ 
+++	err = dsa_tree_setup_master(dst);
+++	if (err)
+++		goto teardown_ports;
+++
++ 	err = dsa_tree_setup_lags(dst);
++ 	if (err)
++ 		goto teardown_master;
++@@ -1127,8 +1150,9 @@ static int dsa_tree_setup(struct dsa_swi
++ 
++ teardown_master:
++ 	dsa_tree_teardown_master(dst);
++-teardown_switches:
+++teardown_ports:
++ 	dsa_tree_teardown_ports(dst);
+++teardown_switches:
++ 	dsa_tree_teardown_switches(dst);
++ teardown_cpu_ports:
++ 	dsa_tree_teardown_cpu_ports(dst);
+diff --git a/target/linux/generic/backport-5.15/765-v5.17-06-net-next-net-dsa-setup-master-before-ports.patch b/target/linux/generic/backport-5.15/765-v5.17-06-net-next-net-dsa-setup-master-before-ports.patch
+new file mode 100644
+index 0000000000..93cad0c98a
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/765-v5.17-06-net-next-net-dsa-setup-master-before-ports.patch
+@@ -0,0 +1,115 @@
++From 11fd667dac315ea3f2469961f6d2869271a46cae Mon Sep 17 00:00:00 2001
++From: Vladimir Oltean <vladimir.oltean@nxp.com>
++Date: Thu, 6 Jan 2022 01:11:17 +0200
++Subject: [PATCH 6/6] net: dsa: setup master before ports
++
++It is said that as soon as a network interface is registered, all its
++resources should have already been prepared, so that it is available for
++sending and receiving traffic. One of the resources needed by a DSA
++slave interface is the master.
++
++dsa_tree_setup
++-> dsa_tree_setup_ports
++   -> dsa_port_setup
++      -> dsa_slave_create
++         -> register_netdevice
++-> dsa_tree_setup_master
++   -> dsa_master_setup
++      -> sets up master->dsa_ptr, which enables reception
++
++Therefore, there is a short period of time after register_netdevice()
++during which the master isn't prepared to pass traffic to the DSA layer
++(master->dsa_ptr is checked by eth_type_trans). Same thing during
++unregistration, there is a time frame in which packets might be missed.
++
++Note that this change opens us to another race: dsa_master_find_slave()
++will get invoked potentially earlier than the slave creation, and later
++than the slave deletion. Since dp->slave starts off as a NULL pointer,
++the earlier calls aren't a problem, but the later calls are. To avoid
++use-after-free, we should zeroize dp->slave before calling
++dsa_slave_destroy().
++
++In practice I cannot really test real life improvements brought by this
++change, since in my systems, netdevice creation races with PHY autoneg
++which takes a few seconds to complete, and that masks quite a few races.
++Effects might be noticeable in a setup with fixed links all the way to
++an external system.
++
++Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ net/dsa/dsa2.c | 23 +++++++++++++----------
++ 1 file changed, 13 insertions(+), 10 deletions(-)
++
++--- a/net/dsa/dsa2.c
+++++ b/net/dsa/dsa2.c
++@@ -545,6 +545,7 @@ static void dsa_port_teardown(struct dsa
++ 	struct devlink_port *dlp = &dp->devlink_port;
++ 	struct dsa_switch *ds = dp->ds;
++ 	struct dsa_mac_addr *a, *tmp;
+++	struct net_device *slave;
++ 
++ 	if (!dp->setup)
++ 		return;
++@@ -566,9 +567,11 @@ static void dsa_port_teardown(struct dsa
++ 		dsa_port_link_unregister_of(dp);
++ 		break;
++ 	case DSA_PORT_TYPE_USER:
++-		if (dp->slave) {
++-			dsa_slave_destroy(dp->slave);
+++		slave = dp->slave;
+++
+++		if (slave) {
++ 			dp->slave = NULL;
+++			dsa_slave_destroy(slave);
++ 		}
++ 		break;
++ 	}
++@@ -1130,17 +1133,17 @@ static int dsa_tree_setup(struct dsa_swi
++ 	if (err)
++ 		goto teardown_cpu_ports;
++ 
++-	err = dsa_tree_setup_ports(dst);
+++	err = dsa_tree_setup_master(dst);
++ 	if (err)
++ 		goto teardown_switches;
++ 
++-	err = dsa_tree_setup_master(dst);
+++	err = dsa_tree_setup_ports(dst);
++ 	if (err)
++-		goto teardown_ports;
+++		goto teardown_master;
++ 
++ 	err = dsa_tree_setup_lags(dst);
++ 	if (err)
++-		goto teardown_master;
+++		goto teardown_ports;
++ 
++ 	dst->setup = true;
++ 
++@@ -1148,10 +1151,10 @@ static int dsa_tree_setup(struct dsa_swi
++ 
++ 	return 0;
++ 
++-teardown_master:
++-	dsa_tree_teardown_master(dst);
++ teardown_ports:
++ 	dsa_tree_teardown_ports(dst);
+++teardown_master:
+++	dsa_tree_teardown_master(dst);
++ teardown_switches:
++ 	dsa_tree_teardown_switches(dst);
++ teardown_cpu_ports:
++@@ -1169,10 +1172,10 @@ static void dsa_tree_teardown(struct dsa
++ 
++ 	dsa_tree_teardown_lags(dst);
++ 
++-	dsa_tree_teardown_master(dst);
++-
++ 	dsa_tree_teardown_ports(dst);
++ 
+++	dsa_tree_teardown_master(dst);
+++
++ 	dsa_tree_teardown_switches(dst);
++ 
++ 	dsa_tree_teardown_cpu_ports(dst);
+diff --git a/target/linux/generic/backport-5.15/766-v5.18-01-net-dsa-provide-switch-operations-for-tracking-the-m.patch b/target/linux/generic/backport-5.15/766-v5.18-01-net-dsa-provide-switch-operations-for-tracking-the-m.patch
+new file mode 100644
+index 0000000000..bffdcb2881
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/766-v5.18-01-net-dsa-provide-switch-operations-for-tracking-the-m.patch
+@@ -0,0 +1,254 @@
++From 295ab96f478d0fa56393e85406f19a867e26ce22 Mon Sep 17 00:00:00 2001
++From: Vladimir Oltean <vladimir.oltean@nxp.com>
++Date: Wed, 2 Feb 2022 01:03:20 +0100
++Subject: [PATCH 01/16] net: dsa: provide switch operations for tracking the
++ master state
++
++Certain drivers may need to send management traffic to the switch for
++things like register access, FDB dump, etc, to accelerate what their
++slow bus (SPI, I2C, MDIO) can already do.
++
++Ethernet is faster (especially in bulk transactions) but is also more
++unreliable, since the user may decide to bring the DSA master down (or
++not bring it up), therefore severing the link between the host and the
++attached switch.
++
++Drivers needing Ethernet-based register access already should have
++fallback logic to the slow bus if the Ethernet method fails, but that
++fallback may be based on a timeout, and the I/O to the switch may slow
++down to a halt if the master is down, because every Ethernet packet will
++have to time out. The driver also doesn't have the option to turn off
++Ethernet-based I/O momentarily, because it wouldn't know when to turn it
++back on.
++
++Which is where this change comes in. By tracking NETDEV_CHANGE,
++NETDEV_UP and NETDEV_GOING_DOWN events on the DSA master, we should know
++the exact interval of time during which this interface is reliably
++available for traffic. Provide this information to switches so they can
++use it as they wish.
++
++An helper is added dsa_port_master_is_operational() to check if a master
++port is operational.
++
++Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ include/net/dsa.h  | 17 +++++++++++++++++
++ net/dsa/dsa2.c     | 46 ++++++++++++++++++++++++++++++++++++++++++++++
++ net/dsa/dsa_priv.h | 13 +++++++++++++
++ net/dsa/slave.c    | 32 ++++++++++++++++++++++++++++++++
++ net/dsa/switch.c   | 15 +++++++++++++++
++ 5 files changed, 123 insertions(+)
++
++--- a/include/net/dsa.h
+++++ b/include/net/dsa.h
++@@ -291,6 +291,10 @@ struct dsa_port {
++ 	struct list_head	mdbs;
++ 
++ 	bool setup;
+++	/* Master state bits, valid only on CPU ports */
+++	u8			master_admin_up:1;
+++	u8			master_oper_up:1;
+++
++ };
++ 
++ /* TODO: ideally DSA ports would have a single dp->link_dp member,
++@@ -456,6 +460,12 @@ static inline bool dsa_port_is_unused(st
++ 	return dp->type == DSA_PORT_TYPE_UNUSED;
++ }
++ 
+++static inline bool dsa_port_master_is_operational(struct dsa_port *dp)
+++{
+++	return dsa_port_is_cpu(dp) && dp->master_admin_up &&
+++	       dp->master_oper_up;
+++}
+++
++ static inline bool dsa_is_unused_port(struct dsa_switch *ds, int p)
++ {
++ 	return dsa_to_port(ds, p)->type == DSA_PORT_TYPE_UNUSED;
++@@ -949,6 +959,13 @@ struct dsa_switch_ops {
++ 	int	(*tag_8021q_vlan_add)(struct dsa_switch *ds, int port, u16 vid,
++ 				      u16 flags);
++ 	int	(*tag_8021q_vlan_del)(struct dsa_switch *ds, int port, u16 vid);
+++
+++	/*
+++	 * DSA master tracking operations
+++	 */
+++	void	(*master_state_change)(struct dsa_switch *ds,
+++				       const struct net_device *master,
+++				       bool operational);
++ };
++ 
++ #define DSA_DEVLINK_PARAM_DRIVER(_id, _name, _type, _cmodes)		\
++--- a/net/dsa/dsa2.c
+++++ b/net/dsa/dsa2.c
++@@ -1275,6 +1275,52 @@ out_unlock:
++ 	return err;
++ }
++ 
+++static void dsa_tree_master_state_change(struct dsa_switch_tree *dst,
+++					 struct net_device *master)
+++{
+++	struct dsa_notifier_master_state_info info;
+++	struct dsa_port *cpu_dp = master->dsa_ptr;
+++
+++	info.master = master;
+++	info.operational = dsa_port_master_is_operational(cpu_dp);
+++
+++	dsa_tree_notify(dst, DSA_NOTIFIER_MASTER_STATE_CHANGE, &info);
+++}
+++
+++void dsa_tree_master_admin_state_change(struct dsa_switch_tree *dst,
+++					struct net_device *master,
+++					bool up)
+++{
+++	struct dsa_port *cpu_dp = master->dsa_ptr;
+++	bool notify = false;
+++
+++	if ((dsa_port_master_is_operational(cpu_dp)) !=
+++	    (up && cpu_dp->master_oper_up))
+++		notify = true;
+++
+++	cpu_dp->master_admin_up = up;
+++
+++	if (notify)
+++		dsa_tree_master_state_change(dst, master);
+++}
+++
+++void dsa_tree_master_oper_state_change(struct dsa_switch_tree *dst,
+++				       struct net_device *master,
+++				       bool up)
+++{
+++	struct dsa_port *cpu_dp = master->dsa_ptr;
+++	bool notify = false;
+++
+++	if ((dsa_port_master_is_operational(cpu_dp)) !=
+++	    (cpu_dp->master_admin_up && up))
+++		notify = true;
+++
+++	cpu_dp->master_oper_up = up;
+++
+++	if (notify)
+++		dsa_tree_master_state_change(dst, master);
+++}
+++
++ static struct dsa_port *dsa_port_touch(struct dsa_switch *ds, int index)
++ {
++ 	struct dsa_switch_tree *dst = ds->dst;
++--- a/net/dsa/dsa_priv.h
+++++ b/net/dsa/dsa_priv.h
++@@ -45,6 +45,7 @@ enum {
++ 	DSA_NOTIFIER_MRP_DEL_RING_ROLE,
++ 	DSA_NOTIFIER_TAG_8021Q_VLAN_ADD,
++ 	DSA_NOTIFIER_TAG_8021Q_VLAN_DEL,
+++	DSA_NOTIFIER_MASTER_STATE_CHANGE,
++ };
++ 
++ /* DSA_NOTIFIER_AGEING_TIME */
++@@ -127,6 +128,12 @@ struct dsa_notifier_tag_8021q_vlan_info
++ 	u16 vid;
++ };
++ 
+++/* DSA_NOTIFIER_MASTER_STATE_CHANGE */
+++struct dsa_notifier_master_state_info {
+++	const struct net_device *master;
+++	bool operational;
+++};
+++
++ struct dsa_switchdev_event_work {
++ 	struct dsa_switch *ds;
++ 	int port;
++@@ -549,6 +556,12 @@ int dsa_tree_change_tag_proto(struct dsa
++ 			      struct net_device *master,
++ 			      const struct dsa_device_ops *tag_ops,
++ 			      const struct dsa_device_ops *old_tag_ops);
+++void dsa_tree_master_admin_state_change(struct dsa_switch_tree *dst,
+++					struct net_device *master,
+++					bool up);
+++void dsa_tree_master_oper_state_change(struct dsa_switch_tree *dst,
+++				       struct net_device *master,
+++				       bool up);
++ int dsa_bridge_num_get(const struct net_device *bridge_dev, int max);
++ void dsa_bridge_num_put(const struct net_device *bridge_dev, int bridge_num);
++ 
++--- a/net/dsa/slave.c
+++++ b/net/dsa/slave.c
++@@ -2311,6 +2311,36 @@ static int dsa_slave_netdevice_event(str
++ 		err = dsa_port_lag_change(dp, info->lower_state_info);
++ 		return notifier_from_errno(err);
++ 	}
+++	case NETDEV_CHANGE:
+++	case NETDEV_UP: {
+++		/* Track state of master port.
+++		 * DSA driver may require the master port (and indirectly
+++		 * the tagger) to be available for some special operation.
+++		 */
+++		if (netdev_uses_dsa(dev)) {
+++			struct dsa_port *cpu_dp = dev->dsa_ptr;
+++			struct dsa_switch_tree *dst = cpu_dp->ds->dst;
+++
+++			/* Track when the master port is UP */
+++			dsa_tree_master_oper_state_change(dst, dev,
+++							  netif_oper_up(dev));
+++
+++			/* Track when the master port is ready and can accept
+++			 * packet.
+++			 * NETDEV_UP event is not enough to flag a port as ready.
+++			 * We also have to wait for linkwatch_do_dev to dev_activate
+++			 * and emit a NETDEV_CHANGE event.
+++			 * We check if a master port is ready by checking if the dev
+++			 * have a qdisc assigned and is not noop.
+++			 */
+++			dsa_tree_master_admin_state_change(dst, dev,
+++							   !qdisc_tx_is_noop(dev));
+++
+++			return NOTIFY_OK;
+++		}
+++
+++		return NOTIFY_DONE;
+++	}
++ 	case NETDEV_GOING_DOWN: {
++ 		struct dsa_port *dp, *cpu_dp;
++ 		struct dsa_switch_tree *dst;
++@@ -2322,6 +2352,8 @@ static int dsa_slave_netdevice_event(str
++ 		cpu_dp = dev->dsa_ptr;
++ 		dst = cpu_dp->ds->dst;
++ 
+++		dsa_tree_master_admin_state_change(dst, dev, false);
+++
++ 		list_for_each_entry(dp, &dst->ports, list) {
++ 			if (!dsa_is_user_port(dp->ds, dp->index))
++ 				continue;
++--- a/net/dsa/switch.c
+++++ b/net/dsa/switch.c
++@@ -722,6 +722,18 @@ dsa_switch_mrp_del_ring_role(struct dsa_
++ 	return 0;
++ }
++ 
+++static int
+++dsa_switch_master_state_change(struct dsa_switch *ds,
+++			       struct dsa_notifier_master_state_info *info)
+++{
+++	if (!ds->ops->master_state_change)
+++		return 0;
+++
+++	ds->ops->master_state_change(ds, info->master, info->operational);
+++
+++	return 0;
+++}
+++
++ static int dsa_switch_event(struct notifier_block *nb,
++ 			    unsigned long event, void *info)
++ {
++@@ -813,6 +825,9 @@ static int dsa_switch_event(struct notif
++ 	case DSA_NOTIFIER_TAG_8021Q_VLAN_DEL:
++ 		err = dsa_switch_tag_8021q_vlan_del(ds, info);
++ 		break;
+++	case DSA_NOTIFIER_MASTER_STATE_CHANGE:
+++		err = dsa_switch_master_state_change(ds, info);
+++		break;
++ 	default:
++ 		err = -EOPNOTSUPP;
++ 		break;
+diff --git a/target/linux/generic/backport-5.15/766-v5.18-02-net-dsa-replay-master-state-events-in-dsa_tree_-setu.patch b/target/linux/generic/backport-5.15/766-v5.18-02-net-dsa-replay-master-state-events-in-dsa_tree_-setu.patch
+new file mode 100644
+index 0000000000..6478d580c0
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/766-v5.18-02-net-dsa-replay-master-state-events-in-dsa_tree_-setu.patch
+@@ -0,0 +1,89 @@
++From e83d56537859849f2223b90749e554831b1f3c27 Mon Sep 17 00:00:00 2001
++From: Vladimir Oltean <vladimir.oltean@nxp.com>
++Date: Wed, 2 Feb 2022 01:03:21 +0100
++Subject: [PATCH 02/16] net: dsa: replay master state events in
++ dsa_tree_{setup,teardown}_master
++
++In order for switch driver to be able to make simple and reliable use of
++the master tracking operations, they must also be notified of the
++initial state of the DSA master, not just of the changes. This is
++because they might enable certain features only during the time when
++they know that the DSA master is up and running.
++
++Therefore, this change explicitly checks the state of the DSA master
++under the same rtnl_mutex as we were holding during the
++dsa_master_setup() and dsa_master_teardown() call. The idea being that
++if the DSA master became operational in between the moment in which it
++became a DSA master (dsa_master_setup set dev->dsa_ptr) and the moment
++when we checked for the master being up, there is a chance that we
++would emit a ->master_state_change() call with no actual state change.
++We need to avoid that by serializing the concurrent netdevice event with
++us. If the netdevice event started before, we force it to finish before
++we begin, because we take rtnl_lock before making netdev_uses_dsa()
++return true. So we also handle that early event and do nothing on it.
++Similarly, if the dev_open() attempt is concurrent with us, it will
++attempt to take the rtnl_mutex, but we're holding it. We'll see that
++the master flag IFF_UP isn't set, then when we release the rtnl_mutex
++we'll process the NETDEV_UP notifier.
++
++Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ net/dsa/dsa2.c | 28 ++++++++++++++++++++++++----
++ 1 file changed, 24 insertions(+), 4 deletions(-)
++
++--- a/net/dsa/dsa2.c
+++++ b/net/dsa/dsa2.c
++@@ -15,6 +15,7 @@
++ #include <linux/of.h>
++ #include <linux/of_net.h>
++ #include <net/devlink.h>
+++#include <net/sch_generic.h>
++ 
++ #include "dsa_priv.h"
++ 
++@@ -1060,9 +1061,18 @@ static int dsa_tree_setup_master(struct
++ 
++ 	list_for_each_entry(dp, &dst->ports, list) {
++ 		if (dsa_port_is_cpu(dp)) {
++-			err = dsa_master_setup(dp->master, dp);
+++			struct net_device *master = dp->master;
+++			bool admin_up = (master->flags & IFF_UP) &&
+++					!qdisc_tx_is_noop(master);
+++
+++			err = dsa_master_setup(master, dp);
++ 			if (err)
++ 				return err;
+++
+++			/* Replay master state event */
+++			dsa_tree_master_admin_state_change(dst, master, admin_up);
+++			dsa_tree_master_oper_state_change(dst, master,
+++							  netif_oper_up(master));
++ 		}
++ 	}
++ 
++@@ -1077,9 +1087,19 @@ static void dsa_tree_teardown_master(str
++ 
++ 	rtnl_lock();
++ 
++-	list_for_each_entry(dp, &dst->ports, list)
++-		if (dsa_port_is_cpu(dp))
++-			dsa_master_teardown(dp->master);
+++	list_for_each_entry(dp, &dst->ports, list) {
+++		if (dsa_port_is_cpu(dp)) {
+++			struct net_device *master = dp->master;
+++
+++			/* Synthesizing an "admin down" state is sufficient for
+++			 * the switches to get a notification if the master is
+++			 * currently up and running.
+++			 */
+++			dsa_tree_master_admin_state_change(dst, master, false);
+++
+++			dsa_master_teardown(master);
+++		}
+++	}
++ 
++ 	rtnl_unlock();
++ }
+diff --git a/target/linux/generic/backport-5.15/766-v5.18-03-net-dsa-tag_qca-convert-to-FIELD-macro.patch b/target/linux/generic/backport-5.15/766-v5.18-03-net-dsa-tag_qca-convert-to-FIELD-macro.patch
+new file mode 100644
+index 0000000000..82c94b385b
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/766-v5.18-03-net-dsa-tag_qca-convert-to-FIELD-macro.patch
+@@ -0,0 +1,86 @@
++From 6b0458299297ca4ab6fb295800e29a4e501d50c1 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Wed, 2 Feb 2022 01:03:22 +0100
++Subject: [PATCH 03/16] net: dsa: tag_qca: convert to FIELD macro
++
++Convert driver to FIELD macro to drop redundant define.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ net/dsa/tag_qca.c | 34 +++++++++++++++-------------------
++ 1 file changed, 15 insertions(+), 19 deletions(-)
++
++--- a/net/dsa/tag_qca.c
+++++ b/net/dsa/tag_qca.c
++@@ -4,29 +4,24 @@
++  */
++ 
++ #include <linux/etherdevice.h>
+++#include <linux/bitfield.h>
++ 
++ #include "dsa_priv.h"
++ 
++ #define QCA_HDR_LEN	2
++ #define QCA_HDR_VERSION	0x2
++ 
++-#define QCA_HDR_RECV_VERSION_MASK	GENMASK(15, 14)
++-#define QCA_HDR_RECV_VERSION_S		14
++-#define QCA_HDR_RECV_PRIORITY_MASK	GENMASK(13, 11)
++-#define QCA_HDR_RECV_PRIORITY_S		11
++-#define QCA_HDR_RECV_TYPE_MASK		GENMASK(10, 6)
++-#define QCA_HDR_RECV_TYPE_S		6
+++#define QCA_HDR_RECV_VERSION		GENMASK(15, 14)
+++#define QCA_HDR_RECV_PRIORITY		GENMASK(13, 11)
+++#define QCA_HDR_RECV_TYPE		GENMASK(10, 6)
++ #define QCA_HDR_RECV_FRAME_IS_TAGGED	BIT(3)
++-#define QCA_HDR_RECV_SOURCE_PORT_MASK	GENMASK(2, 0)
+++#define QCA_HDR_RECV_SOURCE_PORT	GENMASK(2, 0)
++ 
++-#define QCA_HDR_XMIT_VERSION_MASK	GENMASK(15, 14)
++-#define QCA_HDR_XMIT_VERSION_S		14
++-#define QCA_HDR_XMIT_PRIORITY_MASK	GENMASK(13, 11)
++-#define QCA_HDR_XMIT_PRIORITY_S		11
++-#define QCA_HDR_XMIT_CONTROL_MASK	GENMASK(10, 8)
++-#define QCA_HDR_XMIT_CONTROL_S		8
+++#define QCA_HDR_XMIT_VERSION		GENMASK(15, 14)
+++#define QCA_HDR_XMIT_PRIORITY		GENMASK(13, 11)
+++#define QCA_HDR_XMIT_CONTROL		GENMASK(10, 8)
++ #define QCA_HDR_XMIT_FROM_CPU		BIT(7)
++-#define QCA_HDR_XMIT_DP_BIT_MASK	GENMASK(6, 0)
+++#define QCA_HDR_XMIT_DP_BIT		GENMASK(6, 0)
++ 
++ static struct sk_buff *qca_tag_xmit(struct sk_buff *skb, struct net_device *dev)
++ {
++@@ -40,8 +35,9 @@ static struct sk_buff *qca_tag_xmit(stru
++ 	phdr = dsa_etype_header_pos_tx(skb);
++ 
++ 	/* Set the version field, and set destination port information */
++-	hdr = QCA_HDR_VERSION << QCA_HDR_XMIT_VERSION_S |
++-		QCA_HDR_XMIT_FROM_CPU | BIT(dp->index);
+++	hdr = FIELD_PREP(QCA_HDR_XMIT_VERSION, QCA_HDR_VERSION);
+++	hdr |= QCA_HDR_XMIT_FROM_CPU;
+++	hdr |= FIELD_PREP(QCA_HDR_XMIT_DP_BIT, BIT(dp->index));
++ 
++ 	*phdr = htons(hdr);
++ 
++@@ -62,7 +58,7 @@ static struct sk_buff *qca_tag_rcv(struc
++ 	hdr = ntohs(*phdr);
++ 
++ 	/* Make sure the version is correct */
++-	ver = (hdr & QCA_HDR_RECV_VERSION_MASK) >> QCA_HDR_RECV_VERSION_S;
+++	ver = FIELD_GET(QCA_HDR_RECV_VERSION, hdr);
++ 	if (unlikely(ver != QCA_HDR_VERSION))
++ 		return NULL;
++ 
++@@ -71,7 +67,7 @@ static struct sk_buff *qca_tag_rcv(struc
++ 	dsa_strip_etype_header(skb, QCA_HDR_LEN);
++ 
++ 	/* Get source port information */
++-	port = (hdr & QCA_HDR_RECV_SOURCE_PORT_MASK);
+++	port = FIELD_GET(QCA_HDR_RECV_SOURCE_PORT, hdr);
++ 
++ 	skb->dev = dsa_master_find_slave(dev, 0, port);
++ 	if (!skb->dev)
+diff --git a/target/linux/generic/backport-5.15/766-v5.18-04-net-dsa-tag_qca-move-define-to-include-linux-dsa.patch b/target/linux/generic/backport-5.15/766-v5.18-04-net-dsa-tag_qca-move-define-to-include-linux-dsa.patch
+new file mode 100644
+index 0000000000..c1e74ceeeb
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/766-v5.18-04-net-dsa-tag_qca-move-define-to-include-linux-dsa.patch
+@@ -0,0 +1,71 @@
++From 3ec762fb13c7e7273800b94c80db1c2cc37590d1 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Wed, 2 Feb 2022 01:03:23 +0100
++Subject: [PATCH 04/16] net: dsa: tag_qca: move define to include linux/dsa
++
++Move tag_qca define to include dir linux/dsa as the qca8k require access
++to the tagger define to support in-band mdio read/write using ethernet
++packet.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ include/linux/dsa/tag_qca.h | 21 +++++++++++++++++++++
++ net/dsa/tag_qca.c           | 16 +---------------
++ 2 files changed, 22 insertions(+), 15 deletions(-)
++ create mode 100644 include/linux/dsa/tag_qca.h
++
++--- /dev/null
+++++ b/include/linux/dsa/tag_qca.h
++@@ -0,0 +1,21 @@
+++/* SPDX-License-Identifier: GPL-2.0 */
+++
+++#ifndef __TAG_QCA_H
+++#define __TAG_QCA_H
+++
+++#define QCA_HDR_LEN	2
+++#define QCA_HDR_VERSION	0x2
+++
+++#define QCA_HDR_RECV_VERSION		GENMASK(15, 14)
+++#define QCA_HDR_RECV_PRIORITY		GENMASK(13, 11)
+++#define QCA_HDR_RECV_TYPE		GENMASK(10, 6)
+++#define QCA_HDR_RECV_FRAME_IS_TAGGED	BIT(3)
+++#define QCA_HDR_RECV_SOURCE_PORT	GENMASK(2, 0)
+++
+++#define QCA_HDR_XMIT_VERSION		GENMASK(15, 14)
+++#define QCA_HDR_XMIT_PRIORITY		GENMASK(13, 11)
+++#define QCA_HDR_XMIT_CONTROL		GENMASK(10, 8)
+++#define QCA_HDR_XMIT_FROM_CPU		BIT(7)
+++#define QCA_HDR_XMIT_DP_BIT		GENMASK(6, 0)
+++
+++#endif /* __TAG_QCA_H */
++--- a/net/dsa/tag_qca.c
+++++ b/net/dsa/tag_qca.c
++@@ -5,24 +5,10 @@
++ 
++ #include <linux/etherdevice.h>
++ #include <linux/bitfield.h>
+++#include <linux/dsa/tag_qca.h>
++ 
++ #include "dsa_priv.h"
++ 
++-#define QCA_HDR_LEN	2
++-#define QCA_HDR_VERSION	0x2
++-
++-#define QCA_HDR_RECV_VERSION		GENMASK(15, 14)
++-#define QCA_HDR_RECV_PRIORITY		GENMASK(13, 11)
++-#define QCA_HDR_RECV_TYPE		GENMASK(10, 6)
++-#define QCA_HDR_RECV_FRAME_IS_TAGGED	BIT(3)
++-#define QCA_HDR_RECV_SOURCE_PORT	GENMASK(2, 0)
++-
++-#define QCA_HDR_XMIT_VERSION		GENMASK(15, 14)
++-#define QCA_HDR_XMIT_PRIORITY		GENMASK(13, 11)
++-#define QCA_HDR_XMIT_CONTROL		GENMASK(10, 8)
++-#define QCA_HDR_XMIT_FROM_CPU		BIT(7)
++-#define QCA_HDR_XMIT_DP_BIT		GENMASK(6, 0)
++-
++ static struct sk_buff *qca_tag_xmit(struct sk_buff *skb, struct net_device *dev)
++ {
++ 	struct dsa_port *dp = dsa_slave_to_port(dev);
+diff --git a/target/linux/generic/backport-5.15/766-v5.18-05-net-dsa-tag_qca-enable-promisc_on_master-flag.patch b/target/linux/generic/backport-5.15/766-v5.18-05-net-dsa-tag_qca-enable-promisc_on_master-flag.patch
+new file mode 100644
+index 0000000000..9394a0dabb
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/766-v5.18-05-net-dsa-tag_qca-enable-promisc_on_master-flag.patch
+@@ -0,0 +1,27 @@
++From 101c04c3463b87061e6a3d4f72c1bc57670685a6 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Wed, 2 Feb 2022 01:03:24 +0100
++Subject: [PATCH 05/16] net: dsa: tag_qca: enable promisc_on_master flag
++
++Ethernet MDIO packets are non-standard and DSA master expects the first
++6 octets to be the MAC DA. To address these kind of packet, enable
++promisc_on_master flag for the tagger.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ net/dsa/tag_qca.c | 1 +
++ 1 file changed, 1 insertion(+)
++
++--- a/net/dsa/tag_qca.c
+++++ b/net/dsa/tag_qca.c
++@@ -68,6 +68,7 @@ static const struct dsa_device_ops qca_n
++ 	.xmit	= qca_tag_xmit,
++ 	.rcv	= qca_tag_rcv,
++ 	.needed_headroom = QCA_HDR_LEN,
+++	.promisc_on_master = true,
++ };
++ 
++ MODULE_LICENSE("GPL");
+diff --git a/target/linux/generic/backport-5.15/766-v5.18-06-net-dsa-tag_qca-add-define-for-handling-mgmt-Etherne.patch b/target/linux/generic/backport-5.15/766-v5.18-06-net-dsa-tag_qca-add-define-for-handling-mgmt-Etherne.patch
+new file mode 100644
+index 0000000000..459454e03b
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/766-v5.18-06-net-dsa-tag_qca-add-define-for-handling-mgmt-Etherne.patch
+@@ -0,0 +1,110 @@
++From c2ee8181fddb293d296477f60b3eb4fa3ce4e1a6 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Wed, 2 Feb 2022 01:03:25 +0100
++Subject: [PATCH 06/16] net: dsa: tag_qca: add define for handling mgmt
++ Ethernet packet
++
++Add all the required define to prepare support for mgmt read/write in
++Ethernet packet. Any packet of this type has to be dropped as the only
++use of these special packet is receive ack for an mgmt write request or
++receive data for an mgmt read request.
++A struct is used that emulates the Ethernet header but is used for a
++different purpose.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ include/linux/dsa/tag_qca.h | 44 +++++++++++++++++++++++++++++++++++++
++ net/dsa/tag_qca.c           | 15 ++++++++++---
++ 2 files changed, 56 insertions(+), 3 deletions(-)
++
++--- a/include/linux/dsa/tag_qca.h
+++++ b/include/linux/dsa/tag_qca.h
++@@ -12,10 +12,54 @@
++ #define QCA_HDR_RECV_FRAME_IS_TAGGED	BIT(3)
++ #define QCA_HDR_RECV_SOURCE_PORT	GENMASK(2, 0)
++ 
+++/* Packet type for recv */
+++#define QCA_HDR_RECV_TYPE_NORMAL	0x0
+++#define QCA_HDR_RECV_TYPE_MIB		0x1
+++#define QCA_HDR_RECV_TYPE_RW_REG_ACK	0x2
+++
++ #define QCA_HDR_XMIT_VERSION		GENMASK(15, 14)
++ #define QCA_HDR_XMIT_PRIORITY		GENMASK(13, 11)
++ #define QCA_HDR_XMIT_CONTROL		GENMASK(10, 8)
++ #define QCA_HDR_XMIT_FROM_CPU		BIT(7)
++ #define QCA_HDR_XMIT_DP_BIT		GENMASK(6, 0)
++ 
+++/* Packet type for xmit */
+++#define QCA_HDR_XMIT_TYPE_NORMAL	0x0
+++#define QCA_HDR_XMIT_TYPE_RW_REG	0x1
+++
+++/* Check code for a valid mgmt packet. Switch will ignore the packet
+++ * with this wrong.
+++ */
+++#define QCA_HDR_MGMT_CHECK_CODE_VAL	0x5
+++
+++/* Specific define for in-band MDIO read/write with Ethernet packet */
+++#define QCA_HDR_MGMT_SEQ_LEN		4 /* 4 byte for the seq */
+++#define QCA_HDR_MGMT_COMMAND_LEN	4 /* 4 byte for the command */
+++#define QCA_HDR_MGMT_DATA1_LEN		4 /* First 4 byte for the mdio data */
+++#define QCA_HDR_MGMT_HEADER_LEN		(QCA_HDR_MGMT_SEQ_LEN + \
+++					QCA_HDR_MGMT_COMMAND_LEN + \
+++					QCA_HDR_MGMT_DATA1_LEN)
+++
+++#define QCA_HDR_MGMT_DATA2_LEN		12 /* Other 12 byte for the mdio data */
+++#define QCA_HDR_MGMT_PADDING_LEN	34 /* Padding to reach the min Ethernet packet */
+++
+++#define QCA_HDR_MGMT_PKT_LEN		(QCA_HDR_MGMT_HEADER_LEN + \
+++					QCA_HDR_LEN + \
+++					QCA_HDR_MGMT_DATA2_LEN + \
+++					QCA_HDR_MGMT_PADDING_LEN)
+++
+++#define QCA_HDR_MGMT_SEQ_NUM		GENMASK(31, 0)  /* 63, 32 */
+++#define QCA_HDR_MGMT_CHECK_CODE		GENMASK(31, 29) /* 31, 29 */
+++#define QCA_HDR_MGMT_CMD		BIT(28)		/* 28 */
+++#define QCA_HDR_MGMT_LENGTH		GENMASK(23, 20) /* 23, 20 */
+++#define QCA_HDR_MGMT_ADDR		GENMASK(18, 0)  /* 18, 0 */
+++
+++/* Special struct emulating a Ethernet header */
+++struct qca_mgmt_ethhdr {
+++	u32 command;		/* command bit 31:0 */
+++	u32 seq;		/* seq 63:32 */
+++	u32 mdio_data;		/* first 4byte mdio */
+++	__be16 hdr;		/* qca hdr */
+++} __packed;
+++
++ #endif /* __TAG_QCA_H */
++--- a/net/dsa/tag_qca.c
+++++ b/net/dsa/tag_qca.c
++@@ -32,10 +32,12 @@ static struct sk_buff *qca_tag_xmit(stru
++ 
++ static struct sk_buff *qca_tag_rcv(struct sk_buff *skb, struct net_device *dev)
++ {
++-	u8 ver;
++-	u16  hdr;
++-	int port;
+++	u8 ver, pk_type;
++ 	__be16 *phdr;
+++	int port;
+++	u16 hdr;
+++
+++	BUILD_BUG_ON(sizeof(struct qca_mgmt_ethhdr) != QCA_HDR_MGMT_HEADER_LEN + QCA_HDR_LEN);
++ 
++ 	if (unlikely(!pskb_may_pull(skb, QCA_HDR_LEN)))
++ 		return NULL;
++@@ -48,6 +50,13 @@ static struct sk_buff *qca_tag_rcv(struc
++ 	if (unlikely(ver != QCA_HDR_VERSION))
++ 		return NULL;
++ 
+++	/* Get pk type */
+++	pk_type = FIELD_GET(QCA_HDR_RECV_TYPE, hdr);
+++
+++	/* Ethernet MDIO read/write packet */
+++	if (pk_type == QCA_HDR_RECV_TYPE_RW_REG_ACK)
+++		return NULL;
+++
++ 	/* Remove QCA tag and recalculate checksum */
++ 	skb_pull_rcsum(skb, QCA_HDR_LEN);
++ 	dsa_strip_etype_header(skb, QCA_HDR_LEN);
+diff --git a/target/linux/generic/backport-5.15/766-v5.18-07-net-dsa-tag_qca-add-define-for-handling-MIB-packet.patch b/target/linux/generic/backport-5.15/766-v5.18-07-net-dsa-tag_qca-add-define-for-handling-MIB-packet.patch
+new file mode 100644
+index 0000000000..7e5dc65730
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/766-v5.18-07-net-dsa-tag_qca-add-define-for-handling-MIB-packet.patch
+@@ -0,0 +1,45 @@
++From 18be654a4345f7d937b4bfbad74bea8093e3a93c Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Wed, 2 Feb 2022 01:03:26 +0100
++Subject: [PATCH 07/16] net: dsa: tag_qca: add define for handling MIB packet
++
++Add struct to correctly parse a mib Ethernet packet.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ include/linux/dsa/tag_qca.h | 10 ++++++++++
++ net/dsa/tag_qca.c           |  4 ++++
++ 2 files changed, 14 insertions(+)
++
++--- a/include/linux/dsa/tag_qca.h
+++++ b/include/linux/dsa/tag_qca.h
++@@ -62,4 +62,14 @@ struct qca_mgmt_ethhdr {
++ 	__be16 hdr;		/* qca hdr */
++ } __packed;
++ 
+++enum mdio_cmd {
+++	MDIO_WRITE = 0x0,
+++	MDIO_READ
+++};
+++
+++struct mib_ethhdr {
+++	u32 data[3];		/* first 3 mib counter */
+++	__be16 hdr;		/* qca hdr */
+++} __packed;
+++
++ #endif /* __TAG_QCA_H */
++--- a/net/dsa/tag_qca.c
+++++ b/net/dsa/tag_qca.c
++@@ -57,6 +57,10 @@ static struct sk_buff *qca_tag_rcv(struc
++ 	if (pk_type == QCA_HDR_RECV_TYPE_RW_REG_ACK)
++ 		return NULL;
++ 
+++	/* Ethernet MIB counter packet */
+++	if (pk_type == QCA_HDR_RECV_TYPE_MIB)
+++		return NULL;
+++
++ 	/* Remove QCA tag and recalculate checksum */
++ 	skb_pull_rcsum(skb, QCA_HDR_LEN);
++ 	dsa_strip_etype_header(skb, QCA_HDR_LEN);
+diff --git a/target/linux/generic/backport-5.15/766-v5.18-08-net-dsa-tag_qca-add-support-for-handling-mgmt-and-MI.patch b/target/linux/generic/backport-5.15/766-v5.18-08-net-dsa-tag_qca-add-support-for-handling-mgmt-and-MI.patch
+new file mode 100644
+index 0000000000..ad25da30e6
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/766-v5.18-08-net-dsa-tag_qca-add-support-for-handling-mgmt-and-MI.patch
+@@ -0,0 +1,116 @@
++From 31eb6b4386ad91930417e3f5c8157a4b5e31cbd5 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Wed, 2 Feb 2022 01:03:27 +0100
++Subject: [PATCH 08/16] net: dsa: tag_qca: add support for handling mgmt and
++ MIB Ethernet packet
++
++Add connect/disconnect helper to assign private struct to the DSA switch.
++Add support for Ethernet mgmt and MIB if the DSA driver provide an handler
++to correctly parse and elaborate the data.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ include/linux/dsa/tag_qca.h |  7 +++++++
++ net/dsa/tag_qca.c           | 39 ++++++++++++++++++++++++++++++++++---
++ 2 files changed, 43 insertions(+), 3 deletions(-)
++
++--- a/include/linux/dsa/tag_qca.h
+++++ b/include/linux/dsa/tag_qca.h
++@@ -72,4 +72,11 @@ struct mib_ethhdr {
++ 	__be16 hdr;		/* qca hdr */
++ } __packed;
++ 
+++struct qca_tagger_data {
+++	void (*rw_reg_ack_handler)(struct dsa_switch *ds,
+++				   struct sk_buff *skb);
+++	void (*mib_autocast_handler)(struct dsa_switch *ds,
+++				     struct sk_buff *skb);
+++};
+++
++ #endif /* __TAG_QCA_H */
++--- a/net/dsa/tag_qca.c
+++++ b/net/dsa/tag_qca.c
++@@ -5,6 +5,7 @@
++ 
++ #include <linux/etherdevice.h>
++ #include <linux/bitfield.h>
+++#include <net/dsa.h>
++ #include <linux/dsa/tag_qca.h>
++ 
++ #include "dsa_priv.h"
++@@ -32,6 +33,9 @@ static struct sk_buff *qca_tag_xmit(stru
++ 
++ static struct sk_buff *qca_tag_rcv(struct sk_buff *skb, struct net_device *dev)
++ {
+++	struct qca_tagger_data *tagger_data;
+++	struct dsa_port *dp = dev->dsa_ptr;
+++	struct dsa_switch *ds = dp->ds;
++ 	u8 ver, pk_type;
++ 	__be16 *phdr;
++ 	int port;
++@@ -39,6 +43,8 @@ static struct sk_buff *qca_tag_rcv(struc
++ 
++ 	BUILD_BUG_ON(sizeof(struct qca_mgmt_ethhdr) != QCA_HDR_MGMT_HEADER_LEN + QCA_HDR_LEN);
++ 
+++	tagger_data = ds->tagger_data;
+++
++ 	if (unlikely(!pskb_may_pull(skb, QCA_HDR_LEN)))
++ 		return NULL;
++ 
++@@ -53,13 +59,19 @@ static struct sk_buff *qca_tag_rcv(struc
++ 	/* Get pk type */
++ 	pk_type = FIELD_GET(QCA_HDR_RECV_TYPE, hdr);
++ 
++-	/* Ethernet MDIO read/write packet */
++-	if (pk_type == QCA_HDR_RECV_TYPE_RW_REG_ACK)
+++	/* Ethernet mgmt read/write packet */
+++	if (pk_type == QCA_HDR_RECV_TYPE_RW_REG_ACK) {
+++		if (likely(tagger_data->rw_reg_ack_handler))
+++			tagger_data->rw_reg_ack_handler(ds, skb);
++ 		return NULL;
+++	}
++ 
++ 	/* Ethernet MIB counter packet */
++-	if (pk_type == QCA_HDR_RECV_TYPE_MIB)
+++	if (pk_type == QCA_HDR_RECV_TYPE_MIB) {
+++		if (likely(tagger_data->mib_autocast_handler))
+++			tagger_data->mib_autocast_handler(ds, skb);
++ 		return NULL;
+++	}
++ 
++ 	/* Remove QCA tag and recalculate checksum */
++ 	skb_pull_rcsum(skb, QCA_HDR_LEN);
++@@ -75,9 +87,30 @@ static struct sk_buff *qca_tag_rcv(struc
++ 	return skb;
++ }
++ 
+++static int qca_tag_connect(struct dsa_switch *ds)
+++{
+++	struct qca_tagger_data *tagger_data;
+++
+++	tagger_data = kzalloc(sizeof(*tagger_data), GFP_KERNEL);
+++	if (!tagger_data)
+++		return -ENOMEM;
+++
+++	ds->tagger_data = tagger_data;
+++
+++	return 0;
+++}
+++
+++static void qca_tag_disconnect(struct dsa_switch *ds)
+++{
+++	kfree(ds->tagger_data);
+++	ds->tagger_data = NULL;
+++}
+++
++ static const struct dsa_device_ops qca_netdev_ops = {
++ 	.name	= "qca",
++ 	.proto	= DSA_TAG_PROTO_QCA,
+++	.connect = qca_tag_connect,
+++	.disconnect = qca_tag_disconnect,
++ 	.xmit	= qca_tag_xmit,
++ 	.rcv	= qca_tag_rcv,
++ 	.needed_headroom = QCA_HDR_LEN,
+diff --git a/target/linux/generic/backport-5.15/766-v5.18-09-net-dsa-qca8k-add-tracking-state-of-master-port.patch b/target/linux/generic/backport-5.15/766-v5.18-09-net-dsa-qca8k-add-tracking-state-of-master-port.patch
+new file mode 100644
+index 0000000000..eb21cc3912
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/766-v5.18-09-net-dsa-qca8k-add-tracking-state-of-master-port.patch
+@@ -0,0 +1,67 @@
++From cddbec19466a1dfb4d45ddd507d9f09f991d54ae Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Wed, 2 Feb 2022 01:03:28 +0100
++Subject: [PATCH 09/16] net: dsa: qca8k: add tracking state of master port
++
++MDIO/MIB Ethernet require the master port and the tagger availabale to
++correctly work. Use the new api master_state_change to track when master
++is operational or not and set a bool in qca8k_priv.
++We cache the first cached master available and we check if other cpu
++port are operational when the cached one goes down.
++This cached master will later be used by mdio read/write and mib request to
++correctly use the working function.
++
++qca8k implementation for MDIO/MIB Ethernet is bad. CPU port0 is the only
++one that answers with the ack packet or sends MIB Ethernet packets. For
++this reason the master_state_change ignore CPU port6 and only checks
++CPU port0 if it's operational and enables this mode.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 15 +++++++++++++++
++ drivers/net/dsa/qca8k.h |  1 +
++ 2 files changed, 16 insertions(+)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -2401,6 +2401,20 @@ qca8k_port_lag_leave(struct dsa_switch *
++ 	return qca8k_lag_refresh_portmap(ds, port, lag, true);
++ }
++ 
+++static void
+++qca8k_master_change(struct dsa_switch *ds, const struct net_device *master,
+++		    bool operational)
+++{
+++	struct dsa_port *dp = master->dsa_ptr;
+++	struct qca8k_priv *priv = ds->priv;
+++
+++	/* Ethernet MIB/MDIO is only supported for CPU port 0 */
+++	if (dp->index != 0)
+++		return;
+++
+++	priv->mgmt_master = operational ? (struct net_device *)master : NULL;
+++}
+++
++ static const struct dsa_switch_ops qca8k_switch_ops = {
++ 	.get_tag_protocol	= qca8k_get_tag_protocol,
++ 	.setup			= qca8k_setup,
++@@ -2436,6 +2450,7 @@ static const struct dsa_switch_ops qca8k
++ 	.get_phy_flags		= qca8k_get_phy_flags,
++ 	.port_lag_join		= qca8k_port_lag_join,
++ 	.port_lag_leave		= qca8k_port_lag_leave,
+++	.master_state_change	= qca8k_master_change,
++ };
++ 
++ static int qca8k_read_switch_id(struct qca8k_priv *priv)
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -353,6 +353,7 @@ struct qca8k_priv {
++ 	struct dsa_switch_ops ops;
++ 	struct gpio_desc *reset_gpio;
++ 	unsigned int port_mtu[QCA8K_NUM_PORTS];
+++	struct net_device *mgmt_master; /* Track if mdio/mib Ethernet is available */
++ };
++ 
++ struct qca8k_mib_desc {
+diff --git a/target/linux/generic/backport-5.15/766-v5.18-10-net-dsa-qca8k-add-support-for-mgmt-read-write-in-Eth.patch b/target/linux/generic/backport-5.15/766-v5.18-10-net-dsa-qca8k-add-support-for-mgmt-read-write-in-Eth.patch
+new file mode 100644
+index 0000000000..07c5ba4621
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/766-v5.18-10-net-dsa-qca8k-add-support-for-mgmt-read-write-in-Eth.patch
+@@ -0,0 +1,363 @@
++From 5950c7c0a68c915b336c70f79388626e2d576ab7 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Wed, 2 Feb 2022 01:03:29 +0100
++Subject: [PATCH 10/16] net: dsa: qca8k: add support for mgmt read/write in
++ Ethernet packet
++
++Add qca8k side support for mgmt read/write in Ethernet packet.
++qca8k supports some specially crafted Ethernet packet that can be used
++for mgmt read/write instead of the legacy method uart/internal mdio.
++This add support for the qca8k side to craft the packet and enqueue it.
++Each port and the qca8k_priv have a special struct to put data in it.
++The completion API is used to wait for the packet to be received back
++with the requested data.
++
++The various steps are:
++1. Craft the special packet with the qca hdr set to mgmt read/write
++   mode.
++2. Set the lock in the dedicated mgmt struct.
++3. Increment the seq number and set it in the mgmt pkt
++4. Reinit the completion.
++5. Enqueue the packet.
++6. Wait the packet to be received.
++7. Use the data set by the tagger to complete the mdio operation.
++
++If the completion timeouts or the ack value is not true, the legacy
++mdio way is used.
++
++It has to be considered that in the initial setup mdio is still used and
++mdio is still used until DSA is ready to accept and tag packet.
++
++tag_proto_connect() is used to fill the required handler for the tagger
++to correctly parse and elaborate the special Ethernet mdio packet.
++
++Locking is added to qca8k_master_change() to make sure no mgmt Ethernet
++are in progress.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 225 ++++++++++++++++++++++++++++++++++++++++
++ drivers/net/dsa/qca8k.h |  13 +++
++ 2 files changed, 238 insertions(+)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -20,6 +20,7 @@
++ #include <linux/phylink.h>
++ #include <linux/gpio/consumer.h>
++ #include <linux/etherdevice.h>
+++#include <linux/dsa/tag_qca.h>
++ 
++ #include "qca8k.h"
++ 
++@@ -170,6 +171,194 @@ qca8k_rmw(struct qca8k_priv *priv, u32 r
++ 	return regmap_update_bits(priv->regmap, reg, mask, write_val);
++ }
++ 
+++static void qca8k_rw_reg_ack_handler(struct dsa_switch *ds, struct sk_buff *skb)
+++{
+++	struct qca8k_mgmt_eth_data *mgmt_eth_data;
+++	struct qca8k_priv *priv = ds->priv;
+++	struct qca_mgmt_ethhdr *mgmt_ethhdr;
+++	u8 len, cmd;
+++
+++	mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb_mac_header(skb);
+++	mgmt_eth_data = &priv->mgmt_eth_data;
+++
+++	cmd = FIELD_GET(QCA_HDR_MGMT_CMD, mgmt_ethhdr->command);
+++	len = FIELD_GET(QCA_HDR_MGMT_LENGTH, mgmt_ethhdr->command);
+++
+++	/* Make sure the seq match the requested packet */
+++	if (mgmt_ethhdr->seq == mgmt_eth_data->seq)
+++		mgmt_eth_data->ack = true;
+++
+++	if (cmd == MDIO_READ) {
+++		mgmt_eth_data->data[0] = mgmt_ethhdr->mdio_data;
+++
+++		/* Get the rest of the 12 byte of data */
+++		if (len > QCA_HDR_MGMT_DATA1_LEN)
+++			memcpy(mgmt_eth_data->data + 1, skb->data,
+++			       QCA_HDR_MGMT_DATA2_LEN);
+++	}
+++
+++	complete(&mgmt_eth_data->rw_done);
+++}
+++
+++static struct sk_buff *qca8k_alloc_mdio_header(enum mdio_cmd cmd, u32 reg, u32 *val,
+++					       int priority)
+++{
+++	struct qca_mgmt_ethhdr *mgmt_ethhdr;
+++	struct sk_buff *skb;
+++	u16 hdr;
+++
+++	skb = dev_alloc_skb(QCA_HDR_MGMT_PKT_LEN);
+++	if (!skb)
+++		return NULL;
+++
+++	skb_reset_mac_header(skb);
+++	skb_set_network_header(skb, skb->len);
+++
+++	mgmt_ethhdr = skb_push(skb, QCA_HDR_MGMT_HEADER_LEN + QCA_HDR_LEN);
+++
+++	hdr = FIELD_PREP(QCA_HDR_XMIT_VERSION, QCA_HDR_VERSION);
+++	hdr |= FIELD_PREP(QCA_HDR_XMIT_PRIORITY, priority);
+++	hdr |= QCA_HDR_XMIT_FROM_CPU;
+++	hdr |= FIELD_PREP(QCA_HDR_XMIT_DP_BIT, BIT(0));
+++	hdr |= FIELD_PREP(QCA_HDR_XMIT_CONTROL, QCA_HDR_XMIT_TYPE_RW_REG);
+++
+++	mgmt_ethhdr->command = FIELD_PREP(QCA_HDR_MGMT_ADDR, reg);
+++	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_LENGTH, 4);
+++	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CMD, cmd);
+++	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CHECK_CODE,
+++					   QCA_HDR_MGMT_CHECK_CODE_VAL);
+++
+++	if (cmd == MDIO_WRITE)
+++		mgmt_ethhdr->mdio_data = *val;
+++
+++	mgmt_ethhdr->hdr = htons(hdr);
+++
+++	skb_put_zero(skb, QCA_HDR_MGMT_DATA2_LEN + QCA_HDR_MGMT_PADDING_LEN);
+++
+++	return skb;
+++}
+++
+++static void qca8k_mdio_header_fill_seq_num(struct sk_buff *skb, u32 seq_num)
+++{
+++	struct qca_mgmt_ethhdr *mgmt_ethhdr;
+++
+++	mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb->data;
+++	mgmt_ethhdr->seq = FIELD_PREP(QCA_HDR_MGMT_SEQ_NUM, seq_num);
+++}
+++
+++static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val)
+++{
+++	struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
+++	struct sk_buff *skb;
+++	bool ack;
+++	int ret;
+++
+++	skb = qca8k_alloc_mdio_header(MDIO_READ, reg, NULL,
+++				      QCA8K_ETHERNET_MDIO_PRIORITY);
+++	if (!skb)
+++		return -ENOMEM;
+++
+++	mutex_lock(&mgmt_eth_data->mutex);
+++
+++	/* Check mgmt_master if is operational */
+++	if (!priv->mgmt_master) {
+++		kfree_skb(skb);
+++		mutex_unlock(&mgmt_eth_data->mutex);
+++		return -EINVAL;
+++	}
+++
+++	skb->dev = priv->mgmt_master;
+++
+++	reinit_completion(&mgmt_eth_data->rw_done);
+++
+++	/* Increment seq_num and set it in the mdio pkt */
+++	mgmt_eth_data->seq++;
+++	qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
+++	mgmt_eth_data->ack = false;
+++
+++	dev_queue_xmit(skb);
+++
+++	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
+++					  msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
+++
+++	*val = mgmt_eth_data->data[0];
+++	ack = mgmt_eth_data->ack;
+++
+++	mutex_unlock(&mgmt_eth_data->mutex);
+++
+++	if (ret <= 0)
+++		return -ETIMEDOUT;
+++
+++	if (!ack)
+++		return -EINVAL;
+++
+++	return 0;
+++}
+++
+++static int qca8k_write_eth(struct qca8k_priv *priv, u32 reg, u32 val)
+++{
+++	struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
+++	struct sk_buff *skb;
+++	bool ack;
+++	int ret;
+++
+++	skb = qca8k_alloc_mdio_header(MDIO_WRITE, reg, &val,
+++				      QCA8K_ETHERNET_MDIO_PRIORITY);
+++	if (!skb)
+++		return -ENOMEM;
+++
+++	mutex_lock(&mgmt_eth_data->mutex);
+++
+++	/* Check mgmt_master if is operational */
+++	if (!priv->mgmt_master) {
+++		kfree_skb(skb);
+++		mutex_unlock(&mgmt_eth_data->mutex);
+++		return -EINVAL;
+++	}
+++
+++	skb->dev = priv->mgmt_master;
+++
+++	reinit_completion(&mgmt_eth_data->rw_done);
+++
+++	/* Increment seq_num and set it in the mdio pkt */
+++	mgmt_eth_data->seq++;
+++	qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
+++	mgmt_eth_data->ack = false;
+++
+++	dev_queue_xmit(skb);
+++
+++	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
+++					  msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
+++
+++	ack = mgmt_eth_data->ack;
+++
+++	mutex_unlock(&mgmt_eth_data->mutex);
+++
+++	if (ret <= 0)
+++		return -ETIMEDOUT;
+++
+++	if (!ack)
+++		return -EINVAL;
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_regmap_update_bits_eth(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
+++{
+++	u32 val = 0;
+++	int ret;
+++
+++	ret = qca8k_read_eth(priv, reg, &val);
+++	if (ret)
+++		return ret;
+++
+++	val &= ~mask;
+++	val |= write_val;
+++
+++	return qca8k_write_eth(priv, reg, val);
+++}
+++
++ static int
++ qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
++ {
++@@ -178,6 +367,9 @@ qca8k_regmap_read(void *ctx, uint32_t re
++ 	u16 r1, r2, page;
++ 	int ret;
++ 
+++	if (!qca8k_read_eth(priv, reg, val))
+++		return 0;
+++
++ 	qca8k_split_addr(reg, &r1, &r2, &page);
++ 
++ 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++@@ -201,6 +393,9 @@ qca8k_regmap_write(void *ctx, uint32_t r
++ 	u16 r1, r2, page;
++ 	int ret;
++ 
+++	if (!qca8k_write_eth(priv, reg, val))
+++		return 0;
+++
++ 	qca8k_split_addr(reg, &r1, &r2, &page);
++ 
++ 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++@@ -225,6 +420,9 @@ qca8k_regmap_update_bits(void *ctx, uint
++ 	u32 val;
++ 	int ret;
++ 
+++	if (!qca8k_regmap_update_bits_eth(priv, reg, mask, write_val))
+++		return 0;
+++
++ 	qca8k_split_addr(reg, &r1, &r2, &page);
++ 
++ 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++@@ -2412,7 +2610,30 @@ qca8k_master_change(struct dsa_switch *d
++ 	if (dp->index != 0)
++ 		return;
++ 
+++	mutex_lock(&priv->mgmt_eth_data.mutex);
+++
++ 	priv->mgmt_master = operational ? (struct net_device *)master : NULL;
+++
+++	mutex_unlock(&priv->mgmt_eth_data.mutex);
+++}
+++
+++static int qca8k_connect_tag_protocol(struct dsa_switch *ds,
+++				      enum dsa_tag_protocol proto)
+++{
+++	struct qca_tagger_data *tagger_data;
+++
+++	switch (proto) {
+++	case DSA_TAG_PROTO_QCA:
+++		tagger_data = ds->tagger_data;
+++
+++		tagger_data->rw_reg_ack_handler = qca8k_rw_reg_ack_handler;
+++
+++		break;
+++	default:
+++		return -EOPNOTSUPP;
+++	}
+++
+++	return 0;
++ }
++ 
++ static const struct dsa_switch_ops qca8k_switch_ops = {
++@@ -2451,6 +2672,7 @@ static const struct dsa_switch_ops qca8k
++ 	.port_lag_join		= qca8k_port_lag_join,
++ 	.port_lag_leave		= qca8k_port_lag_leave,
++ 	.master_state_change	= qca8k_master_change,
+++	.connect_tag_protocol	= qca8k_connect_tag_protocol,
++ };
++ 
++ static int qca8k_read_switch_id(struct qca8k_priv *priv)
++@@ -2530,6 +2752,9 @@ qca8k_sw_probe(struct mdio_device *mdiod
++ 	if (!priv->ds)
++ 		return -ENOMEM;
++ 
+++	mutex_init(&priv->mgmt_eth_data.mutex);
+++	init_completion(&priv->mgmt_eth_data.rw_done);
+++
++ 	priv->ds->dev = &mdiodev->dev;
++ 	priv->ds->num_ports = QCA8K_NUM_PORTS;
++ 	priv->ds->priv = priv;
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -11,6 +11,10 @@
++ #include <linux/delay.h>
++ #include <linux/regmap.h>
++ #include <linux/gpio.h>
+++#include <linux/dsa/tag_qca.h>
+++
+++#define QCA8K_ETHERNET_MDIO_PRIORITY			7
+++#define QCA8K_ETHERNET_TIMEOUT				100
++ 
++ #define QCA8K_NUM_PORTS					7
++ #define QCA8K_NUM_CPU_PORTS				2
++@@ -328,6 +332,14 @@ enum {
++ 	QCA8K_CPU_PORT6,
++ };
++ 
+++struct qca8k_mgmt_eth_data {
+++	struct completion rw_done;
+++	struct mutex mutex; /* Enforce one mdio read/write at time */
+++	bool ack;
+++	u32 seq;
+++	u32 data[4];
+++};
+++
++ struct qca8k_ports_config {
++ 	bool sgmii_rx_clk_falling_edge;
++ 	bool sgmii_tx_clk_falling_edge;
++@@ -354,6 +366,7 @@ struct qca8k_priv {
++ 	struct gpio_desc *reset_gpio;
++ 	unsigned int port_mtu[QCA8K_NUM_PORTS];
++ 	struct net_device *mgmt_master; /* Track if mdio/mib Ethernet is available */
+++	struct qca8k_mgmt_eth_data mgmt_eth_data;
++ };
++ 
++ struct qca8k_mib_desc {
+diff --git a/target/linux/generic/backport-5.15/766-v5.18-11-net-dsa-qca8k-add-support-for-mib-autocast-in-Ethern.patch b/target/linux/generic/backport-5.15/766-v5.18-11-net-dsa-qca8k-add-support-for-mib-autocast-in-Ethern.patch
+new file mode 100644
+index 0000000000..0dcf279433
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/766-v5.18-11-net-dsa-qca8k-add-support-for-mib-autocast-in-Ethern.patch
+@@ -0,0 +1,226 @@
++From 5c957c7ca78cce5e4b96866722b0115bd758d945 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Wed, 2 Feb 2022 01:03:30 +0100
++Subject: [PATCH 11/16] net: dsa: qca8k: add support for mib autocast in
++ Ethernet packet
++
++The switch can autocast MIB counter using Ethernet packet.
++Add support for this and provide a handler for the tagger.
++The switch will send packet with MIB counter for each port, the switch
++will use completion API to wait for the correct packet to be received
++and will complete the task only when each packet is received.
++Although the handler will drop all the other packet, we still have to
++consume each MIB packet to complete the request. This is done to prevent
++mixed data with concurrent ethtool request.
++
++connect_tag_protocol() is used to add the handler to the tag_qca tagger,
++master_state_change() use the MIB lock to make sure no MIB Ethernet is
++in progress.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 106 +++++++++++++++++++++++++++++++++++++++-
++ drivers/net/dsa/qca8k.h |  17 ++++++-
++ 2 files changed, 121 insertions(+), 2 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -830,7 +830,10 @@ qca8k_mib_init(struct qca8k_priv *priv)
++ 	int ret;
++ 
++ 	mutex_lock(&priv->reg_mutex);
++-	ret = regmap_set_bits(priv->regmap, QCA8K_REG_MIB, QCA8K_MIB_FLUSH | QCA8K_MIB_BUSY);
+++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB,
+++				 QCA8K_MIB_FUNC | QCA8K_MIB_BUSY,
+++				 FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_FLUSH) |
+++				 QCA8K_MIB_BUSY);
++ 	if (ret)
++ 		goto exit;
++ 
++@@ -1901,6 +1904,97 @@ qca8k_get_strings(struct dsa_switch *ds,
++ 			ETH_GSTRING_LEN);
++ }
++ 
+++static void qca8k_mib_autocast_handler(struct dsa_switch *ds, struct sk_buff *skb)
+++{
+++	const struct qca8k_match_data *match_data;
+++	struct qca8k_mib_eth_data *mib_eth_data;
+++	struct qca8k_priv *priv = ds->priv;
+++	const struct qca8k_mib_desc *mib;
+++	struct mib_ethhdr *mib_ethhdr;
+++	int i, mib_len, offset = 0;
+++	u64 *data;
+++	u8 port;
+++
+++	mib_ethhdr = (struct mib_ethhdr *)skb_mac_header(skb);
+++	mib_eth_data = &priv->mib_eth_data;
+++
+++	/* The switch autocast every port. Ignore other packet and
+++	 * parse only the requested one.
+++	 */
+++	port = FIELD_GET(QCA_HDR_RECV_SOURCE_PORT, ntohs(mib_ethhdr->hdr));
+++	if (port != mib_eth_data->req_port)
+++		goto exit;
+++
+++	match_data = device_get_match_data(priv->dev);
+++	data = mib_eth_data->data;
+++
+++	for (i = 0; i < match_data->mib_count; i++) {
+++		mib = &ar8327_mib[i];
+++
+++		/* First 3 mib are present in the skb head */
+++		if (i < 3) {
+++			data[i] = mib_ethhdr->data[i];
+++			continue;
+++		}
+++
+++		mib_len = sizeof(uint32_t);
+++
+++		/* Some mib are 64 bit wide */
+++		if (mib->size == 2)
+++			mib_len = sizeof(uint64_t);
+++
+++		/* Copy the mib value from packet to the */
+++		memcpy(data + i, skb->data + offset, mib_len);
+++
+++		/* Set the offset for the next mib */
+++		offset += mib_len;
+++	}
+++
+++exit:
+++	/* Complete on receiving all the mib packet */
+++	if (refcount_dec_and_test(&mib_eth_data->port_parsed))
+++		complete(&mib_eth_data->rw_done);
+++}
+++
+++static int
+++qca8k_get_ethtool_stats_eth(struct dsa_switch *ds, int port, u64 *data)
+++{
+++	struct dsa_port *dp = dsa_to_port(ds, port);
+++	struct qca8k_mib_eth_data *mib_eth_data;
+++	struct qca8k_priv *priv = ds->priv;
+++	int ret;
+++
+++	mib_eth_data = &priv->mib_eth_data;
+++
+++	mutex_lock(&mib_eth_data->mutex);
+++
+++	reinit_completion(&mib_eth_data->rw_done);
+++
+++	mib_eth_data->req_port = dp->index;
+++	mib_eth_data->data = data;
+++	refcount_set(&mib_eth_data->port_parsed, QCA8K_NUM_PORTS);
+++
+++	mutex_lock(&priv->reg_mutex);
+++
+++	/* Send mib autocast request */
+++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB,
+++				 QCA8K_MIB_FUNC | QCA8K_MIB_BUSY,
+++				 FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_CAST) |
+++				 QCA8K_MIB_BUSY);
+++
+++	mutex_unlock(&priv->reg_mutex);
+++
+++	if (ret)
+++		goto exit;
+++
+++	ret = wait_for_completion_timeout(&mib_eth_data->rw_done, QCA8K_ETHERNET_TIMEOUT);
+++
+++exit:
+++	mutex_unlock(&mib_eth_data->mutex);
+++
+++	return ret;
+++}
+++
++ static void
++ qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
++ 			uint64_t *data)
++@@ -1912,6 +2006,10 @@ qca8k_get_ethtool_stats(struct dsa_switc
++ 	u32 hi = 0;
++ 	int ret;
++ 
+++	if (priv->mgmt_master &&
+++	    qca8k_get_ethtool_stats_eth(ds, port, data) > 0)
+++		return;
+++
++ 	match_data = of_device_get_match_data(priv->dev);
++ 
++ 	for (i = 0; i < match_data->mib_count; i++) {
++@@ -2611,9 +2709,11 @@ qca8k_master_change(struct dsa_switch *d
++ 		return;
++ 
++ 	mutex_lock(&priv->mgmt_eth_data.mutex);
+++	mutex_lock(&priv->mib_eth_data.mutex);
++ 
++ 	priv->mgmt_master = operational ? (struct net_device *)master : NULL;
++ 
+++	mutex_unlock(&priv->mib_eth_data.mutex);
++ 	mutex_unlock(&priv->mgmt_eth_data.mutex);
++ }
++ 
++@@ -2627,6 +2727,7 @@ static int qca8k_connect_tag_protocol(st
++ 		tagger_data = ds->tagger_data;
++ 
++ 		tagger_data->rw_reg_ack_handler = qca8k_rw_reg_ack_handler;
+++		tagger_data->mib_autocast_handler = qca8k_mib_autocast_handler;
++ 
++ 		break;
++ 	default:
++@@ -2755,6 +2856,9 @@ qca8k_sw_probe(struct mdio_device *mdiod
++ 	mutex_init(&priv->mgmt_eth_data.mutex);
++ 	init_completion(&priv->mgmt_eth_data.rw_done);
++ 
+++	mutex_init(&priv->mib_eth_data.mutex);
+++	init_completion(&priv->mib_eth_data.rw_done);
+++
++ 	priv->ds->dev = &mdiodev->dev;
++ 	priv->ds->num_ports = QCA8K_NUM_PORTS;
++ 	priv->ds->priv = priv;
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -67,7 +67,7 @@
++ #define QCA8K_REG_MODULE_EN				0x030
++ #define   QCA8K_MODULE_EN_MIB				BIT(0)
++ #define QCA8K_REG_MIB					0x034
++-#define   QCA8K_MIB_FLUSH				BIT(24)
+++#define   QCA8K_MIB_FUNC				GENMASK(26, 24)
++ #define   QCA8K_MIB_CPU_KEEP				BIT(20)
++ #define   QCA8K_MIB_BUSY				BIT(17)
++ #define QCA8K_MDIO_MASTER_CTRL				0x3c
++@@ -317,6 +317,12 @@ enum qca8k_vlan_cmd {
++ 	QCA8K_VLAN_READ = 6,
++ };
++ 
+++enum qca8k_mid_cmd {
+++	QCA8K_MIB_FLUSH = 1,
+++	QCA8K_MIB_FLUSH_PORT = 2,
+++	QCA8K_MIB_CAST = 3,
+++};
+++
++ struct ar8xxx_port_status {
++ 	int enabled;
++ };
++@@ -340,6 +346,14 @@ struct qca8k_mgmt_eth_data {
++ 	u32 data[4];
++ };
++ 
+++struct qca8k_mib_eth_data {
+++	struct completion rw_done;
+++	struct mutex mutex; /* Process one command at time */
+++	refcount_t port_parsed; /* Counter to track parsed port */
+++	u8 req_port;
+++	u64 *data; /* pointer to ethtool data */
+++};
+++
++ struct qca8k_ports_config {
++ 	bool sgmii_rx_clk_falling_edge;
++ 	bool sgmii_tx_clk_falling_edge;
++@@ -367,6 +381,7 @@ struct qca8k_priv {
++ 	unsigned int port_mtu[QCA8K_NUM_PORTS];
++ 	struct net_device *mgmt_master; /* Track if mdio/mib Ethernet is available */
++ 	struct qca8k_mgmt_eth_data mgmt_eth_data;
+++	struct qca8k_mib_eth_data mib_eth_data;
++ };
++ 
++ struct qca8k_mib_desc {
+diff --git a/target/linux/generic/backport-5.15/766-v5.18-12-net-dsa-qca8k-add-support-for-phy-read-write-with-mg.patch b/target/linux/generic/backport-5.15/766-v5.18-12-net-dsa-qca8k-add-support-for-phy-read-write-with-mg.patch
+new file mode 100644
+index 0000000000..f5899eb590
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/766-v5.18-12-net-dsa-qca8k-add-support-for-phy-read-write-with-mg.patch
+@@ -0,0 +1,287 @@
++From 2cd5485663847d468dc207b3ff85fb1fab44d97f Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Wed, 2 Feb 2022 01:03:31 +0100
++Subject: [PATCH 12/16] net: dsa: qca8k: add support for phy read/write with
++ mgmt Ethernet
++
++Use mgmt Ethernet also for phy read/write if availabale. Use a different
++seq number to make sure we receive the correct packet.
++On any error, we fallback to the legacy mdio read/write.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 216 ++++++++++++++++++++++++++++++++++++++++
++ drivers/net/dsa/qca8k.h |   1 +
++ 2 files changed, 217 insertions(+)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -867,6 +867,199 @@ qca8k_port_set_status(struct qca8k_priv
++ 		regmap_clear_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
++ }
++ 
+++static int
+++qca8k_phy_eth_busy_wait(struct qca8k_mgmt_eth_data *mgmt_eth_data,
+++			struct sk_buff *read_skb, u32 *val)
+++{
+++	struct sk_buff *skb = skb_copy(read_skb, GFP_KERNEL);
+++	bool ack;
+++	int ret;
+++
+++	reinit_completion(&mgmt_eth_data->rw_done);
+++
+++	/* Increment seq_num and set it in the copy pkt */
+++	mgmt_eth_data->seq++;
+++	qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
+++	mgmt_eth_data->ack = false;
+++
+++	dev_queue_xmit(skb);
+++
+++	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
+++					  QCA8K_ETHERNET_TIMEOUT);
+++
+++	ack = mgmt_eth_data->ack;
+++
+++	if (ret <= 0)
+++		return -ETIMEDOUT;
+++
+++	if (!ack)
+++		return -EINVAL;
+++
+++	*val = mgmt_eth_data->data[0];
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_phy_eth_command(struct qca8k_priv *priv, bool read, int phy,
+++		      int regnum, u16 data)
+++{
+++	struct sk_buff *write_skb, *clear_skb, *read_skb;
+++	struct qca8k_mgmt_eth_data *mgmt_eth_data;
+++	u32 write_val, clear_val = 0, val;
+++	struct net_device *mgmt_master;
+++	int ret, ret1;
+++	bool ack;
+++
+++	if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
+++		return -EINVAL;
+++
+++	mgmt_eth_data = &priv->mgmt_eth_data;
+++
+++	write_val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
+++		    QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
+++		    QCA8K_MDIO_MASTER_REG_ADDR(regnum);
+++
+++	if (read) {
+++		write_val |= QCA8K_MDIO_MASTER_READ;
+++	} else {
+++		write_val |= QCA8K_MDIO_MASTER_WRITE;
+++		write_val |= QCA8K_MDIO_MASTER_DATA(data);
+++	}
+++
+++	/* Prealloc all the needed skb before the lock */
+++	write_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL,
+++					    &write_val, QCA8K_ETHERNET_PHY_PRIORITY);
+++	if (!write_skb)
+++		return -ENOMEM;
+++
+++	clear_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL,
+++					    &clear_val, QCA8K_ETHERNET_PHY_PRIORITY);
+++	if (!write_skb) {
+++		ret = -ENOMEM;
+++		goto err_clear_skb;
+++	}
+++
+++	read_skb = qca8k_alloc_mdio_header(MDIO_READ, QCA8K_MDIO_MASTER_CTRL,
+++					   &clear_val, QCA8K_ETHERNET_PHY_PRIORITY);
+++	if (!write_skb) {
+++		ret = -ENOMEM;
+++		goto err_read_skb;
+++	}
+++
+++	/* Actually start the request:
+++	 * 1. Send mdio master packet
+++	 * 2. Busy Wait for mdio master command
+++	 * 3. Get the data if we are reading
+++	 * 4. Reset the mdio master (even with error)
+++	 */
+++	mutex_lock(&mgmt_eth_data->mutex);
+++
+++	/* Check if mgmt_master is operational */
+++	mgmt_master = priv->mgmt_master;
+++	if (!mgmt_master) {
+++		mutex_unlock(&mgmt_eth_data->mutex);
+++		ret = -EINVAL;
+++		goto err_mgmt_master;
+++	}
+++
+++	read_skb->dev = mgmt_master;
+++	clear_skb->dev = mgmt_master;
+++	write_skb->dev = mgmt_master;
+++
+++	reinit_completion(&mgmt_eth_data->rw_done);
+++
+++	/* Increment seq_num and set it in the write pkt */
+++	mgmt_eth_data->seq++;
+++	qca8k_mdio_header_fill_seq_num(write_skb, mgmt_eth_data->seq);
+++	mgmt_eth_data->ack = false;
+++
+++	dev_queue_xmit(write_skb);
+++
+++	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
+++					  QCA8K_ETHERNET_TIMEOUT);
+++
+++	ack = mgmt_eth_data->ack;
+++
+++	if (ret <= 0) {
+++		ret = -ETIMEDOUT;
+++		kfree_skb(read_skb);
+++		goto exit;
+++	}
+++
+++	if (!ack) {
+++		ret = -EINVAL;
+++		kfree_skb(read_skb);
+++		goto exit;
+++	}
+++
+++	ret = read_poll_timeout(qca8k_phy_eth_busy_wait, ret1,
+++				!(val & QCA8K_MDIO_MASTER_BUSY), 0,
+++				QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
+++				mgmt_eth_data, read_skb, &val);
+++
+++	if (ret < 0 && ret1 < 0) {
+++		ret = ret1;
+++		goto exit;
+++	}
+++
+++	if (read) {
+++		reinit_completion(&mgmt_eth_data->rw_done);
+++
+++		/* Increment seq_num and set it in the read pkt */
+++		mgmt_eth_data->seq++;
+++		qca8k_mdio_header_fill_seq_num(read_skb, mgmt_eth_data->seq);
+++		mgmt_eth_data->ack = false;
+++
+++		dev_queue_xmit(read_skb);
+++
+++		ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
+++						  QCA8K_ETHERNET_TIMEOUT);
+++
+++		ack = mgmt_eth_data->ack;
+++
+++		if (ret <= 0) {
+++			ret = -ETIMEDOUT;
+++			goto exit;
+++		}
+++
+++		if (!ack) {
+++			ret = -EINVAL;
+++			goto exit;
+++		}
+++
+++		ret = mgmt_eth_data->data[0] & QCA8K_MDIO_MASTER_DATA_MASK;
+++	} else {
+++		kfree_skb(read_skb);
+++	}
+++exit:
+++	reinit_completion(&mgmt_eth_data->rw_done);
+++
+++	/* Increment seq_num and set it in the clear pkt */
+++	mgmt_eth_data->seq++;
+++	qca8k_mdio_header_fill_seq_num(clear_skb, mgmt_eth_data->seq);
+++	mgmt_eth_data->ack = false;
+++
+++	dev_queue_xmit(clear_skb);
+++
+++	wait_for_completion_timeout(&mgmt_eth_data->rw_done,
+++				    QCA8K_ETHERNET_TIMEOUT);
+++
+++	mutex_unlock(&mgmt_eth_data->mutex);
+++
+++	return ret;
+++
+++	/* Error handling before lock */
+++err_mgmt_master:
+++	kfree_skb(read_skb);
+++err_read_skb:
+++	kfree_skb(clear_skb);
+++err_clear_skb:
+++	kfree_skb(write_skb);
+++
+++	return ret;
+++}
+++
++ static u32
++ qca8k_port_to_phy(int port)
++ {
++@@ -989,6 +1182,12 @@ qca8k_internal_mdio_write(struct mii_bus
++ {
++ 	struct qca8k_priv *priv = slave_bus->priv;
++ 	struct mii_bus *bus = priv->bus;
+++	int ret;
+++
+++	/* Use mdio Ethernet when available, fallback to legacy one on error */
+++	ret = qca8k_phy_eth_command(priv, false, phy, regnum, data);
+++	if (!ret)
+++		return 0;
++ 
++ 	return qca8k_mdio_write(bus, phy, regnum, data);
++ }
++@@ -998,6 +1197,12 @@ qca8k_internal_mdio_read(struct mii_bus
++ {
++ 	struct qca8k_priv *priv = slave_bus->priv;
++ 	struct mii_bus *bus = priv->bus;
+++	int ret;
+++
+++	/* Use mdio Ethernet when available, fallback to legacy one on error */
+++	ret = qca8k_phy_eth_command(priv, true, phy, regnum, 0);
+++	if (ret >= 0)
+++		return ret;
++ 
++ 	return qca8k_mdio_read(bus, phy, regnum);
++ }
++@@ -1006,6 +1211,7 @@ static int
++ qca8k_phy_write(struct dsa_switch *ds, int port, int regnum, u16 data)
++ {
++ 	struct qca8k_priv *priv = ds->priv;
+++	int ret;
++ 
++ 	/* Check if the legacy mapping should be used and the
++ 	 * port is not correctly mapped to the right PHY in the
++@@ -1014,6 +1220,11 @@ qca8k_phy_write(struct dsa_switch *ds, i
++ 	if (priv->legacy_phy_port_mapping)
++ 		port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
++ 
+++	/* Use mdio Ethernet when available, fallback to legacy one on error */
+++	ret = qca8k_phy_eth_command(priv, false, port, regnum, 0);
+++	if (!ret)
+++		return ret;
+++
++ 	return qca8k_mdio_write(priv->bus, port, regnum, data);
++ }
++ 
++@@ -1030,6 +1241,11 @@ qca8k_phy_read(struct dsa_switch *ds, in
++ 	if (priv->legacy_phy_port_mapping)
++ 		port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
++ 
+++	/* Use mdio Ethernet when available, fallback to legacy one on error */
+++	ret = qca8k_phy_eth_command(priv, true, port, regnum, 0);
+++	if (ret >= 0)
+++		return ret;
+++
++ 	ret = qca8k_mdio_read(priv->bus, port, regnum);
++ 
++ 	if (ret < 0)
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -14,6 +14,7 @@
++ #include <linux/dsa/tag_qca.h>
++ 
++ #define QCA8K_ETHERNET_MDIO_PRIORITY			7
+++#define QCA8K_ETHERNET_PHY_PRIORITY			6
++ #define QCA8K_ETHERNET_TIMEOUT				100
++ 
++ #define QCA8K_NUM_PORTS					7
+diff --git a/target/linux/generic/backport-5.15/766-v5.18-13-net-dsa-qca8k-move-page-cache-to-driver-priv.patch b/target/linux/generic/backport-5.15/766-v5.18-13-net-dsa-qca8k-move-page-cache-to-driver-priv.patch
+new file mode 100644
+index 0000000000..4ac0bc32fd
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/766-v5.18-13-net-dsa-qca8k-move-page-cache-to-driver-priv.patch
+@@ -0,0 +1,208 @@
++From 4264350acb75430d5021a1d7de56a33faf69a097 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Wed, 2 Feb 2022 01:03:32 +0100
++Subject: [PATCH 13/16] net: dsa: qca8k: move page cache to driver priv
++
++There can be multiple qca8k switch on the same system. Move the static
++qca8k_current_page to qca8k_priv and make it specific for each switch.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 42 ++++++++++++++++++++---------------------
++ drivers/net/dsa/qca8k.h |  9 +++++++++
++ 2 files changed, 29 insertions(+), 22 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -75,12 +75,6 @@ static const struct qca8k_mib_desc ar832
++ 	MIB_DESC(1, 0xac, "TXUnicast"),
++ };
++ 
++-/* The 32bit switch registers are accessed indirectly. To achieve this we need
++- * to set the page of the register. Track the last page that was set to reduce
++- * mdio writes
++- */
++-static u16 qca8k_current_page = 0xffff;
++-
++ static void
++ qca8k_split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
++ {
++@@ -134,11 +128,13 @@ qca8k_mii_write32(struct mii_bus *bus, i
++ }
++ 
++ static int
++-qca8k_set_page(struct mii_bus *bus, u16 page)
+++qca8k_set_page(struct qca8k_priv *priv, u16 page)
++ {
+++	u16 *cached_page = &priv->mdio_cache.page;
+++	struct mii_bus *bus = priv->bus;
++ 	int ret;
++ 
++-	if (page == qca8k_current_page)
+++	if (page == *cached_page)
++ 		return 0;
++ 
++ 	ret = bus->write(bus, 0x18, 0, page);
++@@ -148,7 +144,7 @@ qca8k_set_page(struct mii_bus *bus, u16
++ 		return ret;
++ 	}
++ 
++-	qca8k_current_page = page;
+++	*cached_page = page;
++ 	usleep_range(1000, 2000);
++ 	return 0;
++ }
++@@ -374,7 +370,7 @@ qca8k_regmap_read(void *ctx, uint32_t re
++ 
++ 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ 
++-	ret = qca8k_set_page(bus, page);
+++	ret = qca8k_set_page(priv, page);
++ 	if (ret < 0)
++ 		goto exit;
++ 
++@@ -400,7 +396,7 @@ qca8k_regmap_write(void *ctx, uint32_t r
++ 
++ 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ 
++-	ret = qca8k_set_page(bus, page);
+++	ret = qca8k_set_page(priv, page);
++ 	if (ret < 0)
++ 		goto exit;
++ 
++@@ -427,7 +423,7 @@ qca8k_regmap_update_bits(void *ctx, uint
++ 
++ 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ 
++-	ret = qca8k_set_page(bus, page);
+++	ret = qca8k_set_page(priv, page);
++ 	if (ret < 0)
++ 		goto exit;
++ 
++@@ -1098,8 +1094,9 @@ qca8k_mdio_busy_wait(struct mii_bus *bus
++ }
++ 
++ static int
++-qca8k_mdio_write(struct mii_bus *bus, int phy, int regnum, u16 data)
+++qca8k_mdio_write(struct qca8k_priv *priv, int phy, int regnum, u16 data)
++ {
+++	struct mii_bus *bus = priv->bus;
++ 	u16 r1, r2, page;
++ 	u32 val;
++ 	int ret;
++@@ -1116,7 +1113,7 @@ qca8k_mdio_write(struct mii_bus *bus, in
++ 
++ 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ 
++-	ret = qca8k_set_page(bus, page);
+++	ret = qca8k_set_page(priv, page);
++ 	if (ret)
++ 		goto exit;
++ 
++@@ -1135,8 +1132,9 @@ exit:
++ }
++ 
++ static int
++-qca8k_mdio_read(struct mii_bus *bus, int phy, int regnum)
+++qca8k_mdio_read(struct qca8k_priv *priv, int phy, int regnum)
++ {
+++	struct mii_bus *bus = priv->bus;
++ 	u16 r1, r2, page;
++ 	u32 val;
++ 	int ret;
++@@ -1152,7 +1150,7 @@ qca8k_mdio_read(struct mii_bus *bus, int
++ 
++ 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ 
++-	ret = qca8k_set_page(bus, page);
+++	ret = qca8k_set_page(priv, page);
++ 	if (ret)
++ 		goto exit;
++ 
++@@ -1181,7 +1179,6 @@ static int
++ qca8k_internal_mdio_write(struct mii_bus *slave_bus, int phy, int regnum, u16 data)
++ {
++ 	struct qca8k_priv *priv = slave_bus->priv;
++-	struct mii_bus *bus = priv->bus;
++ 	int ret;
++ 
++ 	/* Use mdio Ethernet when available, fallback to legacy one on error */
++@@ -1189,14 +1186,13 @@ qca8k_internal_mdio_write(struct mii_bus
++ 	if (!ret)
++ 		return 0;
++ 
++-	return qca8k_mdio_write(bus, phy, regnum, data);
+++	return qca8k_mdio_write(priv, phy, regnum, data);
++ }
++ 
++ static int
++ qca8k_internal_mdio_read(struct mii_bus *slave_bus, int phy, int regnum)
++ {
++ 	struct qca8k_priv *priv = slave_bus->priv;
++-	struct mii_bus *bus = priv->bus;
++ 	int ret;
++ 
++ 	/* Use mdio Ethernet when available, fallback to legacy one on error */
++@@ -1204,7 +1200,7 @@ qca8k_internal_mdio_read(struct mii_bus
++ 	if (ret >= 0)
++ 		return ret;
++ 
++-	return qca8k_mdio_read(bus, phy, regnum);
+++	return qca8k_mdio_read(priv, phy, regnum);
++ }
++ 
++ static int
++@@ -1225,7 +1221,7 @@ qca8k_phy_write(struct dsa_switch *ds, i
++ 	if (!ret)
++ 		return ret;
++ 
++-	return qca8k_mdio_write(priv->bus, port, regnum, data);
+++	return qca8k_mdio_write(priv, port, regnum, data);
++ }
++ 
++ static int
++@@ -1246,7 +1242,7 @@ qca8k_phy_read(struct dsa_switch *ds, in
++ 	if (ret >= 0)
++ 		return ret;
++ 
++-	ret = qca8k_mdio_read(priv->bus, port, regnum);
+++	ret = qca8k_mdio_read(priv, port, regnum);
++ 
++ 	if (ret < 0)
++ 		return 0xffff;
++@@ -3060,6 +3056,8 @@ qca8k_sw_probe(struct mdio_device *mdiod
++ 		return PTR_ERR(priv->regmap);
++ 	}
++ 
+++	priv->mdio_cache.page = 0xffff;
+++
++ 	/* Check the detected switch id */
++ 	ret = qca8k_read_switch_id(priv);
++ 	if (ret)
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -363,6 +363,14 @@ struct qca8k_ports_config {
++ 	u8 rgmii_tx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
++ };
++ 
+++struct qca8k_mdio_cache {
+++/* The 32bit switch registers are accessed indirectly. To achieve this we need
+++ * to set the page of the register. Track the last page that was set to reduce
+++ * mdio writes
+++ */
+++	u16 page;
+++};
+++
++ struct qca8k_priv {
++ 	u8 switch_id;
++ 	u8 switch_revision;
++@@ -383,6 +391,7 @@ struct qca8k_priv {
++ 	struct net_device *mgmt_master; /* Track if mdio/mib Ethernet is available */
++ 	struct qca8k_mgmt_eth_data mgmt_eth_data;
++ 	struct qca8k_mib_eth_data mib_eth_data;
+++	struct qca8k_mdio_cache mdio_cache;
++ };
++ 
++ struct qca8k_mib_desc {
+diff --git a/target/linux/generic/backport-5.15/766-v5.18-14-net-dsa-qca8k-cache-lo-and-hi-for-mdio-write.patch b/target/linux/generic/backport-5.15/766-v5.18-14-net-dsa-qca8k-cache-lo-and-hi-for-mdio-write.patch
+new file mode 100644
+index 0000000000..e2cb2721ce
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/766-v5.18-14-net-dsa-qca8k-cache-lo-and-hi-for-mdio-write.patch
+@@ -0,0 +1,164 @@
++From 2481d206fae7884cd07014fd1318e63af35e99eb Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Wed, 2 Feb 2022 01:03:33 +0100
++Subject: [PATCH 14/16] net: dsa: qca8k: cache lo and hi for mdio write
++
++From Documentation, we can cache lo and hi the same way we do with the
++page. This massively reduce the mdio write as 3/4 of the time as we only
++require to write the lo or hi part for a mdio write.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 61 +++++++++++++++++++++++++++++++++--------
++ drivers/net/dsa/qca8k.h |  5 ++++
++ 2 files changed, 54 insertions(+), 12 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -89,6 +89,44 @@ qca8k_split_addr(u32 regaddr, u16 *r1, u
++ }
++ 
++ static int
+++qca8k_set_lo(struct qca8k_priv *priv, int phy_id, u32 regnum, u16 lo)
+++{
+++	u16 *cached_lo = &priv->mdio_cache.lo;
+++	struct mii_bus *bus = priv->bus;
+++	int ret;
+++
+++	if (lo == *cached_lo)
+++		return 0;
+++
+++	ret = bus->write(bus, phy_id, regnum, lo);
+++	if (ret < 0)
+++		dev_err_ratelimited(&bus->dev,
+++				    "failed to write qca8k 32bit lo register\n");
+++
+++	*cached_lo = lo;
+++	return 0;
+++}
+++
+++static int
+++qca8k_set_hi(struct qca8k_priv *priv, int phy_id, u32 regnum, u16 hi)
+++{
+++	u16 *cached_hi = &priv->mdio_cache.hi;
+++	struct mii_bus *bus = priv->bus;
+++	int ret;
+++
+++	if (hi == *cached_hi)
+++		return 0;
+++
+++	ret = bus->write(bus, phy_id, regnum, hi);
+++	if (ret < 0)
+++		dev_err_ratelimited(&bus->dev,
+++				    "failed to write qca8k 32bit hi register\n");
+++
+++	*cached_hi = hi;
+++	return 0;
+++}
+++
+++static int
++ qca8k_mii_read32(struct mii_bus *bus, int phy_id, u32 regnum, u32 *val)
++ {
++ 	int ret;
++@@ -111,7 +149,7 @@ qca8k_mii_read32(struct mii_bus *bus, in
++ }
++ 
++ static void
++-qca8k_mii_write32(struct mii_bus *bus, int phy_id, u32 regnum, u32 val)
+++qca8k_mii_write32(struct qca8k_priv *priv, int phy_id, u32 regnum, u32 val)
++ {
++ 	u16 lo, hi;
++ 	int ret;
++@@ -119,12 +157,9 @@ qca8k_mii_write32(struct mii_bus *bus, i
++ 	lo = val & 0xffff;
++ 	hi = (u16)(val >> 16);
++ 
++-	ret = bus->write(bus, phy_id, regnum, lo);
+++	ret = qca8k_set_lo(priv, phy_id, regnum, lo);
++ 	if (ret >= 0)
++-		ret = bus->write(bus, phy_id, regnum + 1, hi);
++-	if (ret < 0)
++-		dev_err_ratelimited(&bus->dev,
++-				    "failed to write qca8k 32bit register\n");
+++		ret = qca8k_set_hi(priv, phy_id, regnum + 1, hi);
++ }
++ 
++ static int
++@@ -400,7 +435,7 @@ qca8k_regmap_write(void *ctx, uint32_t r
++ 	if (ret < 0)
++ 		goto exit;
++ 
++-	qca8k_mii_write32(bus, 0x10 | r2, r1, val);
+++	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
++ 
++ exit:
++ 	mutex_unlock(&bus->mdio_lock);
++@@ -433,7 +468,7 @@ qca8k_regmap_update_bits(void *ctx, uint
++ 
++ 	val &= ~mask;
++ 	val |= write_val;
++-	qca8k_mii_write32(bus, 0x10 | r2, r1, val);
+++	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
++ 
++ exit:
++ 	mutex_unlock(&bus->mdio_lock);
++@@ -1117,14 +1152,14 @@ qca8k_mdio_write(struct qca8k_priv *priv
++ 	if (ret)
++ 		goto exit;
++ 
++-	qca8k_mii_write32(bus, 0x10 | r2, r1, val);
+++	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
++ 
++ 	ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
++ 				   QCA8K_MDIO_MASTER_BUSY);
++ 
++ exit:
++ 	/* even if the busy_wait timeouts try to clear the MASTER_EN */
++-	qca8k_mii_write32(bus, 0x10 | r2, r1, 0);
+++	qca8k_mii_write32(priv, 0x10 | r2, r1, 0);
++ 
++ 	mutex_unlock(&bus->mdio_lock);
++ 
++@@ -1154,7 +1189,7 @@ qca8k_mdio_read(struct qca8k_priv *priv,
++ 	if (ret)
++ 		goto exit;
++ 
++-	qca8k_mii_write32(bus, 0x10 | r2, r1, val);
+++	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
++ 
++ 	ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
++ 				   QCA8K_MDIO_MASTER_BUSY);
++@@ -1165,7 +1200,7 @@ qca8k_mdio_read(struct qca8k_priv *priv,
++ 
++ exit:
++ 	/* even if the busy_wait timeouts try to clear the MASTER_EN */
++-	qca8k_mii_write32(bus, 0x10 | r2, r1, 0);
+++	qca8k_mii_write32(priv, 0x10 | r2, r1, 0);
++ 
++ 	mutex_unlock(&bus->mdio_lock);
++ 
++@@ -3057,6 +3092,8 @@ qca8k_sw_probe(struct mdio_device *mdiod
++ 	}
++ 
++ 	priv->mdio_cache.page = 0xffff;
+++	priv->mdio_cache.lo = 0xffff;
+++	priv->mdio_cache.hi = 0xffff;
++ 
++ 	/* Check the detected switch id */
++ 	ret = qca8k_read_switch_id(priv);
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -369,6 +369,11 @@ struct qca8k_mdio_cache {
++  * mdio writes
++  */
++ 	u16 page;
+++/* lo and hi can also be cached and from Documentation we can skip one
+++ * extra mdio write if lo or hi is didn't change.
+++ */
+++	u16 lo;
+++	u16 hi;
++ };
++ 
++ struct qca8k_priv {
+diff --git a/target/linux/generic/backport-5.15/766-v5.18-15-net-dsa-qca8k-add-support-for-larger-read-write-size.patch b/target/linux/generic/backport-5.15/766-v5.18-15-net-dsa-qca8k-add-support-for-larger-read-write-size.patch
+new file mode 100644
+index 0000000000..5acd13dbad
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/766-v5.18-15-net-dsa-qca8k-add-support-for-larger-read-write-size.patch
+@@ -0,0 +1,206 @@
++From 90386223f44e2a751d7e9e9ac8f78ea33358a891 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Wed, 2 Feb 2022 01:03:34 +0100
++Subject: [PATCH 15/16] net: dsa: qca8k: add support for larger read/write size
++ with mgmt Ethernet
++
++mgmt Ethernet packet can read/write up to 16byte at times. The len reg
++is limited to 15 (0xf). The switch actually sends and accepts data in 4
++different steps of len values.
++Len steps:
++- 0: nothing
++- 1-4: first 4 byte
++- 5-6: first 12 byte
++- 7-15: all 16 byte
++
++In the alloc skb function we check if the len is 16 and we fix it to a
++len of 15. It the read/write function interest to extract the real asked
++data. The tagger handler will always copy the fully 16byte with a READ
++command. This is useful for some big regs like the fdb reg that are
++more than 4byte of data. This permits to introduce a bulk function that
++will send and request the entire entry in one go.
++Write function is changed and it does now require to pass the pointer to
++val to also handle array val.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 61 +++++++++++++++++++++++++++--------------
++ 1 file changed, 41 insertions(+), 20 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -222,7 +222,9 @@ static void qca8k_rw_reg_ack_handler(str
++ 	if (cmd == MDIO_READ) {
++ 		mgmt_eth_data->data[0] = mgmt_ethhdr->mdio_data;
++ 
++-		/* Get the rest of the 12 byte of data */
+++		/* Get the rest of the 12 byte of data.
+++		 * The read/write function will extract the requested data.
+++		 */
++ 		if (len > QCA_HDR_MGMT_DATA1_LEN)
++ 			memcpy(mgmt_eth_data->data + 1, skb->data,
++ 			       QCA_HDR_MGMT_DATA2_LEN);
++@@ -232,16 +234,30 @@ static void qca8k_rw_reg_ack_handler(str
++ }
++ 
++ static struct sk_buff *qca8k_alloc_mdio_header(enum mdio_cmd cmd, u32 reg, u32 *val,
++-					       int priority)
+++					       int priority, unsigned int len)
++ {
++ 	struct qca_mgmt_ethhdr *mgmt_ethhdr;
+++	unsigned int real_len;
++ 	struct sk_buff *skb;
+++	u32 *data2;
++ 	u16 hdr;
++ 
++ 	skb = dev_alloc_skb(QCA_HDR_MGMT_PKT_LEN);
++ 	if (!skb)
++ 		return NULL;
++ 
+++	/* Max value for len reg is 15 (0xf) but the switch actually return 16 byte
+++	 * Actually for some reason the steps are:
+++	 * 0: nothing
+++	 * 1-4: first 4 byte
+++	 * 5-6: first 12 byte
+++	 * 7-15: all 16 byte
+++	 */
+++	if (len == 16)
+++		real_len = 15;
+++	else
+++		real_len = len;
+++
++ 	skb_reset_mac_header(skb);
++ 	skb_set_network_header(skb, skb->len);
++ 
++@@ -254,7 +270,7 @@ static struct sk_buff *qca8k_alloc_mdio_
++ 	hdr |= FIELD_PREP(QCA_HDR_XMIT_CONTROL, QCA_HDR_XMIT_TYPE_RW_REG);
++ 
++ 	mgmt_ethhdr->command = FIELD_PREP(QCA_HDR_MGMT_ADDR, reg);
++-	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_LENGTH, 4);
+++	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_LENGTH, real_len);
++ 	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CMD, cmd);
++ 	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CHECK_CODE,
++ 					   QCA_HDR_MGMT_CHECK_CODE_VAL);
++@@ -264,7 +280,9 @@ static struct sk_buff *qca8k_alloc_mdio_
++ 
++ 	mgmt_ethhdr->hdr = htons(hdr);
++ 
++-	skb_put_zero(skb, QCA_HDR_MGMT_DATA2_LEN + QCA_HDR_MGMT_PADDING_LEN);
+++	data2 = skb_put_zero(skb, QCA_HDR_MGMT_DATA2_LEN + QCA_HDR_MGMT_PADDING_LEN);
+++	if (cmd == MDIO_WRITE && len > QCA_HDR_MGMT_DATA1_LEN)
+++		memcpy(data2, val + 1, len - QCA_HDR_MGMT_DATA1_LEN);
++ 
++ 	return skb;
++ }
++@@ -277,7 +295,7 @@ static void qca8k_mdio_header_fill_seq_n
++ 	mgmt_ethhdr->seq = FIELD_PREP(QCA_HDR_MGMT_SEQ_NUM, seq_num);
++ }
++ 
++-static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val)
+++static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++ {
++ 	struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
++ 	struct sk_buff *skb;
++@@ -285,7 +303,7 @@ static int qca8k_read_eth(struct qca8k_p
++ 	int ret;
++ 
++ 	skb = qca8k_alloc_mdio_header(MDIO_READ, reg, NULL,
++-				      QCA8K_ETHERNET_MDIO_PRIORITY);
+++				      QCA8K_ETHERNET_MDIO_PRIORITY, len);
++ 	if (!skb)
++ 		return -ENOMEM;
++ 
++@@ -313,6 +331,9 @@ static int qca8k_read_eth(struct qca8k_p
++ 					  msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
++ 
++ 	*val = mgmt_eth_data->data[0];
+++	if (len > QCA_HDR_MGMT_DATA1_LEN)
+++		memcpy(val + 1, mgmt_eth_data->data + 1, len - QCA_HDR_MGMT_DATA1_LEN);
+++
++ 	ack = mgmt_eth_data->ack;
++ 
++ 	mutex_unlock(&mgmt_eth_data->mutex);
++@@ -326,15 +347,15 @@ static int qca8k_read_eth(struct qca8k_p
++ 	return 0;
++ }
++ 
++-static int qca8k_write_eth(struct qca8k_priv *priv, u32 reg, u32 val)
+++static int qca8k_write_eth(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++ {
++ 	struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
++ 	struct sk_buff *skb;
++ 	bool ack;
++ 	int ret;
++ 
++-	skb = qca8k_alloc_mdio_header(MDIO_WRITE, reg, &val,
++-				      QCA8K_ETHERNET_MDIO_PRIORITY);
+++	skb = qca8k_alloc_mdio_header(MDIO_WRITE, reg, val,
+++				      QCA8K_ETHERNET_MDIO_PRIORITY, len);
++ 	if (!skb)
++ 		return -ENOMEM;
++ 
++@@ -380,14 +401,14 @@ qca8k_regmap_update_bits_eth(struct qca8
++ 	u32 val = 0;
++ 	int ret;
++ 
++-	ret = qca8k_read_eth(priv, reg, &val);
+++	ret = qca8k_read_eth(priv, reg, &val, sizeof(val));
++ 	if (ret)
++ 		return ret;
++ 
++ 	val &= ~mask;
++ 	val |= write_val;
++ 
++-	return qca8k_write_eth(priv, reg, val);
+++	return qca8k_write_eth(priv, reg, &val, sizeof(val));
++ }
++ 
++ static int
++@@ -398,7 +419,7 @@ qca8k_regmap_read(void *ctx, uint32_t re
++ 	u16 r1, r2, page;
++ 	int ret;
++ 
++-	if (!qca8k_read_eth(priv, reg, val))
+++	if (!qca8k_read_eth(priv, reg, val, sizeof(val)))
++ 		return 0;
++ 
++ 	qca8k_split_addr(reg, &r1, &r2, &page);
++@@ -424,7 +445,7 @@ qca8k_regmap_write(void *ctx, uint32_t r
++ 	u16 r1, r2, page;
++ 	int ret;
++ 
++-	if (!qca8k_write_eth(priv, reg, val))
+++	if (!qca8k_write_eth(priv, reg, &val, sizeof(val)))
++ 		return 0;
++ 
++ 	qca8k_split_addr(reg, &r1, &r2, &page);
++@@ -959,21 +980,21 @@ qca8k_phy_eth_command(struct qca8k_priv
++ 	}
++ 
++ 	/* Prealloc all the needed skb before the lock */
++-	write_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL,
++-					    &write_val, QCA8K_ETHERNET_PHY_PRIORITY);
+++	write_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL, &write_val,
+++					    QCA8K_ETHERNET_PHY_PRIORITY, sizeof(write_val));
++ 	if (!write_skb)
++ 		return -ENOMEM;
++ 
++-	clear_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL,
++-					    &clear_val, QCA8K_ETHERNET_PHY_PRIORITY);
+++	clear_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL, &clear_val,
+++					    QCA8K_ETHERNET_PHY_PRIORITY, sizeof(clear_val));
++ 	if (!write_skb) {
++ 		ret = -ENOMEM;
++ 		goto err_clear_skb;
++ 	}
++ 
++-	read_skb = qca8k_alloc_mdio_header(MDIO_READ, QCA8K_MDIO_MASTER_CTRL,
++-					   &clear_val, QCA8K_ETHERNET_PHY_PRIORITY);
++-	if (!write_skb) {
+++	read_skb = qca8k_alloc_mdio_header(MDIO_READ, QCA8K_MDIO_MASTER_CTRL, &clear_val,
+++					   QCA8K_ETHERNET_PHY_PRIORITY, sizeof(clear_val));
+++	if (!read_skb) {
++ 		ret = -ENOMEM;
++ 		goto err_read_skb;
++ 	}
+diff --git a/target/linux/generic/backport-5.15/766-v5.18-16-net-dsa-qca8k-introduce-qca8k_bulk_read-write-functi.patch b/target/linux/generic/backport-5.15/766-v5.18-16-net-dsa-qca8k-introduce-qca8k_bulk_read-write-functi.patch
+new file mode 100644
+index 0000000000..f26c6b91ac
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/766-v5.18-16-net-dsa-qca8k-introduce-qca8k_bulk_read-write-functi.patch
+@@ -0,0 +1,104 @@
++From 4f3701fc599820568ba4395070d34e4248800fc0 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Wed, 2 Feb 2022 01:03:35 +0100
++Subject: [PATCH 16/16] net: dsa: qca8k: introduce qca8k_bulk_read/write
++ function
++
++Introduce qca8k_bulk_read/write() function to use mgmt Ethernet way to
++read/write packet in bulk. Make use of this new function in the fdb
++function and while at it reduce the reg for fdb_read from 4 to 3 as the
++max bit for the ARL(fdb) table is 83 bits.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 55 ++++++++++++++++++++++++++++++++---------
++ 1 file changed, 43 insertions(+), 12 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -412,6 +412,43 @@ qca8k_regmap_update_bits_eth(struct qca8
++ }
++ 
++ static int
+++qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
+++{
+++	int i, count = len / sizeof(u32), ret;
+++
+++	if (priv->mgmt_master && !qca8k_read_eth(priv, reg, val, len))
+++		return 0;
+++
+++	for (i = 0; i < count; i++) {
+++		ret = regmap_read(priv->regmap, reg + (i * 4), val + i);
+++		if (ret < 0)
+++			return ret;
+++	}
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
+++{
+++	int i, count = len / sizeof(u32), ret;
+++	u32 tmp;
+++
+++	if (priv->mgmt_master && !qca8k_write_eth(priv, reg, val, len))
+++		return 0;
+++
+++	for (i = 0; i < count; i++) {
+++		tmp = val[i];
+++
+++		ret = regmap_write(priv->regmap, reg + (i * 4), tmp);
+++		if (ret < 0)
+++			return ret;
+++	}
+++
+++	return 0;
+++}
+++
+++static int
++ qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
++ {
++ 	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
++@@ -546,17 +583,13 @@ qca8k_busy_wait(struct qca8k_priv *priv,
++ static int
++ qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb)
++ {
++-	u32 reg[4], val;
++-	int i, ret;
+++	u32 reg[3];
+++	int ret;
++ 
++ 	/* load the ARL table into an array */
++-	for (i = 0; i < 4; i++) {
++-		ret = qca8k_read(priv, QCA8K_REG_ATU_DATA0 + (i * 4), &val);
++-		if (ret < 0)
++-			return ret;
++-
++-		reg[i] = val;
++-	}
+++	ret = qca8k_bulk_read(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
+++	if (ret)
+++		return ret;
++ 
++ 	/* vid - 83:72 */
++ 	fdb->vid = FIELD_GET(QCA8K_ATU_VID_MASK, reg[2]);
++@@ -580,7 +613,6 @@ qca8k_fdb_write(struct qca8k_priv *priv,
++ 		u8 aging)
++ {
++ 	u32 reg[3] = { 0 };
++-	int i;
++ 
++ 	/* vid - 83:72 */
++ 	reg[2] = FIELD_PREP(QCA8K_ATU_VID_MASK, vid);
++@@ -597,8 +629,7 @@ qca8k_fdb_write(struct qca8k_priv *priv,
++ 	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR5_MASK, mac[5]);
++ 
++ 	/* load the array into the ARL table */
++-	for (i = 0; i < 3; i++)
++-		qca8k_write(priv, QCA8K_REG_ATU_DATA0 + (i * 4), reg[i]);
+++	qca8k_bulk_write(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
++ }
++ 
++ static int
+diff --git a/target/linux/generic/backport-5.15/767-v5.18-net-dsa-qca8k-check-correct-variable-in-qca8k_phy_et.patch b/target/linux/generic/backport-5.15/767-v5.18-net-dsa-qca8k-check-correct-variable-in-qca8k_phy_et.patch
+new file mode 100644
+index 0000000000..34607c223c
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/767-v5.18-net-dsa-qca8k-check-correct-variable-in-qca8k_phy_et.patch
+@@ -0,0 +1,28 @@
++From c3664d913dc115cab4a5fdb5634df4887048000e Mon Sep 17 00:00:00 2001
++From: Dan Carpenter <dan.carpenter@oracle.com>
++Date: Fri, 4 Feb 2022 13:03:36 +0300
++Subject: [PATCH 1/1] net: dsa: qca8k: check correct variable in
++ qca8k_phy_eth_command()
++
++This is a copy and paste bug.  It was supposed to check "clear_skb"
++instead of "write_skb".
++
++Fixes: 2cd548566384 ("net: dsa: qca8k: add support for phy read/write with mgmt Ethernet")
++Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -1018,7 +1018,7 @@ qca8k_phy_eth_command(struct qca8k_priv
++ 
++ 	clear_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL, &clear_val,
++ 					    QCA8K_ETHERNET_PHY_PRIORITY, sizeof(clear_val));
++-	if (!write_skb) {
+++	if (!clear_skb) {
++ 		ret = -ENOMEM;
++ 		goto err_clear_skb;
++ 	}
+diff --git a/target/linux/generic/backport-5.15/768-v5.18-net-dsa-qca8k-fix-noderef.cocci-warnings.patch b/target/linux/generic/backport-5.15/768-v5.18-net-dsa-qca8k-fix-noderef.cocci-warnings.patch
+new file mode 100644
+index 0000000000..d8cf266309
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/768-v5.18-net-dsa-qca8k-fix-noderef.cocci-warnings.patch
+@@ -0,0 +1,34 @@
++From 4f5e483b8c7a644733db941a1ae00173baa7b463 Mon Sep 17 00:00:00 2001
++From: kernel test robot <lkp@intel.com>
++Date: Thu, 10 Feb 2022 06:13:04 +0800
++Subject: [PATCH 1/1] net: dsa: qca8k: fix noderef.cocci warnings
++
++drivers/net/dsa/qca8k.c:422:37-43: ERROR: application of sizeof to pointer
++
++ sizeof when applied to a pointer typed expression gives the size of
++ the pointer
++
++Generated by: scripts/coccinelle/misc/noderef.cocci
++
++Fixes: 90386223f44e ("net: dsa: qca8k: add support for larger read/write size with mgmt Ethernet")
++CC: Ansuel Smith <ansuelsmth@gmail.com>
++Reported-by: kernel test robot <lkp@intel.com>
++Signed-off-by: kernel test robot <lkp@intel.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Link: https://lore.kernel.org/r/20220209221304.GA17529@d2214a582157
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/dsa/qca8k.c | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -456,7 +456,7 @@ qca8k_regmap_read(void *ctx, uint32_t re
++ 	u16 r1, r2, page;
++ 	int ret;
++ 
++-	if (!qca8k_read_eth(priv, reg, val, sizeof(val)))
+++	if (!qca8k_read_eth(priv, reg, val, sizeof(*val)))
++ 		return 0;
++ 
++ 	qca8k_split_addr(reg, &r1, &r2, &page);
+diff --git a/target/linux/generic/backport-5.15/769-v5.19-01-net-dsa-qca8k-drop-MTU-tracking-from-qca8k_priv.patch b/target/linux/generic/backport-5.15/769-v5.19-01-net-dsa-qca8k-drop-MTU-tracking-from-qca8k_priv.patch
+new file mode 100644
+index 0000000000..57df4c126e
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/769-v5.19-01-net-dsa-qca8k-drop-MTU-tracking-from-qca8k_priv.patch
+@@ -0,0 +1,79 @@
++From 69fd055957a02309ffdc23d887a01988b6e5bab1 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Sat, 16 Apr 2022 01:30:12 +0200
++Subject: [PATCH 1/6] net: dsa: qca8k: drop MTU tracking from qca8k_priv
++
++DSA set the CPU port based on the largest MTU of all the slave ports.
++Based on this we can drop the MTU array from qca8k_priv and set the
++port_change_mtu logic on DSA changing MTU of the CPU port as the switch
++have a global MTU settingfor each port.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 26 +++++++++-----------------
++ drivers/net/dsa/qca8k.h |  1 -
++ 2 files changed, 9 insertions(+), 18 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -1803,16 +1803,6 @@ qca8k_setup(struct dsa_switch *ds)
++ 				  QCA8K_PORT_HOL_CTRL1_WRED_EN,
++ 				  mask);
++ 		}
++-
++-		/* Set initial MTU for every port.
++-		 * We have only have a general MTU setting. So track
++-		 * every port and set the max across all port.
++-		 * Set per port MTU to 1500 as the MTU change function
++-		 * will add the overhead and if its set to 1518 then it
++-		 * will apply the overhead again and we will end up with
++-		 * MTU of 1536 instead of 1518
++-		 */
++-		priv->port_mtu[i] = ETH_DATA_LEN;
++ 	}
++ 
++ 	/* Special GLOBAL_FC_THRESH value are needed for ar8327 switch */
++@@ -2525,13 +2515,16 @@ static int
++ qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
++ {
++ 	struct qca8k_priv *priv = ds->priv;
++-	int ret, i, mtu = 0;
++-
++-	priv->port_mtu[port] = new_mtu;
+++	int ret;
++ 
++-	for (i = 0; i < QCA8K_NUM_PORTS; i++)
++-		if (priv->port_mtu[i] > mtu)
++-			mtu = priv->port_mtu[i];
+++	/* We have only have a general MTU setting.
+++	 * DSA always set the CPU port's MTU to the largest MTU of the slave
+++	 * ports.
+++	 * Setting MTU just for the CPU port is sufficient to correctly set a
+++	 * value for every port.
+++	 */
+++	if (!dsa_is_cpu_port(ds, port))
+++		return 0;
++ 
++ 	/* To change the MAX_FRAME_SIZE the cpu ports must be off or
++ 	 * the switch panics.
++@@ -2545,7 +2538,7 @@ qca8k_port_change_mtu(struct dsa_switch
++ 		qca8k_port_set_status(priv, 6, 0);
++ 
++ 	/* Include L2 header / FCS length */
++-	ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, mtu + ETH_HLEN + ETH_FCS_LEN);
+++	ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, new_mtu + ETH_HLEN + ETH_FCS_LEN);
++ 
++ 	if (priv->port_sts[0].enabled)
++ 		qca8k_port_set_status(priv, 0, 1);
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -392,7 +392,6 @@ struct qca8k_priv {
++ 	struct device *dev;
++ 	struct dsa_switch_ops ops;
++ 	struct gpio_desc *reset_gpio;
++-	unsigned int port_mtu[QCA8K_NUM_PORTS];
++ 	struct net_device *mgmt_master; /* Track if mdio/mib Ethernet is available */
++ 	struct qca8k_mgmt_eth_data mgmt_eth_data;
++ 	struct qca8k_mib_eth_data mib_eth_data;
+diff --git a/target/linux/generic/backport-5.15/769-v5.19-02-net-dsa-qca8k-drop-port_sts-from-qca8k_priv.patch b/target/linux/generic/backport-5.15/769-v5.19-02-net-dsa-qca8k-drop-port_sts-from-qca8k_priv.patch
+new file mode 100644
+index 0000000000..3cacd7e4fd
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/769-v5.19-02-net-dsa-qca8k-drop-port_sts-from-qca8k_priv.patch
+@@ -0,0 +1,116 @@
++From 2b8fd87af7f156942971789abac8ee2bb60c03bc Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Sat, 16 Apr 2022 01:30:13 +0200
++Subject: [PATCH 2/6] net: dsa: qca8k: drop port_sts from qca8k_priv
++
++Port_sts is a thing of the past for this driver. It was something
++present on the initial implementation of this driver and parts of the
++original struct were dropped over time. Using an array of int to store if
++a port is enabled or not to handle PM operation seems overkill. Switch
++and use a simple u8 to store the port status where each bit correspond
++to a port. (bit is set port is enabled, bit is not set, port is disabled)
++Also add some comments to better describe why we need to track port
++status.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 15 +++++++++------
++ drivers/net/dsa/qca8k.h |  9 ++++-----
++ 2 files changed, 13 insertions(+), 11 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -2494,7 +2494,7 @@ qca8k_port_enable(struct dsa_switch *ds,
++ 	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++ 
++ 	qca8k_port_set_status(priv, port, 1);
++-	priv->port_sts[port].enabled = 1;
+++	priv->port_enabled_map |= BIT(port);
++ 
++ 	if (dsa_is_user_port(ds, port))
++ 		phy_support_asym_pause(phy);
++@@ -2508,7 +2508,7 @@ qca8k_port_disable(struct dsa_switch *ds
++ 	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++ 
++ 	qca8k_port_set_status(priv, port, 0);
++-	priv->port_sts[port].enabled = 0;
+++	priv->port_enabled_map &= ~BIT(port);
++ }
++ 
++ static int
++@@ -2531,19 +2531,19 @@ qca8k_port_change_mtu(struct dsa_switch
++ 	 * Turn off both cpu ports before applying the new value to prevent
++ 	 * this.
++ 	 */
++-	if (priv->port_sts[0].enabled)
+++	if (priv->port_enabled_map & BIT(0))
++ 		qca8k_port_set_status(priv, 0, 0);
++ 
++-	if (priv->port_sts[6].enabled)
+++	if (priv->port_enabled_map & BIT(6))
++ 		qca8k_port_set_status(priv, 6, 0);
++ 
++ 	/* Include L2 header / FCS length */
++ 	ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, new_mtu + ETH_HLEN + ETH_FCS_LEN);
++ 
++-	if (priv->port_sts[0].enabled)
+++	if (priv->port_enabled_map & BIT(0))
++ 		qca8k_port_set_status(priv, 0, 1);
++ 
++-	if (priv->port_sts[6].enabled)
+++	if (priv->port_enabled_map & BIT(6))
++ 		qca8k_port_set_status(priv, 6, 1);
++ 
++ 	return ret;
++@@ -3199,13 +3199,16 @@ static void qca8k_sw_shutdown(struct mdi
++ static void
++ qca8k_set_pm(struct qca8k_priv *priv, int enable)
++ {
++-	int i;
+++	int port;
++ 
++-	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++-		if (!priv->port_sts[i].enabled)
+++	for (port = 0; port < QCA8K_NUM_PORTS; port++) {
+++		/* Do not enable on resume if the port was
+++		 * disabled before.
+++		 */
+++		if (!(priv->port_enabled_map & BIT(port)))
++ 			continue;
++ 
++-		qca8k_port_set_status(priv, i, enable);
+++		qca8k_port_set_status(priv, port, enable);
++ 	}
++ }
++ 
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -324,10 +324,6 @@ enum qca8k_mid_cmd {
++ 	QCA8K_MIB_CAST = 3,
++ };
++ 
++-struct ar8xxx_port_status {
++-	int enabled;
++-};
++-
++ struct qca8k_match_data {
++ 	u8 id;
++ 	bool reduced_package;
++@@ -382,11 +378,14 @@ struct qca8k_priv {
++ 	u8 mirror_rx;
++ 	u8 mirror_tx;
++ 	u8 lag_hash_mode;
+++	/* Each bit correspond to a port. This switch can support a max of 7 port.
+++	 * Bit 1: port enabled. Bit 0: port disabled.
+++	 */
+++	u8 port_enabled_map;
++ 	bool legacy_phy_port_mapping;
++ 	struct qca8k_ports_config ports_config;
++ 	struct regmap *regmap;
++ 	struct mii_bus *bus;
++-	struct ar8xxx_port_status port_sts[QCA8K_NUM_PORTS];
++ 	struct dsa_switch *ds;
++ 	struct mutex reg_mutex;
++ 	struct device *dev;
+diff --git a/target/linux/generic/backport-5.15/769-v5.19-03-net-dsa-qca8k-rework-and-simplify-mdiobus-logic.patch b/target/linux/generic/backport-5.15/769-v5.19-03-net-dsa-qca8k-rework-and-simplify-mdiobus-logic.patch
+new file mode 100644
+index 0000000000..12c3221077
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/769-v5.19-03-net-dsa-qca8k-rework-and-simplify-mdiobus-logic.patch
+@@ -0,0 +1,173 @@
++From 8255212e4130bd2dc1463286a3dddb74797bbdc1 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Sat, 16 Apr 2022 01:30:14 +0200
++Subject: [PATCH 3/6] net: dsa: qca8k: rework and simplify mdiobus logic
++
++In an attempt to reduce qca8k_priv space, rework and simplify mdiobus
++logic.
++We now declare a mdiobus instead of relying on DSA phy_read/write even
++if a mdio node is not present. This is all to make the qca8k ops static
++and not switch specific. With a legacy implementation where port doesn't
++have a phy map declared in the dts with a mdio node, we declare a
++'qca8k-legacy' mdiobus. The conversion logic is used as legacy read and
++write ops are used instead of the internal one.
++Also drop the legacy_phy_port_mapping as we now declare mdiobus with ops
++that already address the workaround.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 95 +++++++++++++----------------------------
++ drivers/net/dsa/qca8k.h |  1 -
++ 2 files changed, 29 insertions(+), 67 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -1291,83 +1291,63 @@ qca8k_internal_mdio_read(struct mii_bus
++ }
++ 
++ static int
++-qca8k_phy_write(struct dsa_switch *ds, int port, int regnum, u16 data)
+++qca8k_legacy_mdio_write(struct mii_bus *slave_bus, int port, int regnum, u16 data)
++ {
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret;
+++	port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
++ 
++-	/* Check if the legacy mapping should be used and the
++-	 * port is not correctly mapped to the right PHY in the
++-	 * devicetree
++-	 */
++-	if (priv->legacy_phy_port_mapping)
++-		port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
++-
++-	/* Use mdio Ethernet when available, fallback to legacy one on error */
++-	ret = qca8k_phy_eth_command(priv, false, port, regnum, 0);
++-	if (!ret)
++-		return ret;
++-
++-	return qca8k_mdio_write(priv, port, regnum, data);
+++	return qca8k_internal_mdio_write(slave_bus, port, regnum, data);
++ }
++ 
++ static int
++-qca8k_phy_read(struct dsa_switch *ds, int port, int regnum)
+++qca8k_legacy_mdio_read(struct mii_bus *slave_bus, int port, int regnum)
++ {
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret;
++-
++-	/* Check if the legacy mapping should be used and the
++-	 * port is not correctly mapped to the right PHY in the
++-	 * devicetree
++-	 */
++-	if (priv->legacy_phy_port_mapping)
++-		port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
++-
++-	/* Use mdio Ethernet when available, fallback to legacy one on error */
++-	ret = qca8k_phy_eth_command(priv, true, port, regnum, 0);
++-	if (ret >= 0)
++-		return ret;
++-
++-	ret = qca8k_mdio_read(priv, port, regnum);
++-
++-	if (ret < 0)
++-		return 0xffff;
+++	port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
++ 
++-	return ret;
+++	return qca8k_internal_mdio_read(slave_bus, port, regnum);
++ }
++ 
++ static int
++-qca8k_mdio_register(struct qca8k_priv *priv, struct device_node *mdio)
+++qca8k_mdio_register(struct qca8k_priv *priv)
++ {
++ 	struct dsa_switch *ds = priv->ds;
+++	struct device_node *mdio;
++ 	struct mii_bus *bus;
++ 
++ 	bus = devm_mdiobus_alloc(ds->dev);
++-
++ 	if (!bus)
++ 		return -ENOMEM;
++ 
++ 	bus->priv = (void *)priv;
++-	bus->name = "qca8k slave mii";
++-	bus->read = qca8k_internal_mdio_read;
++-	bus->write = qca8k_internal_mdio_write;
++-	snprintf(bus->id, MII_BUS_ID_SIZE, "qca8k-%d",
++-		 ds->index);
++-
++ 	bus->parent = ds->dev;
++ 	bus->phy_mask = ~ds->phys_mii_mask;
++-
++ 	ds->slave_mii_bus = bus;
++ 
++-	return devm_of_mdiobus_register(priv->dev, bus, mdio);
+++	/* Check if the devicetree declare the port:phy mapping */
+++	mdio = of_get_child_by_name(priv->dev->of_node, "mdio");
+++	if (of_device_is_available(mdio)) {
+++		snprintf(bus->id, MII_BUS_ID_SIZE, "qca8k-%d", ds->index);
+++		bus->name = "qca8k slave mii";
+++		bus->read = qca8k_internal_mdio_read;
+++		bus->write = qca8k_internal_mdio_write;
+++		return devm_of_mdiobus_register(priv->dev, bus, mdio);
+++	}
+++
+++	/* If a mapping can't be found the legacy mapping is used,
+++	 * using the qca8k_port_to_phy function
+++	 */
+++	snprintf(bus->id, MII_BUS_ID_SIZE, "qca8k-%d.%d",
+++		 ds->dst->index, ds->index);
+++	bus->name = "qca8k-legacy slave mii";
+++	bus->read = qca8k_legacy_mdio_read;
+++	bus->write = qca8k_legacy_mdio_write;
+++	return devm_mdiobus_register(priv->dev, bus);
++ }
++ 
++ static int
++ qca8k_setup_mdio_bus(struct qca8k_priv *priv)
++ {
++ 	u32 internal_mdio_mask = 0, external_mdio_mask = 0, reg;
++-	struct device_node *ports, *port, *mdio;
+++	struct device_node *ports, *port;
++ 	phy_interface_t mode;
++ 	int err;
++ 
++@@ -1429,24 +1409,7 @@ qca8k_setup_mdio_bus(struct qca8k_priv *
++ 					 QCA8K_MDIO_MASTER_EN);
++ 	}
++ 
++-	/* Check if the devicetree declare the port:phy mapping */
++-	mdio = of_get_child_by_name(priv->dev->of_node, "mdio");
++-	if (of_device_is_available(mdio)) {
++-		err = qca8k_mdio_register(priv, mdio);
++-		if (err)
++-			of_node_put(mdio);
++-
++-		return err;
++-	}
++-
++-	/* If a mapping can't be found the legacy mapping is used,
++-	 * using the qca8k_port_to_phy function
++-	 */
++-	priv->legacy_phy_port_mapping = true;
++-	priv->ops.phy_read = qca8k_phy_read;
++-	priv->ops.phy_write = qca8k_phy_write;
++-
++-	return 0;
+++	return qca8k_mdio_register(priv);
++ }
++ 
++ static int
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -382,7 +382,6 @@ struct qca8k_priv {
++ 	 * Bit 1: port enabled. Bit 0: port disabled.
++ 	 */
++ 	u8 port_enabled_map;
++-	bool legacy_phy_port_mapping;
++ 	struct qca8k_ports_config ports_config;
++ 	struct regmap *regmap;
++ 	struct mii_bus *bus;
+diff --git a/target/linux/generic/backport-5.15/769-v5.19-04-net-dsa-qca8k-drop-dsa_switch_ops-from-qca8k_priv.patch b/target/linux/generic/backport-5.15/769-v5.19-04-net-dsa-qca8k-drop-dsa_switch_ops-from-qca8k_priv.patch
+new file mode 100644
+index 0000000000..8641000abb
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/769-v5.19-04-net-dsa-qca8k-drop-dsa_switch_ops-from-qca8k_priv.patch
+@@ -0,0 +1,39 @@
++From 2349b83a2486c55b9dd225326f0172a84a43c5e4 Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Sat, 16 Apr 2022 01:30:15 +0200
++Subject: [PATCH 4/6] net: dsa: qca8k: drop dsa_switch_ops from qca8k_priv
++
++Now that dsa_switch_ops is not switch specific anymore, we can drop it
++from qca8k_priv and use the static ops directly for the dsa_switch
++pointer.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 3 +--
++ drivers/net/dsa/qca8k.h | 1 -
++ 2 files changed, 1 insertion(+), 3 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -3121,8 +3121,7 @@ qca8k_sw_probe(struct mdio_device *mdiod
++ 	priv->ds->dev = &mdiodev->dev;
++ 	priv->ds->num_ports = QCA8K_NUM_PORTS;
++ 	priv->ds->priv = priv;
++-	priv->ops = qca8k_switch_ops;
++-	priv->ds->ops = &priv->ops;
+++	priv->ds->ops = &qca8k_switch_ops;
++ 	mutex_init(&priv->reg_mutex);
++ 	dev_set_drvdata(&mdiodev->dev, priv);
++ 
++--- a/drivers/net/dsa/qca8k.h
+++++ b/drivers/net/dsa/qca8k.h
++@@ -388,7 +388,6 @@ struct qca8k_priv {
++ 	struct dsa_switch *ds;
++ 	struct mutex reg_mutex;
++ 	struct device *dev;
++-	struct dsa_switch_ops ops;
++ 	struct gpio_desc *reset_gpio;
++ 	struct net_device *mgmt_master; /* Track if mdio/mib Ethernet is available */
++ 	struct qca8k_mgmt_eth_data mgmt_eth_data;
+diff --git a/target/linux/generic/backport-5.15/769-v5.19-05-net-dsa-qca8k-correctly-handle-mdio-read-error.patch b/target/linux/generic/backport-5.15/769-v5.19-05-net-dsa-qca8k-correctly-handle-mdio-read-error.patch
+new file mode 100644
+index 0000000000..b14b22091b
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/769-v5.19-05-net-dsa-qca8k-correctly-handle-mdio-read-error.patch
+@@ -0,0 +1,33 @@
++From 6cfc03b602200c5cbbd8d906fd905547814e83df Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Sat, 16 Apr 2022 01:30:16 +0200
++Subject: [PATCH 5/6] net: dsa: qca8k: correctly handle mdio read error
++
++Restore original way to handle mdio read error by returning 0xffff.
++This was wrongly changed when the internal_mdio_read was introduced,
++now that both legacy and internal use the same function, make sure that
++they behave the same way.
++
++Fixes: ce062a0adbfe ("net: dsa: qca8k: fix kernel panic with legacy mdio mapping")
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 7 ++++++-
++ 1 file changed, 6 insertions(+), 1 deletion(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -1287,7 +1287,12 @@ qca8k_internal_mdio_read(struct mii_bus
++ 	if (ret >= 0)
++ 		return ret;
++ 
++-	return qca8k_mdio_read(priv, phy, regnum);
+++	ret = qca8k_mdio_read(priv, phy, regnum);
+++
+++	if (ret < 0)
+++		return 0xffff;
+++
+++	return ret;
++ }
++ 
++ static int
+diff --git a/target/linux/generic/backport-5.15/769-v5.19-06-net-dsa-qca8k-unify-bus-id-naming-with-legacy-and-OF.patch b/target/linux/generic/backport-5.15/769-v5.19-06-net-dsa-qca8k-unify-bus-id-naming-with-legacy-and-OF.patch
+new file mode 100644
+index 0000000000..094686f11b
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/769-v5.19-06-net-dsa-qca8k-unify-bus-id-naming-with-legacy-and-OF.patch
+@@ -0,0 +1,44 @@
++From 8d1af50842bf2774f4edc57054206e909117469b Mon Sep 17 00:00:00 2001
++From: Ansuel Smith <ansuelsmth@gmail.com>
++Date: Sat, 16 Apr 2022 01:30:17 +0200
++Subject: [PATCH 6/6] net: dsa: qca8k: unify bus id naming with legacy and OF
++ mdio bus
++
++Add support for multiple switch with OF mdio bus declaration.
++Unify the bus id naming and use the same logic for both legacy and OF
++mdio bus.
++
++Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/qca8k.c | 5 ++---
++ 1 file changed, 2 insertions(+), 3 deletions(-)
++
++--- a/drivers/net/dsa/qca8k.c
+++++ b/drivers/net/dsa/qca8k.c
++@@ -1323,6 +1323,8 @@ qca8k_mdio_register(struct qca8k_priv *p
++ 		return -ENOMEM;
++ 
++ 	bus->priv = (void *)priv;
+++	snprintf(bus->id, MII_BUS_ID_SIZE, "qca8k-%d.%d",
+++		 ds->dst->index, ds->index);
++ 	bus->parent = ds->dev;
++ 	bus->phy_mask = ~ds->phys_mii_mask;
++ 	ds->slave_mii_bus = bus;
++@@ -1330,7 +1332,6 @@ qca8k_mdio_register(struct qca8k_priv *p
++ 	/* Check if the devicetree declare the port:phy mapping */
++ 	mdio = of_get_child_by_name(priv->dev->of_node, "mdio");
++ 	if (of_device_is_available(mdio)) {
++-		snprintf(bus->id, MII_BUS_ID_SIZE, "qca8k-%d", ds->index);
++ 		bus->name = "qca8k slave mii";
++ 		bus->read = qca8k_internal_mdio_read;
++ 		bus->write = qca8k_internal_mdio_write;
++@@ -1340,8 +1341,6 @@ qca8k_mdio_register(struct qca8k_priv *p
++ 	/* If a mapping can't be found the legacy mapping is used,
++ 	 * using the qca8k_port_to_phy function
++ 	 */
++-	snprintf(bus->id, MII_BUS_ID_SIZE, "qca8k-%d.%d",
++-		 ds->dst->index, ds->index);
++ 	bus->name = "qca8k-legacy slave mii";
++ 	bus->read = qca8k_legacy_mdio_read;
++ 	bus->write = qca8k_legacy_mdio_write;
+diff --git a/target/linux/generic/backport-5.15/770-v6.0-net-dsa-qca8k-move-driver-to-qca-dir.patch b/target/linux/generic/backport-5.15/770-v6.0-net-dsa-qca8k-move-driver-to-qca-dir.patch
+new file mode 100644
+index 0000000000..1534113f1d
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/770-v6.0-net-dsa-qca8k-move-driver-to-qca-dir.patch
+@@ -0,0 +1,7389 @@
++From 4bbaf764e1e1786eb937fdb62172f656f512e116 Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Wed, 13 Jul 2022 22:53:50 +0200
++Subject: [PATCH 1/1] net: dsa: qca8k: move driver to qca dir
++
++Move qca8k driver to qca dir in preparation for code split and
++introduction of ipq4019 switch based on qca8k.
++
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/Kconfig           | 8 --------
++ drivers/net/dsa/Makefile          | 1 -
++ drivers/net/dsa/qca/Kconfig       | 8 ++++++++
++ drivers/net/dsa/qca/Makefile      | 1 +
++ drivers/net/dsa/{ => qca}/qca8k.c | 0
++ drivers/net/dsa/{ => qca}/qca8k.h | 0
++ 6 files changed, 9 insertions(+), 9 deletions(-)
++ rename drivers/net/dsa/{ => qca}/qca8k.c (100%)
++ rename drivers/net/dsa/{ => qca}/qca8k.h (100%)
++
++--- a/drivers/net/dsa/Kconfig
+++++ b/drivers/net/dsa/Kconfig
++@@ -60,14 +60,6 @@ source "drivers/net/dsa/sja1105/Kconfig"
++ 
++ source "drivers/net/dsa/xrs700x/Kconfig"
++ 
++-config NET_DSA_QCA8K
++-	tristate "Qualcomm Atheros QCA8K Ethernet switch family support"
++-	select NET_DSA_TAG_QCA
++-	select REGMAP
++-	help
++-	  This enables support for the Qualcomm Atheros QCA8K Ethernet
++-	  switch chips.
++-
++ config NET_DSA_REALTEK_SMI
++ 	tristate "Realtek SMI Ethernet switch family support"
++ 	select NET_DSA_TAG_RTL4_A
++--- a/drivers/net/dsa/Makefile
+++++ b/drivers/net/dsa/Makefile
++@@ -8,7 +8,6 @@ endif
++ obj-$(CONFIG_NET_DSA_LANTIQ_GSWIP) += lantiq_gswip.o
++ obj-$(CONFIG_NET_DSA_MT7530)	+= mt7530.o
++ obj-$(CONFIG_NET_DSA_MV88E6060) += mv88e6060.o
++-obj-$(CONFIG_NET_DSA_QCA8K)	+= qca8k.o
++ obj-$(CONFIG_NET_DSA_REALTEK_SMI) += realtek-smi.o
++ realtek-smi-objs		:= realtek-smi-core.o rtl8366.o rtl8366rb.o
++ obj-$(CONFIG_NET_DSA_SMSC_LAN9303) += lan9303-core.o
++--- a/drivers/net/dsa/qca/Kconfig
+++++ b/drivers/net/dsa/qca/Kconfig
++@@ -7,3 +7,11 @@ config NET_DSA_AR9331
++ 	help
++ 	  This enables support for the Qualcomm Atheros AR9331 built-in Ethernet
++ 	  switch.
+++
+++config NET_DSA_QCA8K
+++	tristate "Qualcomm Atheros QCA8K Ethernet switch family support"
+++	select NET_DSA_TAG_QCA
+++	select REGMAP
+++	help
+++	  This enables support for the Qualcomm Atheros QCA8K Ethernet
+++	  switch chips.
++--- a/drivers/net/dsa/qca/Makefile
+++++ b/drivers/net/dsa/qca/Makefile
++@@ -1,2 +1,3 @@
++ # SPDX-License-Identifier: GPL-2.0-only
++ obj-$(CONFIG_NET_DSA_AR9331)	+= ar9331.o
+++obj-$(CONFIG_NET_DSA_QCA8K)	+= qca8k.o
++--- /dev/null
+++++ b/drivers/net/dsa/qca/qca8k.c
++@@ -0,0 +1,3243 @@
+++// SPDX-License-Identifier: GPL-2.0
+++/*
+++ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+++ * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
+++ * Copyright (c) 2015, 2019, The Linux Foundation. All rights reserved.
+++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
+++ */
+++
+++#include <linux/module.h>
+++#include <linux/phy.h>
+++#include <linux/netdevice.h>
+++#include <linux/bitfield.h>
+++#include <linux/regmap.h>
+++#include <net/dsa.h>
+++#include <linux/of_net.h>
+++#include <linux/of_mdio.h>
+++#include <linux/of_platform.h>
+++#include <linux/if_bridge.h>
+++#include <linux/mdio.h>
+++#include <linux/phylink.h>
+++#include <linux/gpio/consumer.h>
+++#include <linux/etherdevice.h>
+++#include <linux/dsa/tag_qca.h>
+++
+++#include "qca8k.h"
+++
+++#define MIB_DESC(_s, _o, _n)	\
+++	{			\
+++		.size = (_s),	\
+++		.offset = (_o),	\
+++		.name = (_n),	\
+++	}
+++
+++static const struct qca8k_mib_desc ar8327_mib[] = {
+++	MIB_DESC(1, 0x00, "RxBroad"),
+++	MIB_DESC(1, 0x04, "RxPause"),
+++	MIB_DESC(1, 0x08, "RxMulti"),
+++	MIB_DESC(1, 0x0c, "RxFcsErr"),
+++	MIB_DESC(1, 0x10, "RxAlignErr"),
+++	MIB_DESC(1, 0x14, "RxRunt"),
+++	MIB_DESC(1, 0x18, "RxFragment"),
+++	MIB_DESC(1, 0x1c, "Rx64Byte"),
+++	MIB_DESC(1, 0x20, "Rx128Byte"),
+++	MIB_DESC(1, 0x24, "Rx256Byte"),
+++	MIB_DESC(1, 0x28, "Rx512Byte"),
+++	MIB_DESC(1, 0x2c, "Rx1024Byte"),
+++	MIB_DESC(1, 0x30, "Rx1518Byte"),
+++	MIB_DESC(1, 0x34, "RxMaxByte"),
+++	MIB_DESC(1, 0x38, "RxTooLong"),
+++	MIB_DESC(2, 0x3c, "RxGoodByte"),
+++	MIB_DESC(2, 0x44, "RxBadByte"),
+++	MIB_DESC(1, 0x4c, "RxOverFlow"),
+++	MIB_DESC(1, 0x50, "Filtered"),
+++	MIB_DESC(1, 0x54, "TxBroad"),
+++	MIB_DESC(1, 0x58, "TxPause"),
+++	MIB_DESC(1, 0x5c, "TxMulti"),
+++	MIB_DESC(1, 0x60, "TxUnderRun"),
+++	MIB_DESC(1, 0x64, "Tx64Byte"),
+++	MIB_DESC(1, 0x68, "Tx128Byte"),
+++	MIB_DESC(1, 0x6c, "Tx256Byte"),
+++	MIB_DESC(1, 0x70, "Tx512Byte"),
+++	MIB_DESC(1, 0x74, "Tx1024Byte"),
+++	MIB_DESC(1, 0x78, "Tx1518Byte"),
+++	MIB_DESC(1, 0x7c, "TxMaxByte"),
+++	MIB_DESC(1, 0x80, "TxOverSize"),
+++	MIB_DESC(2, 0x84, "TxByte"),
+++	MIB_DESC(1, 0x8c, "TxCollision"),
+++	MIB_DESC(1, 0x90, "TxAbortCol"),
+++	MIB_DESC(1, 0x94, "TxMultiCol"),
+++	MIB_DESC(1, 0x98, "TxSingleCol"),
+++	MIB_DESC(1, 0x9c, "TxExcDefer"),
+++	MIB_DESC(1, 0xa0, "TxDefer"),
+++	MIB_DESC(1, 0xa4, "TxLateCol"),
+++	MIB_DESC(1, 0xa8, "RXUnicast"),
+++	MIB_DESC(1, 0xac, "TXUnicast"),
+++};
+++
+++static void
+++qca8k_split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
+++{
+++	regaddr >>= 1;
+++	*r1 = regaddr & 0x1e;
+++
+++	regaddr >>= 5;
+++	*r2 = regaddr & 0x7;
+++
+++	regaddr >>= 3;
+++	*page = regaddr & 0x3ff;
+++}
+++
+++static int
+++qca8k_set_lo(struct qca8k_priv *priv, int phy_id, u32 regnum, u16 lo)
+++{
+++	u16 *cached_lo = &priv->mdio_cache.lo;
+++	struct mii_bus *bus = priv->bus;
+++	int ret;
+++
+++	if (lo == *cached_lo)
+++		return 0;
+++
+++	ret = bus->write(bus, phy_id, regnum, lo);
+++	if (ret < 0)
+++		dev_err_ratelimited(&bus->dev,
+++				    "failed to write qca8k 32bit lo register\n");
+++
+++	*cached_lo = lo;
+++	return 0;
+++}
+++
+++static int
+++qca8k_set_hi(struct qca8k_priv *priv, int phy_id, u32 regnum, u16 hi)
+++{
+++	u16 *cached_hi = &priv->mdio_cache.hi;
+++	struct mii_bus *bus = priv->bus;
+++	int ret;
+++
+++	if (hi == *cached_hi)
+++		return 0;
+++
+++	ret = bus->write(bus, phy_id, regnum, hi);
+++	if (ret < 0)
+++		dev_err_ratelimited(&bus->dev,
+++				    "failed to write qca8k 32bit hi register\n");
+++
+++	*cached_hi = hi;
+++	return 0;
+++}
+++
+++static int
+++qca8k_mii_read32(struct mii_bus *bus, int phy_id, u32 regnum, u32 *val)
+++{
+++	int ret;
+++
+++	ret = bus->read(bus, phy_id, regnum);
+++	if (ret >= 0) {
+++		*val = ret;
+++		ret = bus->read(bus, phy_id, regnum + 1);
+++		*val |= ret << 16;
+++	}
+++
+++	if (ret < 0) {
+++		dev_err_ratelimited(&bus->dev,
+++				    "failed to read qca8k 32bit register\n");
+++		*val = 0;
+++		return ret;
+++	}
+++
+++	return 0;
+++}
+++
+++static void
+++qca8k_mii_write32(struct qca8k_priv *priv, int phy_id, u32 regnum, u32 val)
+++{
+++	u16 lo, hi;
+++	int ret;
+++
+++	lo = val & 0xffff;
+++	hi = (u16)(val >> 16);
+++
+++	ret = qca8k_set_lo(priv, phy_id, regnum, lo);
+++	if (ret >= 0)
+++		ret = qca8k_set_hi(priv, phy_id, regnum + 1, hi);
+++}
+++
+++static int
+++qca8k_set_page(struct qca8k_priv *priv, u16 page)
+++{
+++	u16 *cached_page = &priv->mdio_cache.page;
+++	struct mii_bus *bus = priv->bus;
+++	int ret;
+++
+++	if (page == *cached_page)
+++		return 0;
+++
+++	ret = bus->write(bus, 0x18, 0, page);
+++	if (ret < 0) {
+++		dev_err_ratelimited(&bus->dev,
+++				    "failed to set qca8k page\n");
+++		return ret;
+++	}
+++
+++	*cached_page = page;
+++	usleep_range(1000, 2000);
+++	return 0;
+++}
+++
+++static int
+++qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val)
+++{
+++	return regmap_read(priv->regmap, reg, val);
+++}
+++
+++static int
+++qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val)
+++{
+++	return regmap_write(priv->regmap, reg, val);
+++}
+++
+++static int
+++qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
+++{
+++	return regmap_update_bits(priv->regmap, reg, mask, write_val);
+++}
+++
+++static void qca8k_rw_reg_ack_handler(struct dsa_switch *ds, struct sk_buff *skb)
+++{
+++	struct qca8k_mgmt_eth_data *mgmt_eth_data;
+++	struct qca8k_priv *priv = ds->priv;
+++	struct qca_mgmt_ethhdr *mgmt_ethhdr;
+++	u8 len, cmd;
+++
+++	mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb_mac_header(skb);
+++	mgmt_eth_data = &priv->mgmt_eth_data;
+++
+++	cmd = FIELD_GET(QCA_HDR_MGMT_CMD, mgmt_ethhdr->command);
+++	len = FIELD_GET(QCA_HDR_MGMT_LENGTH, mgmt_ethhdr->command);
+++
+++	/* Make sure the seq match the requested packet */
+++	if (mgmt_ethhdr->seq == mgmt_eth_data->seq)
+++		mgmt_eth_data->ack = true;
+++
+++	if (cmd == MDIO_READ) {
+++		mgmt_eth_data->data[0] = mgmt_ethhdr->mdio_data;
+++
+++		/* Get the rest of the 12 byte of data.
+++		 * The read/write function will extract the requested data.
+++		 */
+++		if (len > QCA_HDR_MGMT_DATA1_LEN)
+++			memcpy(mgmt_eth_data->data + 1, skb->data,
+++			       QCA_HDR_MGMT_DATA2_LEN);
+++	}
+++
+++	complete(&mgmt_eth_data->rw_done);
+++}
+++
+++static struct sk_buff *qca8k_alloc_mdio_header(enum mdio_cmd cmd, u32 reg, u32 *val,
+++					       int priority, unsigned int len)
+++{
+++	struct qca_mgmt_ethhdr *mgmt_ethhdr;
+++	unsigned int real_len;
+++	struct sk_buff *skb;
+++	u32 *data2;
+++	u16 hdr;
+++
+++	skb = dev_alloc_skb(QCA_HDR_MGMT_PKT_LEN);
+++	if (!skb)
+++		return NULL;
+++
+++	/* Max value for len reg is 15 (0xf) but the switch actually return 16 byte
+++	 * Actually for some reason the steps are:
+++	 * 0: nothing
+++	 * 1-4: first 4 byte
+++	 * 5-6: first 12 byte
+++	 * 7-15: all 16 byte
+++	 */
+++	if (len == 16)
+++		real_len = 15;
+++	else
+++		real_len = len;
+++
+++	skb_reset_mac_header(skb);
+++	skb_set_network_header(skb, skb->len);
+++
+++	mgmt_ethhdr = skb_push(skb, QCA_HDR_MGMT_HEADER_LEN + QCA_HDR_LEN);
+++
+++	hdr = FIELD_PREP(QCA_HDR_XMIT_VERSION, QCA_HDR_VERSION);
+++	hdr |= FIELD_PREP(QCA_HDR_XMIT_PRIORITY, priority);
+++	hdr |= QCA_HDR_XMIT_FROM_CPU;
+++	hdr |= FIELD_PREP(QCA_HDR_XMIT_DP_BIT, BIT(0));
+++	hdr |= FIELD_PREP(QCA_HDR_XMIT_CONTROL, QCA_HDR_XMIT_TYPE_RW_REG);
+++
+++	mgmt_ethhdr->command = FIELD_PREP(QCA_HDR_MGMT_ADDR, reg);
+++	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_LENGTH, real_len);
+++	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CMD, cmd);
+++	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CHECK_CODE,
+++					   QCA_HDR_MGMT_CHECK_CODE_VAL);
+++
+++	if (cmd == MDIO_WRITE)
+++		mgmt_ethhdr->mdio_data = *val;
+++
+++	mgmt_ethhdr->hdr = htons(hdr);
+++
+++	data2 = skb_put_zero(skb, QCA_HDR_MGMT_DATA2_LEN + QCA_HDR_MGMT_PADDING_LEN);
+++	if (cmd == MDIO_WRITE && len > QCA_HDR_MGMT_DATA1_LEN)
+++		memcpy(data2, val + 1, len - QCA_HDR_MGMT_DATA1_LEN);
+++
+++	return skb;
+++}
+++
+++static void qca8k_mdio_header_fill_seq_num(struct sk_buff *skb, u32 seq_num)
+++{
+++	struct qca_mgmt_ethhdr *mgmt_ethhdr;
+++
+++	mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb->data;
+++	mgmt_ethhdr->seq = FIELD_PREP(QCA_HDR_MGMT_SEQ_NUM, seq_num);
+++}
+++
+++static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
+++{
+++	struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
+++	struct sk_buff *skb;
+++	bool ack;
+++	int ret;
+++
+++	skb = qca8k_alloc_mdio_header(MDIO_READ, reg, NULL,
+++				      QCA8K_ETHERNET_MDIO_PRIORITY, len);
+++	if (!skb)
+++		return -ENOMEM;
+++
+++	mutex_lock(&mgmt_eth_data->mutex);
+++
+++	/* Check mgmt_master if is operational */
+++	if (!priv->mgmt_master) {
+++		kfree_skb(skb);
+++		mutex_unlock(&mgmt_eth_data->mutex);
+++		return -EINVAL;
+++	}
+++
+++	skb->dev = priv->mgmt_master;
+++
+++	reinit_completion(&mgmt_eth_data->rw_done);
+++
+++	/* Increment seq_num and set it in the mdio pkt */
+++	mgmt_eth_data->seq++;
+++	qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
+++	mgmt_eth_data->ack = false;
+++
+++	dev_queue_xmit(skb);
+++
+++	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
+++					  msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
+++
+++	*val = mgmt_eth_data->data[0];
+++	if (len > QCA_HDR_MGMT_DATA1_LEN)
+++		memcpy(val + 1, mgmt_eth_data->data + 1, len - QCA_HDR_MGMT_DATA1_LEN);
+++
+++	ack = mgmt_eth_data->ack;
+++
+++	mutex_unlock(&mgmt_eth_data->mutex);
+++
+++	if (ret <= 0)
+++		return -ETIMEDOUT;
+++
+++	if (!ack)
+++		return -EINVAL;
+++
+++	return 0;
+++}
+++
+++static int qca8k_write_eth(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
+++{
+++	struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
+++	struct sk_buff *skb;
+++	bool ack;
+++	int ret;
+++
+++	skb = qca8k_alloc_mdio_header(MDIO_WRITE, reg, val,
+++				      QCA8K_ETHERNET_MDIO_PRIORITY, len);
+++	if (!skb)
+++		return -ENOMEM;
+++
+++	mutex_lock(&mgmt_eth_data->mutex);
+++
+++	/* Check mgmt_master if is operational */
+++	if (!priv->mgmt_master) {
+++		kfree_skb(skb);
+++		mutex_unlock(&mgmt_eth_data->mutex);
+++		return -EINVAL;
+++	}
+++
+++	skb->dev = priv->mgmt_master;
+++
+++	reinit_completion(&mgmt_eth_data->rw_done);
+++
+++	/* Increment seq_num and set it in the mdio pkt */
+++	mgmt_eth_data->seq++;
+++	qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
+++	mgmt_eth_data->ack = false;
+++
+++	dev_queue_xmit(skb);
+++
+++	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
+++					  msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
+++
+++	ack = mgmt_eth_data->ack;
+++
+++	mutex_unlock(&mgmt_eth_data->mutex);
+++
+++	if (ret <= 0)
+++		return -ETIMEDOUT;
+++
+++	if (!ack)
+++		return -EINVAL;
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_regmap_update_bits_eth(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
+++{
+++	u32 val = 0;
+++	int ret;
+++
+++	ret = qca8k_read_eth(priv, reg, &val, sizeof(val));
+++	if (ret)
+++		return ret;
+++
+++	val &= ~mask;
+++	val |= write_val;
+++
+++	return qca8k_write_eth(priv, reg, &val, sizeof(val));
+++}
+++
+++static int
+++qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
+++{
+++	int i, count = len / sizeof(u32), ret;
+++
+++	if (priv->mgmt_master && !qca8k_read_eth(priv, reg, val, len))
+++		return 0;
+++
+++	for (i = 0; i < count; i++) {
+++		ret = regmap_read(priv->regmap, reg + (i * 4), val + i);
+++		if (ret < 0)
+++			return ret;
+++	}
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
+++{
+++	int i, count = len / sizeof(u32), ret;
+++	u32 tmp;
+++
+++	if (priv->mgmt_master && !qca8k_write_eth(priv, reg, val, len))
+++		return 0;
+++
+++	for (i = 0; i < count; i++) {
+++		tmp = val[i];
+++
+++		ret = regmap_write(priv->regmap, reg + (i * 4), tmp);
+++		if (ret < 0)
+++			return ret;
+++	}
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
+++	struct mii_bus *bus = priv->bus;
+++	u16 r1, r2, page;
+++	int ret;
+++
+++	if (!qca8k_read_eth(priv, reg, val, sizeof(*val)))
+++		return 0;
+++
+++	qca8k_split_addr(reg, &r1, &r2, &page);
+++
+++	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+++
+++	ret = qca8k_set_page(priv, page);
+++	if (ret < 0)
+++		goto exit;
+++
+++	ret = qca8k_mii_read32(bus, 0x10 | r2, r1, val);
+++
+++exit:
+++	mutex_unlock(&bus->mdio_lock);
+++	return ret;
+++}
+++
+++static int
+++qca8k_regmap_write(void *ctx, uint32_t reg, uint32_t val)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
+++	struct mii_bus *bus = priv->bus;
+++	u16 r1, r2, page;
+++	int ret;
+++
+++	if (!qca8k_write_eth(priv, reg, &val, sizeof(val)))
+++		return 0;
+++
+++	qca8k_split_addr(reg, &r1, &r2, &page);
+++
+++	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+++
+++	ret = qca8k_set_page(priv, page);
+++	if (ret < 0)
+++		goto exit;
+++
+++	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
+++
+++exit:
+++	mutex_unlock(&bus->mdio_lock);
+++	return ret;
+++}
+++
+++static int
+++qca8k_regmap_update_bits(void *ctx, uint32_t reg, uint32_t mask, uint32_t write_val)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
+++	struct mii_bus *bus = priv->bus;
+++	u16 r1, r2, page;
+++	u32 val;
+++	int ret;
+++
+++	if (!qca8k_regmap_update_bits_eth(priv, reg, mask, write_val))
+++		return 0;
+++
+++	qca8k_split_addr(reg, &r1, &r2, &page);
+++
+++	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+++
+++	ret = qca8k_set_page(priv, page);
+++	if (ret < 0)
+++		goto exit;
+++
+++	ret = qca8k_mii_read32(bus, 0x10 | r2, r1, &val);
+++	if (ret < 0)
+++		goto exit;
+++
+++	val &= ~mask;
+++	val |= write_val;
+++	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
+++
+++exit:
+++	mutex_unlock(&bus->mdio_lock);
+++
+++	return ret;
+++}
+++
+++static const struct regmap_range qca8k_readable_ranges[] = {
+++	regmap_reg_range(0x0000, 0x00e4), /* Global control */
+++	regmap_reg_range(0x0100, 0x0168), /* EEE control */
+++	regmap_reg_range(0x0200, 0x0270), /* Parser control */
+++	regmap_reg_range(0x0400, 0x0454), /* ACL */
+++	regmap_reg_range(0x0600, 0x0718), /* Lookup */
+++	regmap_reg_range(0x0800, 0x0b70), /* QM */
+++	regmap_reg_range(0x0c00, 0x0c80), /* PKT */
+++	regmap_reg_range(0x0e00, 0x0e98), /* L3 */
+++	regmap_reg_range(0x1000, 0x10ac), /* MIB - Port0 */
+++	regmap_reg_range(0x1100, 0x11ac), /* MIB - Port1 */
+++	regmap_reg_range(0x1200, 0x12ac), /* MIB - Port2 */
+++	regmap_reg_range(0x1300, 0x13ac), /* MIB - Port3 */
+++	regmap_reg_range(0x1400, 0x14ac), /* MIB - Port4 */
+++	regmap_reg_range(0x1500, 0x15ac), /* MIB - Port5 */
+++	regmap_reg_range(0x1600, 0x16ac), /* MIB - Port6 */
+++
+++};
+++
+++static const struct regmap_access_table qca8k_readable_table = {
+++	.yes_ranges = qca8k_readable_ranges,
+++	.n_yes_ranges = ARRAY_SIZE(qca8k_readable_ranges),
+++};
+++
+++static struct regmap_config qca8k_regmap_config = {
+++	.reg_bits = 16,
+++	.val_bits = 32,
+++	.reg_stride = 4,
+++	.max_register = 0x16ac, /* end MIB - Port6 range */
+++	.reg_read = qca8k_regmap_read,
+++	.reg_write = qca8k_regmap_write,
+++	.reg_update_bits = qca8k_regmap_update_bits,
+++	.rd_table = &qca8k_readable_table,
+++	.disable_locking = true, /* Locking is handled by qca8k read/write */
+++	.cache_type = REGCACHE_NONE, /* Explicitly disable CACHE */
+++};
+++
+++static int
+++qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask)
+++{
+++	u32 val;
+++
+++	return regmap_read_poll_timeout(priv->regmap, reg, val, !(val & mask), 0,
+++				       QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC);
+++}
+++
+++static int
+++qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb)
+++{
+++	u32 reg[3];
+++	int ret;
+++
+++	/* load the ARL table into an array */
+++	ret = qca8k_bulk_read(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
+++	if (ret)
+++		return ret;
+++
+++	/* vid - 83:72 */
+++	fdb->vid = FIELD_GET(QCA8K_ATU_VID_MASK, reg[2]);
+++	/* aging - 67:64 */
+++	fdb->aging = FIELD_GET(QCA8K_ATU_STATUS_MASK, reg[2]);
+++	/* portmask - 54:48 */
+++	fdb->port_mask = FIELD_GET(QCA8K_ATU_PORT_MASK, reg[1]);
+++	/* mac - 47:0 */
+++	fdb->mac[0] = FIELD_GET(QCA8K_ATU_ADDR0_MASK, reg[1]);
+++	fdb->mac[1] = FIELD_GET(QCA8K_ATU_ADDR1_MASK, reg[1]);
+++	fdb->mac[2] = FIELD_GET(QCA8K_ATU_ADDR2_MASK, reg[0]);
+++	fdb->mac[3] = FIELD_GET(QCA8K_ATU_ADDR3_MASK, reg[0]);
+++	fdb->mac[4] = FIELD_GET(QCA8K_ATU_ADDR4_MASK, reg[0]);
+++	fdb->mac[5] = FIELD_GET(QCA8K_ATU_ADDR5_MASK, reg[0]);
+++
+++	return 0;
+++}
+++
+++static void
+++qca8k_fdb_write(struct qca8k_priv *priv, u16 vid, u8 port_mask, const u8 *mac,
+++		u8 aging)
+++{
+++	u32 reg[3] = { 0 };
+++
+++	/* vid - 83:72 */
+++	reg[2] = FIELD_PREP(QCA8K_ATU_VID_MASK, vid);
+++	/* aging - 67:64 */
+++	reg[2] |= FIELD_PREP(QCA8K_ATU_STATUS_MASK, aging);
+++	/* portmask - 54:48 */
+++	reg[1] = FIELD_PREP(QCA8K_ATU_PORT_MASK, port_mask);
+++	/* mac - 47:0 */
+++	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR0_MASK, mac[0]);
+++	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR1_MASK, mac[1]);
+++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR2_MASK, mac[2]);
+++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR3_MASK, mac[3]);
+++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR4_MASK, mac[4]);
+++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR5_MASK, mac[5]);
+++
+++	/* load the array into the ARL table */
+++	qca8k_bulk_write(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
+++}
+++
+++static int
+++qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd, int port)
+++{
+++	u32 reg;
+++	int ret;
+++
+++	/* Set the command and FDB index */
+++	reg = QCA8K_ATU_FUNC_BUSY;
+++	reg |= cmd;
+++	if (port >= 0) {
+++		reg |= QCA8K_ATU_FUNC_PORT_EN;
+++		reg |= FIELD_PREP(QCA8K_ATU_FUNC_PORT_MASK, port);
+++	}
+++
+++	/* Write the function register triggering the table access */
+++	ret = qca8k_write(priv, QCA8K_REG_ATU_FUNC, reg);
+++	if (ret)
+++		return ret;
+++
+++	/* wait for completion */
+++	ret = qca8k_busy_wait(priv, QCA8K_REG_ATU_FUNC, QCA8K_ATU_FUNC_BUSY);
+++	if (ret)
+++		return ret;
+++
+++	/* Check for table full violation when adding an entry */
+++	if (cmd == QCA8K_FDB_LOAD) {
+++		ret = qca8k_read(priv, QCA8K_REG_ATU_FUNC, &reg);
+++		if (ret < 0)
+++			return ret;
+++		if (reg & QCA8K_ATU_FUNC_FULL)
+++			return -1;
+++	}
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb, int port)
+++{
+++	int ret;
+++
+++	qca8k_fdb_write(priv, fdb->vid, fdb->port_mask, fdb->mac, fdb->aging);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_NEXT, port);
+++	if (ret < 0)
+++		return ret;
+++
+++	return qca8k_fdb_read(priv, fdb);
+++}
+++
+++static int
+++qca8k_fdb_add(struct qca8k_priv *priv, const u8 *mac, u16 port_mask,
+++	      u16 vid, u8 aging)
+++{
+++	int ret;
+++
+++	mutex_lock(&priv->reg_mutex);
+++	qca8k_fdb_write(priv, vid, port_mask, mac, aging);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
+++	mutex_unlock(&priv->reg_mutex);
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac, u16 port_mask, u16 vid)
+++{
+++	int ret;
+++
+++	mutex_lock(&priv->reg_mutex);
+++	qca8k_fdb_write(priv, vid, port_mask, mac, 0);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
+++	mutex_unlock(&priv->reg_mutex);
+++
+++	return ret;
+++}
+++
+++static void
+++qca8k_fdb_flush(struct qca8k_priv *priv)
+++{
+++	mutex_lock(&priv->reg_mutex);
+++	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH, -1);
+++	mutex_unlock(&priv->reg_mutex);
+++}
+++
+++static int
+++qca8k_fdb_search_and_insert(struct qca8k_priv *priv, u8 port_mask,
+++			    const u8 *mac, u16 vid)
+++{
+++	struct qca8k_fdb fdb = { 0 };
+++	int ret;
+++
+++	mutex_lock(&priv->reg_mutex);
+++
+++	qca8k_fdb_write(priv, vid, 0, mac, 0);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
+++	if (ret < 0)
+++		goto exit;
+++
+++	ret = qca8k_fdb_read(priv, &fdb);
+++	if (ret < 0)
+++		goto exit;
+++
+++	/* Rule exist. Delete first */
+++	if (!fdb.aging) {
+++		ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
+++		if (ret)
+++			goto exit;
+++	}
+++
+++	/* Add port to fdb portmask */
+++	fdb.port_mask |= port_mask;
+++
+++	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
+++
+++exit:
+++	mutex_unlock(&priv->reg_mutex);
+++	return ret;
+++}
+++
+++static int
+++qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask,
+++			 const u8 *mac, u16 vid)
+++{
+++	struct qca8k_fdb fdb = { 0 };
+++	int ret;
+++
+++	mutex_lock(&priv->reg_mutex);
+++
+++	qca8k_fdb_write(priv, vid, 0, mac, 0);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
+++	if (ret < 0)
+++		goto exit;
+++
+++	/* Rule doesn't exist. Why delete? */
+++	if (!fdb.aging) {
+++		ret = -EINVAL;
+++		goto exit;
+++	}
+++
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
+++	if (ret)
+++		goto exit;
+++
+++	/* Only port in the rule is this port. Don't re insert */
+++	if (fdb.port_mask == port_mask)
+++		goto exit;
+++
+++	/* Remove port from port mask */
+++	fdb.port_mask &= ~port_mask;
+++
+++	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
+++
+++exit:
+++	mutex_unlock(&priv->reg_mutex);
+++	return ret;
+++}
+++
+++static int
+++qca8k_vlan_access(struct qca8k_priv *priv, enum qca8k_vlan_cmd cmd, u16 vid)
+++{
+++	u32 reg;
+++	int ret;
+++
+++	/* Set the command and VLAN index */
+++	reg = QCA8K_VTU_FUNC1_BUSY;
+++	reg |= cmd;
+++	reg |= FIELD_PREP(QCA8K_VTU_FUNC1_VID_MASK, vid);
+++
+++	/* Write the function register triggering the table access */
+++	ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC1, reg);
+++	if (ret)
+++		return ret;
+++
+++	/* wait for completion */
+++	ret = qca8k_busy_wait(priv, QCA8K_REG_VTU_FUNC1, QCA8K_VTU_FUNC1_BUSY);
+++	if (ret)
+++		return ret;
+++
+++	/* Check for table full violation when adding an entry */
+++	if (cmd == QCA8K_VLAN_LOAD) {
+++		ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC1, &reg);
+++		if (ret < 0)
+++			return ret;
+++		if (reg & QCA8K_VTU_FUNC1_FULL)
+++			return -ENOMEM;
+++	}
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid, bool untagged)
+++{
+++	u32 reg;
+++	int ret;
+++
+++	/*
+++	   We do the right thing with VLAN 0 and treat it as untagged while
+++	   preserving the tag on egress.
+++	 */
+++	if (vid == 0)
+++		return 0;
+++
+++	mutex_lock(&priv->reg_mutex);
+++	ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid);
+++	if (ret < 0)
+++		goto out;
+++
+++	ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, &reg);
+++	if (ret < 0)
+++		goto out;
+++	reg |= QCA8K_VTU_FUNC0_VALID | QCA8K_VTU_FUNC0_IVL_EN;
+++	reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port);
+++	if (untagged)
+++		reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_UNTAG(port);
+++	else
+++		reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_TAG(port);
+++
+++	ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
+++	if (ret)
+++		goto out;
+++	ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid);
+++
+++out:
+++	mutex_unlock(&priv->reg_mutex);
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid)
+++{
+++	u32 reg, mask;
+++	int ret, i;
+++	bool del;
+++
+++	mutex_lock(&priv->reg_mutex);
+++	ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid);
+++	if (ret < 0)
+++		goto out;
+++
+++	ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, &reg);
+++	if (ret < 0)
+++		goto out;
+++	reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port);
+++	reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(port);
+++
+++	/* Check if we're the last member to be removed */
+++	del = true;
+++	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+++		mask = QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(i);
+++
+++		if ((reg & mask) != mask) {
+++			del = false;
+++			break;
+++		}
+++	}
+++
+++	if (del) {
+++		ret = qca8k_vlan_access(priv, QCA8K_VLAN_PURGE, vid);
+++	} else {
+++		ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
+++		if (ret)
+++			goto out;
+++		ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid);
+++	}
+++
+++out:
+++	mutex_unlock(&priv->reg_mutex);
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_mib_init(struct qca8k_priv *priv)
+++{
+++	int ret;
+++
+++	mutex_lock(&priv->reg_mutex);
+++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB,
+++				 QCA8K_MIB_FUNC | QCA8K_MIB_BUSY,
+++				 FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_FLUSH) |
+++				 QCA8K_MIB_BUSY);
+++	if (ret)
+++		goto exit;
+++
+++	ret = qca8k_busy_wait(priv, QCA8K_REG_MIB, QCA8K_MIB_BUSY);
+++	if (ret)
+++		goto exit;
+++
+++	ret = regmap_set_bits(priv->regmap, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP);
+++	if (ret)
+++		goto exit;
+++
+++	ret = qca8k_write(priv, QCA8K_REG_MODULE_EN, QCA8K_MODULE_EN_MIB);
+++
+++exit:
+++	mutex_unlock(&priv->reg_mutex);
+++	return ret;
+++}
+++
+++static void
+++qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable)
+++{
+++	u32 mask = QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
+++
+++	/* Port 0 and 6 have no internal PHY */
+++	if (port > 0 && port < 6)
+++		mask |= QCA8K_PORT_STATUS_LINK_AUTO;
+++
+++	if (enable)
+++		regmap_set_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
+++	else
+++		regmap_clear_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
+++}
+++
+++static int
+++qca8k_phy_eth_busy_wait(struct qca8k_mgmt_eth_data *mgmt_eth_data,
+++			struct sk_buff *read_skb, u32 *val)
+++{
+++	struct sk_buff *skb = skb_copy(read_skb, GFP_KERNEL);
+++	bool ack;
+++	int ret;
+++
+++	reinit_completion(&mgmt_eth_data->rw_done);
+++
+++	/* Increment seq_num and set it in the copy pkt */
+++	mgmt_eth_data->seq++;
+++	qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
+++	mgmt_eth_data->ack = false;
+++
+++	dev_queue_xmit(skb);
+++
+++	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
+++					  QCA8K_ETHERNET_TIMEOUT);
+++
+++	ack = mgmt_eth_data->ack;
+++
+++	if (ret <= 0)
+++		return -ETIMEDOUT;
+++
+++	if (!ack)
+++		return -EINVAL;
+++
+++	*val = mgmt_eth_data->data[0];
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_phy_eth_command(struct qca8k_priv *priv, bool read, int phy,
+++		      int regnum, u16 data)
+++{
+++	struct sk_buff *write_skb, *clear_skb, *read_skb;
+++	struct qca8k_mgmt_eth_data *mgmt_eth_data;
+++	u32 write_val, clear_val = 0, val;
+++	struct net_device *mgmt_master;
+++	int ret, ret1;
+++	bool ack;
+++
+++	if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
+++		return -EINVAL;
+++
+++	mgmt_eth_data = &priv->mgmt_eth_data;
+++
+++	write_val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
+++		    QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
+++		    QCA8K_MDIO_MASTER_REG_ADDR(regnum);
+++
+++	if (read) {
+++		write_val |= QCA8K_MDIO_MASTER_READ;
+++	} else {
+++		write_val |= QCA8K_MDIO_MASTER_WRITE;
+++		write_val |= QCA8K_MDIO_MASTER_DATA(data);
+++	}
+++
+++	/* Prealloc all the needed skb before the lock */
+++	write_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL, &write_val,
+++					    QCA8K_ETHERNET_PHY_PRIORITY, sizeof(write_val));
+++	if (!write_skb)
+++		return -ENOMEM;
+++
+++	clear_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL, &clear_val,
+++					    QCA8K_ETHERNET_PHY_PRIORITY, sizeof(clear_val));
+++	if (!clear_skb) {
+++		ret = -ENOMEM;
+++		goto err_clear_skb;
+++	}
+++
+++	read_skb = qca8k_alloc_mdio_header(MDIO_READ, QCA8K_MDIO_MASTER_CTRL, &clear_val,
+++					   QCA8K_ETHERNET_PHY_PRIORITY, sizeof(clear_val));
+++	if (!read_skb) {
+++		ret = -ENOMEM;
+++		goto err_read_skb;
+++	}
+++
+++	/* Actually start the request:
+++	 * 1. Send mdio master packet
+++	 * 2. Busy Wait for mdio master command
+++	 * 3. Get the data if we are reading
+++	 * 4. Reset the mdio master (even with error)
+++	 */
+++	mutex_lock(&mgmt_eth_data->mutex);
+++
+++	/* Check if mgmt_master is operational */
+++	mgmt_master = priv->mgmt_master;
+++	if (!mgmt_master) {
+++		mutex_unlock(&mgmt_eth_data->mutex);
+++		ret = -EINVAL;
+++		goto err_mgmt_master;
+++	}
+++
+++	read_skb->dev = mgmt_master;
+++	clear_skb->dev = mgmt_master;
+++	write_skb->dev = mgmt_master;
+++
+++	reinit_completion(&mgmt_eth_data->rw_done);
+++
+++	/* Increment seq_num and set it in the write pkt */
+++	mgmt_eth_data->seq++;
+++	qca8k_mdio_header_fill_seq_num(write_skb, mgmt_eth_data->seq);
+++	mgmt_eth_data->ack = false;
+++
+++	dev_queue_xmit(write_skb);
+++
+++	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
+++					  QCA8K_ETHERNET_TIMEOUT);
+++
+++	ack = mgmt_eth_data->ack;
+++
+++	if (ret <= 0) {
+++		ret = -ETIMEDOUT;
+++		kfree_skb(read_skb);
+++		goto exit;
+++	}
+++
+++	if (!ack) {
+++		ret = -EINVAL;
+++		kfree_skb(read_skb);
+++		goto exit;
+++	}
+++
+++	ret = read_poll_timeout(qca8k_phy_eth_busy_wait, ret1,
+++				!(val & QCA8K_MDIO_MASTER_BUSY), 0,
+++				QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
+++				mgmt_eth_data, read_skb, &val);
+++
+++	if (ret < 0 && ret1 < 0) {
+++		ret = ret1;
+++		goto exit;
+++	}
+++
+++	if (read) {
+++		reinit_completion(&mgmt_eth_data->rw_done);
+++
+++		/* Increment seq_num and set it in the read pkt */
+++		mgmt_eth_data->seq++;
+++		qca8k_mdio_header_fill_seq_num(read_skb, mgmt_eth_data->seq);
+++		mgmt_eth_data->ack = false;
+++
+++		dev_queue_xmit(read_skb);
+++
+++		ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
+++						  QCA8K_ETHERNET_TIMEOUT);
+++
+++		ack = mgmt_eth_data->ack;
+++
+++		if (ret <= 0) {
+++			ret = -ETIMEDOUT;
+++			goto exit;
+++		}
+++
+++		if (!ack) {
+++			ret = -EINVAL;
+++			goto exit;
+++		}
+++
+++		ret = mgmt_eth_data->data[0] & QCA8K_MDIO_MASTER_DATA_MASK;
+++	} else {
+++		kfree_skb(read_skb);
+++	}
+++exit:
+++	reinit_completion(&mgmt_eth_data->rw_done);
+++
+++	/* Increment seq_num and set it in the clear pkt */
+++	mgmt_eth_data->seq++;
+++	qca8k_mdio_header_fill_seq_num(clear_skb, mgmt_eth_data->seq);
+++	mgmt_eth_data->ack = false;
+++
+++	dev_queue_xmit(clear_skb);
+++
+++	wait_for_completion_timeout(&mgmt_eth_data->rw_done,
+++				    QCA8K_ETHERNET_TIMEOUT);
+++
+++	mutex_unlock(&mgmt_eth_data->mutex);
+++
+++	return ret;
+++
+++	/* Error handling before lock */
+++err_mgmt_master:
+++	kfree_skb(read_skb);
+++err_read_skb:
+++	kfree_skb(clear_skb);
+++err_clear_skb:
+++	kfree_skb(write_skb);
+++
+++	return ret;
+++}
+++
+++static u32
+++qca8k_port_to_phy(int port)
+++{
+++	/* From Andrew Lunn:
+++	 * Port 0 has no internal phy.
+++	 * Port 1 has an internal PHY at MDIO address 0.
+++	 * Port 2 has an internal PHY at MDIO address 1.
+++	 * ...
+++	 * Port 5 has an internal PHY at MDIO address 4.
+++	 * Port 6 has no internal PHY.
+++	 */
+++
+++	return port - 1;
+++}
+++
+++static int
+++qca8k_mdio_busy_wait(struct mii_bus *bus, u32 reg, u32 mask)
+++{
+++	u16 r1, r2, page;
+++	u32 val;
+++	int ret, ret1;
+++
+++	qca8k_split_addr(reg, &r1, &r2, &page);
+++
+++	ret = read_poll_timeout(qca8k_mii_read32, ret1, !(val & mask), 0,
+++				QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
+++				bus, 0x10 | r2, r1, &val);
+++
+++	/* Check if qca8k_read has failed for a different reason
+++	 * before returnting -ETIMEDOUT
+++	 */
+++	if (ret < 0 && ret1 < 0)
+++		return ret1;
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_mdio_write(struct qca8k_priv *priv, int phy, int regnum, u16 data)
+++{
+++	struct mii_bus *bus = priv->bus;
+++	u16 r1, r2, page;
+++	u32 val;
+++	int ret;
+++
+++	if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
+++		return -EINVAL;
+++
+++	val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
+++	      QCA8K_MDIO_MASTER_WRITE | QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
+++	      QCA8K_MDIO_MASTER_REG_ADDR(regnum) |
+++	      QCA8K_MDIO_MASTER_DATA(data);
+++
+++	qca8k_split_addr(QCA8K_MDIO_MASTER_CTRL, &r1, &r2, &page);
+++
+++	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+++
+++	ret = qca8k_set_page(priv, page);
+++	if (ret)
+++		goto exit;
+++
+++	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
+++
+++	ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
+++				   QCA8K_MDIO_MASTER_BUSY);
+++
+++exit:
+++	/* even if the busy_wait timeouts try to clear the MASTER_EN */
+++	qca8k_mii_write32(priv, 0x10 | r2, r1, 0);
+++
+++	mutex_unlock(&bus->mdio_lock);
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_mdio_read(struct qca8k_priv *priv, int phy, int regnum)
+++{
+++	struct mii_bus *bus = priv->bus;
+++	u16 r1, r2, page;
+++	u32 val;
+++	int ret;
+++
+++	if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
+++		return -EINVAL;
+++
+++	val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
+++	      QCA8K_MDIO_MASTER_READ | QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
+++	      QCA8K_MDIO_MASTER_REG_ADDR(regnum);
+++
+++	qca8k_split_addr(QCA8K_MDIO_MASTER_CTRL, &r1, &r2, &page);
+++
+++	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+++
+++	ret = qca8k_set_page(priv, page);
+++	if (ret)
+++		goto exit;
+++
+++	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
+++
+++	ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
+++				   QCA8K_MDIO_MASTER_BUSY);
+++	if (ret)
+++		goto exit;
+++
+++	ret = qca8k_mii_read32(bus, 0x10 | r2, r1, &val);
+++
+++exit:
+++	/* even if the busy_wait timeouts try to clear the MASTER_EN */
+++	qca8k_mii_write32(priv, 0x10 | r2, r1, 0);
+++
+++	mutex_unlock(&bus->mdio_lock);
+++
+++	if (ret >= 0)
+++		ret = val & QCA8K_MDIO_MASTER_DATA_MASK;
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_internal_mdio_write(struct mii_bus *slave_bus, int phy, int regnum, u16 data)
+++{
+++	struct qca8k_priv *priv = slave_bus->priv;
+++	int ret;
+++
+++	/* Use mdio Ethernet when available, fallback to legacy one on error */
+++	ret = qca8k_phy_eth_command(priv, false, phy, regnum, data);
+++	if (!ret)
+++		return 0;
+++
+++	return qca8k_mdio_write(priv, phy, regnum, data);
+++}
+++
+++static int
+++qca8k_internal_mdio_read(struct mii_bus *slave_bus, int phy, int regnum)
+++{
+++	struct qca8k_priv *priv = slave_bus->priv;
+++	int ret;
+++
+++	/* Use mdio Ethernet when available, fallback to legacy one on error */
+++	ret = qca8k_phy_eth_command(priv, true, phy, regnum, 0);
+++	if (ret >= 0)
+++		return ret;
+++
+++	ret = qca8k_mdio_read(priv, phy, regnum);
+++
+++	if (ret < 0)
+++		return 0xffff;
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_legacy_mdio_write(struct mii_bus *slave_bus, int port, int regnum, u16 data)
+++{
+++	port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
+++
+++	return qca8k_internal_mdio_write(slave_bus, port, regnum, data);
+++}
+++
+++static int
+++qca8k_legacy_mdio_read(struct mii_bus *slave_bus, int port, int regnum)
+++{
+++	port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
+++
+++	return qca8k_internal_mdio_read(slave_bus, port, regnum);
+++}
+++
+++static int
+++qca8k_mdio_register(struct qca8k_priv *priv)
+++{
+++	struct dsa_switch *ds = priv->ds;
+++	struct device_node *mdio;
+++	struct mii_bus *bus;
+++
+++	bus = devm_mdiobus_alloc(ds->dev);
+++	if (!bus)
+++		return -ENOMEM;
+++
+++	bus->priv = (void *)priv;
+++	snprintf(bus->id, MII_BUS_ID_SIZE, "qca8k-%d.%d",
+++		 ds->dst->index, ds->index);
+++	bus->parent = ds->dev;
+++	bus->phy_mask = ~ds->phys_mii_mask;
+++	ds->slave_mii_bus = bus;
+++
+++	/* Check if the devicetree declare the port:phy mapping */
+++	mdio = of_get_child_by_name(priv->dev->of_node, "mdio");
+++	if (of_device_is_available(mdio)) {
+++		bus->name = "qca8k slave mii";
+++		bus->read = qca8k_internal_mdio_read;
+++		bus->write = qca8k_internal_mdio_write;
+++		return devm_of_mdiobus_register(priv->dev, bus, mdio);
+++	}
+++
+++	/* If a mapping can't be found the legacy mapping is used,
+++	 * using the qca8k_port_to_phy function
+++	 */
+++	bus->name = "qca8k-legacy slave mii";
+++	bus->read = qca8k_legacy_mdio_read;
+++	bus->write = qca8k_legacy_mdio_write;
+++	return devm_mdiobus_register(priv->dev, bus);
+++}
+++
+++static int
+++qca8k_setup_mdio_bus(struct qca8k_priv *priv)
+++{
+++	u32 internal_mdio_mask = 0, external_mdio_mask = 0, reg;
+++	struct device_node *ports, *port;
+++	phy_interface_t mode;
+++	int err;
+++
+++	ports = of_get_child_by_name(priv->dev->of_node, "ports");
+++	if (!ports)
+++		ports = of_get_child_by_name(priv->dev->of_node, "ethernet-ports");
+++
+++	if (!ports)
+++		return -EINVAL;
+++
+++	for_each_available_child_of_node(ports, port) {
+++		err = of_property_read_u32(port, "reg", &reg);
+++		if (err) {
+++			of_node_put(port);
+++			of_node_put(ports);
+++			return err;
+++		}
+++
+++		if (!dsa_is_user_port(priv->ds, reg))
+++			continue;
+++
+++		of_get_phy_mode(port, &mode);
+++
+++		if (of_property_read_bool(port, "phy-handle") &&
+++		    mode != PHY_INTERFACE_MODE_INTERNAL)
+++			external_mdio_mask |= BIT(reg);
+++		else
+++			internal_mdio_mask |= BIT(reg);
+++	}
+++
+++	of_node_put(ports);
+++	if (!external_mdio_mask && !internal_mdio_mask) {
+++		dev_err(priv->dev, "no PHYs are defined.\n");
+++		return -EINVAL;
+++	}
+++
+++	/* The QCA8K_MDIO_MASTER_EN Bit, which grants access to PHYs through
+++	 * the MDIO_MASTER register also _disconnects_ the external MDC
+++	 * passthrough to the internal PHYs. It's not possible to use both
+++	 * configurations at the same time!
+++	 *
+++	 * Because this came up during the review process:
+++	 * If the external mdio-bus driver is capable magically disabling
+++	 * the QCA8K_MDIO_MASTER_EN and mutex/spin-locking out the qca8k's
+++	 * accessors for the time being, it would be possible to pull this
+++	 * off.
+++	 */
+++	if (!!external_mdio_mask && !!internal_mdio_mask) {
+++		dev_err(priv->dev, "either internal or external mdio bus configuration is supported.\n");
+++		return -EINVAL;
+++	}
+++
+++	if (external_mdio_mask) {
+++		/* Make sure to disable the internal mdio bus in cases
+++		 * a dt-overlay and driver reload changed the configuration
+++		 */
+++
+++		return regmap_clear_bits(priv->regmap, QCA8K_MDIO_MASTER_CTRL,
+++					 QCA8K_MDIO_MASTER_EN);
+++	}
+++
+++	return qca8k_mdio_register(priv);
+++}
+++
+++static int
+++qca8k_setup_mac_pwr_sel(struct qca8k_priv *priv)
+++{
+++	u32 mask = 0;
+++	int ret = 0;
+++
+++	/* SoC specific settings for ipq8064.
+++	 * If more device require this consider adding
+++	 * a dedicated binding.
+++	 */
+++	if (of_machine_is_compatible("qcom,ipq8064"))
+++		mask |= QCA8K_MAC_PWR_RGMII0_1_8V;
+++
+++	/* SoC specific settings for ipq8065 */
+++	if (of_machine_is_compatible("qcom,ipq8065"))
+++		mask |= QCA8K_MAC_PWR_RGMII1_1_8V;
+++
+++	if (mask) {
+++		ret = qca8k_rmw(priv, QCA8K_REG_MAC_PWR_SEL,
+++				QCA8K_MAC_PWR_RGMII0_1_8V |
+++				QCA8K_MAC_PWR_RGMII1_1_8V,
+++				mask);
+++	}
+++
+++	return ret;
+++}
+++
+++static int qca8k_find_cpu_port(struct dsa_switch *ds)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++
+++	/* Find the connected cpu port. Valid port are 0 or 6 */
+++	if (dsa_is_cpu_port(ds, 0))
+++		return 0;
+++
+++	dev_dbg(priv->dev, "port 0 is not the CPU port. Checking port 6");
+++
+++	if (dsa_is_cpu_port(ds, 6))
+++		return 6;
+++
+++	return -EINVAL;
+++}
+++
+++static int
+++qca8k_setup_of_pws_reg(struct qca8k_priv *priv)
+++{
+++	struct device_node *node = priv->dev->of_node;
+++	const struct qca8k_match_data *data;
+++	u32 val = 0;
+++	int ret;
+++
+++	/* QCA8327 require to set to the correct mode.
+++	 * His bigger brother QCA8328 have the 172 pin layout.
+++	 * Should be applied by default but we set this just to make sure.
+++	 */
+++	if (priv->switch_id == QCA8K_ID_QCA8327) {
+++		data = of_device_get_match_data(priv->dev);
+++
+++		/* Set the correct package of 148 pin for QCA8327 */
+++		if (data->reduced_package)
+++			val |= QCA8327_PWS_PACKAGE148_EN;
+++
+++		ret = qca8k_rmw(priv, QCA8K_REG_PWS, QCA8327_PWS_PACKAGE148_EN,
+++				val);
+++		if (ret)
+++			return ret;
+++	}
+++
+++	if (of_property_read_bool(node, "qca,ignore-power-on-sel"))
+++		val |= QCA8K_PWS_POWER_ON_SEL;
+++
+++	if (of_property_read_bool(node, "qca,led-open-drain")) {
+++		if (!(val & QCA8K_PWS_POWER_ON_SEL)) {
+++			dev_err(priv->dev, "qca,led-open-drain require qca,ignore-power-on-sel to be set.");
+++			return -EINVAL;
+++		}
+++
+++		val |= QCA8K_PWS_LED_OPEN_EN_CSR;
+++	}
+++
+++	return qca8k_rmw(priv, QCA8K_REG_PWS,
+++			QCA8K_PWS_LED_OPEN_EN_CSR | QCA8K_PWS_POWER_ON_SEL,
+++			val);
+++}
+++
+++static int
+++qca8k_parse_port_config(struct qca8k_priv *priv)
+++{
+++	int port, cpu_port_index = -1, ret;
+++	struct device_node *port_dn;
+++	phy_interface_t mode;
+++	struct dsa_port *dp;
+++	u32 delay;
+++
+++	/* We have 2 CPU port. Check them */
+++	for (port = 0; port < QCA8K_NUM_PORTS; port++) {
+++		/* Skip every other port */
+++		if (port != 0 && port != 6)
+++			continue;
+++
+++		dp = dsa_to_port(priv->ds, port);
+++		port_dn = dp->dn;
+++		cpu_port_index++;
+++
+++		if (!of_device_is_available(port_dn))
+++			continue;
+++
+++		ret = of_get_phy_mode(port_dn, &mode);
+++		if (ret)
+++			continue;
+++
+++		switch (mode) {
+++		case PHY_INTERFACE_MODE_RGMII:
+++		case PHY_INTERFACE_MODE_RGMII_ID:
+++		case PHY_INTERFACE_MODE_RGMII_TXID:
+++		case PHY_INTERFACE_MODE_RGMII_RXID:
+++		case PHY_INTERFACE_MODE_SGMII:
+++			delay = 0;
+++
+++			if (!of_property_read_u32(port_dn, "tx-internal-delay-ps", &delay))
+++				/* Switch regs accept value in ns, convert ps to ns */
+++				delay = delay / 1000;
+++			else if (mode == PHY_INTERFACE_MODE_RGMII_ID ||
+++				 mode == PHY_INTERFACE_MODE_RGMII_TXID)
+++				delay = 1;
+++
+++			if (!FIELD_FIT(QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK, delay)) {
+++				dev_err(priv->dev, "rgmii tx delay is limited to a max value of 3ns, setting to the max value");
+++				delay = 3;
+++			}
+++
+++			priv->ports_config.rgmii_tx_delay[cpu_port_index] = delay;
+++
+++			delay = 0;
+++
+++			if (!of_property_read_u32(port_dn, "rx-internal-delay-ps", &delay))
+++				/* Switch regs accept value in ns, convert ps to ns */
+++				delay = delay / 1000;
+++			else if (mode == PHY_INTERFACE_MODE_RGMII_ID ||
+++				 mode == PHY_INTERFACE_MODE_RGMII_RXID)
+++				delay = 2;
+++
+++			if (!FIELD_FIT(QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK, delay)) {
+++				dev_err(priv->dev, "rgmii rx delay is limited to a max value of 3ns, setting to the max value");
+++				delay = 3;
+++			}
+++
+++			priv->ports_config.rgmii_rx_delay[cpu_port_index] = delay;
+++
+++			/* Skip sgmii parsing for rgmii* mode */
+++			if (mode == PHY_INTERFACE_MODE_RGMII ||
+++			    mode == PHY_INTERFACE_MODE_RGMII_ID ||
+++			    mode == PHY_INTERFACE_MODE_RGMII_TXID ||
+++			    mode == PHY_INTERFACE_MODE_RGMII_RXID)
+++				break;
+++
+++			if (of_property_read_bool(port_dn, "qca,sgmii-txclk-falling-edge"))
+++				priv->ports_config.sgmii_tx_clk_falling_edge = true;
+++
+++			if (of_property_read_bool(port_dn, "qca,sgmii-rxclk-falling-edge"))
+++				priv->ports_config.sgmii_rx_clk_falling_edge = true;
+++
+++			if (of_property_read_bool(port_dn, "qca,sgmii-enable-pll")) {
+++				priv->ports_config.sgmii_enable_pll = true;
+++
+++				if (priv->switch_id == QCA8K_ID_QCA8327) {
+++					dev_err(priv->dev, "SGMII PLL should NOT be enabled for qca8327. Aborting enabling");
+++					priv->ports_config.sgmii_enable_pll = false;
+++				}
+++
+++				if (priv->switch_revision < 2)
+++					dev_warn(priv->dev, "SGMII PLL should NOT be enabled for qca8337 with revision 2 or more.");
+++			}
+++
+++			break;
+++		default:
+++			continue;
+++		}
+++	}
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_setup(struct dsa_switch *ds)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	int cpu_port, ret, i;
+++	u32 mask;
+++
+++	cpu_port = qca8k_find_cpu_port(ds);
+++	if (cpu_port < 0) {
+++		dev_err(priv->dev, "No cpu port configured in both cpu port0 and port6");
+++		return cpu_port;
+++	}
+++
+++	/* Parse CPU port config to be later used in phy_link mac_config */
+++	ret = qca8k_parse_port_config(priv);
+++	if (ret)
+++		return ret;
+++
+++	ret = qca8k_setup_mdio_bus(priv);
+++	if (ret)
+++		return ret;
+++
+++	ret = qca8k_setup_of_pws_reg(priv);
+++	if (ret)
+++		return ret;
+++
+++	ret = qca8k_setup_mac_pwr_sel(priv);
+++	if (ret)
+++		return ret;
+++
+++	/* Make sure MAC06 is disabled */
+++	ret = regmap_clear_bits(priv->regmap, QCA8K_REG_PORT0_PAD_CTRL,
+++				QCA8K_PORT0_PAD_MAC06_EXCHANGE_EN);
+++	if (ret) {
+++		dev_err(priv->dev, "failed disabling MAC06 exchange");
+++		return ret;
+++	}
+++
+++	/* Enable CPU Port */
+++	ret = regmap_set_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
+++			      QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
+++	if (ret) {
+++		dev_err(priv->dev, "failed enabling CPU port");
+++		return ret;
+++	}
+++
+++	/* Enable MIB counters */
+++	ret = qca8k_mib_init(priv);
+++	if (ret)
+++		dev_warn(priv->dev, "mib init failed");
+++
+++	/* Initial setup of all ports */
+++	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+++		/* Disable forwarding by default on all ports */
+++		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
+++				QCA8K_PORT_LOOKUP_MEMBER, 0);
+++		if (ret)
+++			return ret;
+++
+++		/* Enable QCA header mode on all cpu ports */
+++		if (dsa_is_cpu_port(ds, i)) {
+++			ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(i),
+++					  FIELD_PREP(QCA8K_PORT_HDR_CTRL_TX_MASK, QCA8K_PORT_HDR_CTRL_ALL) |
+++					  FIELD_PREP(QCA8K_PORT_HDR_CTRL_RX_MASK, QCA8K_PORT_HDR_CTRL_ALL));
+++			if (ret) {
+++				dev_err(priv->dev, "failed enabling QCA header mode");
+++				return ret;
+++			}
+++		}
+++
+++		/* Disable MAC by default on all user ports */
+++		if (dsa_is_user_port(ds, i))
+++			qca8k_port_set_status(priv, i, 0);
+++	}
+++
+++	/* Forward all unknown frames to CPU port for Linux processing
+++	 * Notice that in multi-cpu config only one port should be set
+++	 * for igmp, unknown, multicast and broadcast packet
+++	 */
+++	ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1,
+++			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK, BIT(cpu_port)) |
+++			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK, BIT(cpu_port)) |
+++			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_MC_DP_MASK, BIT(cpu_port)) |
+++			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_UC_DP_MASK, BIT(cpu_port)));
+++	if (ret)
+++		return ret;
+++
+++	/* Setup connection between CPU port & user ports
+++	 * Configure specific switch configuration for ports
+++	 */
+++	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+++		/* CPU port gets connected to all user ports of the switch */
+++		if (dsa_is_cpu_port(ds, i)) {
+++			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
+++					QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds));
+++			if (ret)
+++				return ret;
+++		}
+++
+++		/* Individual user ports get connected to CPU port only */
+++		if (dsa_is_user_port(ds, i)) {
+++			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
+++					QCA8K_PORT_LOOKUP_MEMBER,
+++					BIT(cpu_port));
+++			if (ret)
+++				return ret;
+++
+++			/* Enable ARP Auto-learning by default */
+++			ret = regmap_set_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(i),
+++					      QCA8K_PORT_LOOKUP_LEARN);
+++			if (ret)
+++				return ret;
+++
+++			/* For port based vlans to work we need to set the
+++			 * default egress vid
+++			 */
+++			ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(i),
+++					QCA8K_EGREES_VLAN_PORT_MASK(i),
+++					QCA8K_EGREES_VLAN_PORT(i, QCA8K_PORT_VID_DEF));
+++			if (ret)
+++				return ret;
+++
+++			ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(i),
+++					  QCA8K_PORT_VLAN_CVID(QCA8K_PORT_VID_DEF) |
+++					  QCA8K_PORT_VLAN_SVID(QCA8K_PORT_VID_DEF));
+++			if (ret)
+++				return ret;
+++		}
+++
+++		/* The port 5 of the qca8337 have some problem in flood condition. The
+++		 * original legacy driver had some specific buffer and priority settings
+++		 * for the different port suggested by the QCA switch team. Add this
+++		 * missing settings to improve switch stability under load condition.
+++		 * This problem is limited to qca8337 and other qca8k switch are not affected.
+++		 */
+++		if (priv->switch_id == QCA8K_ID_QCA8337) {
+++			switch (i) {
+++			/* The 2 CPU port and port 5 requires some different
+++			 * priority than any other ports.
+++			 */
+++			case 0:
+++			case 5:
+++			case 6:
+++				mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x4) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x4) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PRI4(0x6) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PRI5(0x8) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PORT(0x1e);
+++				break;
+++			default:
+++				mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x6) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x8) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PORT(0x19);
+++			}
+++			qca8k_write(priv, QCA8K_REG_PORT_HOL_CTRL0(i), mask);
+++
+++			mask = QCA8K_PORT_HOL_CTRL1_ING(0x6) |
+++			QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
+++			QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
+++			QCA8K_PORT_HOL_CTRL1_WRED_EN;
+++			qca8k_rmw(priv, QCA8K_REG_PORT_HOL_CTRL1(i),
+++				  QCA8K_PORT_HOL_CTRL1_ING_BUF_MASK |
+++				  QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
+++				  QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
+++				  QCA8K_PORT_HOL_CTRL1_WRED_EN,
+++				  mask);
+++		}
+++	}
+++
+++	/* Special GLOBAL_FC_THRESH value are needed for ar8327 switch */
+++	if (priv->switch_id == QCA8K_ID_QCA8327) {
+++		mask = QCA8K_GLOBAL_FC_GOL_XON_THRES(288) |
+++		       QCA8K_GLOBAL_FC_GOL_XOFF_THRES(496);
+++		qca8k_rmw(priv, QCA8K_REG_GLOBAL_FC_THRESH,
+++			  QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK |
+++			  QCA8K_GLOBAL_FC_GOL_XOFF_THRES_MASK,
+++			  mask);
+++	}
+++
+++	/* Setup our port MTUs to match power on defaults */
+++	ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, ETH_FRAME_LEN + ETH_FCS_LEN);
+++	if (ret)
+++		dev_warn(priv->dev, "failed setting MTU settings");
+++
+++	/* Flush the FDB table */
+++	qca8k_fdb_flush(priv);
+++
+++	/* We don't have interrupts for link changes, so we need to poll */
+++	ds->pcs_poll = true;
+++
+++	/* Set min a max ageing value supported */
+++	ds->ageing_time_min = 7000;
+++	ds->ageing_time_max = 458745000;
+++
+++	/* Set max number of LAGs supported */
+++	ds->num_lag_ids = QCA8K_NUM_LAGS;
+++
+++	return 0;
+++}
+++
+++static void
+++qca8k_mac_config_setup_internal_delay(struct qca8k_priv *priv, int cpu_port_index,
+++				      u32 reg)
+++{
+++	u32 delay, val = 0;
+++	int ret;
+++
+++	/* Delay can be declared in 3 different way.
+++	 * Mode to rgmii and internal-delay standard binding defined
+++	 * rgmii-id or rgmii-tx/rx phy mode set.
+++	 * The parse logic set a delay different than 0 only when one
+++	 * of the 3 different way is used. In all other case delay is
+++	 * not enabled. With ID or TX/RXID delay is enabled and set
+++	 * to the default and recommended value.
+++	 */
+++	if (priv->ports_config.rgmii_tx_delay[cpu_port_index]) {
+++		delay = priv->ports_config.rgmii_tx_delay[cpu_port_index];
+++
+++		val |= QCA8K_PORT_PAD_RGMII_TX_DELAY(delay) |
+++			QCA8K_PORT_PAD_RGMII_TX_DELAY_EN;
+++	}
+++
+++	if (priv->ports_config.rgmii_rx_delay[cpu_port_index]) {
+++		delay = priv->ports_config.rgmii_rx_delay[cpu_port_index];
+++
+++		val |= QCA8K_PORT_PAD_RGMII_RX_DELAY(delay) |
+++			QCA8K_PORT_PAD_RGMII_RX_DELAY_EN;
+++	}
+++
+++	/* Set RGMII delay based on the selected values */
+++	ret = qca8k_rmw(priv, reg,
+++			QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK |
+++			QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK |
+++			QCA8K_PORT_PAD_RGMII_TX_DELAY_EN |
+++			QCA8K_PORT_PAD_RGMII_RX_DELAY_EN,
+++			val);
+++	if (ret)
+++		dev_err(priv->dev, "Failed to set internal delay for CPU port%d",
+++			cpu_port_index == QCA8K_CPU_PORT0 ? 0 : 6);
+++}
+++
+++static void
+++qca8k_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode,
+++			 const struct phylink_link_state *state)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int cpu_port_index, ret;
+++	u32 reg, val;
+++
+++	switch (port) {
+++	case 0: /* 1st CPU port */
+++		if (state->interface != PHY_INTERFACE_MODE_RGMII &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
+++		    state->interface != PHY_INTERFACE_MODE_SGMII)
+++			return;
+++
+++		reg = QCA8K_REG_PORT0_PAD_CTRL;
+++		cpu_port_index = QCA8K_CPU_PORT0;
+++		break;
+++	case 1:
+++	case 2:
+++	case 3:
+++	case 4:
+++	case 5:
+++		/* Internal PHY, nothing to do */
+++		return;
+++	case 6: /* 2nd CPU port / external PHY */
+++		if (state->interface != PHY_INTERFACE_MODE_RGMII &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
+++		    state->interface != PHY_INTERFACE_MODE_SGMII &&
+++		    state->interface != PHY_INTERFACE_MODE_1000BASEX)
+++			return;
+++
+++		reg = QCA8K_REG_PORT6_PAD_CTRL;
+++		cpu_port_index = QCA8K_CPU_PORT6;
+++		break;
+++	default:
+++		dev_err(ds->dev, "%s: unsupported port: %i\n", __func__, port);
+++		return;
+++	}
+++
+++	if (port != 6 && phylink_autoneg_inband(mode)) {
+++		dev_err(ds->dev, "%s: in-band negotiation unsupported\n",
+++			__func__);
+++		return;
+++	}
+++
+++	switch (state->interface) {
+++	case PHY_INTERFACE_MODE_RGMII:
+++	case PHY_INTERFACE_MODE_RGMII_ID:
+++	case PHY_INTERFACE_MODE_RGMII_TXID:
+++	case PHY_INTERFACE_MODE_RGMII_RXID:
+++		qca8k_write(priv, reg, QCA8K_PORT_PAD_RGMII_EN);
+++
+++		/* Configure rgmii delay */
+++		qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg);
+++
+++		/* QCA8337 requires to set rgmii rx delay for all ports.
+++		 * This is enabled through PORT5_PAD_CTRL for all ports,
+++		 * rather than individual port registers.
+++		 */
+++		if (priv->switch_id == QCA8K_ID_QCA8337)
+++			qca8k_write(priv, QCA8K_REG_PORT5_PAD_CTRL,
+++				    QCA8K_PORT_PAD_RGMII_RX_DELAY_EN);
+++		break;
+++	case PHY_INTERFACE_MODE_SGMII:
+++	case PHY_INTERFACE_MODE_1000BASEX:
+++		/* Enable SGMII on the port */
+++		qca8k_write(priv, reg, QCA8K_PORT_PAD_SGMII_EN);
+++
+++		/* Enable/disable SerDes auto-negotiation as necessary */
+++		ret = qca8k_read(priv, QCA8K_REG_PWS, &val);
+++		if (ret)
+++			return;
+++		if (phylink_autoneg_inband(mode))
+++			val &= ~QCA8K_PWS_SERDES_AEN_DIS;
+++		else
+++			val |= QCA8K_PWS_SERDES_AEN_DIS;
+++		qca8k_write(priv, QCA8K_REG_PWS, val);
+++
+++		/* Configure the SGMII parameters */
+++		ret = qca8k_read(priv, QCA8K_REG_SGMII_CTRL, &val);
+++		if (ret)
+++			return;
+++
+++		val |= QCA8K_SGMII_EN_SD;
+++
+++		if (priv->ports_config.sgmii_enable_pll)
+++			val |= QCA8K_SGMII_EN_PLL | QCA8K_SGMII_EN_RX |
+++			       QCA8K_SGMII_EN_TX;
+++
+++		if (dsa_is_cpu_port(ds, port)) {
+++			/* CPU port, we're talking to the CPU MAC, be a PHY */
+++			val &= ~QCA8K_SGMII_MODE_CTRL_MASK;
+++			val |= QCA8K_SGMII_MODE_CTRL_PHY;
+++		} else if (state->interface == PHY_INTERFACE_MODE_SGMII) {
+++			val &= ~QCA8K_SGMII_MODE_CTRL_MASK;
+++			val |= QCA8K_SGMII_MODE_CTRL_MAC;
+++		} else if (state->interface == PHY_INTERFACE_MODE_1000BASEX) {
+++			val &= ~QCA8K_SGMII_MODE_CTRL_MASK;
+++			val |= QCA8K_SGMII_MODE_CTRL_BASEX;
+++		}
+++
+++		qca8k_write(priv, QCA8K_REG_SGMII_CTRL, val);
+++
+++		/* From original code is reported port instability as SGMII also
+++		 * require delay set. Apply advised values here or take them from DT.
+++		 */
+++		if (state->interface == PHY_INTERFACE_MODE_SGMII)
+++			qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg);
+++
+++		/* For qca8327/qca8328/qca8334/qca8338 sgmii is unique and
+++		 * falling edge is set writing in the PORT0 PAD reg
+++		 */
+++		if (priv->switch_id == QCA8K_ID_QCA8327 ||
+++		    priv->switch_id == QCA8K_ID_QCA8337)
+++			reg = QCA8K_REG_PORT0_PAD_CTRL;
+++
+++		val = 0;
+++
+++		/* SGMII Clock phase configuration */
+++		if (priv->ports_config.sgmii_rx_clk_falling_edge)
+++			val |= QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE;
+++
+++		if (priv->ports_config.sgmii_tx_clk_falling_edge)
+++			val |= QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE;
+++
+++		if (val)
+++			ret = qca8k_rmw(priv, reg,
+++					QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE |
+++					QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE,
+++					val);
+++
+++		break;
+++	default:
+++		dev_err(ds->dev, "xMII mode %s not supported for port %d\n",
+++			phy_modes(state->interface), port);
+++		return;
+++	}
+++}
+++
+++static void
+++qca8k_phylink_validate(struct dsa_switch *ds, int port,
+++		       unsigned long *supported,
+++		       struct phylink_link_state *state)
+++{
+++	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
+++
+++	switch (port) {
+++	case 0: /* 1st CPU port */
+++		if (state->interface != PHY_INTERFACE_MODE_NA &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
+++		    state->interface != PHY_INTERFACE_MODE_SGMII)
+++			goto unsupported;
+++		break;
+++	case 1:
+++	case 2:
+++	case 3:
+++	case 4:
+++	case 5:
+++		/* Internal PHY */
+++		if (state->interface != PHY_INTERFACE_MODE_NA &&
+++		    state->interface != PHY_INTERFACE_MODE_GMII &&
+++		    state->interface != PHY_INTERFACE_MODE_INTERNAL)
+++			goto unsupported;
+++		break;
+++	case 6: /* 2nd CPU port / external PHY */
+++		if (state->interface != PHY_INTERFACE_MODE_NA &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
+++		    state->interface != PHY_INTERFACE_MODE_SGMII &&
+++		    state->interface != PHY_INTERFACE_MODE_1000BASEX)
+++			goto unsupported;
+++		break;
+++	default:
+++unsupported:
+++		linkmode_zero(supported);
+++		return;
+++	}
+++
+++	phylink_set_port_modes(mask);
+++	phylink_set(mask, Autoneg);
+++
+++	phylink_set(mask, 1000baseT_Full);
+++	phylink_set(mask, 10baseT_Half);
+++	phylink_set(mask, 10baseT_Full);
+++	phylink_set(mask, 100baseT_Half);
+++	phylink_set(mask, 100baseT_Full);
+++
+++	if (state->interface == PHY_INTERFACE_MODE_1000BASEX)
+++		phylink_set(mask, 1000baseX_Full);
+++
+++	phylink_set(mask, Pause);
+++	phylink_set(mask, Asym_Pause);
+++
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
+++}
+++
+++static int
+++qca8k_phylink_mac_link_state(struct dsa_switch *ds, int port,
+++			     struct phylink_link_state *state)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	u32 reg;
+++	int ret;
+++
+++	ret = qca8k_read(priv, QCA8K_REG_PORT_STATUS(port), &reg);
+++	if (ret < 0)
+++		return ret;
+++
+++	state->link = !!(reg & QCA8K_PORT_STATUS_LINK_UP);
+++	state->an_complete = state->link;
+++	state->an_enabled = !!(reg & QCA8K_PORT_STATUS_LINK_AUTO);
+++	state->duplex = (reg & QCA8K_PORT_STATUS_DUPLEX) ? DUPLEX_FULL :
+++							   DUPLEX_HALF;
+++
+++	switch (reg & QCA8K_PORT_STATUS_SPEED) {
+++	case QCA8K_PORT_STATUS_SPEED_10:
+++		state->speed = SPEED_10;
+++		break;
+++	case QCA8K_PORT_STATUS_SPEED_100:
+++		state->speed = SPEED_100;
+++		break;
+++	case QCA8K_PORT_STATUS_SPEED_1000:
+++		state->speed = SPEED_1000;
+++		break;
+++	default:
+++		state->speed = SPEED_UNKNOWN;
+++		break;
+++	}
+++
+++	state->pause = MLO_PAUSE_NONE;
+++	if (reg & QCA8K_PORT_STATUS_RXFLOW)
+++		state->pause |= MLO_PAUSE_RX;
+++	if (reg & QCA8K_PORT_STATUS_TXFLOW)
+++		state->pause |= MLO_PAUSE_TX;
+++
+++	return 1;
+++}
+++
+++static void
+++qca8k_phylink_mac_link_down(struct dsa_switch *ds, int port, unsigned int mode,
+++			    phy_interface_t interface)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++
+++	qca8k_port_set_status(priv, port, 0);
+++}
+++
+++static void
+++qca8k_phylink_mac_link_up(struct dsa_switch *ds, int port, unsigned int mode,
+++			  phy_interface_t interface, struct phy_device *phydev,
+++			  int speed, int duplex, bool tx_pause, bool rx_pause)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	u32 reg;
+++
+++	if (phylink_autoneg_inband(mode)) {
+++		reg = QCA8K_PORT_STATUS_LINK_AUTO;
+++	} else {
+++		switch (speed) {
+++		case SPEED_10:
+++			reg = QCA8K_PORT_STATUS_SPEED_10;
+++			break;
+++		case SPEED_100:
+++			reg = QCA8K_PORT_STATUS_SPEED_100;
+++			break;
+++		case SPEED_1000:
+++			reg = QCA8K_PORT_STATUS_SPEED_1000;
+++			break;
+++		default:
+++			reg = QCA8K_PORT_STATUS_LINK_AUTO;
+++			break;
+++		}
+++
+++		if (duplex == DUPLEX_FULL)
+++			reg |= QCA8K_PORT_STATUS_DUPLEX;
+++
+++		if (rx_pause || dsa_is_cpu_port(ds, port))
+++			reg |= QCA8K_PORT_STATUS_RXFLOW;
+++
+++		if (tx_pause || dsa_is_cpu_port(ds, port))
+++			reg |= QCA8K_PORT_STATUS_TXFLOW;
+++	}
+++
+++	reg |= QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
+++
+++	qca8k_write(priv, QCA8K_REG_PORT_STATUS(port), reg);
+++}
+++
+++static void
+++qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data)
+++{
+++	const struct qca8k_match_data *match_data;
+++	struct qca8k_priv *priv = ds->priv;
+++	int i;
+++
+++	if (stringset != ETH_SS_STATS)
+++		return;
+++
+++	match_data = of_device_get_match_data(priv->dev);
+++
+++	for (i = 0; i < match_data->mib_count; i++)
+++		strncpy(data + i * ETH_GSTRING_LEN, ar8327_mib[i].name,
+++			ETH_GSTRING_LEN);
+++}
+++
+++static void qca8k_mib_autocast_handler(struct dsa_switch *ds, struct sk_buff *skb)
+++{
+++	const struct qca8k_match_data *match_data;
+++	struct qca8k_mib_eth_data *mib_eth_data;
+++	struct qca8k_priv *priv = ds->priv;
+++	const struct qca8k_mib_desc *mib;
+++	struct mib_ethhdr *mib_ethhdr;
+++	int i, mib_len, offset = 0;
+++	u64 *data;
+++	u8 port;
+++
+++	mib_ethhdr = (struct mib_ethhdr *)skb_mac_header(skb);
+++	mib_eth_data = &priv->mib_eth_data;
+++
+++	/* The switch autocast every port. Ignore other packet and
+++	 * parse only the requested one.
+++	 */
+++	port = FIELD_GET(QCA_HDR_RECV_SOURCE_PORT, ntohs(mib_ethhdr->hdr));
+++	if (port != mib_eth_data->req_port)
+++		goto exit;
+++
+++	match_data = device_get_match_data(priv->dev);
+++	data = mib_eth_data->data;
+++
+++	for (i = 0; i < match_data->mib_count; i++) {
+++		mib = &ar8327_mib[i];
+++
+++		/* First 3 mib are present in the skb head */
+++		if (i < 3) {
+++			data[i] = mib_ethhdr->data[i];
+++			continue;
+++		}
+++
+++		mib_len = sizeof(uint32_t);
+++
+++		/* Some mib are 64 bit wide */
+++		if (mib->size == 2)
+++			mib_len = sizeof(uint64_t);
+++
+++		/* Copy the mib value from packet to the */
+++		memcpy(data + i, skb->data + offset, mib_len);
+++
+++		/* Set the offset for the next mib */
+++		offset += mib_len;
+++	}
+++
+++exit:
+++	/* Complete on receiving all the mib packet */
+++	if (refcount_dec_and_test(&mib_eth_data->port_parsed))
+++		complete(&mib_eth_data->rw_done);
+++}
+++
+++static int
+++qca8k_get_ethtool_stats_eth(struct dsa_switch *ds, int port, u64 *data)
+++{
+++	struct dsa_port *dp = dsa_to_port(ds, port);
+++	struct qca8k_mib_eth_data *mib_eth_data;
+++	struct qca8k_priv *priv = ds->priv;
+++	int ret;
+++
+++	mib_eth_data = &priv->mib_eth_data;
+++
+++	mutex_lock(&mib_eth_data->mutex);
+++
+++	reinit_completion(&mib_eth_data->rw_done);
+++
+++	mib_eth_data->req_port = dp->index;
+++	mib_eth_data->data = data;
+++	refcount_set(&mib_eth_data->port_parsed, QCA8K_NUM_PORTS);
+++
+++	mutex_lock(&priv->reg_mutex);
+++
+++	/* Send mib autocast request */
+++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB,
+++				 QCA8K_MIB_FUNC | QCA8K_MIB_BUSY,
+++				 FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_CAST) |
+++				 QCA8K_MIB_BUSY);
+++
+++	mutex_unlock(&priv->reg_mutex);
+++
+++	if (ret)
+++		goto exit;
+++
+++	ret = wait_for_completion_timeout(&mib_eth_data->rw_done, QCA8K_ETHERNET_TIMEOUT);
+++
+++exit:
+++	mutex_unlock(&mib_eth_data->mutex);
+++
+++	return ret;
+++}
+++
+++static void
+++qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
+++			uint64_t *data)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	const struct qca8k_match_data *match_data;
+++	const struct qca8k_mib_desc *mib;
+++	u32 reg, i, val;
+++	u32 hi = 0;
+++	int ret;
+++
+++	if (priv->mgmt_master &&
+++	    qca8k_get_ethtool_stats_eth(ds, port, data) > 0)
+++		return;
+++
+++	match_data = of_device_get_match_data(priv->dev);
+++
+++	for (i = 0; i < match_data->mib_count; i++) {
+++		mib = &ar8327_mib[i];
+++		reg = QCA8K_PORT_MIB_COUNTER(port) + mib->offset;
+++
+++		ret = qca8k_read(priv, reg, &val);
+++		if (ret < 0)
+++			continue;
+++
+++		if (mib->size == 2) {
+++			ret = qca8k_read(priv, reg + 4, &hi);
+++			if (ret < 0)
+++				continue;
+++		}
+++
+++		data[i] = val;
+++		if (mib->size == 2)
+++			data[i] |= (u64)hi << 32;
+++	}
+++}
+++
+++static int
+++qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset)
+++{
+++	const struct qca8k_match_data *match_data;
+++	struct qca8k_priv *priv = ds->priv;
+++
+++	if (sset != ETH_SS_STATS)
+++		return 0;
+++
+++	match_data = of_device_get_match_data(priv->dev);
+++
+++	return match_data->mib_count;
+++}
+++
+++static int
+++qca8k_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *eee)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	u32 lpi_en = QCA8K_REG_EEE_CTRL_LPI_EN(port);
+++	u32 reg;
+++	int ret;
+++
+++	mutex_lock(&priv->reg_mutex);
+++	ret = qca8k_read(priv, QCA8K_REG_EEE_CTRL, &reg);
+++	if (ret < 0)
+++		goto exit;
+++
+++	if (eee->eee_enabled)
+++		reg |= lpi_en;
+++	else
+++		reg &= ~lpi_en;
+++	ret = qca8k_write(priv, QCA8K_REG_EEE_CTRL, reg);
+++
+++exit:
+++	mutex_unlock(&priv->reg_mutex);
+++	return ret;
+++}
+++
+++static int
+++qca8k_get_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e)
+++{
+++	/* Nothing to do on the port's MAC */
+++	return 0;
+++}
+++
+++static void
+++qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	u32 stp_state;
+++
+++	switch (state) {
+++	case BR_STATE_DISABLED:
+++		stp_state = QCA8K_PORT_LOOKUP_STATE_DISABLED;
+++		break;
+++	case BR_STATE_BLOCKING:
+++		stp_state = QCA8K_PORT_LOOKUP_STATE_BLOCKING;
+++		break;
+++	case BR_STATE_LISTENING:
+++		stp_state = QCA8K_PORT_LOOKUP_STATE_LISTENING;
+++		break;
+++	case BR_STATE_LEARNING:
+++		stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING;
+++		break;
+++	case BR_STATE_FORWARDING:
+++	default:
+++		stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD;
+++		break;
+++	}
+++
+++	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+++		  QCA8K_PORT_LOOKUP_STATE_MASK, stp_state);
+++}
+++
+++static int
+++qca8k_port_bridge_join(struct dsa_switch *ds, int port, struct net_device *br)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	int port_mask, cpu_port;
+++	int i, ret;
+++
+++	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
+++	port_mask = BIT(cpu_port);
+++
+++	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+++		if (dsa_is_cpu_port(ds, i))
+++			continue;
+++		if (dsa_to_port(ds, i)->bridge_dev != br)
+++			continue;
+++		/* Add this port to the portvlan mask of the other ports
+++		 * in the bridge
+++		 */
+++		ret = regmap_set_bits(priv->regmap,
+++				      QCA8K_PORT_LOOKUP_CTRL(i),
+++				      BIT(port));
+++		if (ret)
+++			return ret;
+++		if (i != port)
+++			port_mask |= BIT(i);
+++	}
+++
+++	/* Add all other ports to this ports portvlan mask */
+++	ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+++			QCA8K_PORT_LOOKUP_MEMBER, port_mask);
+++
+++	return ret;
+++}
+++
+++static void
+++qca8k_port_bridge_leave(struct dsa_switch *ds, int port, struct net_device *br)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	int cpu_port, i;
+++
+++	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
+++
+++	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+++		if (dsa_is_cpu_port(ds, i))
+++			continue;
+++		if (dsa_to_port(ds, i)->bridge_dev != br)
+++			continue;
+++		/* Remove this port to the portvlan mask of the other ports
+++		 * in the bridge
+++		 */
+++		regmap_clear_bits(priv->regmap,
+++				  QCA8K_PORT_LOOKUP_CTRL(i),
+++				  BIT(port));
+++	}
+++
+++	/* Set the cpu port to be the only one in the portvlan mask of
+++	 * this port
+++	 */
+++	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+++		  QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port));
+++}
+++
+++static void
+++qca8k_port_fast_age(struct dsa_switch *ds, int port)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++
+++	mutex_lock(&priv->reg_mutex);
+++	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port);
+++	mutex_unlock(&priv->reg_mutex);
+++}
+++
+++static int
+++qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	unsigned int secs = msecs / 1000;
+++	u32 val;
+++
+++	/* AGE_TIME reg is set in 7s step */
+++	val = secs / 7;
+++
+++	/* Handle case with 0 as val to NOT disable
+++	 * learning
+++	 */
+++	if (!val)
+++		val = 1;
+++
+++	return regmap_update_bits(priv->regmap, QCA8K_REG_ATU_CTRL, QCA8K_ATU_AGE_TIME_MASK,
+++				  QCA8K_ATU_AGE_TIME(val));
+++}
+++
+++static int
+++qca8k_port_enable(struct dsa_switch *ds, int port,
+++		  struct phy_device *phy)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++
+++	qca8k_port_set_status(priv, port, 1);
+++	priv->port_enabled_map |= BIT(port);
+++
+++	if (dsa_is_user_port(ds, port))
+++		phy_support_asym_pause(phy);
+++
+++	return 0;
+++}
+++
+++static void
+++qca8k_port_disable(struct dsa_switch *ds, int port)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++
+++	qca8k_port_set_status(priv, port, 0);
+++	priv->port_enabled_map &= ~BIT(port);
+++}
+++
+++static int
+++qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int ret;
+++
+++	/* We have only have a general MTU setting.
+++	 * DSA always set the CPU port's MTU to the largest MTU of the slave
+++	 * ports.
+++	 * Setting MTU just for the CPU port is sufficient to correctly set a
+++	 * value for every port.
+++	 */
+++	if (!dsa_is_cpu_port(ds, port))
+++		return 0;
+++
+++	/* To change the MAX_FRAME_SIZE the cpu ports must be off or
+++	 * the switch panics.
+++	 * Turn off both cpu ports before applying the new value to prevent
+++	 * this.
+++	 */
+++	if (priv->port_enabled_map & BIT(0))
+++		qca8k_port_set_status(priv, 0, 0);
+++
+++	if (priv->port_enabled_map & BIT(6))
+++		qca8k_port_set_status(priv, 6, 0);
+++
+++	/* Include L2 header / FCS length */
+++	ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, new_mtu + ETH_HLEN + ETH_FCS_LEN);
+++
+++	if (priv->port_enabled_map & BIT(0))
+++		qca8k_port_set_status(priv, 0, 1);
+++
+++	if (priv->port_enabled_map & BIT(6))
+++		qca8k_port_set_status(priv, 6, 1);
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_port_max_mtu(struct dsa_switch *ds, int port)
+++{
+++	return QCA8K_MAX_MTU;
+++}
+++
+++static int
+++qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
+++		      u16 port_mask, u16 vid)
+++{
+++	/* Set the vid to the port vlan id if no vid is set */
+++	if (!vid)
+++		vid = QCA8K_PORT_VID_DEF;
+++
+++	return qca8k_fdb_add(priv, addr, port_mask, vid,
+++			     QCA8K_ATU_STATUS_STATIC);
+++}
+++
+++static int
+++qca8k_port_fdb_add(struct dsa_switch *ds, int port,
+++		   const unsigned char *addr, u16 vid)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	u16 port_mask = BIT(port);
+++
+++	return qca8k_port_fdb_insert(priv, addr, port_mask, vid);
+++}
+++
+++static int
+++qca8k_port_fdb_del(struct dsa_switch *ds, int port,
+++		   const unsigned char *addr, u16 vid)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	u16 port_mask = BIT(port);
+++
+++	if (!vid)
+++		vid = QCA8K_PORT_VID_DEF;
+++
+++	return qca8k_fdb_del(priv, addr, port_mask, vid);
+++}
+++
+++static int
+++qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
+++		    dsa_fdb_dump_cb_t *cb, void *data)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	struct qca8k_fdb _fdb = { 0 };
+++	int cnt = QCA8K_NUM_FDB_RECORDS;
+++	bool is_static;
+++	int ret = 0;
+++
+++	mutex_lock(&priv->reg_mutex);
+++	while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port)) {
+++		if (!_fdb.aging)
+++			break;
+++		is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC);
+++		ret = cb(_fdb.mac, _fdb.vid, is_static, data);
+++		if (ret)
+++			break;
+++	}
+++	mutex_unlock(&priv->reg_mutex);
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_port_mdb_add(struct dsa_switch *ds, int port,
+++		   const struct switchdev_obj_port_mdb *mdb)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	const u8 *addr = mdb->addr;
+++	u16 vid = mdb->vid;
+++
+++	return qca8k_fdb_search_and_insert(priv, BIT(port), addr, vid);
+++}
+++
+++static int
+++qca8k_port_mdb_del(struct dsa_switch *ds, int port,
+++		   const struct switchdev_obj_port_mdb *mdb)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	const u8 *addr = mdb->addr;
+++	u16 vid = mdb->vid;
+++
+++	return qca8k_fdb_search_and_del(priv, BIT(port), addr, vid);
+++}
+++
+++static int
+++qca8k_port_mirror_add(struct dsa_switch *ds, int port,
+++		      struct dsa_mall_mirror_tc_entry *mirror,
+++		      bool ingress)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int monitor_port, ret;
+++	u32 reg, val;
+++
+++	/* Check for existent entry */
+++	if ((ingress ? priv->mirror_rx : priv->mirror_tx) & BIT(port))
+++		return -EEXIST;
+++
+++	ret = regmap_read(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0, &val);
+++	if (ret)
+++		return ret;
+++
+++	/* QCA83xx can have only one port set to mirror mode.
+++	 * Check that the correct port is requested and return error otherwise.
+++	 * When no mirror port is set, the values is set to 0xF
+++	 */
+++	monitor_port = FIELD_GET(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
+++	if (monitor_port != 0xF && monitor_port != mirror->to_local_port)
+++		return -EEXIST;
+++
+++	/* Set the monitor port */
+++	val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM,
+++			 mirror->to_local_port);
+++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
+++				 QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
+++	if (ret)
+++		return ret;
+++
+++	if (ingress) {
+++		reg = QCA8K_PORT_LOOKUP_CTRL(port);
+++		val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN;
+++	} else {
+++		reg = QCA8K_REG_PORT_HOL_CTRL1(port);
+++		val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN;
+++	}
+++
+++	ret = regmap_update_bits(priv->regmap, reg, val, val);
+++	if (ret)
+++		return ret;
+++
+++	/* Track mirror port for tx and rx to decide when the
+++	 * mirror port has to be disabled.
+++	 */
+++	if (ingress)
+++		priv->mirror_rx |= BIT(port);
+++	else
+++		priv->mirror_tx |= BIT(port);
+++
+++	return 0;
+++}
+++
+++static void
+++qca8k_port_mirror_del(struct dsa_switch *ds, int port,
+++		      struct dsa_mall_mirror_tc_entry *mirror)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	u32 reg, val;
+++	int ret;
+++
+++	if (mirror->ingress) {
+++		reg = QCA8K_PORT_LOOKUP_CTRL(port);
+++		val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN;
+++	} else {
+++		reg = QCA8K_REG_PORT_HOL_CTRL1(port);
+++		val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN;
+++	}
+++
+++	ret = regmap_clear_bits(priv->regmap, reg, val);
+++	if (ret)
+++		goto err;
+++
+++	if (mirror->ingress)
+++		priv->mirror_rx &= ~BIT(port);
+++	else
+++		priv->mirror_tx &= ~BIT(port);
+++
+++	/* No port set to send packet to mirror port. Disable mirror port */
+++	if (!priv->mirror_rx && !priv->mirror_tx) {
+++		val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, 0xF);
+++		ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
+++					 QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
+++		if (ret)
+++			goto err;
+++	}
+++err:
+++	dev_err(priv->dev, "Failed to del mirror port from %d", port);
+++}
+++
+++static int
+++qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
+++			  struct netlink_ext_ack *extack)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int ret;
+++
+++	if (vlan_filtering) {
+++		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+++				QCA8K_PORT_LOOKUP_VLAN_MODE_MASK,
+++				QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE);
+++	} else {
+++		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+++				QCA8K_PORT_LOOKUP_VLAN_MODE_MASK,
+++				QCA8K_PORT_LOOKUP_VLAN_MODE_NONE);
+++	}
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_port_vlan_add(struct dsa_switch *ds, int port,
+++		    const struct switchdev_obj_port_vlan *vlan,
+++		    struct netlink_ext_ack *extack)
+++{
+++	bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
+++	bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
+++	struct qca8k_priv *priv = ds->priv;
+++	int ret;
+++
+++	ret = qca8k_vlan_add(priv, port, vlan->vid, untagged);
+++	if (ret) {
+++		dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port, ret);
+++		return ret;
+++	}
+++
+++	if (pvid) {
+++		ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port),
+++				QCA8K_EGREES_VLAN_PORT_MASK(port),
+++				QCA8K_EGREES_VLAN_PORT(port, vlan->vid));
+++		if (ret)
+++			return ret;
+++
+++		ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port),
+++				  QCA8K_PORT_VLAN_CVID(vlan->vid) |
+++				  QCA8K_PORT_VLAN_SVID(vlan->vid));
+++	}
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_port_vlan_del(struct dsa_switch *ds, int port,
+++		    const struct switchdev_obj_port_vlan *vlan)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int ret;
+++
+++	ret = qca8k_vlan_del(priv, port, vlan->vid);
+++	if (ret)
+++		dev_err(priv->dev, "Failed to delete VLAN from port %d (%d)", port, ret);
+++
+++	return ret;
+++}
+++
+++static u32 qca8k_get_phy_flags(struct dsa_switch *ds, int port)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++
+++	/* Communicate to the phy internal driver the switch revision.
+++	 * Based on the switch revision different values needs to be
+++	 * set to the dbg and mmd reg on the phy.
+++	 * The first 2 bit are used to communicate the switch revision
+++	 * to the phy driver.
+++	 */
+++	if (port > 0 && port < 6)
+++		return priv->switch_revision;
+++
+++	return 0;
+++}
+++
+++static enum dsa_tag_protocol
+++qca8k_get_tag_protocol(struct dsa_switch *ds, int port,
+++		       enum dsa_tag_protocol mp)
+++{
+++	return DSA_TAG_PROTO_QCA;
+++}
+++
+++static bool
+++qca8k_lag_can_offload(struct dsa_switch *ds,
+++		      struct net_device *lag,
+++		      struct netdev_lag_upper_info *info)
+++{
+++	struct dsa_port *dp;
+++	int id, members = 0;
+++
+++	id = dsa_lag_id(ds->dst, lag);
+++	if (id < 0 || id >= ds->num_lag_ids)
+++		return false;
+++
+++	dsa_lag_foreach_port(dp, ds->dst, lag)
+++		/* Includes the port joining the LAG */
+++		members++;
+++
+++	if (members > QCA8K_NUM_PORTS_FOR_LAG)
+++		return false;
+++
+++	if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH)
+++		return false;
+++
+++	if (info->hash_type != NETDEV_LAG_HASH_L2 &&
+++	    info->hash_type != NETDEV_LAG_HASH_L23)
+++		return false;
+++
+++	return true;
+++}
+++
+++static int
+++qca8k_lag_setup_hash(struct dsa_switch *ds,
+++		     struct net_device *lag,
+++		     struct netdev_lag_upper_info *info)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	bool unique_lag = true;
+++	u32 hash = 0;
+++	int i, id;
+++
+++	id = dsa_lag_id(ds->dst, lag);
+++
+++	switch (info->hash_type) {
+++	case NETDEV_LAG_HASH_L23:
+++		hash |= QCA8K_TRUNK_HASH_SIP_EN;
+++		hash |= QCA8K_TRUNK_HASH_DIP_EN;
+++		fallthrough;
+++	case NETDEV_LAG_HASH_L2:
+++		hash |= QCA8K_TRUNK_HASH_SA_EN;
+++		hash |= QCA8K_TRUNK_HASH_DA_EN;
+++		break;
+++	default: /* We should NEVER reach this */
+++		return -EOPNOTSUPP;
+++	}
+++
+++	/* Check if we are the unique configured LAG */
+++	dsa_lags_foreach_id(i, ds->dst)
+++		if (i != id && dsa_lag_dev(ds->dst, i)) {
+++			unique_lag = false;
+++			break;
+++		}
+++
+++	/* Hash Mode is global. Make sure the same Hash Mode
+++	 * is set to all the 4 possible lag.
+++	 * If we are the unique LAG we can set whatever hash
+++	 * mode we want.
+++	 * To change hash mode it's needed to remove all LAG
+++	 * and change the mode with the latest.
+++	 */
+++	if (unique_lag) {
+++		priv->lag_hash_mode = hash;
+++	} else if (priv->lag_hash_mode != hash) {
+++		netdev_err(lag, "Error: Mismateched Hash Mode across different lag is not supported\n");
+++		return -EOPNOTSUPP;
+++	}
+++
+++	return regmap_update_bits(priv->regmap, QCA8K_TRUNK_HASH_EN_CTRL,
+++				  QCA8K_TRUNK_HASH_MASK, hash);
+++}
+++
+++static int
+++qca8k_lag_refresh_portmap(struct dsa_switch *ds, int port,
+++			  struct net_device *lag, bool delete)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int ret, id, i;
+++	u32 val;
+++
+++	id = dsa_lag_id(ds->dst, lag);
+++
+++	/* Read current port member */
+++	ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0, &val);
+++	if (ret)
+++		return ret;
+++
+++	/* Shift val to the correct trunk */
+++	val >>= QCA8K_REG_GOL_TRUNK_SHIFT(id);
+++	val &= QCA8K_REG_GOL_TRUNK_MEMBER_MASK;
+++	if (delete)
+++		val &= ~BIT(port);
+++	else
+++		val |= BIT(port);
+++
+++	/* Update port member. With empty portmap disable trunk */
+++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0,
+++				 QCA8K_REG_GOL_TRUNK_MEMBER(id) |
+++				 QCA8K_REG_GOL_TRUNK_EN(id),
+++				 !val << QCA8K_REG_GOL_TRUNK_SHIFT(id) |
+++				 val << QCA8K_REG_GOL_TRUNK_SHIFT(id));
+++
+++	/* Search empty member if adding or port on deleting */
+++	for (i = 0; i < QCA8K_NUM_PORTS_FOR_LAG; i++) {
+++		ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id), &val);
+++		if (ret)
+++			return ret;
+++
+++		val >>= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i);
+++		val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_MASK;
+++
+++		if (delete) {
+++			/* If port flagged to be disabled assume this member is
+++			 * empty
+++			 */
+++			if (val != QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
+++				continue;
+++
+++			val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK;
+++			if (val != port)
+++				continue;
+++		} else {
+++			/* If port flagged to be enabled assume this member is
+++			 * already set
+++			 */
+++			if (val == QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
+++				continue;
+++		}
+++
+++		/* We have found the member to add/remove */
+++		break;
+++	}
+++
+++	/* Set port in the correct port mask or disable port if in delete mode */
+++	return regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id),
+++				  QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN(id, i) |
+++				  QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT(id, i),
+++				  !delete << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i) |
+++				  port << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i));
+++}
+++
+++static int
+++qca8k_port_lag_join(struct dsa_switch *ds, int port,
+++		    struct net_device *lag,
+++		    struct netdev_lag_upper_info *info)
+++{
+++	int ret;
+++
+++	if (!qca8k_lag_can_offload(ds, lag, info))
+++		return -EOPNOTSUPP;
+++
+++	ret = qca8k_lag_setup_hash(ds, lag, info);
+++	if (ret)
+++		return ret;
+++
+++	return qca8k_lag_refresh_portmap(ds, port, lag, false);
+++}
+++
+++static int
+++qca8k_port_lag_leave(struct dsa_switch *ds, int port,
+++		     struct net_device *lag)
+++{
+++	return qca8k_lag_refresh_portmap(ds, port, lag, true);
+++}
+++
+++static void
+++qca8k_master_change(struct dsa_switch *ds, const struct net_device *master,
+++		    bool operational)
+++{
+++	struct dsa_port *dp = master->dsa_ptr;
+++	struct qca8k_priv *priv = ds->priv;
+++
+++	/* Ethernet MIB/MDIO is only supported for CPU port 0 */
+++	if (dp->index != 0)
+++		return;
+++
+++	mutex_lock(&priv->mgmt_eth_data.mutex);
+++	mutex_lock(&priv->mib_eth_data.mutex);
+++
+++	priv->mgmt_master = operational ? (struct net_device *)master : NULL;
+++
+++	mutex_unlock(&priv->mib_eth_data.mutex);
+++	mutex_unlock(&priv->mgmt_eth_data.mutex);
+++}
+++
+++static int qca8k_connect_tag_protocol(struct dsa_switch *ds,
+++				      enum dsa_tag_protocol proto)
+++{
+++	struct qca_tagger_data *tagger_data;
+++
+++	switch (proto) {
+++	case DSA_TAG_PROTO_QCA:
+++		tagger_data = ds->tagger_data;
+++
+++		tagger_data->rw_reg_ack_handler = qca8k_rw_reg_ack_handler;
+++		tagger_data->mib_autocast_handler = qca8k_mib_autocast_handler;
+++
+++		break;
+++	default:
+++		return -EOPNOTSUPP;
+++	}
+++
+++	return 0;
+++}
+++
+++static const struct dsa_switch_ops qca8k_switch_ops = {
+++	.get_tag_protocol	= qca8k_get_tag_protocol,
+++	.setup			= qca8k_setup,
+++	.get_strings		= qca8k_get_strings,
+++	.get_ethtool_stats	= qca8k_get_ethtool_stats,
+++	.get_sset_count		= qca8k_get_sset_count,
+++	.set_ageing_time	= qca8k_set_ageing_time,
+++	.get_mac_eee		= qca8k_get_mac_eee,
+++	.set_mac_eee		= qca8k_set_mac_eee,
+++	.port_enable		= qca8k_port_enable,
+++	.port_disable		= qca8k_port_disable,
+++	.port_change_mtu	= qca8k_port_change_mtu,
+++	.port_max_mtu		= qca8k_port_max_mtu,
+++	.port_stp_state_set	= qca8k_port_stp_state_set,
+++	.port_bridge_join	= qca8k_port_bridge_join,
+++	.port_bridge_leave	= qca8k_port_bridge_leave,
+++	.port_fast_age		= qca8k_port_fast_age,
+++	.port_fdb_add		= qca8k_port_fdb_add,
+++	.port_fdb_del		= qca8k_port_fdb_del,
+++	.port_fdb_dump		= qca8k_port_fdb_dump,
+++	.port_mdb_add		= qca8k_port_mdb_add,
+++	.port_mdb_del		= qca8k_port_mdb_del,
+++	.port_mirror_add	= qca8k_port_mirror_add,
+++	.port_mirror_del	= qca8k_port_mirror_del,
+++	.port_vlan_filtering	= qca8k_port_vlan_filtering,
+++	.port_vlan_add		= qca8k_port_vlan_add,
+++	.port_vlan_del		= qca8k_port_vlan_del,
+++	.phylink_validate	= qca8k_phylink_validate,
+++	.phylink_mac_link_state	= qca8k_phylink_mac_link_state,
+++	.phylink_mac_config	= qca8k_phylink_mac_config,
+++	.phylink_mac_link_down	= qca8k_phylink_mac_link_down,
+++	.phylink_mac_link_up	= qca8k_phylink_mac_link_up,
+++	.get_phy_flags		= qca8k_get_phy_flags,
+++	.port_lag_join		= qca8k_port_lag_join,
+++	.port_lag_leave		= qca8k_port_lag_leave,
+++	.master_state_change	= qca8k_master_change,
+++	.connect_tag_protocol	= qca8k_connect_tag_protocol,
+++};
+++
+++static int qca8k_read_switch_id(struct qca8k_priv *priv)
+++{
+++	const struct qca8k_match_data *data;
+++	u32 val;
+++	u8 id;
+++	int ret;
+++
+++	/* get the switches ID from the compatible */
+++	data = of_device_get_match_data(priv->dev);
+++	if (!data)
+++		return -ENODEV;
+++
+++	ret = qca8k_read(priv, QCA8K_REG_MASK_CTRL, &val);
+++	if (ret < 0)
+++		return -ENODEV;
+++
+++	id = QCA8K_MASK_CTRL_DEVICE_ID(val);
+++	if (id != data->id) {
+++		dev_err(priv->dev, "Switch id detected %x but expected %x", id, data->id);
+++		return -ENODEV;
+++	}
+++
+++	priv->switch_id = id;
+++
+++	/* Save revision to communicate to the internal PHY driver */
+++	priv->switch_revision = QCA8K_MASK_CTRL_REV_ID(val);
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_sw_probe(struct mdio_device *mdiodev)
+++{
+++	struct qca8k_priv *priv;
+++	int ret;
+++
+++	/* allocate the private data struct so that we can probe the switches
+++	 * ID register
+++	 */
+++	priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL);
+++	if (!priv)
+++		return -ENOMEM;
+++
+++	priv->bus = mdiodev->bus;
+++	priv->dev = &mdiodev->dev;
+++
+++	priv->reset_gpio = devm_gpiod_get_optional(priv->dev, "reset",
+++						   GPIOD_ASIS);
+++	if (IS_ERR(priv->reset_gpio))
+++		return PTR_ERR(priv->reset_gpio);
+++
+++	if (priv->reset_gpio) {
+++		gpiod_set_value_cansleep(priv->reset_gpio, 1);
+++		/* The active low duration must be greater than 10 ms
+++		 * and checkpatch.pl wants 20 ms.
+++		 */
+++		msleep(20);
+++		gpiod_set_value_cansleep(priv->reset_gpio, 0);
+++	}
+++
+++	/* Start by setting up the register mapping */
+++	priv->regmap = devm_regmap_init(&mdiodev->dev, NULL, priv,
+++					&qca8k_regmap_config);
+++	if (IS_ERR(priv->regmap)) {
+++		dev_err(priv->dev, "regmap initialization failed");
+++		return PTR_ERR(priv->regmap);
+++	}
+++
+++	priv->mdio_cache.page = 0xffff;
+++	priv->mdio_cache.lo = 0xffff;
+++	priv->mdio_cache.hi = 0xffff;
+++
+++	/* Check the detected switch id */
+++	ret = qca8k_read_switch_id(priv);
+++	if (ret)
+++		return ret;
+++
+++	priv->ds = devm_kzalloc(&mdiodev->dev, sizeof(*priv->ds), GFP_KERNEL);
+++	if (!priv->ds)
+++		return -ENOMEM;
+++
+++	mutex_init(&priv->mgmt_eth_data.mutex);
+++	init_completion(&priv->mgmt_eth_data.rw_done);
+++
+++	mutex_init(&priv->mib_eth_data.mutex);
+++	init_completion(&priv->mib_eth_data.rw_done);
+++
+++	priv->ds->dev = &mdiodev->dev;
+++	priv->ds->num_ports = QCA8K_NUM_PORTS;
+++	priv->ds->priv = priv;
+++	priv->ds->ops = &qca8k_switch_ops;
+++	mutex_init(&priv->reg_mutex);
+++	dev_set_drvdata(&mdiodev->dev, priv);
+++
+++	return dsa_register_switch(priv->ds);
+++}
+++
+++static void
+++qca8k_sw_remove(struct mdio_device *mdiodev)
+++{
+++	struct qca8k_priv *priv = dev_get_drvdata(&mdiodev->dev);
+++	int i;
+++
+++	if (!priv)
+++		return;
+++
+++	for (i = 0; i < QCA8K_NUM_PORTS; i++)
+++		qca8k_port_set_status(priv, i, 0);
+++
+++	dsa_unregister_switch(priv->ds);
+++
+++	dev_set_drvdata(&mdiodev->dev, NULL);
+++}
+++
+++static void qca8k_sw_shutdown(struct mdio_device *mdiodev)
+++{
+++	struct qca8k_priv *priv = dev_get_drvdata(&mdiodev->dev);
+++
+++	if (!priv)
+++		return;
+++
+++	dsa_switch_shutdown(priv->ds);
+++
+++	dev_set_drvdata(&mdiodev->dev, NULL);
+++}
+++
+++#ifdef CONFIG_PM_SLEEP
+++static void
+++qca8k_set_pm(struct qca8k_priv *priv, int enable)
+++{
+++	int port;
+++
+++	for (port = 0; port < QCA8K_NUM_PORTS; port++) {
+++		/* Do not enable on resume if the port was
+++		 * disabled before.
+++		 */
+++		if (!(priv->port_enabled_map & BIT(port)))
+++			continue;
+++
+++		qca8k_port_set_status(priv, port, enable);
+++	}
+++}
+++
+++static int qca8k_suspend(struct device *dev)
+++{
+++	struct qca8k_priv *priv = dev_get_drvdata(dev);
+++
+++	qca8k_set_pm(priv, 0);
+++
+++	return dsa_switch_suspend(priv->ds);
+++}
+++
+++static int qca8k_resume(struct device *dev)
+++{
+++	struct qca8k_priv *priv = dev_get_drvdata(dev);
+++
+++	qca8k_set_pm(priv, 1);
+++
+++	return dsa_switch_resume(priv->ds);
+++}
+++#endif /* CONFIG_PM_SLEEP */
+++
+++static SIMPLE_DEV_PM_OPS(qca8k_pm_ops,
+++			 qca8k_suspend, qca8k_resume);
+++
+++static const struct qca8k_match_data qca8327 = {
+++	.id = QCA8K_ID_QCA8327,
+++	.reduced_package = true,
+++	.mib_count = QCA8K_QCA832X_MIB_COUNT,
+++};
+++
+++static const struct qca8k_match_data qca8328 = {
+++	.id = QCA8K_ID_QCA8327,
+++	.mib_count = QCA8K_QCA832X_MIB_COUNT,
+++};
+++
+++static const struct qca8k_match_data qca833x = {
+++	.id = QCA8K_ID_QCA8337,
+++	.mib_count = QCA8K_QCA833X_MIB_COUNT,
+++};
+++
+++static const struct of_device_id qca8k_of_match[] = {
+++	{ .compatible = "qca,qca8327", .data = &qca8327 },
+++	{ .compatible = "qca,qca8328", .data = &qca8328 },
+++	{ .compatible = "qca,qca8334", .data = &qca833x },
+++	{ .compatible = "qca,qca8337", .data = &qca833x },
+++	{ /* sentinel */ },
+++};
+++
+++static struct mdio_driver qca8kmdio_driver = {
+++	.probe  = qca8k_sw_probe,
+++	.remove = qca8k_sw_remove,
+++	.shutdown = qca8k_sw_shutdown,
+++	.mdiodrv.driver = {
+++		.name = "qca8k",
+++		.of_match_table = qca8k_of_match,
+++		.pm = &qca8k_pm_ops,
+++	},
+++};
+++
+++mdio_module_driver(qca8kmdio_driver);
+++
+++MODULE_AUTHOR("Mathieu Olivari, John Crispin <john@phrozen.org>");
+++MODULE_DESCRIPTION("Driver for QCA8K ethernet switch family");
+++MODULE_LICENSE("GPL v2");
+++MODULE_ALIAS("platform:qca8k");
++--- /dev/null
+++++ b/drivers/net/dsa/qca/qca8k.h
++@@ -0,0 +1,411 @@
+++/* SPDX-License-Identifier: GPL-2.0-only */
+++/*
+++ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+++ * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
+++ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+++ */
+++
+++#ifndef __QCA8K_H
+++#define __QCA8K_H
+++
+++#include <linux/delay.h>
+++#include <linux/regmap.h>
+++#include <linux/gpio.h>
+++#include <linux/dsa/tag_qca.h>
+++
+++#define QCA8K_ETHERNET_MDIO_PRIORITY			7
+++#define QCA8K_ETHERNET_PHY_PRIORITY			6
+++#define QCA8K_ETHERNET_TIMEOUT				100
+++
+++#define QCA8K_NUM_PORTS					7
+++#define QCA8K_NUM_CPU_PORTS				2
+++#define QCA8K_MAX_MTU					9000
+++#define QCA8K_NUM_LAGS					4
+++#define QCA8K_NUM_PORTS_FOR_LAG				4
+++
+++#define PHY_ID_QCA8327					0x004dd034
+++#define QCA8K_ID_QCA8327				0x12
+++#define PHY_ID_QCA8337					0x004dd036
+++#define QCA8K_ID_QCA8337				0x13
+++
+++#define QCA8K_QCA832X_MIB_COUNT				39
+++#define QCA8K_QCA833X_MIB_COUNT				41
+++
+++#define QCA8K_BUSY_WAIT_TIMEOUT				2000
+++
+++#define QCA8K_NUM_FDB_RECORDS				2048
+++
+++#define QCA8K_PORT_VID_DEF				1
+++
+++/* Global control registers */
+++#define QCA8K_REG_MASK_CTRL				0x000
+++#define   QCA8K_MASK_CTRL_REV_ID_MASK			GENMASK(7, 0)
+++#define   QCA8K_MASK_CTRL_REV_ID(x)			FIELD_GET(QCA8K_MASK_CTRL_REV_ID_MASK, x)
+++#define   QCA8K_MASK_CTRL_DEVICE_ID_MASK		GENMASK(15, 8)
+++#define   QCA8K_MASK_CTRL_DEVICE_ID(x)			FIELD_GET(QCA8K_MASK_CTRL_DEVICE_ID_MASK, x)
+++#define QCA8K_REG_PORT0_PAD_CTRL			0x004
+++#define   QCA8K_PORT0_PAD_MAC06_EXCHANGE_EN		BIT(31)
+++#define   QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE	BIT(19)
+++#define   QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE	BIT(18)
+++#define QCA8K_REG_PORT5_PAD_CTRL			0x008
+++#define QCA8K_REG_PORT6_PAD_CTRL			0x00c
+++#define   QCA8K_PORT_PAD_RGMII_EN			BIT(26)
+++#define   QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK		GENMASK(23, 22)
+++#define   QCA8K_PORT_PAD_RGMII_TX_DELAY(x)		FIELD_PREP(QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK, x)
+++#define   QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK		GENMASK(21, 20)
+++#define   QCA8K_PORT_PAD_RGMII_RX_DELAY(x)		FIELD_PREP(QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK, x)
+++#define	  QCA8K_PORT_PAD_RGMII_TX_DELAY_EN		BIT(25)
+++#define   QCA8K_PORT_PAD_RGMII_RX_DELAY_EN		BIT(24)
+++#define   QCA8K_PORT_PAD_SGMII_EN			BIT(7)
+++#define QCA8K_REG_PWS					0x010
+++#define   QCA8K_PWS_POWER_ON_SEL			BIT(31)
+++/* This reg is only valid for QCA832x and toggle the package
+++ * type from 176 pin (by default) to 148 pin used on QCA8327
+++ */
+++#define   QCA8327_PWS_PACKAGE148_EN			BIT(30)
+++#define   QCA8K_PWS_LED_OPEN_EN_CSR			BIT(24)
+++#define   QCA8K_PWS_SERDES_AEN_DIS			BIT(7)
+++#define QCA8K_REG_MODULE_EN				0x030
+++#define   QCA8K_MODULE_EN_MIB				BIT(0)
+++#define QCA8K_REG_MIB					0x034
+++#define   QCA8K_MIB_FUNC				GENMASK(26, 24)
+++#define   QCA8K_MIB_CPU_KEEP				BIT(20)
+++#define   QCA8K_MIB_BUSY				BIT(17)
+++#define QCA8K_MDIO_MASTER_CTRL				0x3c
+++#define   QCA8K_MDIO_MASTER_BUSY			BIT(31)
+++#define   QCA8K_MDIO_MASTER_EN				BIT(30)
+++#define   QCA8K_MDIO_MASTER_READ			BIT(27)
+++#define   QCA8K_MDIO_MASTER_WRITE			0
+++#define   QCA8K_MDIO_MASTER_SUP_PRE			BIT(26)
+++#define   QCA8K_MDIO_MASTER_PHY_ADDR_MASK		GENMASK(25, 21)
+++#define   QCA8K_MDIO_MASTER_PHY_ADDR(x)			FIELD_PREP(QCA8K_MDIO_MASTER_PHY_ADDR_MASK, x)
+++#define   QCA8K_MDIO_MASTER_REG_ADDR_MASK		GENMASK(20, 16)
+++#define   QCA8K_MDIO_MASTER_REG_ADDR(x)			FIELD_PREP(QCA8K_MDIO_MASTER_REG_ADDR_MASK, x)
+++#define   QCA8K_MDIO_MASTER_DATA_MASK			GENMASK(15, 0)
+++#define   QCA8K_MDIO_MASTER_DATA(x)			FIELD_PREP(QCA8K_MDIO_MASTER_DATA_MASK, x)
+++#define   QCA8K_MDIO_MASTER_MAX_PORTS			5
+++#define   QCA8K_MDIO_MASTER_MAX_REG			32
+++#define QCA8K_GOL_MAC_ADDR0				0x60
+++#define QCA8K_GOL_MAC_ADDR1				0x64
+++#define QCA8K_MAX_FRAME_SIZE				0x78
+++#define QCA8K_REG_PORT_STATUS(_i)			(0x07c + (_i) * 4)
+++#define   QCA8K_PORT_STATUS_SPEED			GENMASK(1, 0)
+++#define   QCA8K_PORT_STATUS_SPEED_10			0
+++#define   QCA8K_PORT_STATUS_SPEED_100			0x1
+++#define   QCA8K_PORT_STATUS_SPEED_1000			0x2
+++#define   QCA8K_PORT_STATUS_TXMAC			BIT(2)
+++#define   QCA8K_PORT_STATUS_RXMAC			BIT(3)
+++#define   QCA8K_PORT_STATUS_TXFLOW			BIT(4)
+++#define   QCA8K_PORT_STATUS_RXFLOW			BIT(5)
+++#define   QCA8K_PORT_STATUS_DUPLEX			BIT(6)
+++#define   QCA8K_PORT_STATUS_LINK_UP			BIT(8)
+++#define   QCA8K_PORT_STATUS_LINK_AUTO			BIT(9)
+++#define   QCA8K_PORT_STATUS_LINK_PAUSE			BIT(10)
+++#define   QCA8K_PORT_STATUS_FLOW_AUTO			BIT(12)
+++#define QCA8K_REG_PORT_HDR_CTRL(_i)			(0x9c + (_i * 4))
+++#define   QCA8K_PORT_HDR_CTRL_RX_MASK			GENMASK(3, 2)
+++#define   QCA8K_PORT_HDR_CTRL_TX_MASK			GENMASK(1, 0)
+++#define   QCA8K_PORT_HDR_CTRL_ALL			2
+++#define   QCA8K_PORT_HDR_CTRL_MGMT			1
+++#define   QCA8K_PORT_HDR_CTRL_NONE			0
+++#define QCA8K_REG_SGMII_CTRL				0x0e0
+++#define   QCA8K_SGMII_EN_PLL				BIT(1)
+++#define   QCA8K_SGMII_EN_RX				BIT(2)
+++#define   QCA8K_SGMII_EN_TX				BIT(3)
+++#define   QCA8K_SGMII_EN_SD				BIT(4)
+++#define   QCA8K_SGMII_CLK125M_DELAY			BIT(7)
+++#define   QCA8K_SGMII_MODE_CTRL_MASK			GENMASK(23, 22)
+++#define   QCA8K_SGMII_MODE_CTRL(x)			FIELD_PREP(QCA8K_SGMII_MODE_CTRL_MASK, x)
+++#define   QCA8K_SGMII_MODE_CTRL_BASEX			QCA8K_SGMII_MODE_CTRL(0x0)
+++#define   QCA8K_SGMII_MODE_CTRL_PHY			QCA8K_SGMII_MODE_CTRL(0x1)
+++#define   QCA8K_SGMII_MODE_CTRL_MAC			QCA8K_SGMII_MODE_CTRL(0x2)
+++
+++/* MAC_PWR_SEL registers */
+++#define QCA8K_REG_MAC_PWR_SEL				0x0e4
+++#define   QCA8K_MAC_PWR_RGMII1_1_8V			BIT(18)
+++#define   QCA8K_MAC_PWR_RGMII0_1_8V			BIT(19)
+++
+++/* EEE control registers */
+++#define QCA8K_REG_EEE_CTRL				0x100
+++#define  QCA8K_REG_EEE_CTRL_LPI_EN(_i)			((_i + 1) * 2)
+++
+++/* TRUNK_HASH_EN registers */
+++#define QCA8K_TRUNK_HASH_EN_CTRL			0x270
+++#define   QCA8K_TRUNK_HASH_SIP_EN			BIT(3)
+++#define   QCA8K_TRUNK_HASH_DIP_EN			BIT(2)
+++#define   QCA8K_TRUNK_HASH_SA_EN			BIT(1)
+++#define   QCA8K_TRUNK_HASH_DA_EN			BIT(0)
+++#define   QCA8K_TRUNK_HASH_MASK				GENMASK(3, 0)
+++
+++/* ACL registers */
+++#define QCA8K_REG_PORT_VLAN_CTRL0(_i)			(0x420 + (_i * 8))
+++#define   QCA8K_PORT_VLAN_CVID_MASK			GENMASK(27, 16)
+++#define   QCA8K_PORT_VLAN_CVID(x)			FIELD_PREP(QCA8K_PORT_VLAN_CVID_MASK, x)
+++#define   QCA8K_PORT_VLAN_SVID_MASK			GENMASK(11, 0)
+++#define   QCA8K_PORT_VLAN_SVID(x)			FIELD_PREP(QCA8K_PORT_VLAN_SVID_MASK, x)
+++#define QCA8K_REG_PORT_VLAN_CTRL1(_i)			(0x424 + (_i * 8))
+++#define QCA8K_REG_IPV4_PRI_BASE_ADDR			0x470
+++#define QCA8K_REG_IPV4_PRI_ADDR_MASK			0x474
+++
+++/* Lookup registers */
+++#define QCA8K_REG_ATU_DATA0				0x600
+++#define   QCA8K_ATU_ADDR2_MASK				GENMASK(31, 24)
+++#define   QCA8K_ATU_ADDR3_MASK				GENMASK(23, 16)
+++#define   QCA8K_ATU_ADDR4_MASK				GENMASK(15, 8)
+++#define   QCA8K_ATU_ADDR5_MASK				GENMASK(7, 0)
+++#define QCA8K_REG_ATU_DATA1				0x604
+++#define   QCA8K_ATU_PORT_MASK				GENMASK(22, 16)
+++#define   QCA8K_ATU_ADDR0_MASK				GENMASK(15, 8)
+++#define   QCA8K_ATU_ADDR1_MASK				GENMASK(7, 0)
+++#define QCA8K_REG_ATU_DATA2				0x608
+++#define   QCA8K_ATU_VID_MASK				GENMASK(19, 8)
+++#define   QCA8K_ATU_STATUS_MASK				GENMASK(3, 0)
+++#define   QCA8K_ATU_STATUS_STATIC			0xf
+++#define QCA8K_REG_ATU_FUNC				0x60c
+++#define   QCA8K_ATU_FUNC_BUSY				BIT(31)
+++#define   QCA8K_ATU_FUNC_PORT_EN			BIT(14)
+++#define   QCA8K_ATU_FUNC_MULTI_EN			BIT(13)
+++#define   QCA8K_ATU_FUNC_FULL				BIT(12)
+++#define   QCA8K_ATU_FUNC_PORT_MASK			GENMASK(11, 8)
+++#define QCA8K_REG_VTU_FUNC0				0x610
+++#define   QCA8K_VTU_FUNC0_VALID				BIT(20)
+++#define   QCA8K_VTU_FUNC0_IVL_EN			BIT(19)
+++/*        QCA8K_VTU_FUNC0_EG_MODE_MASK			GENMASK(17, 4)
+++ *          It does contain VLAN_MODE for each port [5:4] for port0,
+++ *          [7:6] for port1 ... [17:16] for port6. Use virtual port
+++ *          define to handle this.
+++ */
+++#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i)	(4 + (_i) * 2)
+++#define   QCA8K_VTU_FUNC0_EG_MODE_MASK			GENMASK(1, 0)
+++#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(_i)		(GENMASK(1, 0) << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
+++#define   QCA8K_VTU_FUNC0_EG_MODE_UNMOD			FIELD_PREP(QCA8K_VTU_FUNC0_EG_MODE_MASK, 0x0)
+++#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_UNMOD(_i)	(QCA8K_VTU_FUNC0_EG_MODE_UNMOD << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
+++#define   QCA8K_VTU_FUNC0_EG_MODE_UNTAG			FIELD_PREP(QCA8K_VTU_FUNC0_EG_MODE_MASK, 0x1)
+++#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_UNTAG(_i)	(QCA8K_VTU_FUNC0_EG_MODE_UNTAG << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
+++#define   QCA8K_VTU_FUNC0_EG_MODE_TAG			FIELD_PREP(QCA8K_VTU_FUNC0_EG_MODE_MASK, 0x2)
+++#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_TAG(_i)		(QCA8K_VTU_FUNC0_EG_MODE_TAG << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
+++#define   QCA8K_VTU_FUNC0_EG_MODE_NOT			FIELD_PREP(QCA8K_VTU_FUNC0_EG_MODE_MASK, 0x3)
+++#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(_i)		(QCA8K_VTU_FUNC0_EG_MODE_NOT << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
+++#define QCA8K_REG_VTU_FUNC1				0x614
+++#define   QCA8K_VTU_FUNC1_BUSY				BIT(31)
+++#define   QCA8K_VTU_FUNC1_VID_MASK			GENMASK(27, 16)
+++#define   QCA8K_VTU_FUNC1_FULL				BIT(4)
+++#define QCA8K_REG_ATU_CTRL				0x618
+++#define   QCA8K_ATU_AGE_TIME_MASK			GENMASK(15, 0)
+++#define   QCA8K_ATU_AGE_TIME(x)				FIELD_PREP(QCA8K_ATU_AGE_TIME_MASK, (x))
+++#define QCA8K_REG_GLOBAL_FW_CTRL0			0x620
+++#define   QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN		BIT(10)
+++#define   QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM		GENMASK(7, 4)
+++#define QCA8K_REG_GLOBAL_FW_CTRL1			0x624
+++#define   QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK		GENMASK(30, 24)
+++#define   QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK		GENMASK(22, 16)
+++#define   QCA8K_GLOBAL_FW_CTRL1_MC_DP_MASK		GENMASK(14, 8)
+++#define   QCA8K_GLOBAL_FW_CTRL1_UC_DP_MASK		GENMASK(6, 0)
+++#define QCA8K_PORT_LOOKUP_CTRL(_i)			(0x660 + (_i) * 0xc)
+++#define   QCA8K_PORT_LOOKUP_MEMBER			GENMASK(6, 0)
+++#define   QCA8K_PORT_LOOKUP_VLAN_MODE_MASK		GENMASK(9, 8)
+++#define   QCA8K_PORT_LOOKUP_VLAN_MODE(x)		FIELD_PREP(QCA8K_PORT_LOOKUP_VLAN_MODE_MASK, x)
+++#define   QCA8K_PORT_LOOKUP_VLAN_MODE_NONE		QCA8K_PORT_LOOKUP_VLAN_MODE(0x0)
+++#define   QCA8K_PORT_LOOKUP_VLAN_MODE_FALLBACK		QCA8K_PORT_LOOKUP_VLAN_MODE(0x1)
+++#define   QCA8K_PORT_LOOKUP_VLAN_MODE_CHECK		QCA8K_PORT_LOOKUP_VLAN_MODE(0x2)
+++#define   QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE		QCA8K_PORT_LOOKUP_VLAN_MODE(0x3)
+++#define   QCA8K_PORT_LOOKUP_STATE_MASK			GENMASK(18, 16)
+++#define   QCA8K_PORT_LOOKUP_STATE(x)			FIELD_PREP(QCA8K_PORT_LOOKUP_STATE_MASK, x)
+++#define   QCA8K_PORT_LOOKUP_STATE_DISABLED		QCA8K_PORT_LOOKUP_STATE(0x0)
+++#define   QCA8K_PORT_LOOKUP_STATE_BLOCKING		QCA8K_PORT_LOOKUP_STATE(0x1)
+++#define   QCA8K_PORT_LOOKUP_STATE_LISTENING		QCA8K_PORT_LOOKUP_STATE(0x2)
+++#define   QCA8K_PORT_LOOKUP_STATE_LEARNING		QCA8K_PORT_LOOKUP_STATE(0x3)
+++#define   QCA8K_PORT_LOOKUP_STATE_FORWARD		QCA8K_PORT_LOOKUP_STATE(0x4)
+++#define   QCA8K_PORT_LOOKUP_LEARN			BIT(20)
+++#define   QCA8K_PORT_LOOKUP_ING_MIRROR_EN		BIT(25)
+++
+++#define QCA8K_REG_GOL_TRUNK_CTRL0			0x700
+++/* 4 max trunk first
+++ * first 6 bit for member bitmap
+++ * 7th bit is to enable trunk port
+++ */
+++#define QCA8K_REG_GOL_TRUNK_SHIFT(_i)			((_i) * 8)
+++#define QCA8K_REG_GOL_TRUNK_EN_MASK			BIT(7)
+++#define QCA8K_REG_GOL_TRUNK_EN(_i)			(QCA8K_REG_GOL_TRUNK_EN_MASK << QCA8K_REG_GOL_TRUNK_SHIFT(_i))
+++#define QCA8K_REG_GOL_TRUNK_MEMBER_MASK			GENMASK(6, 0)
+++#define QCA8K_REG_GOL_TRUNK_MEMBER(_i)			(QCA8K_REG_GOL_TRUNK_MEMBER_MASK << QCA8K_REG_GOL_TRUNK_SHIFT(_i))
+++/* 0x704 for TRUNK 0-1 --- 0x708 for TRUNK 2-3 */
+++#define QCA8K_REG_GOL_TRUNK_CTRL(_i)			(0x704 + (((_i) / 2) * 4))
+++#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_MASK		GENMASK(3, 0)
+++#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK		BIT(3)
+++#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK		GENMASK(2, 0)
+++#define QCA8K_REG_GOL_TRUNK_ID_SHIFT(_i)		(((_i) / 2) * 16)
+++#define QCA8K_REG_GOL_MEM_ID_SHIFT(_i)			((_i) * 4)
+++/* Complex shift: FIRST shift for port THEN shift for trunk */
+++#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(_i, _j)	(QCA8K_REG_GOL_MEM_ID_SHIFT(_j) + QCA8K_REG_GOL_TRUNK_ID_SHIFT(_i))
+++#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN(_i, _j)	(QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(_i, _j))
+++#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT(_i, _j)	(QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(_i, _j))
+++
+++#define QCA8K_REG_GLOBAL_FC_THRESH			0x800
+++#define   QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK		GENMASK(24, 16)
+++#define   QCA8K_GLOBAL_FC_GOL_XON_THRES(x)		FIELD_PREP(QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK, x)
+++#define   QCA8K_GLOBAL_FC_GOL_XOFF_THRES_MASK		GENMASK(8, 0)
+++#define   QCA8K_GLOBAL_FC_GOL_XOFF_THRES(x)		FIELD_PREP(QCA8K_GLOBAL_FC_GOL_XOFF_THRES_MASK, x)
+++
+++#define QCA8K_REG_PORT_HOL_CTRL0(_i)			(0x970 + (_i) * 0x8)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI0_BUF_MASK		GENMASK(3, 0)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI0(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI0_BUF_MASK, x)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI1_BUF_MASK		GENMASK(7, 4)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI1(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI1_BUF_MASK, x)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI2_BUF_MASK		GENMASK(11, 8)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI2(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI2_BUF_MASK, x)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI3_BUF_MASK		GENMASK(15, 12)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI3(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI3_BUF_MASK, x)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI4_BUF_MASK		GENMASK(19, 16)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI4(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI4_BUF_MASK, x)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI5_BUF_MASK		GENMASK(23, 20)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PRI5(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI5_BUF_MASK, x)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PORT_BUF_MASK		GENMASK(29, 24)
+++#define   QCA8K_PORT_HOL_CTRL0_EG_PORT(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PORT_BUF_MASK, x)
+++
+++#define QCA8K_REG_PORT_HOL_CTRL1(_i)			(0x974 + (_i) * 0x8)
+++#define   QCA8K_PORT_HOL_CTRL1_ING_BUF_MASK		GENMASK(3, 0)
+++#define   QCA8K_PORT_HOL_CTRL1_ING(x)			FIELD_PREP(QCA8K_PORT_HOL_CTRL1_ING_BUF_MASK, x)
+++#define   QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN		BIT(6)
+++#define   QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN		BIT(7)
+++#define   QCA8K_PORT_HOL_CTRL1_WRED_EN			BIT(8)
+++#define   QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN		BIT(16)
+++
+++/* Pkt edit registers */
+++#define QCA8K_EGREES_VLAN_PORT_SHIFT(_i)		(16 * ((_i) % 2))
+++#define QCA8K_EGREES_VLAN_PORT_MASK(_i)			(GENMASK(11, 0) << QCA8K_EGREES_VLAN_PORT_SHIFT(_i))
+++#define QCA8K_EGREES_VLAN_PORT(_i, x)			((x) << QCA8K_EGREES_VLAN_PORT_SHIFT(_i))
+++#define QCA8K_EGRESS_VLAN(x)				(0x0c70 + (4 * (x / 2)))
+++
+++/* L3 registers */
+++#define QCA8K_HROUTER_CONTROL				0xe00
+++#define   QCA8K_HROUTER_CONTROL_GLB_LOCKTIME_M		GENMASK(17, 16)
+++#define   QCA8K_HROUTER_CONTROL_GLB_LOCKTIME_S		16
+++#define   QCA8K_HROUTER_CONTROL_ARP_AGE_MODE		1
+++#define QCA8K_HROUTER_PBASED_CONTROL1			0xe08
+++#define QCA8K_HROUTER_PBASED_CONTROL2			0xe0c
+++#define QCA8K_HNAT_CONTROL				0xe38
+++
+++/* MIB registers */
+++#define QCA8K_PORT_MIB_COUNTER(_i)			(0x1000 + (_i) * 0x100)
+++
+++/* QCA specific MII registers */
+++#define MII_ATH_MMD_ADDR				0x0d
+++#define MII_ATH_MMD_DATA				0x0e
+++
+++enum {
+++	QCA8K_PORT_SPEED_10M = 0,
+++	QCA8K_PORT_SPEED_100M = 1,
+++	QCA8K_PORT_SPEED_1000M = 2,
+++	QCA8K_PORT_SPEED_ERR = 3,
+++};
+++
+++enum qca8k_fdb_cmd {
+++	QCA8K_FDB_FLUSH	= 1,
+++	QCA8K_FDB_LOAD = 2,
+++	QCA8K_FDB_PURGE = 3,
+++	QCA8K_FDB_FLUSH_PORT = 5,
+++	QCA8K_FDB_NEXT = 6,
+++	QCA8K_FDB_SEARCH = 7,
+++};
+++
+++enum qca8k_vlan_cmd {
+++	QCA8K_VLAN_FLUSH = 1,
+++	QCA8K_VLAN_LOAD = 2,
+++	QCA8K_VLAN_PURGE = 3,
+++	QCA8K_VLAN_REMOVE_PORT = 4,
+++	QCA8K_VLAN_NEXT = 5,
+++	QCA8K_VLAN_READ = 6,
+++};
+++
+++enum qca8k_mid_cmd {
+++	QCA8K_MIB_FLUSH = 1,
+++	QCA8K_MIB_FLUSH_PORT = 2,
+++	QCA8K_MIB_CAST = 3,
+++};
+++
+++struct qca8k_match_data {
+++	u8 id;
+++	bool reduced_package;
+++	u8 mib_count;
+++};
+++
+++enum {
+++	QCA8K_CPU_PORT0,
+++	QCA8K_CPU_PORT6,
+++};
+++
+++struct qca8k_mgmt_eth_data {
+++	struct completion rw_done;
+++	struct mutex mutex; /* Enforce one mdio read/write at time */
+++	bool ack;
+++	u32 seq;
+++	u32 data[4];
+++};
+++
+++struct qca8k_mib_eth_data {
+++	struct completion rw_done;
+++	struct mutex mutex; /* Process one command at time */
+++	refcount_t port_parsed; /* Counter to track parsed port */
+++	u8 req_port;
+++	u64 *data; /* pointer to ethtool data */
+++};
+++
+++struct qca8k_ports_config {
+++	bool sgmii_rx_clk_falling_edge;
+++	bool sgmii_tx_clk_falling_edge;
+++	bool sgmii_enable_pll;
+++	u8 rgmii_rx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
+++	u8 rgmii_tx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
+++};
+++
+++struct qca8k_mdio_cache {
+++/* The 32bit switch registers are accessed indirectly. To achieve this we need
+++ * to set the page of the register. Track the last page that was set to reduce
+++ * mdio writes
+++ */
+++	u16 page;
+++/* lo and hi can also be cached and from Documentation we can skip one
+++ * extra mdio write if lo or hi is didn't change.
+++ */
+++	u16 lo;
+++	u16 hi;
+++};
+++
+++struct qca8k_priv {
+++	u8 switch_id;
+++	u8 switch_revision;
+++	u8 mirror_rx;
+++	u8 mirror_tx;
+++	u8 lag_hash_mode;
+++	/* Each bit correspond to a port. This switch can support a max of 7 port.
+++	 * Bit 1: port enabled. Bit 0: port disabled.
+++	 */
+++	u8 port_enabled_map;
+++	struct qca8k_ports_config ports_config;
+++	struct regmap *regmap;
+++	struct mii_bus *bus;
+++	struct dsa_switch *ds;
+++	struct mutex reg_mutex;
+++	struct device *dev;
+++	struct gpio_desc *reset_gpio;
+++	struct net_device *mgmt_master; /* Track if mdio/mib Ethernet is available */
+++	struct qca8k_mgmt_eth_data mgmt_eth_data;
+++	struct qca8k_mib_eth_data mib_eth_data;
+++	struct qca8k_mdio_cache mdio_cache;
+++};
+++
+++struct qca8k_mib_desc {
+++	unsigned int size;
+++	unsigned int offset;
+++	const char *name;
+++};
+++
+++struct qca8k_fdb {
+++	u16 vid;
+++	u8 port_mask;
+++	u8 aging;
+++	u8 mac[6];
+++};
+++
+++#endif /* __QCA8K_H */
++--- a/drivers/net/dsa/qca8k.c
+++++ /dev/null
++@@ -1,3243 +0,0 @@
++-// SPDX-License-Identifier: GPL-2.0
++-/*
++- * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
++- * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
++- * Copyright (c) 2015, 2019, The Linux Foundation. All rights reserved.
++- * Copyright (c) 2016 John Crispin <john@phrozen.org>
++- */
++-
++-#include <linux/module.h>
++-#include <linux/phy.h>
++-#include <linux/netdevice.h>
++-#include <linux/bitfield.h>
++-#include <linux/regmap.h>
++-#include <net/dsa.h>
++-#include <linux/of_net.h>
++-#include <linux/of_mdio.h>
++-#include <linux/of_platform.h>
++-#include <linux/if_bridge.h>
++-#include <linux/mdio.h>
++-#include <linux/phylink.h>
++-#include <linux/gpio/consumer.h>
++-#include <linux/etherdevice.h>
++-#include <linux/dsa/tag_qca.h>
++-
++-#include "qca8k.h"
++-
++-#define MIB_DESC(_s, _o, _n)	\
++-	{			\
++-		.size = (_s),	\
++-		.offset = (_o),	\
++-		.name = (_n),	\
++-	}
++-
++-static const struct qca8k_mib_desc ar8327_mib[] = {
++-	MIB_DESC(1, 0x00, "RxBroad"),
++-	MIB_DESC(1, 0x04, "RxPause"),
++-	MIB_DESC(1, 0x08, "RxMulti"),
++-	MIB_DESC(1, 0x0c, "RxFcsErr"),
++-	MIB_DESC(1, 0x10, "RxAlignErr"),
++-	MIB_DESC(1, 0x14, "RxRunt"),
++-	MIB_DESC(1, 0x18, "RxFragment"),
++-	MIB_DESC(1, 0x1c, "Rx64Byte"),
++-	MIB_DESC(1, 0x20, "Rx128Byte"),
++-	MIB_DESC(1, 0x24, "Rx256Byte"),
++-	MIB_DESC(1, 0x28, "Rx512Byte"),
++-	MIB_DESC(1, 0x2c, "Rx1024Byte"),
++-	MIB_DESC(1, 0x30, "Rx1518Byte"),
++-	MIB_DESC(1, 0x34, "RxMaxByte"),
++-	MIB_DESC(1, 0x38, "RxTooLong"),
++-	MIB_DESC(2, 0x3c, "RxGoodByte"),
++-	MIB_DESC(2, 0x44, "RxBadByte"),
++-	MIB_DESC(1, 0x4c, "RxOverFlow"),
++-	MIB_DESC(1, 0x50, "Filtered"),
++-	MIB_DESC(1, 0x54, "TxBroad"),
++-	MIB_DESC(1, 0x58, "TxPause"),
++-	MIB_DESC(1, 0x5c, "TxMulti"),
++-	MIB_DESC(1, 0x60, "TxUnderRun"),
++-	MIB_DESC(1, 0x64, "Tx64Byte"),
++-	MIB_DESC(1, 0x68, "Tx128Byte"),
++-	MIB_DESC(1, 0x6c, "Tx256Byte"),
++-	MIB_DESC(1, 0x70, "Tx512Byte"),
++-	MIB_DESC(1, 0x74, "Tx1024Byte"),
++-	MIB_DESC(1, 0x78, "Tx1518Byte"),
++-	MIB_DESC(1, 0x7c, "TxMaxByte"),
++-	MIB_DESC(1, 0x80, "TxOverSize"),
++-	MIB_DESC(2, 0x84, "TxByte"),
++-	MIB_DESC(1, 0x8c, "TxCollision"),
++-	MIB_DESC(1, 0x90, "TxAbortCol"),
++-	MIB_DESC(1, 0x94, "TxMultiCol"),
++-	MIB_DESC(1, 0x98, "TxSingleCol"),
++-	MIB_DESC(1, 0x9c, "TxExcDefer"),
++-	MIB_DESC(1, 0xa0, "TxDefer"),
++-	MIB_DESC(1, 0xa4, "TxLateCol"),
++-	MIB_DESC(1, 0xa8, "RXUnicast"),
++-	MIB_DESC(1, 0xac, "TXUnicast"),
++-};
++-
++-static void
++-qca8k_split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
++-{
++-	regaddr >>= 1;
++-	*r1 = regaddr & 0x1e;
++-
++-	regaddr >>= 5;
++-	*r2 = regaddr & 0x7;
++-
++-	regaddr >>= 3;
++-	*page = regaddr & 0x3ff;
++-}
++-
++-static int
++-qca8k_set_lo(struct qca8k_priv *priv, int phy_id, u32 regnum, u16 lo)
++-{
++-	u16 *cached_lo = &priv->mdio_cache.lo;
++-	struct mii_bus *bus = priv->bus;
++-	int ret;
++-
++-	if (lo == *cached_lo)
++-		return 0;
++-
++-	ret = bus->write(bus, phy_id, regnum, lo);
++-	if (ret < 0)
++-		dev_err_ratelimited(&bus->dev,
++-				    "failed to write qca8k 32bit lo register\n");
++-
++-	*cached_lo = lo;
++-	return 0;
++-}
++-
++-static int
++-qca8k_set_hi(struct qca8k_priv *priv, int phy_id, u32 regnum, u16 hi)
++-{
++-	u16 *cached_hi = &priv->mdio_cache.hi;
++-	struct mii_bus *bus = priv->bus;
++-	int ret;
++-
++-	if (hi == *cached_hi)
++-		return 0;
++-
++-	ret = bus->write(bus, phy_id, regnum, hi);
++-	if (ret < 0)
++-		dev_err_ratelimited(&bus->dev,
++-				    "failed to write qca8k 32bit hi register\n");
++-
++-	*cached_hi = hi;
++-	return 0;
++-}
++-
++-static int
++-qca8k_mii_read32(struct mii_bus *bus, int phy_id, u32 regnum, u32 *val)
++-{
++-	int ret;
++-
++-	ret = bus->read(bus, phy_id, regnum);
++-	if (ret >= 0) {
++-		*val = ret;
++-		ret = bus->read(bus, phy_id, regnum + 1);
++-		*val |= ret << 16;
++-	}
++-
++-	if (ret < 0) {
++-		dev_err_ratelimited(&bus->dev,
++-				    "failed to read qca8k 32bit register\n");
++-		*val = 0;
++-		return ret;
++-	}
++-
++-	return 0;
++-}
++-
++-static void
++-qca8k_mii_write32(struct qca8k_priv *priv, int phy_id, u32 regnum, u32 val)
++-{
++-	u16 lo, hi;
++-	int ret;
++-
++-	lo = val & 0xffff;
++-	hi = (u16)(val >> 16);
++-
++-	ret = qca8k_set_lo(priv, phy_id, regnum, lo);
++-	if (ret >= 0)
++-		ret = qca8k_set_hi(priv, phy_id, regnum + 1, hi);
++-}
++-
++-static int
++-qca8k_set_page(struct qca8k_priv *priv, u16 page)
++-{
++-	u16 *cached_page = &priv->mdio_cache.page;
++-	struct mii_bus *bus = priv->bus;
++-	int ret;
++-
++-	if (page == *cached_page)
++-		return 0;
++-
++-	ret = bus->write(bus, 0x18, 0, page);
++-	if (ret < 0) {
++-		dev_err_ratelimited(&bus->dev,
++-				    "failed to set qca8k page\n");
++-		return ret;
++-	}
++-
++-	*cached_page = page;
++-	usleep_range(1000, 2000);
++-	return 0;
++-}
++-
++-static int
++-qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val)
++-{
++-	return regmap_read(priv->regmap, reg, val);
++-}
++-
++-static int
++-qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val)
++-{
++-	return regmap_write(priv->regmap, reg, val);
++-}
++-
++-static int
++-qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
++-{
++-	return regmap_update_bits(priv->regmap, reg, mask, write_val);
++-}
++-
++-static void qca8k_rw_reg_ack_handler(struct dsa_switch *ds, struct sk_buff *skb)
++-{
++-	struct qca8k_mgmt_eth_data *mgmt_eth_data;
++-	struct qca8k_priv *priv = ds->priv;
++-	struct qca_mgmt_ethhdr *mgmt_ethhdr;
++-	u8 len, cmd;
++-
++-	mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb_mac_header(skb);
++-	mgmt_eth_data = &priv->mgmt_eth_data;
++-
++-	cmd = FIELD_GET(QCA_HDR_MGMT_CMD, mgmt_ethhdr->command);
++-	len = FIELD_GET(QCA_HDR_MGMT_LENGTH, mgmt_ethhdr->command);
++-
++-	/* Make sure the seq match the requested packet */
++-	if (mgmt_ethhdr->seq == mgmt_eth_data->seq)
++-		mgmt_eth_data->ack = true;
++-
++-	if (cmd == MDIO_READ) {
++-		mgmt_eth_data->data[0] = mgmt_ethhdr->mdio_data;
++-
++-		/* Get the rest of the 12 byte of data.
++-		 * The read/write function will extract the requested data.
++-		 */
++-		if (len > QCA_HDR_MGMT_DATA1_LEN)
++-			memcpy(mgmt_eth_data->data + 1, skb->data,
++-			       QCA_HDR_MGMT_DATA2_LEN);
++-	}
++-
++-	complete(&mgmt_eth_data->rw_done);
++-}
++-
++-static struct sk_buff *qca8k_alloc_mdio_header(enum mdio_cmd cmd, u32 reg, u32 *val,
++-					       int priority, unsigned int len)
++-{
++-	struct qca_mgmt_ethhdr *mgmt_ethhdr;
++-	unsigned int real_len;
++-	struct sk_buff *skb;
++-	u32 *data2;
++-	u16 hdr;
++-
++-	skb = dev_alloc_skb(QCA_HDR_MGMT_PKT_LEN);
++-	if (!skb)
++-		return NULL;
++-
++-	/* Max value for len reg is 15 (0xf) but the switch actually return 16 byte
++-	 * Actually for some reason the steps are:
++-	 * 0: nothing
++-	 * 1-4: first 4 byte
++-	 * 5-6: first 12 byte
++-	 * 7-15: all 16 byte
++-	 */
++-	if (len == 16)
++-		real_len = 15;
++-	else
++-		real_len = len;
++-
++-	skb_reset_mac_header(skb);
++-	skb_set_network_header(skb, skb->len);
++-
++-	mgmt_ethhdr = skb_push(skb, QCA_HDR_MGMT_HEADER_LEN + QCA_HDR_LEN);
++-
++-	hdr = FIELD_PREP(QCA_HDR_XMIT_VERSION, QCA_HDR_VERSION);
++-	hdr |= FIELD_PREP(QCA_HDR_XMIT_PRIORITY, priority);
++-	hdr |= QCA_HDR_XMIT_FROM_CPU;
++-	hdr |= FIELD_PREP(QCA_HDR_XMIT_DP_BIT, BIT(0));
++-	hdr |= FIELD_PREP(QCA_HDR_XMIT_CONTROL, QCA_HDR_XMIT_TYPE_RW_REG);
++-
++-	mgmt_ethhdr->command = FIELD_PREP(QCA_HDR_MGMT_ADDR, reg);
++-	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_LENGTH, real_len);
++-	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CMD, cmd);
++-	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CHECK_CODE,
++-					   QCA_HDR_MGMT_CHECK_CODE_VAL);
++-
++-	if (cmd == MDIO_WRITE)
++-		mgmt_ethhdr->mdio_data = *val;
++-
++-	mgmt_ethhdr->hdr = htons(hdr);
++-
++-	data2 = skb_put_zero(skb, QCA_HDR_MGMT_DATA2_LEN + QCA_HDR_MGMT_PADDING_LEN);
++-	if (cmd == MDIO_WRITE && len > QCA_HDR_MGMT_DATA1_LEN)
++-		memcpy(data2, val + 1, len - QCA_HDR_MGMT_DATA1_LEN);
++-
++-	return skb;
++-}
++-
++-static void qca8k_mdio_header_fill_seq_num(struct sk_buff *skb, u32 seq_num)
++-{
++-	struct qca_mgmt_ethhdr *mgmt_ethhdr;
++-
++-	mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb->data;
++-	mgmt_ethhdr->seq = FIELD_PREP(QCA_HDR_MGMT_SEQ_NUM, seq_num);
++-}
++-
++-static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++-{
++-	struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
++-	struct sk_buff *skb;
++-	bool ack;
++-	int ret;
++-
++-	skb = qca8k_alloc_mdio_header(MDIO_READ, reg, NULL,
++-				      QCA8K_ETHERNET_MDIO_PRIORITY, len);
++-	if (!skb)
++-		return -ENOMEM;
++-
++-	mutex_lock(&mgmt_eth_data->mutex);
++-
++-	/* Check mgmt_master if is operational */
++-	if (!priv->mgmt_master) {
++-		kfree_skb(skb);
++-		mutex_unlock(&mgmt_eth_data->mutex);
++-		return -EINVAL;
++-	}
++-
++-	skb->dev = priv->mgmt_master;
++-
++-	reinit_completion(&mgmt_eth_data->rw_done);
++-
++-	/* Increment seq_num and set it in the mdio pkt */
++-	mgmt_eth_data->seq++;
++-	qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
++-	mgmt_eth_data->ack = false;
++-
++-	dev_queue_xmit(skb);
++-
++-	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++-					  msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
++-
++-	*val = mgmt_eth_data->data[0];
++-	if (len > QCA_HDR_MGMT_DATA1_LEN)
++-		memcpy(val + 1, mgmt_eth_data->data + 1, len - QCA_HDR_MGMT_DATA1_LEN);
++-
++-	ack = mgmt_eth_data->ack;
++-
++-	mutex_unlock(&mgmt_eth_data->mutex);
++-
++-	if (ret <= 0)
++-		return -ETIMEDOUT;
++-
++-	if (!ack)
++-		return -EINVAL;
++-
++-	return 0;
++-}
++-
++-static int qca8k_write_eth(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++-{
++-	struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
++-	struct sk_buff *skb;
++-	bool ack;
++-	int ret;
++-
++-	skb = qca8k_alloc_mdio_header(MDIO_WRITE, reg, val,
++-				      QCA8K_ETHERNET_MDIO_PRIORITY, len);
++-	if (!skb)
++-		return -ENOMEM;
++-
++-	mutex_lock(&mgmt_eth_data->mutex);
++-
++-	/* Check mgmt_master if is operational */
++-	if (!priv->mgmt_master) {
++-		kfree_skb(skb);
++-		mutex_unlock(&mgmt_eth_data->mutex);
++-		return -EINVAL;
++-	}
++-
++-	skb->dev = priv->mgmt_master;
++-
++-	reinit_completion(&mgmt_eth_data->rw_done);
++-
++-	/* Increment seq_num and set it in the mdio pkt */
++-	mgmt_eth_data->seq++;
++-	qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
++-	mgmt_eth_data->ack = false;
++-
++-	dev_queue_xmit(skb);
++-
++-	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++-					  msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
++-
++-	ack = mgmt_eth_data->ack;
++-
++-	mutex_unlock(&mgmt_eth_data->mutex);
++-
++-	if (ret <= 0)
++-		return -ETIMEDOUT;
++-
++-	if (!ack)
++-		return -EINVAL;
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_regmap_update_bits_eth(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
++-{
++-	u32 val = 0;
++-	int ret;
++-
++-	ret = qca8k_read_eth(priv, reg, &val, sizeof(val));
++-	if (ret)
++-		return ret;
++-
++-	val &= ~mask;
++-	val |= write_val;
++-
++-	return qca8k_write_eth(priv, reg, &val, sizeof(val));
++-}
++-
++-static int
++-qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++-{
++-	int i, count = len / sizeof(u32), ret;
++-
++-	if (priv->mgmt_master && !qca8k_read_eth(priv, reg, val, len))
++-		return 0;
++-
++-	for (i = 0; i < count; i++) {
++-		ret = regmap_read(priv->regmap, reg + (i * 4), val + i);
++-		if (ret < 0)
++-			return ret;
++-	}
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++-{
++-	int i, count = len / sizeof(u32), ret;
++-	u32 tmp;
++-
++-	if (priv->mgmt_master && !qca8k_write_eth(priv, reg, val, len))
++-		return 0;
++-
++-	for (i = 0; i < count; i++) {
++-		tmp = val[i];
++-
++-		ret = regmap_write(priv->regmap, reg + (i * 4), tmp);
++-		if (ret < 0)
++-			return ret;
++-	}
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
++-	struct mii_bus *bus = priv->bus;
++-	u16 r1, r2, page;
++-	int ret;
++-
++-	if (!qca8k_read_eth(priv, reg, val, sizeof(*val)))
++-		return 0;
++-
++-	qca8k_split_addr(reg, &r1, &r2, &page);
++-
++-	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++-
++-	ret = qca8k_set_page(priv, page);
++-	if (ret < 0)
++-		goto exit;
++-
++-	ret = qca8k_mii_read32(bus, 0x10 | r2, r1, val);
++-
++-exit:
++-	mutex_unlock(&bus->mdio_lock);
++-	return ret;
++-}
++-
++-static int
++-qca8k_regmap_write(void *ctx, uint32_t reg, uint32_t val)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
++-	struct mii_bus *bus = priv->bus;
++-	u16 r1, r2, page;
++-	int ret;
++-
++-	if (!qca8k_write_eth(priv, reg, &val, sizeof(val)))
++-		return 0;
++-
++-	qca8k_split_addr(reg, &r1, &r2, &page);
++-
++-	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++-
++-	ret = qca8k_set_page(priv, page);
++-	if (ret < 0)
++-		goto exit;
++-
++-	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
++-
++-exit:
++-	mutex_unlock(&bus->mdio_lock);
++-	return ret;
++-}
++-
++-static int
++-qca8k_regmap_update_bits(void *ctx, uint32_t reg, uint32_t mask, uint32_t write_val)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
++-	struct mii_bus *bus = priv->bus;
++-	u16 r1, r2, page;
++-	u32 val;
++-	int ret;
++-
++-	if (!qca8k_regmap_update_bits_eth(priv, reg, mask, write_val))
++-		return 0;
++-
++-	qca8k_split_addr(reg, &r1, &r2, &page);
++-
++-	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++-
++-	ret = qca8k_set_page(priv, page);
++-	if (ret < 0)
++-		goto exit;
++-
++-	ret = qca8k_mii_read32(bus, 0x10 | r2, r1, &val);
++-	if (ret < 0)
++-		goto exit;
++-
++-	val &= ~mask;
++-	val |= write_val;
++-	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
++-
++-exit:
++-	mutex_unlock(&bus->mdio_lock);
++-
++-	return ret;
++-}
++-
++-static const struct regmap_range qca8k_readable_ranges[] = {
++-	regmap_reg_range(0x0000, 0x00e4), /* Global control */
++-	regmap_reg_range(0x0100, 0x0168), /* EEE control */
++-	regmap_reg_range(0x0200, 0x0270), /* Parser control */
++-	regmap_reg_range(0x0400, 0x0454), /* ACL */
++-	regmap_reg_range(0x0600, 0x0718), /* Lookup */
++-	regmap_reg_range(0x0800, 0x0b70), /* QM */
++-	regmap_reg_range(0x0c00, 0x0c80), /* PKT */
++-	regmap_reg_range(0x0e00, 0x0e98), /* L3 */
++-	regmap_reg_range(0x1000, 0x10ac), /* MIB - Port0 */
++-	regmap_reg_range(0x1100, 0x11ac), /* MIB - Port1 */
++-	regmap_reg_range(0x1200, 0x12ac), /* MIB - Port2 */
++-	regmap_reg_range(0x1300, 0x13ac), /* MIB - Port3 */
++-	regmap_reg_range(0x1400, 0x14ac), /* MIB - Port4 */
++-	regmap_reg_range(0x1500, 0x15ac), /* MIB - Port5 */
++-	regmap_reg_range(0x1600, 0x16ac), /* MIB - Port6 */
++-
++-};
++-
++-static const struct regmap_access_table qca8k_readable_table = {
++-	.yes_ranges = qca8k_readable_ranges,
++-	.n_yes_ranges = ARRAY_SIZE(qca8k_readable_ranges),
++-};
++-
++-static struct regmap_config qca8k_regmap_config = {
++-	.reg_bits = 16,
++-	.val_bits = 32,
++-	.reg_stride = 4,
++-	.max_register = 0x16ac, /* end MIB - Port6 range */
++-	.reg_read = qca8k_regmap_read,
++-	.reg_write = qca8k_regmap_write,
++-	.reg_update_bits = qca8k_regmap_update_bits,
++-	.rd_table = &qca8k_readable_table,
++-	.disable_locking = true, /* Locking is handled by qca8k read/write */
++-	.cache_type = REGCACHE_NONE, /* Explicitly disable CACHE */
++-};
++-
++-static int
++-qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask)
++-{
++-	u32 val;
++-
++-	return regmap_read_poll_timeout(priv->regmap, reg, val, !(val & mask), 0,
++-				       QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC);
++-}
++-
++-static int
++-qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb)
++-{
++-	u32 reg[3];
++-	int ret;
++-
++-	/* load the ARL table into an array */
++-	ret = qca8k_bulk_read(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
++-	if (ret)
++-		return ret;
++-
++-	/* vid - 83:72 */
++-	fdb->vid = FIELD_GET(QCA8K_ATU_VID_MASK, reg[2]);
++-	/* aging - 67:64 */
++-	fdb->aging = FIELD_GET(QCA8K_ATU_STATUS_MASK, reg[2]);
++-	/* portmask - 54:48 */
++-	fdb->port_mask = FIELD_GET(QCA8K_ATU_PORT_MASK, reg[1]);
++-	/* mac - 47:0 */
++-	fdb->mac[0] = FIELD_GET(QCA8K_ATU_ADDR0_MASK, reg[1]);
++-	fdb->mac[1] = FIELD_GET(QCA8K_ATU_ADDR1_MASK, reg[1]);
++-	fdb->mac[2] = FIELD_GET(QCA8K_ATU_ADDR2_MASK, reg[0]);
++-	fdb->mac[3] = FIELD_GET(QCA8K_ATU_ADDR3_MASK, reg[0]);
++-	fdb->mac[4] = FIELD_GET(QCA8K_ATU_ADDR4_MASK, reg[0]);
++-	fdb->mac[5] = FIELD_GET(QCA8K_ATU_ADDR5_MASK, reg[0]);
++-
++-	return 0;
++-}
++-
++-static void
++-qca8k_fdb_write(struct qca8k_priv *priv, u16 vid, u8 port_mask, const u8 *mac,
++-		u8 aging)
++-{
++-	u32 reg[3] = { 0 };
++-
++-	/* vid - 83:72 */
++-	reg[2] = FIELD_PREP(QCA8K_ATU_VID_MASK, vid);
++-	/* aging - 67:64 */
++-	reg[2] |= FIELD_PREP(QCA8K_ATU_STATUS_MASK, aging);
++-	/* portmask - 54:48 */
++-	reg[1] = FIELD_PREP(QCA8K_ATU_PORT_MASK, port_mask);
++-	/* mac - 47:0 */
++-	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR0_MASK, mac[0]);
++-	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR1_MASK, mac[1]);
++-	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR2_MASK, mac[2]);
++-	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR3_MASK, mac[3]);
++-	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR4_MASK, mac[4]);
++-	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR5_MASK, mac[5]);
++-
++-	/* load the array into the ARL table */
++-	qca8k_bulk_write(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
++-}
++-
++-static int
++-qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd, int port)
++-{
++-	u32 reg;
++-	int ret;
++-
++-	/* Set the command and FDB index */
++-	reg = QCA8K_ATU_FUNC_BUSY;
++-	reg |= cmd;
++-	if (port >= 0) {
++-		reg |= QCA8K_ATU_FUNC_PORT_EN;
++-		reg |= FIELD_PREP(QCA8K_ATU_FUNC_PORT_MASK, port);
++-	}
++-
++-	/* Write the function register triggering the table access */
++-	ret = qca8k_write(priv, QCA8K_REG_ATU_FUNC, reg);
++-	if (ret)
++-		return ret;
++-
++-	/* wait for completion */
++-	ret = qca8k_busy_wait(priv, QCA8K_REG_ATU_FUNC, QCA8K_ATU_FUNC_BUSY);
++-	if (ret)
++-		return ret;
++-
++-	/* Check for table full violation when adding an entry */
++-	if (cmd == QCA8K_FDB_LOAD) {
++-		ret = qca8k_read(priv, QCA8K_REG_ATU_FUNC, &reg);
++-		if (ret < 0)
++-			return ret;
++-		if (reg & QCA8K_ATU_FUNC_FULL)
++-			return -1;
++-	}
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb, int port)
++-{
++-	int ret;
++-
++-	qca8k_fdb_write(priv, fdb->vid, fdb->port_mask, fdb->mac, fdb->aging);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_NEXT, port);
++-	if (ret < 0)
++-		return ret;
++-
++-	return qca8k_fdb_read(priv, fdb);
++-}
++-
++-static int
++-qca8k_fdb_add(struct qca8k_priv *priv, const u8 *mac, u16 port_mask,
++-	      u16 vid, u8 aging)
++-{
++-	int ret;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	qca8k_fdb_write(priv, vid, port_mask, mac, aging);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
++-	mutex_unlock(&priv->reg_mutex);
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac, u16 port_mask, u16 vid)
++-{
++-	int ret;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	qca8k_fdb_write(priv, vid, port_mask, mac, 0);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
++-	mutex_unlock(&priv->reg_mutex);
++-
++-	return ret;
++-}
++-
++-static void
++-qca8k_fdb_flush(struct qca8k_priv *priv)
++-{
++-	mutex_lock(&priv->reg_mutex);
++-	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH, -1);
++-	mutex_unlock(&priv->reg_mutex);
++-}
++-
++-static int
++-qca8k_fdb_search_and_insert(struct qca8k_priv *priv, u8 port_mask,
++-			    const u8 *mac, u16 vid)
++-{
++-	struct qca8k_fdb fdb = { 0 };
++-	int ret;
++-
++-	mutex_lock(&priv->reg_mutex);
++-
++-	qca8k_fdb_write(priv, vid, 0, mac, 0);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
++-	if (ret < 0)
++-		goto exit;
++-
++-	ret = qca8k_fdb_read(priv, &fdb);
++-	if (ret < 0)
++-		goto exit;
++-
++-	/* Rule exist. Delete first */
++-	if (!fdb.aging) {
++-		ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
++-		if (ret)
++-			goto exit;
++-	}
++-
++-	/* Add port to fdb portmask */
++-	fdb.port_mask |= port_mask;
++-
++-	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
++-
++-exit:
++-	mutex_unlock(&priv->reg_mutex);
++-	return ret;
++-}
++-
++-static int
++-qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask,
++-			 const u8 *mac, u16 vid)
++-{
++-	struct qca8k_fdb fdb = { 0 };
++-	int ret;
++-
++-	mutex_lock(&priv->reg_mutex);
++-
++-	qca8k_fdb_write(priv, vid, 0, mac, 0);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
++-	if (ret < 0)
++-		goto exit;
++-
++-	/* Rule doesn't exist. Why delete? */
++-	if (!fdb.aging) {
++-		ret = -EINVAL;
++-		goto exit;
++-	}
++-
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
++-	if (ret)
++-		goto exit;
++-
++-	/* Only port in the rule is this port. Don't re insert */
++-	if (fdb.port_mask == port_mask)
++-		goto exit;
++-
++-	/* Remove port from port mask */
++-	fdb.port_mask &= ~port_mask;
++-
++-	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
++-
++-exit:
++-	mutex_unlock(&priv->reg_mutex);
++-	return ret;
++-}
++-
++-static int
++-qca8k_vlan_access(struct qca8k_priv *priv, enum qca8k_vlan_cmd cmd, u16 vid)
++-{
++-	u32 reg;
++-	int ret;
++-
++-	/* Set the command and VLAN index */
++-	reg = QCA8K_VTU_FUNC1_BUSY;
++-	reg |= cmd;
++-	reg |= FIELD_PREP(QCA8K_VTU_FUNC1_VID_MASK, vid);
++-
++-	/* Write the function register triggering the table access */
++-	ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC1, reg);
++-	if (ret)
++-		return ret;
++-
++-	/* wait for completion */
++-	ret = qca8k_busy_wait(priv, QCA8K_REG_VTU_FUNC1, QCA8K_VTU_FUNC1_BUSY);
++-	if (ret)
++-		return ret;
++-
++-	/* Check for table full violation when adding an entry */
++-	if (cmd == QCA8K_VLAN_LOAD) {
++-		ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC1, &reg);
++-		if (ret < 0)
++-			return ret;
++-		if (reg & QCA8K_VTU_FUNC1_FULL)
++-			return -ENOMEM;
++-	}
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid, bool untagged)
++-{
++-	u32 reg;
++-	int ret;
++-
++-	/*
++-	   We do the right thing with VLAN 0 and treat it as untagged while
++-	   preserving the tag on egress.
++-	 */
++-	if (vid == 0)
++-		return 0;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid);
++-	if (ret < 0)
++-		goto out;
++-
++-	ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, &reg);
++-	if (ret < 0)
++-		goto out;
++-	reg |= QCA8K_VTU_FUNC0_VALID | QCA8K_VTU_FUNC0_IVL_EN;
++-	reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port);
++-	if (untagged)
++-		reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_UNTAG(port);
++-	else
++-		reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_TAG(port);
++-
++-	ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
++-	if (ret)
++-		goto out;
++-	ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid);
++-
++-out:
++-	mutex_unlock(&priv->reg_mutex);
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid)
++-{
++-	u32 reg, mask;
++-	int ret, i;
++-	bool del;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid);
++-	if (ret < 0)
++-		goto out;
++-
++-	ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, &reg);
++-	if (ret < 0)
++-		goto out;
++-	reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port);
++-	reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(port);
++-
++-	/* Check if we're the last member to be removed */
++-	del = true;
++-	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++-		mask = QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(i);
++-
++-		if ((reg & mask) != mask) {
++-			del = false;
++-			break;
++-		}
++-	}
++-
++-	if (del) {
++-		ret = qca8k_vlan_access(priv, QCA8K_VLAN_PURGE, vid);
++-	} else {
++-		ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
++-		if (ret)
++-			goto out;
++-		ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid);
++-	}
++-
++-out:
++-	mutex_unlock(&priv->reg_mutex);
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_mib_init(struct qca8k_priv *priv)
++-{
++-	int ret;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB,
++-				 QCA8K_MIB_FUNC | QCA8K_MIB_BUSY,
++-				 FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_FLUSH) |
++-				 QCA8K_MIB_BUSY);
++-	if (ret)
++-		goto exit;
++-
++-	ret = qca8k_busy_wait(priv, QCA8K_REG_MIB, QCA8K_MIB_BUSY);
++-	if (ret)
++-		goto exit;
++-
++-	ret = regmap_set_bits(priv->regmap, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP);
++-	if (ret)
++-		goto exit;
++-
++-	ret = qca8k_write(priv, QCA8K_REG_MODULE_EN, QCA8K_MODULE_EN_MIB);
++-
++-exit:
++-	mutex_unlock(&priv->reg_mutex);
++-	return ret;
++-}
++-
++-static void
++-qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable)
++-{
++-	u32 mask = QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
++-
++-	/* Port 0 and 6 have no internal PHY */
++-	if (port > 0 && port < 6)
++-		mask |= QCA8K_PORT_STATUS_LINK_AUTO;
++-
++-	if (enable)
++-		regmap_set_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
++-	else
++-		regmap_clear_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
++-}
++-
++-static int
++-qca8k_phy_eth_busy_wait(struct qca8k_mgmt_eth_data *mgmt_eth_data,
++-			struct sk_buff *read_skb, u32 *val)
++-{
++-	struct sk_buff *skb = skb_copy(read_skb, GFP_KERNEL);
++-	bool ack;
++-	int ret;
++-
++-	reinit_completion(&mgmt_eth_data->rw_done);
++-
++-	/* Increment seq_num and set it in the copy pkt */
++-	mgmt_eth_data->seq++;
++-	qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
++-	mgmt_eth_data->ack = false;
++-
++-	dev_queue_xmit(skb);
++-
++-	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++-					  QCA8K_ETHERNET_TIMEOUT);
++-
++-	ack = mgmt_eth_data->ack;
++-
++-	if (ret <= 0)
++-		return -ETIMEDOUT;
++-
++-	if (!ack)
++-		return -EINVAL;
++-
++-	*val = mgmt_eth_data->data[0];
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_phy_eth_command(struct qca8k_priv *priv, bool read, int phy,
++-		      int regnum, u16 data)
++-{
++-	struct sk_buff *write_skb, *clear_skb, *read_skb;
++-	struct qca8k_mgmt_eth_data *mgmt_eth_data;
++-	u32 write_val, clear_val = 0, val;
++-	struct net_device *mgmt_master;
++-	int ret, ret1;
++-	bool ack;
++-
++-	if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
++-		return -EINVAL;
++-
++-	mgmt_eth_data = &priv->mgmt_eth_data;
++-
++-	write_val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
++-		    QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
++-		    QCA8K_MDIO_MASTER_REG_ADDR(regnum);
++-
++-	if (read) {
++-		write_val |= QCA8K_MDIO_MASTER_READ;
++-	} else {
++-		write_val |= QCA8K_MDIO_MASTER_WRITE;
++-		write_val |= QCA8K_MDIO_MASTER_DATA(data);
++-	}
++-
++-	/* Prealloc all the needed skb before the lock */
++-	write_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL, &write_val,
++-					    QCA8K_ETHERNET_PHY_PRIORITY, sizeof(write_val));
++-	if (!write_skb)
++-		return -ENOMEM;
++-
++-	clear_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL, &clear_val,
++-					    QCA8K_ETHERNET_PHY_PRIORITY, sizeof(clear_val));
++-	if (!clear_skb) {
++-		ret = -ENOMEM;
++-		goto err_clear_skb;
++-	}
++-
++-	read_skb = qca8k_alloc_mdio_header(MDIO_READ, QCA8K_MDIO_MASTER_CTRL, &clear_val,
++-					   QCA8K_ETHERNET_PHY_PRIORITY, sizeof(clear_val));
++-	if (!read_skb) {
++-		ret = -ENOMEM;
++-		goto err_read_skb;
++-	}
++-
++-	/* Actually start the request:
++-	 * 1. Send mdio master packet
++-	 * 2. Busy Wait for mdio master command
++-	 * 3. Get the data if we are reading
++-	 * 4. Reset the mdio master (even with error)
++-	 */
++-	mutex_lock(&mgmt_eth_data->mutex);
++-
++-	/* Check if mgmt_master is operational */
++-	mgmt_master = priv->mgmt_master;
++-	if (!mgmt_master) {
++-		mutex_unlock(&mgmt_eth_data->mutex);
++-		ret = -EINVAL;
++-		goto err_mgmt_master;
++-	}
++-
++-	read_skb->dev = mgmt_master;
++-	clear_skb->dev = mgmt_master;
++-	write_skb->dev = mgmt_master;
++-
++-	reinit_completion(&mgmt_eth_data->rw_done);
++-
++-	/* Increment seq_num and set it in the write pkt */
++-	mgmt_eth_data->seq++;
++-	qca8k_mdio_header_fill_seq_num(write_skb, mgmt_eth_data->seq);
++-	mgmt_eth_data->ack = false;
++-
++-	dev_queue_xmit(write_skb);
++-
++-	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++-					  QCA8K_ETHERNET_TIMEOUT);
++-
++-	ack = mgmt_eth_data->ack;
++-
++-	if (ret <= 0) {
++-		ret = -ETIMEDOUT;
++-		kfree_skb(read_skb);
++-		goto exit;
++-	}
++-
++-	if (!ack) {
++-		ret = -EINVAL;
++-		kfree_skb(read_skb);
++-		goto exit;
++-	}
++-
++-	ret = read_poll_timeout(qca8k_phy_eth_busy_wait, ret1,
++-				!(val & QCA8K_MDIO_MASTER_BUSY), 0,
++-				QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
++-				mgmt_eth_data, read_skb, &val);
++-
++-	if (ret < 0 && ret1 < 0) {
++-		ret = ret1;
++-		goto exit;
++-	}
++-
++-	if (read) {
++-		reinit_completion(&mgmt_eth_data->rw_done);
++-
++-		/* Increment seq_num and set it in the read pkt */
++-		mgmt_eth_data->seq++;
++-		qca8k_mdio_header_fill_seq_num(read_skb, mgmt_eth_data->seq);
++-		mgmt_eth_data->ack = false;
++-
++-		dev_queue_xmit(read_skb);
++-
++-		ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++-						  QCA8K_ETHERNET_TIMEOUT);
++-
++-		ack = mgmt_eth_data->ack;
++-
++-		if (ret <= 0) {
++-			ret = -ETIMEDOUT;
++-			goto exit;
++-		}
++-
++-		if (!ack) {
++-			ret = -EINVAL;
++-			goto exit;
++-		}
++-
++-		ret = mgmt_eth_data->data[0] & QCA8K_MDIO_MASTER_DATA_MASK;
++-	} else {
++-		kfree_skb(read_skb);
++-	}
++-exit:
++-	reinit_completion(&mgmt_eth_data->rw_done);
++-
++-	/* Increment seq_num and set it in the clear pkt */
++-	mgmt_eth_data->seq++;
++-	qca8k_mdio_header_fill_seq_num(clear_skb, mgmt_eth_data->seq);
++-	mgmt_eth_data->ack = false;
++-
++-	dev_queue_xmit(clear_skb);
++-
++-	wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++-				    QCA8K_ETHERNET_TIMEOUT);
++-
++-	mutex_unlock(&mgmt_eth_data->mutex);
++-
++-	return ret;
++-
++-	/* Error handling before lock */
++-err_mgmt_master:
++-	kfree_skb(read_skb);
++-err_read_skb:
++-	kfree_skb(clear_skb);
++-err_clear_skb:
++-	kfree_skb(write_skb);
++-
++-	return ret;
++-}
++-
++-static u32
++-qca8k_port_to_phy(int port)
++-{
++-	/* From Andrew Lunn:
++-	 * Port 0 has no internal phy.
++-	 * Port 1 has an internal PHY at MDIO address 0.
++-	 * Port 2 has an internal PHY at MDIO address 1.
++-	 * ...
++-	 * Port 5 has an internal PHY at MDIO address 4.
++-	 * Port 6 has no internal PHY.
++-	 */
++-
++-	return port - 1;
++-}
++-
++-static int
++-qca8k_mdio_busy_wait(struct mii_bus *bus, u32 reg, u32 mask)
++-{
++-	u16 r1, r2, page;
++-	u32 val;
++-	int ret, ret1;
++-
++-	qca8k_split_addr(reg, &r1, &r2, &page);
++-
++-	ret = read_poll_timeout(qca8k_mii_read32, ret1, !(val & mask), 0,
++-				QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
++-				bus, 0x10 | r2, r1, &val);
++-
++-	/* Check if qca8k_read has failed for a different reason
++-	 * before returnting -ETIMEDOUT
++-	 */
++-	if (ret < 0 && ret1 < 0)
++-		return ret1;
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_mdio_write(struct qca8k_priv *priv, int phy, int regnum, u16 data)
++-{
++-	struct mii_bus *bus = priv->bus;
++-	u16 r1, r2, page;
++-	u32 val;
++-	int ret;
++-
++-	if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
++-		return -EINVAL;
++-
++-	val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
++-	      QCA8K_MDIO_MASTER_WRITE | QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
++-	      QCA8K_MDIO_MASTER_REG_ADDR(regnum) |
++-	      QCA8K_MDIO_MASTER_DATA(data);
++-
++-	qca8k_split_addr(QCA8K_MDIO_MASTER_CTRL, &r1, &r2, &page);
++-
++-	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++-
++-	ret = qca8k_set_page(priv, page);
++-	if (ret)
++-		goto exit;
++-
++-	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
++-
++-	ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
++-				   QCA8K_MDIO_MASTER_BUSY);
++-
++-exit:
++-	/* even if the busy_wait timeouts try to clear the MASTER_EN */
++-	qca8k_mii_write32(priv, 0x10 | r2, r1, 0);
++-
++-	mutex_unlock(&bus->mdio_lock);
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_mdio_read(struct qca8k_priv *priv, int phy, int regnum)
++-{
++-	struct mii_bus *bus = priv->bus;
++-	u16 r1, r2, page;
++-	u32 val;
++-	int ret;
++-
++-	if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
++-		return -EINVAL;
++-
++-	val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
++-	      QCA8K_MDIO_MASTER_READ | QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
++-	      QCA8K_MDIO_MASTER_REG_ADDR(regnum);
++-
++-	qca8k_split_addr(QCA8K_MDIO_MASTER_CTRL, &r1, &r2, &page);
++-
++-	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++-
++-	ret = qca8k_set_page(priv, page);
++-	if (ret)
++-		goto exit;
++-
++-	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
++-
++-	ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
++-				   QCA8K_MDIO_MASTER_BUSY);
++-	if (ret)
++-		goto exit;
++-
++-	ret = qca8k_mii_read32(bus, 0x10 | r2, r1, &val);
++-
++-exit:
++-	/* even if the busy_wait timeouts try to clear the MASTER_EN */
++-	qca8k_mii_write32(priv, 0x10 | r2, r1, 0);
++-
++-	mutex_unlock(&bus->mdio_lock);
++-
++-	if (ret >= 0)
++-		ret = val & QCA8K_MDIO_MASTER_DATA_MASK;
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_internal_mdio_write(struct mii_bus *slave_bus, int phy, int regnum, u16 data)
++-{
++-	struct qca8k_priv *priv = slave_bus->priv;
++-	int ret;
++-
++-	/* Use mdio Ethernet when available, fallback to legacy one on error */
++-	ret = qca8k_phy_eth_command(priv, false, phy, regnum, data);
++-	if (!ret)
++-		return 0;
++-
++-	return qca8k_mdio_write(priv, phy, regnum, data);
++-}
++-
++-static int
++-qca8k_internal_mdio_read(struct mii_bus *slave_bus, int phy, int regnum)
++-{
++-	struct qca8k_priv *priv = slave_bus->priv;
++-	int ret;
++-
++-	/* Use mdio Ethernet when available, fallback to legacy one on error */
++-	ret = qca8k_phy_eth_command(priv, true, phy, regnum, 0);
++-	if (ret >= 0)
++-		return ret;
++-
++-	ret = qca8k_mdio_read(priv, phy, regnum);
++-
++-	if (ret < 0)
++-		return 0xffff;
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_legacy_mdio_write(struct mii_bus *slave_bus, int port, int regnum, u16 data)
++-{
++-	port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
++-
++-	return qca8k_internal_mdio_write(slave_bus, port, regnum, data);
++-}
++-
++-static int
++-qca8k_legacy_mdio_read(struct mii_bus *slave_bus, int port, int regnum)
++-{
++-	port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
++-
++-	return qca8k_internal_mdio_read(slave_bus, port, regnum);
++-}
++-
++-static int
++-qca8k_mdio_register(struct qca8k_priv *priv)
++-{
++-	struct dsa_switch *ds = priv->ds;
++-	struct device_node *mdio;
++-	struct mii_bus *bus;
++-
++-	bus = devm_mdiobus_alloc(ds->dev);
++-	if (!bus)
++-		return -ENOMEM;
++-
++-	bus->priv = (void *)priv;
++-	snprintf(bus->id, MII_BUS_ID_SIZE, "qca8k-%d.%d",
++-		 ds->dst->index, ds->index);
++-	bus->parent = ds->dev;
++-	bus->phy_mask = ~ds->phys_mii_mask;
++-	ds->slave_mii_bus = bus;
++-
++-	/* Check if the devicetree declare the port:phy mapping */
++-	mdio = of_get_child_by_name(priv->dev->of_node, "mdio");
++-	if (of_device_is_available(mdio)) {
++-		bus->name = "qca8k slave mii";
++-		bus->read = qca8k_internal_mdio_read;
++-		bus->write = qca8k_internal_mdio_write;
++-		return devm_of_mdiobus_register(priv->dev, bus, mdio);
++-	}
++-
++-	/* If a mapping can't be found the legacy mapping is used,
++-	 * using the qca8k_port_to_phy function
++-	 */
++-	bus->name = "qca8k-legacy slave mii";
++-	bus->read = qca8k_legacy_mdio_read;
++-	bus->write = qca8k_legacy_mdio_write;
++-	return devm_mdiobus_register(priv->dev, bus);
++-}
++-
++-static int
++-qca8k_setup_mdio_bus(struct qca8k_priv *priv)
++-{
++-	u32 internal_mdio_mask = 0, external_mdio_mask = 0, reg;
++-	struct device_node *ports, *port;
++-	phy_interface_t mode;
++-	int err;
++-
++-	ports = of_get_child_by_name(priv->dev->of_node, "ports");
++-	if (!ports)
++-		ports = of_get_child_by_name(priv->dev->of_node, "ethernet-ports");
++-
++-	if (!ports)
++-		return -EINVAL;
++-
++-	for_each_available_child_of_node(ports, port) {
++-		err = of_property_read_u32(port, "reg", &reg);
++-		if (err) {
++-			of_node_put(port);
++-			of_node_put(ports);
++-			return err;
++-		}
++-
++-		if (!dsa_is_user_port(priv->ds, reg))
++-			continue;
++-
++-		of_get_phy_mode(port, &mode);
++-
++-		if (of_property_read_bool(port, "phy-handle") &&
++-		    mode != PHY_INTERFACE_MODE_INTERNAL)
++-			external_mdio_mask |= BIT(reg);
++-		else
++-			internal_mdio_mask |= BIT(reg);
++-	}
++-
++-	of_node_put(ports);
++-	if (!external_mdio_mask && !internal_mdio_mask) {
++-		dev_err(priv->dev, "no PHYs are defined.\n");
++-		return -EINVAL;
++-	}
++-
++-	/* The QCA8K_MDIO_MASTER_EN Bit, which grants access to PHYs through
++-	 * the MDIO_MASTER register also _disconnects_ the external MDC
++-	 * passthrough to the internal PHYs. It's not possible to use both
++-	 * configurations at the same time!
++-	 *
++-	 * Because this came up during the review process:
++-	 * If the external mdio-bus driver is capable magically disabling
++-	 * the QCA8K_MDIO_MASTER_EN and mutex/spin-locking out the qca8k's
++-	 * accessors for the time being, it would be possible to pull this
++-	 * off.
++-	 */
++-	if (!!external_mdio_mask && !!internal_mdio_mask) {
++-		dev_err(priv->dev, "either internal or external mdio bus configuration is supported.\n");
++-		return -EINVAL;
++-	}
++-
++-	if (external_mdio_mask) {
++-		/* Make sure to disable the internal mdio bus in cases
++-		 * a dt-overlay and driver reload changed the configuration
++-		 */
++-
++-		return regmap_clear_bits(priv->regmap, QCA8K_MDIO_MASTER_CTRL,
++-					 QCA8K_MDIO_MASTER_EN);
++-	}
++-
++-	return qca8k_mdio_register(priv);
++-}
++-
++-static int
++-qca8k_setup_mac_pwr_sel(struct qca8k_priv *priv)
++-{
++-	u32 mask = 0;
++-	int ret = 0;
++-
++-	/* SoC specific settings for ipq8064.
++-	 * If more device require this consider adding
++-	 * a dedicated binding.
++-	 */
++-	if (of_machine_is_compatible("qcom,ipq8064"))
++-		mask |= QCA8K_MAC_PWR_RGMII0_1_8V;
++-
++-	/* SoC specific settings for ipq8065 */
++-	if (of_machine_is_compatible("qcom,ipq8065"))
++-		mask |= QCA8K_MAC_PWR_RGMII1_1_8V;
++-
++-	if (mask) {
++-		ret = qca8k_rmw(priv, QCA8K_REG_MAC_PWR_SEL,
++-				QCA8K_MAC_PWR_RGMII0_1_8V |
++-				QCA8K_MAC_PWR_RGMII1_1_8V,
++-				mask);
++-	}
++-
++-	return ret;
++-}
++-
++-static int qca8k_find_cpu_port(struct dsa_switch *ds)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-
++-	/* Find the connected cpu port. Valid port are 0 or 6 */
++-	if (dsa_is_cpu_port(ds, 0))
++-		return 0;
++-
++-	dev_dbg(priv->dev, "port 0 is not the CPU port. Checking port 6");
++-
++-	if (dsa_is_cpu_port(ds, 6))
++-		return 6;
++-
++-	return -EINVAL;
++-}
++-
++-static int
++-qca8k_setup_of_pws_reg(struct qca8k_priv *priv)
++-{
++-	struct device_node *node = priv->dev->of_node;
++-	const struct qca8k_match_data *data;
++-	u32 val = 0;
++-	int ret;
++-
++-	/* QCA8327 require to set to the correct mode.
++-	 * His bigger brother QCA8328 have the 172 pin layout.
++-	 * Should be applied by default but we set this just to make sure.
++-	 */
++-	if (priv->switch_id == QCA8K_ID_QCA8327) {
++-		data = of_device_get_match_data(priv->dev);
++-
++-		/* Set the correct package of 148 pin for QCA8327 */
++-		if (data->reduced_package)
++-			val |= QCA8327_PWS_PACKAGE148_EN;
++-
++-		ret = qca8k_rmw(priv, QCA8K_REG_PWS, QCA8327_PWS_PACKAGE148_EN,
++-				val);
++-		if (ret)
++-			return ret;
++-	}
++-
++-	if (of_property_read_bool(node, "qca,ignore-power-on-sel"))
++-		val |= QCA8K_PWS_POWER_ON_SEL;
++-
++-	if (of_property_read_bool(node, "qca,led-open-drain")) {
++-		if (!(val & QCA8K_PWS_POWER_ON_SEL)) {
++-			dev_err(priv->dev, "qca,led-open-drain require qca,ignore-power-on-sel to be set.");
++-			return -EINVAL;
++-		}
++-
++-		val |= QCA8K_PWS_LED_OPEN_EN_CSR;
++-	}
++-
++-	return qca8k_rmw(priv, QCA8K_REG_PWS,
++-			QCA8K_PWS_LED_OPEN_EN_CSR | QCA8K_PWS_POWER_ON_SEL,
++-			val);
++-}
++-
++-static int
++-qca8k_parse_port_config(struct qca8k_priv *priv)
++-{
++-	int port, cpu_port_index = -1, ret;
++-	struct device_node *port_dn;
++-	phy_interface_t mode;
++-	struct dsa_port *dp;
++-	u32 delay;
++-
++-	/* We have 2 CPU port. Check them */
++-	for (port = 0; port < QCA8K_NUM_PORTS; port++) {
++-		/* Skip every other port */
++-		if (port != 0 && port != 6)
++-			continue;
++-
++-		dp = dsa_to_port(priv->ds, port);
++-		port_dn = dp->dn;
++-		cpu_port_index++;
++-
++-		if (!of_device_is_available(port_dn))
++-			continue;
++-
++-		ret = of_get_phy_mode(port_dn, &mode);
++-		if (ret)
++-			continue;
++-
++-		switch (mode) {
++-		case PHY_INTERFACE_MODE_RGMII:
++-		case PHY_INTERFACE_MODE_RGMII_ID:
++-		case PHY_INTERFACE_MODE_RGMII_TXID:
++-		case PHY_INTERFACE_MODE_RGMII_RXID:
++-		case PHY_INTERFACE_MODE_SGMII:
++-			delay = 0;
++-
++-			if (!of_property_read_u32(port_dn, "tx-internal-delay-ps", &delay))
++-				/* Switch regs accept value in ns, convert ps to ns */
++-				delay = delay / 1000;
++-			else if (mode == PHY_INTERFACE_MODE_RGMII_ID ||
++-				 mode == PHY_INTERFACE_MODE_RGMII_TXID)
++-				delay = 1;
++-
++-			if (!FIELD_FIT(QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK, delay)) {
++-				dev_err(priv->dev, "rgmii tx delay is limited to a max value of 3ns, setting to the max value");
++-				delay = 3;
++-			}
++-
++-			priv->ports_config.rgmii_tx_delay[cpu_port_index] = delay;
++-
++-			delay = 0;
++-
++-			if (!of_property_read_u32(port_dn, "rx-internal-delay-ps", &delay))
++-				/* Switch regs accept value in ns, convert ps to ns */
++-				delay = delay / 1000;
++-			else if (mode == PHY_INTERFACE_MODE_RGMII_ID ||
++-				 mode == PHY_INTERFACE_MODE_RGMII_RXID)
++-				delay = 2;
++-
++-			if (!FIELD_FIT(QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK, delay)) {
++-				dev_err(priv->dev, "rgmii rx delay is limited to a max value of 3ns, setting to the max value");
++-				delay = 3;
++-			}
++-
++-			priv->ports_config.rgmii_rx_delay[cpu_port_index] = delay;
++-
++-			/* Skip sgmii parsing for rgmii* mode */
++-			if (mode == PHY_INTERFACE_MODE_RGMII ||
++-			    mode == PHY_INTERFACE_MODE_RGMII_ID ||
++-			    mode == PHY_INTERFACE_MODE_RGMII_TXID ||
++-			    mode == PHY_INTERFACE_MODE_RGMII_RXID)
++-				break;
++-
++-			if (of_property_read_bool(port_dn, "qca,sgmii-txclk-falling-edge"))
++-				priv->ports_config.sgmii_tx_clk_falling_edge = true;
++-
++-			if (of_property_read_bool(port_dn, "qca,sgmii-rxclk-falling-edge"))
++-				priv->ports_config.sgmii_rx_clk_falling_edge = true;
++-
++-			if (of_property_read_bool(port_dn, "qca,sgmii-enable-pll")) {
++-				priv->ports_config.sgmii_enable_pll = true;
++-
++-				if (priv->switch_id == QCA8K_ID_QCA8327) {
++-					dev_err(priv->dev, "SGMII PLL should NOT be enabled for qca8327. Aborting enabling");
++-					priv->ports_config.sgmii_enable_pll = false;
++-				}
++-
++-				if (priv->switch_revision < 2)
++-					dev_warn(priv->dev, "SGMII PLL should NOT be enabled for qca8337 with revision 2 or more.");
++-			}
++-
++-			break;
++-		default:
++-			continue;
++-		}
++-	}
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_setup(struct dsa_switch *ds)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	int cpu_port, ret, i;
++-	u32 mask;
++-
++-	cpu_port = qca8k_find_cpu_port(ds);
++-	if (cpu_port < 0) {
++-		dev_err(priv->dev, "No cpu port configured in both cpu port0 and port6");
++-		return cpu_port;
++-	}
++-
++-	/* Parse CPU port config to be later used in phy_link mac_config */
++-	ret = qca8k_parse_port_config(priv);
++-	if (ret)
++-		return ret;
++-
++-	ret = qca8k_setup_mdio_bus(priv);
++-	if (ret)
++-		return ret;
++-
++-	ret = qca8k_setup_of_pws_reg(priv);
++-	if (ret)
++-		return ret;
++-
++-	ret = qca8k_setup_mac_pwr_sel(priv);
++-	if (ret)
++-		return ret;
++-
++-	/* Make sure MAC06 is disabled */
++-	ret = regmap_clear_bits(priv->regmap, QCA8K_REG_PORT0_PAD_CTRL,
++-				QCA8K_PORT0_PAD_MAC06_EXCHANGE_EN);
++-	if (ret) {
++-		dev_err(priv->dev, "failed disabling MAC06 exchange");
++-		return ret;
++-	}
++-
++-	/* Enable CPU Port */
++-	ret = regmap_set_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
++-			      QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
++-	if (ret) {
++-		dev_err(priv->dev, "failed enabling CPU port");
++-		return ret;
++-	}
++-
++-	/* Enable MIB counters */
++-	ret = qca8k_mib_init(priv);
++-	if (ret)
++-		dev_warn(priv->dev, "mib init failed");
++-
++-	/* Initial setup of all ports */
++-	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++-		/* Disable forwarding by default on all ports */
++-		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
++-				QCA8K_PORT_LOOKUP_MEMBER, 0);
++-		if (ret)
++-			return ret;
++-
++-		/* Enable QCA header mode on all cpu ports */
++-		if (dsa_is_cpu_port(ds, i)) {
++-			ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(i),
++-					  FIELD_PREP(QCA8K_PORT_HDR_CTRL_TX_MASK, QCA8K_PORT_HDR_CTRL_ALL) |
++-					  FIELD_PREP(QCA8K_PORT_HDR_CTRL_RX_MASK, QCA8K_PORT_HDR_CTRL_ALL));
++-			if (ret) {
++-				dev_err(priv->dev, "failed enabling QCA header mode");
++-				return ret;
++-			}
++-		}
++-
++-		/* Disable MAC by default on all user ports */
++-		if (dsa_is_user_port(ds, i))
++-			qca8k_port_set_status(priv, i, 0);
++-	}
++-
++-	/* Forward all unknown frames to CPU port for Linux processing
++-	 * Notice that in multi-cpu config only one port should be set
++-	 * for igmp, unknown, multicast and broadcast packet
++-	 */
++-	ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1,
++-			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK, BIT(cpu_port)) |
++-			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK, BIT(cpu_port)) |
++-			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_MC_DP_MASK, BIT(cpu_port)) |
++-			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_UC_DP_MASK, BIT(cpu_port)));
++-	if (ret)
++-		return ret;
++-
++-	/* Setup connection between CPU port & user ports
++-	 * Configure specific switch configuration for ports
++-	 */
++-	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++-		/* CPU port gets connected to all user ports of the switch */
++-		if (dsa_is_cpu_port(ds, i)) {
++-			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
++-					QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds));
++-			if (ret)
++-				return ret;
++-		}
++-
++-		/* Individual user ports get connected to CPU port only */
++-		if (dsa_is_user_port(ds, i)) {
++-			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
++-					QCA8K_PORT_LOOKUP_MEMBER,
++-					BIT(cpu_port));
++-			if (ret)
++-				return ret;
++-
++-			/* Enable ARP Auto-learning by default */
++-			ret = regmap_set_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(i),
++-					      QCA8K_PORT_LOOKUP_LEARN);
++-			if (ret)
++-				return ret;
++-
++-			/* For port based vlans to work we need to set the
++-			 * default egress vid
++-			 */
++-			ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(i),
++-					QCA8K_EGREES_VLAN_PORT_MASK(i),
++-					QCA8K_EGREES_VLAN_PORT(i, QCA8K_PORT_VID_DEF));
++-			if (ret)
++-				return ret;
++-
++-			ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(i),
++-					  QCA8K_PORT_VLAN_CVID(QCA8K_PORT_VID_DEF) |
++-					  QCA8K_PORT_VLAN_SVID(QCA8K_PORT_VID_DEF));
++-			if (ret)
++-				return ret;
++-		}
++-
++-		/* The port 5 of the qca8337 have some problem in flood condition. The
++-		 * original legacy driver had some specific buffer and priority settings
++-		 * for the different port suggested by the QCA switch team. Add this
++-		 * missing settings to improve switch stability under load condition.
++-		 * This problem is limited to qca8337 and other qca8k switch are not affected.
++-		 */
++-		if (priv->switch_id == QCA8K_ID_QCA8337) {
++-			switch (i) {
++-			/* The 2 CPU port and port 5 requires some different
++-			 * priority than any other ports.
++-			 */
++-			case 0:
++-			case 5:
++-			case 6:
++-				mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x4) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x4) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PRI4(0x6) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PRI5(0x8) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PORT(0x1e);
++-				break;
++-			default:
++-				mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x6) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x8) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PORT(0x19);
++-			}
++-			qca8k_write(priv, QCA8K_REG_PORT_HOL_CTRL0(i), mask);
++-
++-			mask = QCA8K_PORT_HOL_CTRL1_ING(0x6) |
++-			QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
++-			QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
++-			QCA8K_PORT_HOL_CTRL1_WRED_EN;
++-			qca8k_rmw(priv, QCA8K_REG_PORT_HOL_CTRL1(i),
++-				  QCA8K_PORT_HOL_CTRL1_ING_BUF_MASK |
++-				  QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
++-				  QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
++-				  QCA8K_PORT_HOL_CTRL1_WRED_EN,
++-				  mask);
++-		}
++-	}
++-
++-	/* Special GLOBAL_FC_THRESH value are needed for ar8327 switch */
++-	if (priv->switch_id == QCA8K_ID_QCA8327) {
++-		mask = QCA8K_GLOBAL_FC_GOL_XON_THRES(288) |
++-		       QCA8K_GLOBAL_FC_GOL_XOFF_THRES(496);
++-		qca8k_rmw(priv, QCA8K_REG_GLOBAL_FC_THRESH,
++-			  QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK |
++-			  QCA8K_GLOBAL_FC_GOL_XOFF_THRES_MASK,
++-			  mask);
++-	}
++-
++-	/* Setup our port MTUs to match power on defaults */
++-	ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, ETH_FRAME_LEN + ETH_FCS_LEN);
++-	if (ret)
++-		dev_warn(priv->dev, "failed setting MTU settings");
++-
++-	/* Flush the FDB table */
++-	qca8k_fdb_flush(priv);
++-
++-	/* We don't have interrupts for link changes, so we need to poll */
++-	ds->pcs_poll = true;
++-
++-	/* Set min a max ageing value supported */
++-	ds->ageing_time_min = 7000;
++-	ds->ageing_time_max = 458745000;
++-
++-	/* Set max number of LAGs supported */
++-	ds->num_lag_ids = QCA8K_NUM_LAGS;
++-
++-	return 0;
++-}
++-
++-static void
++-qca8k_mac_config_setup_internal_delay(struct qca8k_priv *priv, int cpu_port_index,
++-				      u32 reg)
++-{
++-	u32 delay, val = 0;
++-	int ret;
++-
++-	/* Delay can be declared in 3 different way.
++-	 * Mode to rgmii and internal-delay standard binding defined
++-	 * rgmii-id or rgmii-tx/rx phy mode set.
++-	 * The parse logic set a delay different than 0 only when one
++-	 * of the 3 different way is used. In all other case delay is
++-	 * not enabled. With ID or TX/RXID delay is enabled and set
++-	 * to the default and recommended value.
++-	 */
++-	if (priv->ports_config.rgmii_tx_delay[cpu_port_index]) {
++-		delay = priv->ports_config.rgmii_tx_delay[cpu_port_index];
++-
++-		val |= QCA8K_PORT_PAD_RGMII_TX_DELAY(delay) |
++-			QCA8K_PORT_PAD_RGMII_TX_DELAY_EN;
++-	}
++-
++-	if (priv->ports_config.rgmii_rx_delay[cpu_port_index]) {
++-		delay = priv->ports_config.rgmii_rx_delay[cpu_port_index];
++-
++-		val |= QCA8K_PORT_PAD_RGMII_RX_DELAY(delay) |
++-			QCA8K_PORT_PAD_RGMII_RX_DELAY_EN;
++-	}
++-
++-	/* Set RGMII delay based on the selected values */
++-	ret = qca8k_rmw(priv, reg,
++-			QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK |
++-			QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK |
++-			QCA8K_PORT_PAD_RGMII_TX_DELAY_EN |
++-			QCA8K_PORT_PAD_RGMII_RX_DELAY_EN,
++-			val);
++-	if (ret)
++-		dev_err(priv->dev, "Failed to set internal delay for CPU port%d",
++-			cpu_port_index == QCA8K_CPU_PORT0 ? 0 : 6);
++-}
++-
++-static void
++-qca8k_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode,
++-			 const struct phylink_link_state *state)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int cpu_port_index, ret;
++-	u32 reg, val;
++-
++-	switch (port) {
++-	case 0: /* 1st CPU port */
++-		if (state->interface != PHY_INTERFACE_MODE_RGMII &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
++-		    state->interface != PHY_INTERFACE_MODE_SGMII)
++-			return;
++-
++-		reg = QCA8K_REG_PORT0_PAD_CTRL;
++-		cpu_port_index = QCA8K_CPU_PORT0;
++-		break;
++-	case 1:
++-	case 2:
++-	case 3:
++-	case 4:
++-	case 5:
++-		/* Internal PHY, nothing to do */
++-		return;
++-	case 6: /* 2nd CPU port / external PHY */
++-		if (state->interface != PHY_INTERFACE_MODE_RGMII &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
++-		    state->interface != PHY_INTERFACE_MODE_SGMII &&
++-		    state->interface != PHY_INTERFACE_MODE_1000BASEX)
++-			return;
++-
++-		reg = QCA8K_REG_PORT6_PAD_CTRL;
++-		cpu_port_index = QCA8K_CPU_PORT6;
++-		break;
++-	default:
++-		dev_err(ds->dev, "%s: unsupported port: %i\n", __func__, port);
++-		return;
++-	}
++-
++-	if (port != 6 && phylink_autoneg_inband(mode)) {
++-		dev_err(ds->dev, "%s: in-band negotiation unsupported\n",
++-			__func__);
++-		return;
++-	}
++-
++-	switch (state->interface) {
++-	case PHY_INTERFACE_MODE_RGMII:
++-	case PHY_INTERFACE_MODE_RGMII_ID:
++-	case PHY_INTERFACE_MODE_RGMII_TXID:
++-	case PHY_INTERFACE_MODE_RGMII_RXID:
++-		qca8k_write(priv, reg, QCA8K_PORT_PAD_RGMII_EN);
++-
++-		/* Configure rgmii delay */
++-		qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg);
++-
++-		/* QCA8337 requires to set rgmii rx delay for all ports.
++-		 * This is enabled through PORT5_PAD_CTRL for all ports,
++-		 * rather than individual port registers.
++-		 */
++-		if (priv->switch_id == QCA8K_ID_QCA8337)
++-			qca8k_write(priv, QCA8K_REG_PORT5_PAD_CTRL,
++-				    QCA8K_PORT_PAD_RGMII_RX_DELAY_EN);
++-		break;
++-	case PHY_INTERFACE_MODE_SGMII:
++-	case PHY_INTERFACE_MODE_1000BASEX:
++-		/* Enable SGMII on the port */
++-		qca8k_write(priv, reg, QCA8K_PORT_PAD_SGMII_EN);
++-
++-		/* Enable/disable SerDes auto-negotiation as necessary */
++-		ret = qca8k_read(priv, QCA8K_REG_PWS, &val);
++-		if (ret)
++-			return;
++-		if (phylink_autoneg_inband(mode))
++-			val &= ~QCA8K_PWS_SERDES_AEN_DIS;
++-		else
++-			val |= QCA8K_PWS_SERDES_AEN_DIS;
++-		qca8k_write(priv, QCA8K_REG_PWS, val);
++-
++-		/* Configure the SGMII parameters */
++-		ret = qca8k_read(priv, QCA8K_REG_SGMII_CTRL, &val);
++-		if (ret)
++-			return;
++-
++-		val |= QCA8K_SGMII_EN_SD;
++-
++-		if (priv->ports_config.sgmii_enable_pll)
++-			val |= QCA8K_SGMII_EN_PLL | QCA8K_SGMII_EN_RX |
++-			       QCA8K_SGMII_EN_TX;
++-
++-		if (dsa_is_cpu_port(ds, port)) {
++-			/* CPU port, we're talking to the CPU MAC, be a PHY */
++-			val &= ~QCA8K_SGMII_MODE_CTRL_MASK;
++-			val |= QCA8K_SGMII_MODE_CTRL_PHY;
++-		} else if (state->interface == PHY_INTERFACE_MODE_SGMII) {
++-			val &= ~QCA8K_SGMII_MODE_CTRL_MASK;
++-			val |= QCA8K_SGMII_MODE_CTRL_MAC;
++-		} else if (state->interface == PHY_INTERFACE_MODE_1000BASEX) {
++-			val &= ~QCA8K_SGMII_MODE_CTRL_MASK;
++-			val |= QCA8K_SGMII_MODE_CTRL_BASEX;
++-		}
++-
++-		qca8k_write(priv, QCA8K_REG_SGMII_CTRL, val);
++-
++-		/* From original code is reported port instability as SGMII also
++-		 * require delay set. Apply advised values here or take them from DT.
++-		 */
++-		if (state->interface == PHY_INTERFACE_MODE_SGMII)
++-			qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg);
++-
++-		/* For qca8327/qca8328/qca8334/qca8338 sgmii is unique and
++-		 * falling edge is set writing in the PORT0 PAD reg
++-		 */
++-		if (priv->switch_id == QCA8K_ID_QCA8327 ||
++-		    priv->switch_id == QCA8K_ID_QCA8337)
++-			reg = QCA8K_REG_PORT0_PAD_CTRL;
++-
++-		val = 0;
++-
++-		/* SGMII Clock phase configuration */
++-		if (priv->ports_config.sgmii_rx_clk_falling_edge)
++-			val |= QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE;
++-
++-		if (priv->ports_config.sgmii_tx_clk_falling_edge)
++-			val |= QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE;
++-
++-		if (val)
++-			ret = qca8k_rmw(priv, reg,
++-					QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE |
++-					QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE,
++-					val);
++-
++-		break;
++-	default:
++-		dev_err(ds->dev, "xMII mode %s not supported for port %d\n",
++-			phy_modes(state->interface), port);
++-		return;
++-	}
++-}
++-
++-static void
++-qca8k_phylink_validate(struct dsa_switch *ds, int port,
++-		       unsigned long *supported,
++-		       struct phylink_link_state *state)
++-{
++-	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
++-
++-	switch (port) {
++-	case 0: /* 1st CPU port */
++-		if (state->interface != PHY_INTERFACE_MODE_NA &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
++-		    state->interface != PHY_INTERFACE_MODE_SGMII)
++-			goto unsupported;
++-		break;
++-	case 1:
++-	case 2:
++-	case 3:
++-	case 4:
++-	case 5:
++-		/* Internal PHY */
++-		if (state->interface != PHY_INTERFACE_MODE_NA &&
++-		    state->interface != PHY_INTERFACE_MODE_GMII &&
++-		    state->interface != PHY_INTERFACE_MODE_INTERNAL)
++-			goto unsupported;
++-		break;
++-	case 6: /* 2nd CPU port / external PHY */
++-		if (state->interface != PHY_INTERFACE_MODE_NA &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
++-		    state->interface != PHY_INTERFACE_MODE_SGMII &&
++-		    state->interface != PHY_INTERFACE_MODE_1000BASEX)
++-			goto unsupported;
++-		break;
++-	default:
++-unsupported:
++-		linkmode_zero(supported);
++-		return;
++-	}
++-
++-	phylink_set_port_modes(mask);
++-	phylink_set(mask, Autoneg);
++-
++-	phylink_set(mask, 1000baseT_Full);
++-	phylink_set(mask, 10baseT_Half);
++-	phylink_set(mask, 10baseT_Full);
++-	phylink_set(mask, 100baseT_Half);
++-	phylink_set(mask, 100baseT_Full);
++-
++-	if (state->interface == PHY_INTERFACE_MODE_1000BASEX)
++-		phylink_set(mask, 1000baseX_Full);
++-
++-	phylink_set(mask, Pause);
++-	phylink_set(mask, Asym_Pause);
++-
++-	linkmode_and(supported, supported, mask);
++-	linkmode_and(state->advertising, state->advertising, mask);
++-}
++-
++-static int
++-qca8k_phylink_mac_link_state(struct dsa_switch *ds, int port,
++-			     struct phylink_link_state *state)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	u32 reg;
++-	int ret;
++-
++-	ret = qca8k_read(priv, QCA8K_REG_PORT_STATUS(port), &reg);
++-	if (ret < 0)
++-		return ret;
++-
++-	state->link = !!(reg & QCA8K_PORT_STATUS_LINK_UP);
++-	state->an_complete = state->link;
++-	state->an_enabled = !!(reg & QCA8K_PORT_STATUS_LINK_AUTO);
++-	state->duplex = (reg & QCA8K_PORT_STATUS_DUPLEX) ? DUPLEX_FULL :
++-							   DUPLEX_HALF;
++-
++-	switch (reg & QCA8K_PORT_STATUS_SPEED) {
++-	case QCA8K_PORT_STATUS_SPEED_10:
++-		state->speed = SPEED_10;
++-		break;
++-	case QCA8K_PORT_STATUS_SPEED_100:
++-		state->speed = SPEED_100;
++-		break;
++-	case QCA8K_PORT_STATUS_SPEED_1000:
++-		state->speed = SPEED_1000;
++-		break;
++-	default:
++-		state->speed = SPEED_UNKNOWN;
++-		break;
++-	}
++-
++-	state->pause = MLO_PAUSE_NONE;
++-	if (reg & QCA8K_PORT_STATUS_RXFLOW)
++-		state->pause |= MLO_PAUSE_RX;
++-	if (reg & QCA8K_PORT_STATUS_TXFLOW)
++-		state->pause |= MLO_PAUSE_TX;
++-
++-	return 1;
++-}
++-
++-static void
++-qca8k_phylink_mac_link_down(struct dsa_switch *ds, int port, unsigned int mode,
++-			    phy_interface_t interface)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-
++-	qca8k_port_set_status(priv, port, 0);
++-}
++-
++-static void
++-qca8k_phylink_mac_link_up(struct dsa_switch *ds, int port, unsigned int mode,
++-			  phy_interface_t interface, struct phy_device *phydev,
++-			  int speed, int duplex, bool tx_pause, bool rx_pause)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	u32 reg;
++-
++-	if (phylink_autoneg_inband(mode)) {
++-		reg = QCA8K_PORT_STATUS_LINK_AUTO;
++-	} else {
++-		switch (speed) {
++-		case SPEED_10:
++-			reg = QCA8K_PORT_STATUS_SPEED_10;
++-			break;
++-		case SPEED_100:
++-			reg = QCA8K_PORT_STATUS_SPEED_100;
++-			break;
++-		case SPEED_1000:
++-			reg = QCA8K_PORT_STATUS_SPEED_1000;
++-			break;
++-		default:
++-			reg = QCA8K_PORT_STATUS_LINK_AUTO;
++-			break;
++-		}
++-
++-		if (duplex == DUPLEX_FULL)
++-			reg |= QCA8K_PORT_STATUS_DUPLEX;
++-
++-		if (rx_pause || dsa_is_cpu_port(ds, port))
++-			reg |= QCA8K_PORT_STATUS_RXFLOW;
++-
++-		if (tx_pause || dsa_is_cpu_port(ds, port))
++-			reg |= QCA8K_PORT_STATUS_TXFLOW;
++-	}
++-
++-	reg |= QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
++-
++-	qca8k_write(priv, QCA8K_REG_PORT_STATUS(port), reg);
++-}
++-
++-static void
++-qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data)
++-{
++-	const struct qca8k_match_data *match_data;
++-	struct qca8k_priv *priv = ds->priv;
++-	int i;
++-
++-	if (stringset != ETH_SS_STATS)
++-		return;
++-
++-	match_data = of_device_get_match_data(priv->dev);
++-
++-	for (i = 0; i < match_data->mib_count; i++)
++-		strncpy(data + i * ETH_GSTRING_LEN, ar8327_mib[i].name,
++-			ETH_GSTRING_LEN);
++-}
++-
++-static void qca8k_mib_autocast_handler(struct dsa_switch *ds, struct sk_buff *skb)
++-{
++-	const struct qca8k_match_data *match_data;
++-	struct qca8k_mib_eth_data *mib_eth_data;
++-	struct qca8k_priv *priv = ds->priv;
++-	const struct qca8k_mib_desc *mib;
++-	struct mib_ethhdr *mib_ethhdr;
++-	int i, mib_len, offset = 0;
++-	u64 *data;
++-	u8 port;
++-
++-	mib_ethhdr = (struct mib_ethhdr *)skb_mac_header(skb);
++-	mib_eth_data = &priv->mib_eth_data;
++-
++-	/* The switch autocast every port. Ignore other packet and
++-	 * parse only the requested one.
++-	 */
++-	port = FIELD_GET(QCA_HDR_RECV_SOURCE_PORT, ntohs(mib_ethhdr->hdr));
++-	if (port != mib_eth_data->req_port)
++-		goto exit;
++-
++-	match_data = device_get_match_data(priv->dev);
++-	data = mib_eth_data->data;
++-
++-	for (i = 0; i < match_data->mib_count; i++) {
++-		mib = &ar8327_mib[i];
++-
++-		/* First 3 mib are present in the skb head */
++-		if (i < 3) {
++-			data[i] = mib_ethhdr->data[i];
++-			continue;
++-		}
++-
++-		mib_len = sizeof(uint32_t);
++-
++-		/* Some mib are 64 bit wide */
++-		if (mib->size == 2)
++-			mib_len = sizeof(uint64_t);
++-
++-		/* Copy the mib value from packet to the */
++-		memcpy(data + i, skb->data + offset, mib_len);
++-
++-		/* Set the offset for the next mib */
++-		offset += mib_len;
++-	}
++-
++-exit:
++-	/* Complete on receiving all the mib packet */
++-	if (refcount_dec_and_test(&mib_eth_data->port_parsed))
++-		complete(&mib_eth_data->rw_done);
++-}
++-
++-static int
++-qca8k_get_ethtool_stats_eth(struct dsa_switch *ds, int port, u64 *data)
++-{
++-	struct dsa_port *dp = dsa_to_port(ds, port);
++-	struct qca8k_mib_eth_data *mib_eth_data;
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret;
++-
++-	mib_eth_data = &priv->mib_eth_data;
++-
++-	mutex_lock(&mib_eth_data->mutex);
++-
++-	reinit_completion(&mib_eth_data->rw_done);
++-
++-	mib_eth_data->req_port = dp->index;
++-	mib_eth_data->data = data;
++-	refcount_set(&mib_eth_data->port_parsed, QCA8K_NUM_PORTS);
++-
++-	mutex_lock(&priv->reg_mutex);
++-
++-	/* Send mib autocast request */
++-	ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB,
++-				 QCA8K_MIB_FUNC | QCA8K_MIB_BUSY,
++-				 FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_CAST) |
++-				 QCA8K_MIB_BUSY);
++-
++-	mutex_unlock(&priv->reg_mutex);
++-
++-	if (ret)
++-		goto exit;
++-
++-	ret = wait_for_completion_timeout(&mib_eth_data->rw_done, QCA8K_ETHERNET_TIMEOUT);
++-
++-exit:
++-	mutex_unlock(&mib_eth_data->mutex);
++-
++-	return ret;
++-}
++-
++-static void
++-qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
++-			uint64_t *data)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	const struct qca8k_match_data *match_data;
++-	const struct qca8k_mib_desc *mib;
++-	u32 reg, i, val;
++-	u32 hi = 0;
++-	int ret;
++-
++-	if (priv->mgmt_master &&
++-	    qca8k_get_ethtool_stats_eth(ds, port, data) > 0)
++-		return;
++-
++-	match_data = of_device_get_match_data(priv->dev);
++-
++-	for (i = 0; i < match_data->mib_count; i++) {
++-		mib = &ar8327_mib[i];
++-		reg = QCA8K_PORT_MIB_COUNTER(port) + mib->offset;
++-
++-		ret = qca8k_read(priv, reg, &val);
++-		if (ret < 0)
++-			continue;
++-
++-		if (mib->size == 2) {
++-			ret = qca8k_read(priv, reg + 4, &hi);
++-			if (ret < 0)
++-				continue;
++-		}
++-
++-		data[i] = val;
++-		if (mib->size == 2)
++-			data[i] |= (u64)hi << 32;
++-	}
++-}
++-
++-static int
++-qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset)
++-{
++-	const struct qca8k_match_data *match_data;
++-	struct qca8k_priv *priv = ds->priv;
++-
++-	if (sset != ETH_SS_STATS)
++-		return 0;
++-
++-	match_data = of_device_get_match_data(priv->dev);
++-
++-	return match_data->mib_count;
++-}
++-
++-static int
++-qca8k_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *eee)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	u32 lpi_en = QCA8K_REG_EEE_CTRL_LPI_EN(port);
++-	u32 reg;
++-	int ret;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	ret = qca8k_read(priv, QCA8K_REG_EEE_CTRL, &reg);
++-	if (ret < 0)
++-		goto exit;
++-
++-	if (eee->eee_enabled)
++-		reg |= lpi_en;
++-	else
++-		reg &= ~lpi_en;
++-	ret = qca8k_write(priv, QCA8K_REG_EEE_CTRL, reg);
++-
++-exit:
++-	mutex_unlock(&priv->reg_mutex);
++-	return ret;
++-}
++-
++-static int
++-qca8k_get_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e)
++-{
++-	/* Nothing to do on the port's MAC */
++-	return 0;
++-}
++-
++-static void
++-qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	u32 stp_state;
++-
++-	switch (state) {
++-	case BR_STATE_DISABLED:
++-		stp_state = QCA8K_PORT_LOOKUP_STATE_DISABLED;
++-		break;
++-	case BR_STATE_BLOCKING:
++-		stp_state = QCA8K_PORT_LOOKUP_STATE_BLOCKING;
++-		break;
++-	case BR_STATE_LISTENING:
++-		stp_state = QCA8K_PORT_LOOKUP_STATE_LISTENING;
++-		break;
++-	case BR_STATE_LEARNING:
++-		stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING;
++-		break;
++-	case BR_STATE_FORWARDING:
++-	default:
++-		stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD;
++-		break;
++-	}
++-
++-	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++-		  QCA8K_PORT_LOOKUP_STATE_MASK, stp_state);
++-}
++-
++-static int
++-qca8k_port_bridge_join(struct dsa_switch *ds, int port, struct net_device *br)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	int port_mask, cpu_port;
++-	int i, ret;
++-
++-	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
++-	port_mask = BIT(cpu_port);
++-
++-	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++-		if (dsa_is_cpu_port(ds, i))
++-			continue;
++-		if (dsa_to_port(ds, i)->bridge_dev != br)
++-			continue;
++-		/* Add this port to the portvlan mask of the other ports
++-		 * in the bridge
++-		 */
++-		ret = regmap_set_bits(priv->regmap,
++-				      QCA8K_PORT_LOOKUP_CTRL(i),
++-				      BIT(port));
++-		if (ret)
++-			return ret;
++-		if (i != port)
++-			port_mask |= BIT(i);
++-	}
++-
++-	/* Add all other ports to this ports portvlan mask */
++-	ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++-			QCA8K_PORT_LOOKUP_MEMBER, port_mask);
++-
++-	return ret;
++-}
++-
++-static void
++-qca8k_port_bridge_leave(struct dsa_switch *ds, int port, struct net_device *br)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	int cpu_port, i;
++-
++-	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
++-
++-	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++-		if (dsa_is_cpu_port(ds, i))
++-			continue;
++-		if (dsa_to_port(ds, i)->bridge_dev != br)
++-			continue;
++-		/* Remove this port to the portvlan mask of the other ports
++-		 * in the bridge
++-		 */
++-		regmap_clear_bits(priv->regmap,
++-				  QCA8K_PORT_LOOKUP_CTRL(i),
++-				  BIT(port));
++-	}
++-
++-	/* Set the cpu port to be the only one in the portvlan mask of
++-	 * this port
++-	 */
++-	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++-		  QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port));
++-}
++-
++-static void
++-qca8k_port_fast_age(struct dsa_switch *ds, int port)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port);
++-	mutex_unlock(&priv->reg_mutex);
++-}
++-
++-static int
++-qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	unsigned int secs = msecs / 1000;
++-	u32 val;
++-
++-	/* AGE_TIME reg is set in 7s step */
++-	val = secs / 7;
++-
++-	/* Handle case with 0 as val to NOT disable
++-	 * learning
++-	 */
++-	if (!val)
++-		val = 1;
++-
++-	return regmap_update_bits(priv->regmap, QCA8K_REG_ATU_CTRL, QCA8K_ATU_AGE_TIME_MASK,
++-				  QCA8K_ATU_AGE_TIME(val));
++-}
++-
++-static int
++-qca8k_port_enable(struct dsa_switch *ds, int port,
++-		  struct phy_device *phy)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-
++-	qca8k_port_set_status(priv, port, 1);
++-	priv->port_enabled_map |= BIT(port);
++-
++-	if (dsa_is_user_port(ds, port))
++-		phy_support_asym_pause(phy);
++-
++-	return 0;
++-}
++-
++-static void
++-qca8k_port_disable(struct dsa_switch *ds, int port)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-
++-	qca8k_port_set_status(priv, port, 0);
++-	priv->port_enabled_map &= ~BIT(port);
++-}
++-
++-static int
++-qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret;
++-
++-	/* We have only have a general MTU setting.
++-	 * DSA always set the CPU port's MTU to the largest MTU of the slave
++-	 * ports.
++-	 * Setting MTU just for the CPU port is sufficient to correctly set a
++-	 * value for every port.
++-	 */
++-	if (!dsa_is_cpu_port(ds, port))
++-		return 0;
++-
++-	/* To change the MAX_FRAME_SIZE the cpu ports must be off or
++-	 * the switch panics.
++-	 * Turn off both cpu ports before applying the new value to prevent
++-	 * this.
++-	 */
++-	if (priv->port_enabled_map & BIT(0))
++-		qca8k_port_set_status(priv, 0, 0);
++-
++-	if (priv->port_enabled_map & BIT(6))
++-		qca8k_port_set_status(priv, 6, 0);
++-
++-	/* Include L2 header / FCS length */
++-	ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, new_mtu + ETH_HLEN + ETH_FCS_LEN);
++-
++-	if (priv->port_enabled_map & BIT(0))
++-		qca8k_port_set_status(priv, 0, 1);
++-
++-	if (priv->port_enabled_map & BIT(6))
++-		qca8k_port_set_status(priv, 6, 1);
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_port_max_mtu(struct dsa_switch *ds, int port)
++-{
++-	return QCA8K_MAX_MTU;
++-}
++-
++-static int
++-qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
++-		      u16 port_mask, u16 vid)
++-{
++-	/* Set the vid to the port vlan id if no vid is set */
++-	if (!vid)
++-		vid = QCA8K_PORT_VID_DEF;
++-
++-	return qca8k_fdb_add(priv, addr, port_mask, vid,
++-			     QCA8K_ATU_STATUS_STATIC);
++-}
++-
++-static int
++-qca8k_port_fdb_add(struct dsa_switch *ds, int port,
++-		   const unsigned char *addr, u16 vid)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	u16 port_mask = BIT(port);
++-
++-	return qca8k_port_fdb_insert(priv, addr, port_mask, vid);
++-}
++-
++-static int
++-qca8k_port_fdb_del(struct dsa_switch *ds, int port,
++-		   const unsigned char *addr, u16 vid)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	u16 port_mask = BIT(port);
++-
++-	if (!vid)
++-		vid = QCA8K_PORT_VID_DEF;
++-
++-	return qca8k_fdb_del(priv, addr, port_mask, vid);
++-}
++-
++-static int
++-qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
++-		    dsa_fdb_dump_cb_t *cb, void *data)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	struct qca8k_fdb _fdb = { 0 };
++-	int cnt = QCA8K_NUM_FDB_RECORDS;
++-	bool is_static;
++-	int ret = 0;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port)) {
++-		if (!_fdb.aging)
++-			break;
++-		is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC);
++-		ret = cb(_fdb.mac, _fdb.vid, is_static, data);
++-		if (ret)
++-			break;
++-	}
++-	mutex_unlock(&priv->reg_mutex);
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_port_mdb_add(struct dsa_switch *ds, int port,
++-		   const struct switchdev_obj_port_mdb *mdb)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	const u8 *addr = mdb->addr;
++-	u16 vid = mdb->vid;
++-
++-	return qca8k_fdb_search_and_insert(priv, BIT(port), addr, vid);
++-}
++-
++-static int
++-qca8k_port_mdb_del(struct dsa_switch *ds, int port,
++-		   const struct switchdev_obj_port_mdb *mdb)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	const u8 *addr = mdb->addr;
++-	u16 vid = mdb->vid;
++-
++-	return qca8k_fdb_search_and_del(priv, BIT(port), addr, vid);
++-}
++-
++-static int
++-qca8k_port_mirror_add(struct dsa_switch *ds, int port,
++-		      struct dsa_mall_mirror_tc_entry *mirror,
++-		      bool ingress)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int monitor_port, ret;
++-	u32 reg, val;
++-
++-	/* Check for existent entry */
++-	if ((ingress ? priv->mirror_rx : priv->mirror_tx) & BIT(port))
++-		return -EEXIST;
++-
++-	ret = regmap_read(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0, &val);
++-	if (ret)
++-		return ret;
++-
++-	/* QCA83xx can have only one port set to mirror mode.
++-	 * Check that the correct port is requested and return error otherwise.
++-	 * When no mirror port is set, the values is set to 0xF
++-	 */
++-	monitor_port = FIELD_GET(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
++-	if (monitor_port != 0xF && monitor_port != mirror->to_local_port)
++-		return -EEXIST;
++-
++-	/* Set the monitor port */
++-	val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM,
++-			 mirror->to_local_port);
++-	ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
++-				 QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
++-	if (ret)
++-		return ret;
++-
++-	if (ingress) {
++-		reg = QCA8K_PORT_LOOKUP_CTRL(port);
++-		val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN;
++-	} else {
++-		reg = QCA8K_REG_PORT_HOL_CTRL1(port);
++-		val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN;
++-	}
++-
++-	ret = regmap_update_bits(priv->regmap, reg, val, val);
++-	if (ret)
++-		return ret;
++-
++-	/* Track mirror port for tx and rx to decide when the
++-	 * mirror port has to be disabled.
++-	 */
++-	if (ingress)
++-		priv->mirror_rx |= BIT(port);
++-	else
++-		priv->mirror_tx |= BIT(port);
++-
++-	return 0;
++-}
++-
++-static void
++-qca8k_port_mirror_del(struct dsa_switch *ds, int port,
++-		      struct dsa_mall_mirror_tc_entry *mirror)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	u32 reg, val;
++-	int ret;
++-
++-	if (mirror->ingress) {
++-		reg = QCA8K_PORT_LOOKUP_CTRL(port);
++-		val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN;
++-	} else {
++-		reg = QCA8K_REG_PORT_HOL_CTRL1(port);
++-		val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN;
++-	}
++-
++-	ret = regmap_clear_bits(priv->regmap, reg, val);
++-	if (ret)
++-		goto err;
++-
++-	if (mirror->ingress)
++-		priv->mirror_rx &= ~BIT(port);
++-	else
++-		priv->mirror_tx &= ~BIT(port);
++-
++-	/* No port set to send packet to mirror port. Disable mirror port */
++-	if (!priv->mirror_rx && !priv->mirror_tx) {
++-		val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, 0xF);
++-		ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
++-					 QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
++-		if (ret)
++-			goto err;
++-	}
++-err:
++-	dev_err(priv->dev, "Failed to del mirror port from %d", port);
++-}
++-
++-static int
++-qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
++-			  struct netlink_ext_ack *extack)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret;
++-
++-	if (vlan_filtering) {
++-		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++-				QCA8K_PORT_LOOKUP_VLAN_MODE_MASK,
++-				QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE);
++-	} else {
++-		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++-				QCA8K_PORT_LOOKUP_VLAN_MODE_MASK,
++-				QCA8K_PORT_LOOKUP_VLAN_MODE_NONE);
++-	}
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_port_vlan_add(struct dsa_switch *ds, int port,
++-		    const struct switchdev_obj_port_vlan *vlan,
++-		    struct netlink_ext_ack *extack)
++-{
++-	bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
++-	bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret;
++-
++-	ret = qca8k_vlan_add(priv, port, vlan->vid, untagged);
++-	if (ret) {
++-		dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port, ret);
++-		return ret;
++-	}
++-
++-	if (pvid) {
++-		ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port),
++-				QCA8K_EGREES_VLAN_PORT_MASK(port),
++-				QCA8K_EGREES_VLAN_PORT(port, vlan->vid));
++-		if (ret)
++-			return ret;
++-
++-		ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port),
++-				  QCA8K_PORT_VLAN_CVID(vlan->vid) |
++-				  QCA8K_PORT_VLAN_SVID(vlan->vid));
++-	}
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_port_vlan_del(struct dsa_switch *ds, int port,
++-		    const struct switchdev_obj_port_vlan *vlan)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret;
++-
++-	ret = qca8k_vlan_del(priv, port, vlan->vid);
++-	if (ret)
++-		dev_err(priv->dev, "Failed to delete VLAN from port %d (%d)", port, ret);
++-
++-	return ret;
++-}
++-
++-static u32 qca8k_get_phy_flags(struct dsa_switch *ds, int port)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-
++-	/* Communicate to the phy internal driver the switch revision.
++-	 * Based on the switch revision different values needs to be
++-	 * set to the dbg and mmd reg on the phy.
++-	 * The first 2 bit are used to communicate the switch revision
++-	 * to the phy driver.
++-	 */
++-	if (port > 0 && port < 6)
++-		return priv->switch_revision;
++-
++-	return 0;
++-}
++-
++-static enum dsa_tag_protocol
++-qca8k_get_tag_protocol(struct dsa_switch *ds, int port,
++-		       enum dsa_tag_protocol mp)
++-{
++-	return DSA_TAG_PROTO_QCA;
++-}
++-
++-static bool
++-qca8k_lag_can_offload(struct dsa_switch *ds,
++-		      struct net_device *lag,
++-		      struct netdev_lag_upper_info *info)
++-{
++-	struct dsa_port *dp;
++-	int id, members = 0;
++-
++-	id = dsa_lag_id(ds->dst, lag);
++-	if (id < 0 || id >= ds->num_lag_ids)
++-		return false;
++-
++-	dsa_lag_foreach_port(dp, ds->dst, lag)
++-		/* Includes the port joining the LAG */
++-		members++;
++-
++-	if (members > QCA8K_NUM_PORTS_FOR_LAG)
++-		return false;
++-
++-	if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH)
++-		return false;
++-
++-	if (info->hash_type != NETDEV_LAG_HASH_L2 &&
++-	    info->hash_type != NETDEV_LAG_HASH_L23)
++-		return false;
++-
++-	return true;
++-}
++-
++-static int
++-qca8k_lag_setup_hash(struct dsa_switch *ds,
++-		     struct net_device *lag,
++-		     struct netdev_lag_upper_info *info)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	bool unique_lag = true;
++-	u32 hash = 0;
++-	int i, id;
++-
++-	id = dsa_lag_id(ds->dst, lag);
++-
++-	switch (info->hash_type) {
++-	case NETDEV_LAG_HASH_L23:
++-		hash |= QCA8K_TRUNK_HASH_SIP_EN;
++-		hash |= QCA8K_TRUNK_HASH_DIP_EN;
++-		fallthrough;
++-	case NETDEV_LAG_HASH_L2:
++-		hash |= QCA8K_TRUNK_HASH_SA_EN;
++-		hash |= QCA8K_TRUNK_HASH_DA_EN;
++-		break;
++-	default: /* We should NEVER reach this */
++-		return -EOPNOTSUPP;
++-	}
++-
++-	/* Check if we are the unique configured LAG */
++-	dsa_lags_foreach_id(i, ds->dst)
++-		if (i != id && dsa_lag_dev(ds->dst, i)) {
++-			unique_lag = false;
++-			break;
++-		}
++-
++-	/* Hash Mode is global. Make sure the same Hash Mode
++-	 * is set to all the 4 possible lag.
++-	 * If we are the unique LAG we can set whatever hash
++-	 * mode we want.
++-	 * To change hash mode it's needed to remove all LAG
++-	 * and change the mode with the latest.
++-	 */
++-	if (unique_lag) {
++-		priv->lag_hash_mode = hash;
++-	} else if (priv->lag_hash_mode != hash) {
++-		netdev_err(lag, "Error: Mismateched Hash Mode across different lag is not supported\n");
++-		return -EOPNOTSUPP;
++-	}
++-
++-	return regmap_update_bits(priv->regmap, QCA8K_TRUNK_HASH_EN_CTRL,
++-				  QCA8K_TRUNK_HASH_MASK, hash);
++-}
++-
++-static int
++-qca8k_lag_refresh_portmap(struct dsa_switch *ds, int port,
++-			  struct net_device *lag, bool delete)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret, id, i;
++-	u32 val;
++-
++-	id = dsa_lag_id(ds->dst, lag);
++-
++-	/* Read current port member */
++-	ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0, &val);
++-	if (ret)
++-		return ret;
++-
++-	/* Shift val to the correct trunk */
++-	val >>= QCA8K_REG_GOL_TRUNK_SHIFT(id);
++-	val &= QCA8K_REG_GOL_TRUNK_MEMBER_MASK;
++-	if (delete)
++-		val &= ~BIT(port);
++-	else
++-		val |= BIT(port);
++-
++-	/* Update port member. With empty portmap disable trunk */
++-	ret = regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0,
++-				 QCA8K_REG_GOL_TRUNK_MEMBER(id) |
++-				 QCA8K_REG_GOL_TRUNK_EN(id),
++-				 !val << QCA8K_REG_GOL_TRUNK_SHIFT(id) |
++-				 val << QCA8K_REG_GOL_TRUNK_SHIFT(id));
++-
++-	/* Search empty member if adding or port on deleting */
++-	for (i = 0; i < QCA8K_NUM_PORTS_FOR_LAG; i++) {
++-		ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id), &val);
++-		if (ret)
++-			return ret;
++-
++-		val >>= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i);
++-		val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_MASK;
++-
++-		if (delete) {
++-			/* If port flagged to be disabled assume this member is
++-			 * empty
++-			 */
++-			if (val != QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
++-				continue;
++-
++-			val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK;
++-			if (val != port)
++-				continue;
++-		} else {
++-			/* If port flagged to be enabled assume this member is
++-			 * already set
++-			 */
++-			if (val == QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
++-				continue;
++-		}
++-
++-		/* We have found the member to add/remove */
++-		break;
++-	}
++-
++-	/* Set port in the correct port mask or disable port if in delete mode */
++-	return regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id),
++-				  QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN(id, i) |
++-				  QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT(id, i),
++-				  !delete << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i) |
++-				  port << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i));
++-}
++-
++-static int
++-qca8k_port_lag_join(struct dsa_switch *ds, int port,
++-		    struct net_device *lag,
++-		    struct netdev_lag_upper_info *info)
++-{
++-	int ret;
++-
++-	if (!qca8k_lag_can_offload(ds, lag, info))
++-		return -EOPNOTSUPP;
++-
++-	ret = qca8k_lag_setup_hash(ds, lag, info);
++-	if (ret)
++-		return ret;
++-
++-	return qca8k_lag_refresh_portmap(ds, port, lag, false);
++-}
++-
++-static int
++-qca8k_port_lag_leave(struct dsa_switch *ds, int port,
++-		     struct net_device *lag)
++-{
++-	return qca8k_lag_refresh_portmap(ds, port, lag, true);
++-}
++-
++-static void
++-qca8k_master_change(struct dsa_switch *ds, const struct net_device *master,
++-		    bool operational)
++-{
++-	struct dsa_port *dp = master->dsa_ptr;
++-	struct qca8k_priv *priv = ds->priv;
++-
++-	/* Ethernet MIB/MDIO is only supported for CPU port 0 */
++-	if (dp->index != 0)
++-		return;
++-
++-	mutex_lock(&priv->mgmt_eth_data.mutex);
++-	mutex_lock(&priv->mib_eth_data.mutex);
++-
++-	priv->mgmt_master = operational ? (struct net_device *)master : NULL;
++-
++-	mutex_unlock(&priv->mib_eth_data.mutex);
++-	mutex_unlock(&priv->mgmt_eth_data.mutex);
++-}
++-
++-static int qca8k_connect_tag_protocol(struct dsa_switch *ds,
++-				      enum dsa_tag_protocol proto)
++-{
++-	struct qca_tagger_data *tagger_data;
++-
++-	switch (proto) {
++-	case DSA_TAG_PROTO_QCA:
++-		tagger_data = ds->tagger_data;
++-
++-		tagger_data->rw_reg_ack_handler = qca8k_rw_reg_ack_handler;
++-		tagger_data->mib_autocast_handler = qca8k_mib_autocast_handler;
++-
++-		break;
++-	default:
++-		return -EOPNOTSUPP;
++-	}
++-
++-	return 0;
++-}
++-
++-static const struct dsa_switch_ops qca8k_switch_ops = {
++-	.get_tag_protocol	= qca8k_get_tag_protocol,
++-	.setup			= qca8k_setup,
++-	.get_strings		= qca8k_get_strings,
++-	.get_ethtool_stats	= qca8k_get_ethtool_stats,
++-	.get_sset_count		= qca8k_get_sset_count,
++-	.set_ageing_time	= qca8k_set_ageing_time,
++-	.get_mac_eee		= qca8k_get_mac_eee,
++-	.set_mac_eee		= qca8k_set_mac_eee,
++-	.port_enable		= qca8k_port_enable,
++-	.port_disable		= qca8k_port_disable,
++-	.port_change_mtu	= qca8k_port_change_mtu,
++-	.port_max_mtu		= qca8k_port_max_mtu,
++-	.port_stp_state_set	= qca8k_port_stp_state_set,
++-	.port_bridge_join	= qca8k_port_bridge_join,
++-	.port_bridge_leave	= qca8k_port_bridge_leave,
++-	.port_fast_age		= qca8k_port_fast_age,
++-	.port_fdb_add		= qca8k_port_fdb_add,
++-	.port_fdb_del		= qca8k_port_fdb_del,
++-	.port_fdb_dump		= qca8k_port_fdb_dump,
++-	.port_mdb_add		= qca8k_port_mdb_add,
++-	.port_mdb_del		= qca8k_port_mdb_del,
++-	.port_mirror_add	= qca8k_port_mirror_add,
++-	.port_mirror_del	= qca8k_port_mirror_del,
++-	.port_vlan_filtering	= qca8k_port_vlan_filtering,
++-	.port_vlan_add		= qca8k_port_vlan_add,
++-	.port_vlan_del		= qca8k_port_vlan_del,
++-	.phylink_validate	= qca8k_phylink_validate,
++-	.phylink_mac_link_state	= qca8k_phylink_mac_link_state,
++-	.phylink_mac_config	= qca8k_phylink_mac_config,
++-	.phylink_mac_link_down	= qca8k_phylink_mac_link_down,
++-	.phylink_mac_link_up	= qca8k_phylink_mac_link_up,
++-	.get_phy_flags		= qca8k_get_phy_flags,
++-	.port_lag_join		= qca8k_port_lag_join,
++-	.port_lag_leave		= qca8k_port_lag_leave,
++-	.master_state_change	= qca8k_master_change,
++-	.connect_tag_protocol	= qca8k_connect_tag_protocol,
++-};
++-
++-static int qca8k_read_switch_id(struct qca8k_priv *priv)
++-{
++-	const struct qca8k_match_data *data;
++-	u32 val;
++-	u8 id;
++-	int ret;
++-
++-	/* get the switches ID from the compatible */
++-	data = of_device_get_match_data(priv->dev);
++-	if (!data)
++-		return -ENODEV;
++-
++-	ret = qca8k_read(priv, QCA8K_REG_MASK_CTRL, &val);
++-	if (ret < 0)
++-		return -ENODEV;
++-
++-	id = QCA8K_MASK_CTRL_DEVICE_ID(val);
++-	if (id != data->id) {
++-		dev_err(priv->dev, "Switch id detected %x but expected %x", id, data->id);
++-		return -ENODEV;
++-	}
++-
++-	priv->switch_id = id;
++-
++-	/* Save revision to communicate to the internal PHY driver */
++-	priv->switch_revision = QCA8K_MASK_CTRL_REV_ID(val);
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_sw_probe(struct mdio_device *mdiodev)
++-{
++-	struct qca8k_priv *priv;
++-	int ret;
++-
++-	/* allocate the private data struct so that we can probe the switches
++-	 * ID register
++-	 */
++-	priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL);
++-	if (!priv)
++-		return -ENOMEM;
++-
++-	priv->bus = mdiodev->bus;
++-	priv->dev = &mdiodev->dev;
++-
++-	priv->reset_gpio = devm_gpiod_get_optional(priv->dev, "reset",
++-						   GPIOD_ASIS);
++-	if (IS_ERR(priv->reset_gpio))
++-		return PTR_ERR(priv->reset_gpio);
++-
++-	if (priv->reset_gpio) {
++-		gpiod_set_value_cansleep(priv->reset_gpio, 1);
++-		/* The active low duration must be greater than 10 ms
++-		 * and checkpatch.pl wants 20 ms.
++-		 */
++-		msleep(20);
++-		gpiod_set_value_cansleep(priv->reset_gpio, 0);
++-	}
++-
++-	/* Start by setting up the register mapping */
++-	priv->regmap = devm_regmap_init(&mdiodev->dev, NULL, priv,
++-					&qca8k_regmap_config);
++-	if (IS_ERR(priv->regmap)) {
++-		dev_err(priv->dev, "regmap initialization failed");
++-		return PTR_ERR(priv->regmap);
++-	}
++-
++-	priv->mdio_cache.page = 0xffff;
++-	priv->mdio_cache.lo = 0xffff;
++-	priv->mdio_cache.hi = 0xffff;
++-
++-	/* Check the detected switch id */
++-	ret = qca8k_read_switch_id(priv);
++-	if (ret)
++-		return ret;
++-
++-	priv->ds = devm_kzalloc(&mdiodev->dev, sizeof(*priv->ds), GFP_KERNEL);
++-	if (!priv->ds)
++-		return -ENOMEM;
++-
++-	mutex_init(&priv->mgmt_eth_data.mutex);
++-	init_completion(&priv->mgmt_eth_data.rw_done);
++-
++-	mutex_init(&priv->mib_eth_data.mutex);
++-	init_completion(&priv->mib_eth_data.rw_done);
++-
++-	priv->ds->dev = &mdiodev->dev;
++-	priv->ds->num_ports = QCA8K_NUM_PORTS;
++-	priv->ds->priv = priv;
++-	priv->ds->ops = &qca8k_switch_ops;
++-	mutex_init(&priv->reg_mutex);
++-	dev_set_drvdata(&mdiodev->dev, priv);
++-
++-	return dsa_register_switch(priv->ds);
++-}
++-
++-static void
++-qca8k_sw_remove(struct mdio_device *mdiodev)
++-{
++-	struct qca8k_priv *priv = dev_get_drvdata(&mdiodev->dev);
++-	int i;
++-
++-	if (!priv)
++-		return;
++-
++-	for (i = 0; i < QCA8K_NUM_PORTS; i++)
++-		qca8k_port_set_status(priv, i, 0);
++-
++-	dsa_unregister_switch(priv->ds);
++-
++-	dev_set_drvdata(&mdiodev->dev, NULL);
++-}
++-
++-static void qca8k_sw_shutdown(struct mdio_device *mdiodev)
++-{
++-	struct qca8k_priv *priv = dev_get_drvdata(&mdiodev->dev);
++-
++-	if (!priv)
++-		return;
++-
++-	dsa_switch_shutdown(priv->ds);
++-
++-	dev_set_drvdata(&mdiodev->dev, NULL);
++-}
++-
++-#ifdef CONFIG_PM_SLEEP
++-static void
++-qca8k_set_pm(struct qca8k_priv *priv, int enable)
++-{
++-	int port;
++-
++-	for (port = 0; port < QCA8K_NUM_PORTS; port++) {
++-		/* Do not enable on resume if the port was
++-		 * disabled before.
++-		 */
++-		if (!(priv->port_enabled_map & BIT(port)))
++-			continue;
++-
++-		qca8k_port_set_status(priv, port, enable);
++-	}
++-}
++-
++-static int qca8k_suspend(struct device *dev)
++-{
++-	struct qca8k_priv *priv = dev_get_drvdata(dev);
++-
++-	qca8k_set_pm(priv, 0);
++-
++-	return dsa_switch_suspend(priv->ds);
++-}
++-
++-static int qca8k_resume(struct device *dev)
++-{
++-	struct qca8k_priv *priv = dev_get_drvdata(dev);
++-
++-	qca8k_set_pm(priv, 1);
++-
++-	return dsa_switch_resume(priv->ds);
++-}
++-#endif /* CONFIG_PM_SLEEP */
++-
++-static SIMPLE_DEV_PM_OPS(qca8k_pm_ops,
++-			 qca8k_suspend, qca8k_resume);
++-
++-static const struct qca8k_match_data qca8327 = {
++-	.id = QCA8K_ID_QCA8327,
++-	.reduced_package = true,
++-	.mib_count = QCA8K_QCA832X_MIB_COUNT,
++-};
++-
++-static const struct qca8k_match_data qca8328 = {
++-	.id = QCA8K_ID_QCA8327,
++-	.mib_count = QCA8K_QCA832X_MIB_COUNT,
++-};
++-
++-static const struct qca8k_match_data qca833x = {
++-	.id = QCA8K_ID_QCA8337,
++-	.mib_count = QCA8K_QCA833X_MIB_COUNT,
++-};
++-
++-static const struct of_device_id qca8k_of_match[] = {
++-	{ .compatible = "qca,qca8327", .data = &qca8327 },
++-	{ .compatible = "qca,qca8328", .data = &qca8328 },
++-	{ .compatible = "qca,qca8334", .data = &qca833x },
++-	{ .compatible = "qca,qca8337", .data = &qca833x },
++-	{ /* sentinel */ },
++-};
++-
++-static struct mdio_driver qca8kmdio_driver = {
++-	.probe  = qca8k_sw_probe,
++-	.remove = qca8k_sw_remove,
++-	.shutdown = qca8k_sw_shutdown,
++-	.mdiodrv.driver = {
++-		.name = "qca8k",
++-		.of_match_table = qca8k_of_match,
++-		.pm = &qca8k_pm_ops,
++-	},
++-};
++-
++-mdio_module_driver(qca8kmdio_driver);
++-
++-MODULE_AUTHOR("Mathieu Olivari, John Crispin <john@phrozen.org>");
++-MODULE_DESCRIPTION("Driver for QCA8K ethernet switch family");
++-MODULE_LICENSE("GPL v2");
++-MODULE_ALIAS("platform:qca8k");
++--- a/drivers/net/dsa/qca8k.h
+++++ /dev/null
++@@ -1,411 +0,0 @@
++-/* SPDX-License-Identifier: GPL-2.0-only */
++-/*
++- * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
++- * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
++- * Copyright (c) 2015, The Linux Foundation. All rights reserved.
++- */
++-
++-#ifndef __QCA8K_H
++-#define __QCA8K_H
++-
++-#include <linux/delay.h>
++-#include <linux/regmap.h>
++-#include <linux/gpio.h>
++-#include <linux/dsa/tag_qca.h>
++-
++-#define QCA8K_ETHERNET_MDIO_PRIORITY			7
++-#define QCA8K_ETHERNET_PHY_PRIORITY			6
++-#define QCA8K_ETHERNET_TIMEOUT				100
++-
++-#define QCA8K_NUM_PORTS					7
++-#define QCA8K_NUM_CPU_PORTS				2
++-#define QCA8K_MAX_MTU					9000
++-#define QCA8K_NUM_LAGS					4
++-#define QCA8K_NUM_PORTS_FOR_LAG				4
++-
++-#define PHY_ID_QCA8327					0x004dd034
++-#define QCA8K_ID_QCA8327				0x12
++-#define PHY_ID_QCA8337					0x004dd036
++-#define QCA8K_ID_QCA8337				0x13
++-
++-#define QCA8K_QCA832X_MIB_COUNT				39
++-#define QCA8K_QCA833X_MIB_COUNT				41
++-
++-#define QCA8K_BUSY_WAIT_TIMEOUT				2000
++-
++-#define QCA8K_NUM_FDB_RECORDS				2048
++-
++-#define QCA8K_PORT_VID_DEF				1
++-
++-/* Global control registers */
++-#define QCA8K_REG_MASK_CTRL				0x000
++-#define   QCA8K_MASK_CTRL_REV_ID_MASK			GENMASK(7, 0)
++-#define   QCA8K_MASK_CTRL_REV_ID(x)			FIELD_GET(QCA8K_MASK_CTRL_REV_ID_MASK, x)
++-#define   QCA8K_MASK_CTRL_DEVICE_ID_MASK		GENMASK(15, 8)
++-#define   QCA8K_MASK_CTRL_DEVICE_ID(x)			FIELD_GET(QCA8K_MASK_CTRL_DEVICE_ID_MASK, x)
++-#define QCA8K_REG_PORT0_PAD_CTRL			0x004
++-#define   QCA8K_PORT0_PAD_MAC06_EXCHANGE_EN		BIT(31)
++-#define   QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE	BIT(19)
++-#define   QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE	BIT(18)
++-#define QCA8K_REG_PORT5_PAD_CTRL			0x008
++-#define QCA8K_REG_PORT6_PAD_CTRL			0x00c
++-#define   QCA8K_PORT_PAD_RGMII_EN			BIT(26)
++-#define   QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK		GENMASK(23, 22)
++-#define   QCA8K_PORT_PAD_RGMII_TX_DELAY(x)		FIELD_PREP(QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK, x)
++-#define   QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK		GENMASK(21, 20)
++-#define   QCA8K_PORT_PAD_RGMII_RX_DELAY(x)		FIELD_PREP(QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK, x)
++-#define	  QCA8K_PORT_PAD_RGMII_TX_DELAY_EN		BIT(25)
++-#define   QCA8K_PORT_PAD_RGMII_RX_DELAY_EN		BIT(24)
++-#define   QCA8K_PORT_PAD_SGMII_EN			BIT(7)
++-#define QCA8K_REG_PWS					0x010
++-#define   QCA8K_PWS_POWER_ON_SEL			BIT(31)
++-/* This reg is only valid for QCA832x and toggle the package
++- * type from 176 pin (by default) to 148 pin used on QCA8327
++- */
++-#define   QCA8327_PWS_PACKAGE148_EN			BIT(30)
++-#define   QCA8K_PWS_LED_OPEN_EN_CSR			BIT(24)
++-#define   QCA8K_PWS_SERDES_AEN_DIS			BIT(7)
++-#define QCA8K_REG_MODULE_EN				0x030
++-#define   QCA8K_MODULE_EN_MIB				BIT(0)
++-#define QCA8K_REG_MIB					0x034
++-#define   QCA8K_MIB_FUNC				GENMASK(26, 24)
++-#define   QCA8K_MIB_CPU_KEEP				BIT(20)
++-#define   QCA8K_MIB_BUSY				BIT(17)
++-#define QCA8K_MDIO_MASTER_CTRL				0x3c
++-#define   QCA8K_MDIO_MASTER_BUSY			BIT(31)
++-#define   QCA8K_MDIO_MASTER_EN				BIT(30)
++-#define   QCA8K_MDIO_MASTER_READ			BIT(27)
++-#define   QCA8K_MDIO_MASTER_WRITE			0
++-#define   QCA8K_MDIO_MASTER_SUP_PRE			BIT(26)
++-#define   QCA8K_MDIO_MASTER_PHY_ADDR_MASK		GENMASK(25, 21)
++-#define   QCA8K_MDIO_MASTER_PHY_ADDR(x)			FIELD_PREP(QCA8K_MDIO_MASTER_PHY_ADDR_MASK, x)
++-#define   QCA8K_MDIO_MASTER_REG_ADDR_MASK		GENMASK(20, 16)
++-#define   QCA8K_MDIO_MASTER_REG_ADDR(x)			FIELD_PREP(QCA8K_MDIO_MASTER_REG_ADDR_MASK, x)
++-#define   QCA8K_MDIO_MASTER_DATA_MASK			GENMASK(15, 0)
++-#define   QCA8K_MDIO_MASTER_DATA(x)			FIELD_PREP(QCA8K_MDIO_MASTER_DATA_MASK, x)
++-#define   QCA8K_MDIO_MASTER_MAX_PORTS			5
++-#define   QCA8K_MDIO_MASTER_MAX_REG			32
++-#define QCA8K_GOL_MAC_ADDR0				0x60
++-#define QCA8K_GOL_MAC_ADDR1				0x64
++-#define QCA8K_MAX_FRAME_SIZE				0x78
++-#define QCA8K_REG_PORT_STATUS(_i)			(0x07c + (_i) * 4)
++-#define   QCA8K_PORT_STATUS_SPEED			GENMASK(1, 0)
++-#define   QCA8K_PORT_STATUS_SPEED_10			0
++-#define   QCA8K_PORT_STATUS_SPEED_100			0x1
++-#define   QCA8K_PORT_STATUS_SPEED_1000			0x2
++-#define   QCA8K_PORT_STATUS_TXMAC			BIT(2)
++-#define   QCA8K_PORT_STATUS_RXMAC			BIT(3)
++-#define   QCA8K_PORT_STATUS_TXFLOW			BIT(4)
++-#define   QCA8K_PORT_STATUS_RXFLOW			BIT(5)
++-#define   QCA8K_PORT_STATUS_DUPLEX			BIT(6)
++-#define   QCA8K_PORT_STATUS_LINK_UP			BIT(8)
++-#define   QCA8K_PORT_STATUS_LINK_AUTO			BIT(9)
++-#define   QCA8K_PORT_STATUS_LINK_PAUSE			BIT(10)
++-#define   QCA8K_PORT_STATUS_FLOW_AUTO			BIT(12)
++-#define QCA8K_REG_PORT_HDR_CTRL(_i)			(0x9c + (_i * 4))
++-#define   QCA8K_PORT_HDR_CTRL_RX_MASK			GENMASK(3, 2)
++-#define   QCA8K_PORT_HDR_CTRL_TX_MASK			GENMASK(1, 0)
++-#define   QCA8K_PORT_HDR_CTRL_ALL			2
++-#define   QCA8K_PORT_HDR_CTRL_MGMT			1
++-#define   QCA8K_PORT_HDR_CTRL_NONE			0
++-#define QCA8K_REG_SGMII_CTRL				0x0e0
++-#define   QCA8K_SGMII_EN_PLL				BIT(1)
++-#define   QCA8K_SGMII_EN_RX				BIT(2)
++-#define   QCA8K_SGMII_EN_TX				BIT(3)
++-#define   QCA8K_SGMII_EN_SD				BIT(4)
++-#define   QCA8K_SGMII_CLK125M_DELAY			BIT(7)
++-#define   QCA8K_SGMII_MODE_CTRL_MASK			GENMASK(23, 22)
++-#define   QCA8K_SGMII_MODE_CTRL(x)			FIELD_PREP(QCA8K_SGMII_MODE_CTRL_MASK, x)
++-#define   QCA8K_SGMII_MODE_CTRL_BASEX			QCA8K_SGMII_MODE_CTRL(0x0)
++-#define   QCA8K_SGMII_MODE_CTRL_PHY			QCA8K_SGMII_MODE_CTRL(0x1)
++-#define   QCA8K_SGMII_MODE_CTRL_MAC			QCA8K_SGMII_MODE_CTRL(0x2)
++-
++-/* MAC_PWR_SEL registers */
++-#define QCA8K_REG_MAC_PWR_SEL				0x0e4
++-#define   QCA8K_MAC_PWR_RGMII1_1_8V			BIT(18)
++-#define   QCA8K_MAC_PWR_RGMII0_1_8V			BIT(19)
++-
++-/* EEE control registers */
++-#define QCA8K_REG_EEE_CTRL				0x100
++-#define  QCA8K_REG_EEE_CTRL_LPI_EN(_i)			((_i + 1) * 2)
++-
++-/* TRUNK_HASH_EN registers */
++-#define QCA8K_TRUNK_HASH_EN_CTRL			0x270
++-#define   QCA8K_TRUNK_HASH_SIP_EN			BIT(3)
++-#define   QCA8K_TRUNK_HASH_DIP_EN			BIT(2)
++-#define   QCA8K_TRUNK_HASH_SA_EN			BIT(1)
++-#define   QCA8K_TRUNK_HASH_DA_EN			BIT(0)
++-#define   QCA8K_TRUNK_HASH_MASK				GENMASK(3, 0)
++-
++-/* ACL registers */
++-#define QCA8K_REG_PORT_VLAN_CTRL0(_i)			(0x420 + (_i * 8))
++-#define   QCA8K_PORT_VLAN_CVID_MASK			GENMASK(27, 16)
++-#define   QCA8K_PORT_VLAN_CVID(x)			FIELD_PREP(QCA8K_PORT_VLAN_CVID_MASK, x)
++-#define   QCA8K_PORT_VLAN_SVID_MASK			GENMASK(11, 0)
++-#define   QCA8K_PORT_VLAN_SVID(x)			FIELD_PREP(QCA8K_PORT_VLAN_SVID_MASK, x)
++-#define QCA8K_REG_PORT_VLAN_CTRL1(_i)			(0x424 + (_i * 8))
++-#define QCA8K_REG_IPV4_PRI_BASE_ADDR			0x470
++-#define QCA8K_REG_IPV4_PRI_ADDR_MASK			0x474
++-
++-/* Lookup registers */
++-#define QCA8K_REG_ATU_DATA0				0x600
++-#define   QCA8K_ATU_ADDR2_MASK				GENMASK(31, 24)
++-#define   QCA8K_ATU_ADDR3_MASK				GENMASK(23, 16)
++-#define   QCA8K_ATU_ADDR4_MASK				GENMASK(15, 8)
++-#define   QCA8K_ATU_ADDR5_MASK				GENMASK(7, 0)
++-#define QCA8K_REG_ATU_DATA1				0x604
++-#define   QCA8K_ATU_PORT_MASK				GENMASK(22, 16)
++-#define   QCA8K_ATU_ADDR0_MASK				GENMASK(15, 8)
++-#define   QCA8K_ATU_ADDR1_MASK				GENMASK(7, 0)
++-#define QCA8K_REG_ATU_DATA2				0x608
++-#define   QCA8K_ATU_VID_MASK				GENMASK(19, 8)
++-#define   QCA8K_ATU_STATUS_MASK				GENMASK(3, 0)
++-#define   QCA8K_ATU_STATUS_STATIC			0xf
++-#define QCA8K_REG_ATU_FUNC				0x60c
++-#define   QCA8K_ATU_FUNC_BUSY				BIT(31)
++-#define   QCA8K_ATU_FUNC_PORT_EN			BIT(14)
++-#define   QCA8K_ATU_FUNC_MULTI_EN			BIT(13)
++-#define   QCA8K_ATU_FUNC_FULL				BIT(12)
++-#define   QCA8K_ATU_FUNC_PORT_MASK			GENMASK(11, 8)
++-#define QCA8K_REG_VTU_FUNC0				0x610
++-#define   QCA8K_VTU_FUNC0_VALID				BIT(20)
++-#define   QCA8K_VTU_FUNC0_IVL_EN			BIT(19)
++-/*        QCA8K_VTU_FUNC0_EG_MODE_MASK			GENMASK(17, 4)
++- *          It does contain VLAN_MODE for each port [5:4] for port0,
++- *          [7:6] for port1 ... [17:16] for port6. Use virtual port
++- *          define to handle this.
++- */
++-#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i)	(4 + (_i) * 2)
++-#define   QCA8K_VTU_FUNC0_EG_MODE_MASK			GENMASK(1, 0)
++-#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(_i)		(GENMASK(1, 0) << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
++-#define   QCA8K_VTU_FUNC0_EG_MODE_UNMOD			FIELD_PREP(QCA8K_VTU_FUNC0_EG_MODE_MASK, 0x0)
++-#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_UNMOD(_i)	(QCA8K_VTU_FUNC0_EG_MODE_UNMOD << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
++-#define   QCA8K_VTU_FUNC0_EG_MODE_UNTAG			FIELD_PREP(QCA8K_VTU_FUNC0_EG_MODE_MASK, 0x1)
++-#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_UNTAG(_i)	(QCA8K_VTU_FUNC0_EG_MODE_UNTAG << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
++-#define   QCA8K_VTU_FUNC0_EG_MODE_TAG			FIELD_PREP(QCA8K_VTU_FUNC0_EG_MODE_MASK, 0x2)
++-#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_TAG(_i)		(QCA8K_VTU_FUNC0_EG_MODE_TAG << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
++-#define   QCA8K_VTU_FUNC0_EG_MODE_NOT			FIELD_PREP(QCA8K_VTU_FUNC0_EG_MODE_MASK, 0x3)
++-#define   QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(_i)		(QCA8K_VTU_FUNC0_EG_MODE_NOT << QCA8K_VTU_FUNC0_EG_MODE_PORT_SHIFT(_i))
++-#define QCA8K_REG_VTU_FUNC1				0x614
++-#define   QCA8K_VTU_FUNC1_BUSY				BIT(31)
++-#define   QCA8K_VTU_FUNC1_VID_MASK			GENMASK(27, 16)
++-#define   QCA8K_VTU_FUNC1_FULL				BIT(4)
++-#define QCA8K_REG_ATU_CTRL				0x618
++-#define   QCA8K_ATU_AGE_TIME_MASK			GENMASK(15, 0)
++-#define   QCA8K_ATU_AGE_TIME(x)				FIELD_PREP(QCA8K_ATU_AGE_TIME_MASK, (x))
++-#define QCA8K_REG_GLOBAL_FW_CTRL0			0x620
++-#define   QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN		BIT(10)
++-#define   QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM		GENMASK(7, 4)
++-#define QCA8K_REG_GLOBAL_FW_CTRL1			0x624
++-#define   QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK		GENMASK(30, 24)
++-#define   QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK		GENMASK(22, 16)
++-#define   QCA8K_GLOBAL_FW_CTRL1_MC_DP_MASK		GENMASK(14, 8)
++-#define   QCA8K_GLOBAL_FW_CTRL1_UC_DP_MASK		GENMASK(6, 0)
++-#define QCA8K_PORT_LOOKUP_CTRL(_i)			(0x660 + (_i) * 0xc)
++-#define   QCA8K_PORT_LOOKUP_MEMBER			GENMASK(6, 0)
++-#define   QCA8K_PORT_LOOKUP_VLAN_MODE_MASK		GENMASK(9, 8)
++-#define   QCA8K_PORT_LOOKUP_VLAN_MODE(x)		FIELD_PREP(QCA8K_PORT_LOOKUP_VLAN_MODE_MASK, x)
++-#define   QCA8K_PORT_LOOKUP_VLAN_MODE_NONE		QCA8K_PORT_LOOKUP_VLAN_MODE(0x0)
++-#define   QCA8K_PORT_LOOKUP_VLAN_MODE_FALLBACK		QCA8K_PORT_LOOKUP_VLAN_MODE(0x1)
++-#define   QCA8K_PORT_LOOKUP_VLAN_MODE_CHECK		QCA8K_PORT_LOOKUP_VLAN_MODE(0x2)
++-#define   QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE		QCA8K_PORT_LOOKUP_VLAN_MODE(0x3)
++-#define   QCA8K_PORT_LOOKUP_STATE_MASK			GENMASK(18, 16)
++-#define   QCA8K_PORT_LOOKUP_STATE(x)			FIELD_PREP(QCA8K_PORT_LOOKUP_STATE_MASK, x)
++-#define   QCA8K_PORT_LOOKUP_STATE_DISABLED		QCA8K_PORT_LOOKUP_STATE(0x0)
++-#define   QCA8K_PORT_LOOKUP_STATE_BLOCKING		QCA8K_PORT_LOOKUP_STATE(0x1)
++-#define   QCA8K_PORT_LOOKUP_STATE_LISTENING		QCA8K_PORT_LOOKUP_STATE(0x2)
++-#define   QCA8K_PORT_LOOKUP_STATE_LEARNING		QCA8K_PORT_LOOKUP_STATE(0x3)
++-#define   QCA8K_PORT_LOOKUP_STATE_FORWARD		QCA8K_PORT_LOOKUP_STATE(0x4)
++-#define   QCA8K_PORT_LOOKUP_LEARN			BIT(20)
++-#define   QCA8K_PORT_LOOKUP_ING_MIRROR_EN		BIT(25)
++-
++-#define QCA8K_REG_GOL_TRUNK_CTRL0			0x700
++-/* 4 max trunk first
++- * first 6 bit for member bitmap
++- * 7th bit is to enable trunk port
++- */
++-#define QCA8K_REG_GOL_TRUNK_SHIFT(_i)			((_i) * 8)
++-#define QCA8K_REG_GOL_TRUNK_EN_MASK			BIT(7)
++-#define QCA8K_REG_GOL_TRUNK_EN(_i)			(QCA8K_REG_GOL_TRUNK_EN_MASK << QCA8K_REG_GOL_TRUNK_SHIFT(_i))
++-#define QCA8K_REG_GOL_TRUNK_MEMBER_MASK			GENMASK(6, 0)
++-#define QCA8K_REG_GOL_TRUNK_MEMBER(_i)			(QCA8K_REG_GOL_TRUNK_MEMBER_MASK << QCA8K_REG_GOL_TRUNK_SHIFT(_i))
++-/* 0x704 for TRUNK 0-1 --- 0x708 for TRUNK 2-3 */
++-#define QCA8K_REG_GOL_TRUNK_CTRL(_i)			(0x704 + (((_i) / 2) * 4))
++-#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_MASK		GENMASK(3, 0)
++-#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK		BIT(3)
++-#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK		GENMASK(2, 0)
++-#define QCA8K_REG_GOL_TRUNK_ID_SHIFT(_i)		(((_i) / 2) * 16)
++-#define QCA8K_REG_GOL_MEM_ID_SHIFT(_i)			((_i) * 4)
++-/* Complex shift: FIRST shift for port THEN shift for trunk */
++-#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(_i, _j)	(QCA8K_REG_GOL_MEM_ID_SHIFT(_j) + QCA8K_REG_GOL_TRUNK_ID_SHIFT(_i))
++-#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN(_i, _j)	(QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(_i, _j))
++-#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT(_i, _j)	(QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(_i, _j))
++-
++-#define QCA8K_REG_GLOBAL_FC_THRESH			0x800
++-#define   QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK		GENMASK(24, 16)
++-#define   QCA8K_GLOBAL_FC_GOL_XON_THRES(x)		FIELD_PREP(QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK, x)
++-#define   QCA8K_GLOBAL_FC_GOL_XOFF_THRES_MASK		GENMASK(8, 0)
++-#define   QCA8K_GLOBAL_FC_GOL_XOFF_THRES(x)		FIELD_PREP(QCA8K_GLOBAL_FC_GOL_XOFF_THRES_MASK, x)
++-
++-#define QCA8K_REG_PORT_HOL_CTRL0(_i)			(0x970 + (_i) * 0x8)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI0_BUF_MASK		GENMASK(3, 0)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI0(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI0_BUF_MASK, x)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI1_BUF_MASK		GENMASK(7, 4)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI1(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI1_BUF_MASK, x)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI2_BUF_MASK		GENMASK(11, 8)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI2(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI2_BUF_MASK, x)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI3_BUF_MASK		GENMASK(15, 12)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI3(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI3_BUF_MASK, x)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI4_BUF_MASK		GENMASK(19, 16)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI4(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI4_BUF_MASK, x)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI5_BUF_MASK		GENMASK(23, 20)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PRI5(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PRI5_BUF_MASK, x)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PORT_BUF_MASK		GENMASK(29, 24)
++-#define   QCA8K_PORT_HOL_CTRL0_EG_PORT(x)		FIELD_PREP(QCA8K_PORT_HOL_CTRL0_EG_PORT_BUF_MASK, x)
++-
++-#define QCA8K_REG_PORT_HOL_CTRL1(_i)			(0x974 + (_i) * 0x8)
++-#define   QCA8K_PORT_HOL_CTRL1_ING_BUF_MASK		GENMASK(3, 0)
++-#define   QCA8K_PORT_HOL_CTRL1_ING(x)			FIELD_PREP(QCA8K_PORT_HOL_CTRL1_ING_BUF_MASK, x)
++-#define   QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN		BIT(6)
++-#define   QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN		BIT(7)
++-#define   QCA8K_PORT_HOL_CTRL1_WRED_EN			BIT(8)
++-#define   QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN		BIT(16)
++-
++-/* Pkt edit registers */
++-#define QCA8K_EGREES_VLAN_PORT_SHIFT(_i)		(16 * ((_i) % 2))
++-#define QCA8K_EGREES_VLAN_PORT_MASK(_i)			(GENMASK(11, 0) << QCA8K_EGREES_VLAN_PORT_SHIFT(_i))
++-#define QCA8K_EGREES_VLAN_PORT(_i, x)			((x) << QCA8K_EGREES_VLAN_PORT_SHIFT(_i))
++-#define QCA8K_EGRESS_VLAN(x)				(0x0c70 + (4 * (x / 2)))
++-
++-/* L3 registers */
++-#define QCA8K_HROUTER_CONTROL				0xe00
++-#define   QCA8K_HROUTER_CONTROL_GLB_LOCKTIME_M		GENMASK(17, 16)
++-#define   QCA8K_HROUTER_CONTROL_GLB_LOCKTIME_S		16
++-#define   QCA8K_HROUTER_CONTROL_ARP_AGE_MODE		1
++-#define QCA8K_HROUTER_PBASED_CONTROL1			0xe08
++-#define QCA8K_HROUTER_PBASED_CONTROL2			0xe0c
++-#define QCA8K_HNAT_CONTROL				0xe38
++-
++-/* MIB registers */
++-#define QCA8K_PORT_MIB_COUNTER(_i)			(0x1000 + (_i) * 0x100)
++-
++-/* QCA specific MII registers */
++-#define MII_ATH_MMD_ADDR				0x0d
++-#define MII_ATH_MMD_DATA				0x0e
++-
++-enum {
++-	QCA8K_PORT_SPEED_10M = 0,
++-	QCA8K_PORT_SPEED_100M = 1,
++-	QCA8K_PORT_SPEED_1000M = 2,
++-	QCA8K_PORT_SPEED_ERR = 3,
++-};
++-
++-enum qca8k_fdb_cmd {
++-	QCA8K_FDB_FLUSH	= 1,
++-	QCA8K_FDB_LOAD = 2,
++-	QCA8K_FDB_PURGE = 3,
++-	QCA8K_FDB_FLUSH_PORT = 5,
++-	QCA8K_FDB_NEXT = 6,
++-	QCA8K_FDB_SEARCH = 7,
++-};
++-
++-enum qca8k_vlan_cmd {
++-	QCA8K_VLAN_FLUSH = 1,
++-	QCA8K_VLAN_LOAD = 2,
++-	QCA8K_VLAN_PURGE = 3,
++-	QCA8K_VLAN_REMOVE_PORT = 4,
++-	QCA8K_VLAN_NEXT = 5,
++-	QCA8K_VLAN_READ = 6,
++-};
++-
++-enum qca8k_mid_cmd {
++-	QCA8K_MIB_FLUSH = 1,
++-	QCA8K_MIB_FLUSH_PORT = 2,
++-	QCA8K_MIB_CAST = 3,
++-};
++-
++-struct qca8k_match_data {
++-	u8 id;
++-	bool reduced_package;
++-	u8 mib_count;
++-};
++-
++-enum {
++-	QCA8K_CPU_PORT0,
++-	QCA8K_CPU_PORT6,
++-};
++-
++-struct qca8k_mgmt_eth_data {
++-	struct completion rw_done;
++-	struct mutex mutex; /* Enforce one mdio read/write at time */
++-	bool ack;
++-	u32 seq;
++-	u32 data[4];
++-};
++-
++-struct qca8k_mib_eth_data {
++-	struct completion rw_done;
++-	struct mutex mutex; /* Process one command at time */
++-	refcount_t port_parsed; /* Counter to track parsed port */
++-	u8 req_port;
++-	u64 *data; /* pointer to ethtool data */
++-};
++-
++-struct qca8k_ports_config {
++-	bool sgmii_rx_clk_falling_edge;
++-	bool sgmii_tx_clk_falling_edge;
++-	bool sgmii_enable_pll;
++-	u8 rgmii_rx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
++-	u8 rgmii_tx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
++-};
++-
++-struct qca8k_mdio_cache {
++-/* The 32bit switch registers are accessed indirectly. To achieve this we need
++- * to set the page of the register. Track the last page that was set to reduce
++- * mdio writes
++- */
++-	u16 page;
++-/* lo and hi can also be cached and from Documentation we can skip one
++- * extra mdio write if lo or hi is didn't change.
++- */
++-	u16 lo;
++-	u16 hi;
++-};
++-
++-struct qca8k_priv {
++-	u8 switch_id;
++-	u8 switch_revision;
++-	u8 mirror_rx;
++-	u8 mirror_tx;
++-	u8 lag_hash_mode;
++-	/* Each bit correspond to a port. This switch can support a max of 7 port.
++-	 * Bit 1: port enabled. Bit 0: port disabled.
++-	 */
++-	u8 port_enabled_map;
++-	struct qca8k_ports_config ports_config;
++-	struct regmap *regmap;
++-	struct mii_bus *bus;
++-	struct dsa_switch *ds;
++-	struct mutex reg_mutex;
++-	struct device *dev;
++-	struct gpio_desc *reset_gpio;
++-	struct net_device *mgmt_master; /* Track if mdio/mib Ethernet is available */
++-	struct qca8k_mgmt_eth_data mgmt_eth_data;
++-	struct qca8k_mib_eth_data mib_eth_data;
++-	struct qca8k_mdio_cache mdio_cache;
++-};
++-
++-struct qca8k_mib_desc {
++-	unsigned int size;
++-	unsigned int offset;
++-	const char *name;
++-};
++-
++-struct qca8k_fdb {
++-	u16 vid;
++-	u8 port_mask;
++-	u8 aging;
++-	u8 mac[6];
++-};
++-
++-#endif /* __QCA8K_H */
+diff --git a/target/linux/generic/backport-5.15/771-v6.0-01-net-dsa-qca8k-cache-match-data-to-speed-up-access.patch b/target/linux/generic/backport-5.15/771-v6.0-01-net-dsa-qca8k-cache-match-data-to-speed-up-access.patch
+new file mode 100644
+index 0000000000..77fe64632f
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/771-v6.0-01-net-dsa-qca8k-cache-match-data-to-speed-up-access.patch
+@@ -0,0 +1,157 @@
++From 3bb0844e7bcd0fb0bcfab6202b5edd349ef5250a Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Wed, 27 Jul 2022 13:35:10 +0200
++Subject: [PATCH 01/14] net: dsa: qca8k: cache match data to speed up access
++
++Using of_device_get_match_data is expensive. Cache match data to speed
++up access and rework user of match data to use the new cached value.
++
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/dsa/qca/qca8k.c | 35 +++++++++++------------------------
++ drivers/net/dsa/qca/qca8k.h |  1 +
++ 2 files changed, 12 insertions(+), 24 deletions(-)
++
++--- a/drivers/net/dsa/qca/qca8k.c
+++++ b/drivers/net/dsa/qca/qca8k.c
++@@ -1462,8 +1462,8 @@ static int qca8k_find_cpu_port(struct ds
++ static int
++ qca8k_setup_of_pws_reg(struct qca8k_priv *priv)
++ {
+++	const struct qca8k_match_data *data = priv->info;
++ 	struct device_node *node = priv->dev->of_node;
++-	const struct qca8k_match_data *data;
++ 	u32 val = 0;
++ 	int ret;
++ 
++@@ -1472,8 +1472,6 @@ qca8k_setup_of_pws_reg(struct qca8k_priv
++ 	 * Should be applied by default but we set this just to make sure.
++ 	 */
++ 	if (priv->switch_id == QCA8K_ID_QCA8327) {
++-		data = of_device_get_match_data(priv->dev);
++-
++ 		/* Set the correct package of 148 pin for QCA8327 */
++ 		if (data->reduced_package)
++ 			val |= QCA8327_PWS_PACKAGE148_EN;
++@@ -2146,23 +2144,19 @@ qca8k_phylink_mac_link_up(struct dsa_swi
++ static void
++ qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data)
++ {
++-	const struct qca8k_match_data *match_data;
++ 	struct qca8k_priv *priv = ds->priv;
++ 	int i;
++ 
++ 	if (stringset != ETH_SS_STATS)
++ 		return;
++ 
++-	match_data = of_device_get_match_data(priv->dev);
++-
++-	for (i = 0; i < match_data->mib_count; i++)
+++	for (i = 0; i < priv->info->mib_count; i++)
++ 		strncpy(data + i * ETH_GSTRING_LEN, ar8327_mib[i].name,
++ 			ETH_GSTRING_LEN);
++ }
++ 
++ static void qca8k_mib_autocast_handler(struct dsa_switch *ds, struct sk_buff *skb)
++ {
++-	const struct qca8k_match_data *match_data;
++ 	struct qca8k_mib_eth_data *mib_eth_data;
++ 	struct qca8k_priv *priv = ds->priv;
++ 	const struct qca8k_mib_desc *mib;
++@@ -2181,10 +2175,9 @@ static void qca8k_mib_autocast_handler(s
++ 	if (port != mib_eth_data->req_port)
++ 		goto exit;
++ 
++-	match_data = device_get_match_data(priv->dev);
++ 	data = mib_eth_data->data;
++ 
++-	for (i = 0; i < match_data->mib_count; i++) {
+++	for (i = 0; i < priv->info->mib_count; i++) {
++ 		mib = &ar8327_mib[i];
++ 
++ 		/* First 3 mib are present in the skb head */
++@@ -2256,7 +2249,6 @@ qca8k_get_ethtool_stats(struct dsa_switc
++ 			uint64_t *data)
++ {
++ 	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	const struct qca8k_match_data *match_data;
++ 	const struct qca8k_mib_desc *mib;
++ 	u32 reg, i, val;
++ 	u32 hi = 0;
++@@ -2266,9 +2258,7 @@ qca8k_get_ethtool_stats(struct dsa_switc
++ 	    qca8k_get_ethtool_stats_eth(ds, port, data) > 0)
++ 		return;
++ 
++-	match_data = of_device_get_match_data(priv->dev);
++-
++-	for (i = 0; i < match_data->mib_count; i++) {
+++	for (i = 0; i < priv->info->mib_count; i++) {
++ 		mib = &ar8327_mib[i];
++ 		reg = QCA8K_PORT_MIB_COUNTER(port) + mib->offset;
++ 
++@@ -2291,15 +2281,12 @@ qca8k_get_ethtool_stats(struct dsa_switc
++ static int
++ qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset)
++ {
++-	const struct qca8k_match_data *match_data;
++ 	struct qca8k_priv *priv = ds->priv;
++ 
++ 	if (sset != ETH_SS_STATS)
++ 		return 0;
++ 
++-	match_data = of_device_get_match_data(priv->dev);
++-
++-	return match_data->mib_count;
+++	return priv->info->mib_count;
++ }
++ 
++ static int
++@@ -3037,14 +3024,11 @@ static const struct dsa_switch_ops qca8k
++ 
++ static int qca8k_read_switch_id(struct qca8k_priv *priv)
++ {
++-	const struct qca8k_match_data *data;
++ 	u32 val;
++ 	u8 id;
++ 	int ret;
++ 
++-	/* get the switches ID from the compatible */
++-	data = of_device_get_match_data(priv->dev);
++-	if (!data)
+++	if (!priv->info)
++ 		return -ENODEV;
++ 
++ 	ret = qca8k_read(priv, QCA8K_REG_MASK_CTRL, &val);
++@@ -3052,8 +3036,10 @@ static int qca8k_read_switch_id(struct q
++ 		return -ENODEV;
++ 
++ 	id = QCA8K_MASK_CTRL_DEVICE_ID(val);
++-	if (id != data->id) {
++-		dev_err(priv->dev, "Switch id detected %x but expected %x", id, data->id);
+++	if (id != priv->info->id) {
+++		dev_err(priv->dev,
+++			"Switch id detected %x but expected %x",
+++			id, priv->info->id);
++ 		return -ENODEV;
++ 	}
++ 
++@@ -3078,6 +3064,7 @@ qca8k_sw_probe(struct mdio_device *mdiod
++ 	if (!priv)
++ 		return -ENOMEM;
++ 
+++	priv->info = of_device_get_match_data(priv->dev);
++ 	priv->bus = mdiodev->bus;
++ 	priv->dev = &mdiodev->dev;
++ 
++--- a/drivers/net/dsa/qca/qca8k.h
+++++ b/drivers/net/dsa/qca/qca8k.h
++@@ -393,6 +393,7 @@ struct qca8k_priv {
++ 	struct qca8k_mgmt_eth_data mgmt_eth_data;
++ 	struct qca8k_mib_eth_data mib_eth_data;
++ 	struct qca8k_mdio_cache mdio_cache;
+++	const struct qca8k_match_data *info;
++ };
++ 
++ struct qca8k_mib_desc {
+diff --git a/target/linux/generic/backport-5.15/771-v6.0-02-net-dsa-qca8k-make-mib-autocast-feature-optional.patch b/target/linux/generic/backport-5.15/771-v6.0-02-net-dsa-qca8k-make-mib-autocast-feature-optional.patch
+new file mode 100644
+index 0000000000..5b2dce4c55
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/771-v6.0-02-net-dsa-qca8k-make-mib-autocast-feature-optional.patch
+@@ -0,0 +1,77 @@
++From 533c64bca62a8654f00698bc893f639013e38c7b Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Wed, 27 Jul 2022 13:35:11 +0200
++Subject: [PATCH 02/14] net: dsa: qca8k: make mib autocast feature optional
++
++Some switch may not support mib autocast feature and require the legacy
++way of reading the regs directly.
++Make the mib autocast feature optional and permit to declare support for
++it using match_data struct in a dedicated qca8k_info_ops struct.
++
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/dsa/qca/qca8k.c | 11 +++++++++--
++ drivers/net/dsa/qca/qca8k.h |  5 +++++
++ 2 files changed, 14 insertions(+), 2 deletions(-)
++
++--- a/drivers/net/dsa/qca/qca8k.c
+++++ b/drivers/net/dsa/qca/qca8k.c
++@@ -2254,8 +2254,8 @@ qca8k_get_ethtool_stats(struct dsa_switc
++ 	u32 hi = 0;
++ 	int ret;
++ 
++-	if (priv->mgmt_master &&
++-	    qca8k_get_ethtool_stats_eth(ds, port, data) > 0)
+++	if (priv->mgmt_master && priv->info->ops->autocast_mib &&
+++	    priv->info->ops->autocast_mib(ds, port, data) > 0)
++ 		return;
++ 
++ 	for (i = 0; i < priv->info->mib_count; i++) {
++@@ -3187,20 +3187,27 @@ static int qca8k_resume(struct device *d
++ static SIMPLE_DEV_PM_OPS(qca8k_pm_ops,
++ 			 qca8k_suspend, qca8k_resume);
++ 
+++static const struct qca8k_info_ops qca8xxx_ops = {
+++	.autocast_mib = qca8k_get_ethtool_stats_eth,
+++};
+++
++ static const struct qca8k_match_data qca8327 = {
++ 	.id = QCA8K_ID_QCA8327,
++ 	.reduced_package = true,
++ 	.mib_count = QCA8K_QCA832X_MIB_COUNT,
+++	.ops = &qca8xxx_ops,
++ };
++ 
++ static const struct qca8k_match_data qca8328 = {
++ 	.id = QCA8K_ID_QCA8327,
++ 	.mib_count = QCA8K_QCA832X_MIB_COUNT,
+++	.ops = &qca8xxx_ops,
++ };
++ 
++ static const struct qca8k_match_data qca833x = {
++ 	.id = QCA8K_ID_QCA8337,
++ 	.mib_count = QCA8K_QCA833X_MIB_COUNT,
+++	.ops = &qca8xxx_ops,
++ };
++ 
++ static const struct of_device_id qca8k_of_match[] = {
++--- a/drivers/net/dsa/qca/qca8k.h
+++++ b/drivers/net/dsa/qca/qca8k.h
++@@ -324,10 +324,15 @@ enum qca8k_mid_cmd {
++ 	QCA8K_MIB_CAST = 3,
++ };
++ 
+++struct qca8k_info_ops {
+++	int (*autocast_mib)(struct dsa_switch *ds, int port, u64 *data);
+++};
+++
++ struct qca8k_match_data {
++ 	u8 id;
++ 	bool reduced_package;
++ 	u8 mib_count;
+++	const struct qca8k_info_ops *ops;
++ };
++ 
++ enum {
+diff --git a/target/linux/generic/backport-5.15/771-v6.0-03-net-dsa-qca8k-move-mib-struct-to-common-code.patch b/target/linux/generic/backport-5.15/771-v6.0-03-net-dsa-qca8k-move-mib-struct-to-common-code.patch
+new file mode 100644
+index 0000000000..afa466693a
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/771-v6.0-03-net-dsa-qca8k-move-mib-struct-to-common-code.patch
+@@ -0,0 +1,6532 @@
++From 027152b830434e3632ad5dd678cc5d4740358dbb Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Wed, 27 Jul 2022 13:35:12 +0200
++Subject: [PATCH 03/14] net: dsa: qca8k: move mib struct to common code
++
++The same MIB struct is used by drivers based on qca8k family switch. Move
++it to common code to make it accessible also by other drivers.
++
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/dsa/qca/Makefile                  |  1 +
++ drivers/net/dsa/qca/{qca8k.c => qca8k-8xxx.c} | 51 ---------------
++ drivers/net/dsa/qca/qca8k-common.c            | 63 +++++++++++++++++++
++ drivers/net/dsa/qca/qca8k.h                   |  3 +
++ 4 files changed, 67 insertions(+), 51 deletions(-)
++ rename drivers/net/dsa/qca/{qca8k.c => qca8k-8xxx.c} (98%)
++ create mode 100644 drivers/net/dsa/qca/qca8k-common.c
++
++--- a/drivers/net/dsa/qca/Makefile
+++++ b/drivers/net/dsa/qca/Makefile
++@@ -1,3 +1,4 @@
++ # SPDX-License-Identifier: GPL-2.0-only
++ obj-$(CONFIG_NET_DSA_AR9331)	+= ar9331.o
++ obj-$(CONFIG_NET_DSA_QCA8K)	+= qca8k.o
+++qca8k-y 			+= qca8k-common.o qca8k-8xxx.o
++--- a/drivers/net/dsa/qca/qca8k.c
+++++ /dev/null
++@@ -1,3237 +0,0 @@
++-// SPDX-License-Identifier: GPL-2.0
++-/*
++- * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
++- * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
++- * Copyright (c) 2015, 2019, The Linux Foundation. All rights reserved.
++- * Copyright (c) 2016 John Crispin <john@phrozen.org>
++- */
++-
++-#include <linux/module.h>
++-#include <linux/phy.h>
++-#include <linux/netdevice.h>
++-#include <linux/bitfield.h>
++-#include <linux/regmap.h>
++-#include <net/dsa.h>
++-#include <linux/of_net.h>
++-#include <linux/of_mdio.h>
++-#include <linux/of_platform.h>
++-#include <linux/if_bridge.h>
++-#include <linux/mdio.h>
++-#include <linux/phylink.h>
++-#include <linux/gpio/consumer.h>
++-#include <linux/etherdevice.h>
++-#include <linux/dsa/tag_qca.h>
++-
++-#include "qca8k.h"
++-
++-#define MIB_DESC(_s, _o, _n)	\
++-	{			\
++-		.size = (_s),	\
++-		.offset = (_o),	\
++-		.name = (_n),	\
++-	}
++-
++-static const struct qca8k_mib_desc ar8327_mib[] = {
++-	MIB_DESC(1, 0x00, "RxBroad"),
++-	MIB_DESC(1, 0x04, "RxPause"),
++-	MIB_DESC(1, 0x08, "RxMulti"),
++-	MIB_DESC(1, 0x0c, "RxFcsErr"),
++-	MIB_DESC(1, 0x10, "RxAlignErr"),
++-	MIB_DESC(1, 0x14, "RxRunt"),
++-	MIB_DESC(1, 0x18, "RxFragment"),
++-	MIB_DESC(1, 0x1c, "Rx64Byte"),
++-	MIB_DESC(1, 0x20, "Rx128Byte"),
++-	MIB_DESC(1, 0x24, "Rx256Byte"),
++-	MIB_DESC(1, 0x28, "Rx512Byte"),
++-	MIB_DESC(1, 0x2c, "Rx1024Byte"),
++-	MIB_DESC(1, 0x30, "Rx1518Byte"),
++-	MIB_DESC(1, 0x34, "RxMaxByte"),
++-	MIB_DESC(1, 0x38, "RxTooLong"),
++-	MIB_DESC(2, 0x3c, "RxGoodByte"),
++-	MIB_DESC(2, 0x44, "RxBadByte"),
++-	MIB_DESC(1, 0x4c, "RxOverFlow"),
++-	MIB_DESC(1, 0x50, "Filtered"),
++-	MIB_DESC(1, 0x54, "TxBroad"),
++-	MIB_DESC(1, 0x58, "TxPause"),
++-	MIB_DESC(1, 0x5c, "TxMulti"),
++-	MIB_DESC(1, 0x60, "TxUnderRun"),
++-	MIB_DESC(1, 0x64, "Tx64Byte"),
++-	MIB_DESC(1, 0x68, "Tx128Byte"),
++-	MIB_DESC(1, 0x6c, "Tx256Byte"),
++-	MIB_DESC(1, 0x70, "Tx512Byte"),
++-	MIB_DESC(1, 0x74, "Tx1024Byte"),
++-	MIB_DESC(1, 0x78, "Tx1518Byte"),
++-	MIB_DESC(1, 0x7c, "TxMaxByte"),
++-	MIB_DESC(1, 0x80, "TxOverSize"),
++-	MIB_DESC(2, 0x84, "TxByte"),
++-	MIB_DESC(1, 0x8c, "TxCollision"),
++-	MIB_DESC(1, 0x90, "TxAbortCol"),
++-	MIB_DESC(1, 0x94, "TxMultiCol"),
++-	MIB_DESC(1, 0x98, "TxSingleCol"),
++-	MIB_DESC(1, 0x9c, "TxExcDefer"),
++-	MIB_DESC(1, 0xa0, "TxDefer"),
++-	MIB_DESC(1, 0xa4, "TxLateCol"),
++-	MIB_DESC(1, 0xa8, "RXUnicast"),
++-	MIB_DESC(1, 0xac, "TXUnicast"),
++-};
++-
++-static void
++-qca8k_split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
++-{
++-	regaddr >>= 1;
++-	*r1 = regaddr & 0x1e;
++-
++-	regaddr >>= 5;
++-	*r2 = regaddr & 0x7;
++-
++-	regaddr >>= 3;
++-	*page = regaddr & 0x3ff;
++-}
++-
++-static int
++-qca8k_set_lo(struct qca8k_priv *priv, int phy_id, u32 regnum, u16 lo)
++-{
++-	u16 *cached_lo = &priv->mdio_cache.lo;
++-	struct mii_bus *bus = priv->bus;
++-	int ret;
++-
++-	if (lo == *cached_lo)
++-		return 0;
++-
++-	ret = bus->write(bus, phy_id, regnum, lo);
++-	if (ret < 0)
++-		dev_err_ratelimited(&bus->dev,
++-				    "failed to write qca8k 32bit lo register\n");
++-
++-	*cached_lo = lo;
++-	return 0;
++-}
++-
++-static int
++-qca8k_set_hi(struct qca8k_priv *priv, int phy_id, u32 regnum, u16 hi)
++-{
++-	u16 *cached_hi = &priv->mdio_cache.hi;
++-	struct mii_bus *bus = priv->bus;
++-	int ret;
++-
++-	if (hi == *cached_hi)
++-		return 0;
++-
++-	ret = bus->write(bus, phy_id, regnum, hi);
++-	if (ret < 0)
++-		dev_err_ratelimited(&bus->dev,
++-				    "failed to write qca8k 32bit hi register\n");
++-
++-	*cached_hi = hi;
++-	return 0;
++-}
++-
++-static int
++-qca8k_mii_read32(struct mii_bus *bus, int phy_id, u32 regnum, u32 *val)
++-{
++-	int ret;
++-
++-	ret = bus->read(bus, phy_id, regnum);
++-	if (ret >= 0) {
++-		*val = ret;
++-		ret = bus->read(bus, phy_id, regnum + 1);
++-		*val |= ret << 16;
++-	}
++-
++-	if (ret < 0) {
++-		dev_err_ratelimited(&bus->dev,
++-				    "failed to read qca8k 32bit register\n");
++-		*val = 0;
++-		return ret;
++-	}
++-
++-	return 0;
++-}
++-
++-static void
++-qca8k_mii_write32(struct qca8k_priv *priv, int phy_id, u32 regnum, u32 val)
++-{
++-	u16 lo, hi;
++-	int ret;
++-
++-	lo = val & 0xffff;
++-	hi = (u16)(val >> 16);
++-
++-	ret = qca8k_set_lo(priv, phy_id, regnum, lo);
++-	if (ret >= 0)
++-		ret = qca8k_set_hi(priv, phy_id, regnum + 1, hi);
++-}
++-
++-static int
++-qca8k_set_page(struct qca8k_priv *priv, u16 page)
++-{
++-	u16 *cached_page = &priv->mdio_cache.page;
++-	struct mii_bus *bus = priv->bus;
++-	int ret;
++-
++-	if (page == *cached_page)
++-		return 0;
++-
++-	ret = bus->write(bus, 0x18, 0, page);
++-	if (ret < 0) {
++-		dev_err_ratelimited(&bus->dev,
++-				    "failed to set qca8k page\n");
++-		return ret;
++-	}
++-
++-	*cached_page = page;
++-	usleep_range(1000, 2000);
++-	return 0;
++-}
++-
++-static int
++-qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val)
++-{
++-	return regmap_read(priv->regmap, reg, val);
++-}
++-
++-static int
++-qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val)
++-{
++-	return regmap_write(priv->regmap, reg, val);
++-}
++-
++-static int
++-qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
++-{
++-	return regmap_update_bits(priv->regmap, reg, mask, write_val);
++-}
++-
++-static void qca8k_rw_reg_ack_handler(struct dsa_switch *ds, struct sk_buff *skb)
++-{
++-	struct qca8k_mgmt_eth_data *mgmt_eth_data;
++-	struct qca8k_priv *priv = ds->priv;
++-	struct qca_mgmt_ethhdr *mgmt_ethhdr;
++-	u8 len, cmd;
++-
++-	mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb_mac_header(skb);
++-	mgmt_eth_data = &priv->mgmt_eth_data;
++-
++-	cmd = FIELD_GET(QCA_HDR_MGMT_CMD, mgmt_ethhdr->command);
++-	len = FIELD_GET(QCA_HDR_MGMT_LENGTH, mgmt_ethhdr->command);
++-
++-	/* Make sure the seq match the requested packet */
++-	if (mgmt_ethhdr->seq == mgmt_eth_data->seq)
++-		mgmt_eth_data->ack = true;
++-
++-	if (cmd == MDIO_READ) {
++-		mgmt_eth_data->data[0] = mgmt_ethhdr->mdio_data;
++-
++-		/* Get the rest of the 12 byte of data.
++-		 * The read/write function will extract the requested data.
++-		 */
++-		if (len > QCA_HDR_MGMT_DATA1_LEN)
++-			memcpy(mgmt_eth_data->data + 1, skb->data,
++-			       QCA_HDR_MGMT_DATA2_LEN);
++-	}
++-
++-	complete(&mgmt_eth_data->rw_done);
++-}
++-
++-static struct sk_buff *qca8k_alloc_mdio_header(enum mdio_cmd cmd, u32 reg, u32 *val,
++-					       int priority, unsigned int len)
++-{
++-	struct qca_mgmt_ethhdr *mgmt_ethhdr;
++-	unsigned int real_len;
++-	struct sk_buff *skb;
++-	u32 *data2;
++-	u16 hdr;
++-
++-	skb = dev_alloc_skb(QCA_HDR_MGMT_PKT_LEN);
++-	if (!skb)
++-		return NULL;
++-
++-	/* Max value for len reg is 15 (0xf) but the switch actually return 16 byte
++-	 * Actually for some reason the steps are:
++-	 * 0: nothing
++-	 * 1-4: first 4 byte
++-	 * 5-6: first 12 byte
++-	 * 7-15: all 16 byte
++-	 */
++-	if (len == 16)
++-		real_len = 15;
++-	else
++-		real_len = len;
++-
++-	skb_reset_mac_header(skb);
++-	skb_set_network_header(skb, skb->len);
++-
++-	mgmt_ethhdr = skb_push(skb, QCA_HDR_MGMT_HEADER_LEN + QCA_HDR_LEN);
++-
++-	hdr = FIELD_PREP(QCA_HDR_XMIT_VERSION, QCA_HDR_VERSION);
++-	hdr |= FIELD_PREP(QCA_HDR_XMIT_PRIORITY, priority);
++-	hdr |= QCA_HDR_XMIT_FROM_CPU;
++-	hdr |= FIELD_PREP(QCA_HDR_XMIT_DP_BIT, BIT(0));
++-	hdr |= FIELD_PREP(QCA_HDR_XMIT_CONTROL, QCA_HDR_XMIT_TYPE_RW_REG);
++-
++-	mgmt_ethhdr->command = FIELD_PREP(QCA_HDR_MGMT_ADDR, reg);
++-	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_LENGTH, real_len);
++-	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CMD, cmd);
++-	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CHECK_CODE,
++-					   QCA_HDR_MGMT_CHECK_CODE_VAL);
++-
++-	if (cmd == MDIO_WRITE)
++-		mgmt_ethhdr->mdio_data = *val;
++-
++-	mgmt_ethhdr->hdr = htons(hdr);
++-
++-	data2 = skb_put_zero(skb, QCA_HDR_MGMT_DATA2_LEN + QCA_HDR_MGMT_PADDING_LEN);
++-	if (cmd == MDIO_WRITE && len > QCA_HDR_MGMT_DATA1_LEN)
++-		memcpy(data2, val + 1, len - QCA_HDR_MGMT_DATA1_LEN);
++-
++-	return skb;
++-}
++-
++-static void qca8k_mdio_header_fill_seq_num(struct sk_buff *skb, u32 seq_num)
++-{
++-	struct qca_mgmt_ethhdr *mgmt_ethhdr;
++-
++-	mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb->data;
++-	mgmt_ethhdr->seq = FIELD_PREP(QCA_HDR_MGMT_SEQ_NUM, seq_num);
++-}
++-
++-static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++-{
++-	struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
++-	struct sk_buff *skb;
++-	bool ack;
++-	int ret;
++-
++-	skb = qca8k_alloc_mdio_header(MDIO_READ, reg, NULL,
++-				      QCA8K_ETHERNET_MDIO_PRIORITY, len);
++-	if (!skb)
++-		return -ENOMEM;
++-
++-	mutex_lock(&mgmt_eth_data->mutex);
++-
++-	/* Check mgmt_master if is operational */
++-	if (!priv->mgmt_master) {
++-		kfree_skb(skb);
++-		mutex_unlock(&mgmt_eth_data->mutex);
++-		return -EINVAL;
++-	}
++-
++-	skb->dev = priv->mgmt_master;
++-
++-	reinit_completion(&mgmt_eth_data->rw_done);
++-
++-	/* Increment seq_num and set it in the mdio pkt */
++-	mgmt_eth_data->seq++;
++-	qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
++-	mgmt_eth_data->ack = false;
++-
++-	dev_queue_xmit(skb);
++-
++-	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++-					  msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
++-
++-	*val = mgmt_eth_data->data[0];
++-	if (len > QCA_HDR_MGMT_DATA1_LEN)
++-		memcpy(val + 1, mgmt_eth_data->data + 1, len - QCA_HDR_MGMT_DATA1_LEN);
++-
++-	ack = mgmt_eth_data->ack;
++-
++-	mutex_unlock(&mgmt_eth_data->mutex);
++-
++-	if (ret <= 0)
++-		return -ETIMEDOUT;
++-
++-	if (!ack)
++-		return -EINVAL;
++-
++-	return 0;
++-}
++-
++-static int qca8k_write_eth(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++-{
++-	struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
++-	struct sk_buff *skb;
++-	bool ack;
++-	int ret;
++-
++-	skb = qca8k_alloc_mdio_header(MDIO_WRITE, reg, val,
++-				      QCA8K_ETHERNET_MDIO_PRIORITY, len);
++-	if (!skb)
++-		return -ENOMEM;
++-
++-	mutex_lock(&mgmt_eth_data->mutex);
++-
++-	/* Check mgmt_master if is operational */
++-	if (!priv->mgmt_master) {
++-		kfree_skb(skb);
++-		mutex_unlock(&mgmt_eth_data->mutex);
++-		return -EINVAL;
++-	}
++-
++-	skb->dev = priv->mgmt_master;
++-
++-	reinit_completion(&mgmt_eth_data->rw_done);
++-
++-	/* Increment seq_num and set it in the mdio pkt */
++-	mgmt_eth_data->seq++;
++-	qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
++-	mgmt_eth_data->ack = false;
++-
++-	dev_queue_xmit(skb);
++-
++-	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++-					  msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
++-
++-	ack = mgmt_eth_data->ack;
++-
++-	mutex_unlock(&mgmt_eth_data->mutex);
++-
++-	if (ret <= 0)
++-		return -ETIMEDOUT;
++-
++-	if (!ack)
++-		return -EINVAL;
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_regmap_update_bits_eth(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
++-{
++-	u32 val = 0;
++-	int ret;
++-
++-	ret = qca8k_read_eth(priv, reg, &val, sizeof(val));
++-	if (ret)
++-		return ret;
++-
++-	val &= ~mask;
++-	val |= write_val;
++-
++-	return qca8k_write_eth(priv, reg, &val, sizeof(val));
++-}
++-
++-static int
++-qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++-{
++-	int i, count = len / sizeof(u32), ret;
++-
++-	if (priv->mgmt_master && !qca8k_read_eth(priv, reg, val, len))
++-		return 0;
++-
++-	for (i = 0; i < count; i++) {
++-		ret = regmap_read(priv->regmap, reg + (i * 4), val + i);
++-		if (ret < 0)
++-			return ret;
++-	}
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++-{
++-	int i, count = len / sizeof(u32), ret;
++-	u32 tmp;
++-
++-	if (priv->mgmt_master && !qca8k_write_eth(priv, reg, val, len))
++-		return 0;
++-
++-	for (i = 0; i < count; i++) {
++-		tmp = val[i];
++-
++-		ret = regmap_write(priv->regmap, reg + (i * 4), tmp);
++-		if (ret < 0)
++-			return ret;
++-	}
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
++-	struct mii_bus *bus = priv->bus;
++-	u16 r1, r2, page;
++-	int ret;
++-
++-	if (!qca8k_read_eth(priv, reg, val, sizeof(*val)))
++-		return 0;
++-
++-	qca8k_split_addr(reg, &r1, &r2, &page);
++-
++-	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++-
++-	ret = qca8k_set_page(priv, page);
++-	if (ret < 0)
++-		goto exit;
++-
++-	ret = qca8k_mii_read32(bus, 0x10 | r2, r1, val);
++-
++-exit:
++-	mutex_unlock(&bus->mdio_lock);
++-	return ret;
++-}
++-
++-static int
++-qca8k_regmap_write(void *ctx, uint32_t reg, uint32_t val)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
++-	struct mii_bus *bus = priv->bus;
++-	u16 r1, r2, page;
++-	int ret;
++-
++-	if (!qca8k_write_eth(priv, reg, &val, sizeof(val)))
++-		return 0;
++-
++-	qca8k_split_addr(reg, &r1, &r2, &page);
++-
++-	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++-
++-	ret = qca8k_set_page(priv, page);
++-	if (ret < 0)
++-		goto exit;
++-
++-	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
++-
++-exit:
++-	mutex_unlock(&bus->mdio_lock);
++-	return ret;
++-}
++-
++-static int
++-qca8k_regmap_update_bits(void *ctx, uint32_t reg, uint32_t mask, uint32_t write_val)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
++-	struct mii_bus *bus = priv->bus;
++-	u16 r1, r2, page;
++-	u32 val;
++-	int ret;
++-
++-	if (!qca8k_regmap_update_bits_eth(priv, reg, mask, write_val))
++-		return 0;
++-
++-	qca8k_split_addr(reg, &r1, &r2, &page);
++-
++-	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++-
++-	ret = qca8k_set_page(priv, page);
++-	if (ret < 0)
++-		goto exit;
++-
++-	ret = qca8k_mii_read32(bus, 0x10 | r2, r1, &val);
++-	if (ret < 0)
++-		goto exit;
++-
++-	val &= ~mask;
++-	val |= write_val;
++-	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
++-
++-exit:
++-	mutex_unlock(&bus->mdio_lock);
++-
++-	return ret;
++-}
++-
++-static const struct regmap_range qca8k_readable_ranges[] = {
++-	regmap_reg_range(0x0000, 0x00e4), /* Global control */
++-	regmap_reg_range(0x0100, 0x0168), /* EEE control */
++-	regmap_reg_range(0x0200, 0x0270), /* Parser control */
++-	regmap_reg_range(0x0400, 0x0454), /* ACL */
++-	regmap_reg_range(0x0600, 0x0718), /* Lookup */
++-	regmap_reg_range(0x0800, 0x0b70), /* QM */
++-	regmap_reg_range(0x0c00, 0x0c80), /* PKT */
++-	regmap_reg_range(0x0e00, 0x0e98), /* L3 */
++-	regmap_reg_range(0x1000, 0x10ac), /* MIB - Port0 */
++-	regmap_reg_range(0x1100, 0x11ac), /* MIB - Port1 */
++-	regmap_reg_range(0x1200, 0x12ac), /* MIB - Port2 */
++-	regmap_reg_range(0x1300, 0x13ac), /* MIB - Port3 */
++-	regmap_reg_range(0x1400, 0x14ac), /* MIB - Port4 */
++-	regmap_reg_range(0x1500, 0x15ac), /* MIB - Port5 */
++-	regmap_reg_range(0x1600, 0x16ac), /* MIB - Port6 */
++-
++-};
++-
++-static const struct regmap_access_table qca8k_readable_table = {
++-	.yes_ranges = qca8k_readable_ranges,
++-	.n_yes_ranges = ARRAY_SIZE(qca8k_readable_ranges),
++-};
++-
++-static struct regmap_config qca8k_regmap_config = {
++-	.reg_bits = 16,
++-	.val_bits = 32,
++-	.reg_stride = 4,
++-	.max_register = 0x16ac, /* end MIB - Port6 range */
++-	.reg_read = qca8k_regmap_read,
++-	.reg_write = qca8k_regmap_write,
++-	.reg_update_bits = qca8k_regmap_update_bits,
++-	.rd_table = &qca8k_readable_table,
++-	.disable_locking = true, /* Locking is handled by qca8k read/write */
++-	.cache_type = REGCACHE_NONE, /* Explicitly disable CACHE */
++-};
++-
++-static int
++-qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask)
++-{
++-	u32 val;
++-
++-	return regmap_read_poll_timeout(priv->regmap, reg, val, !(val & mask), 0,
++-				       QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC);
++-}
++-
++-static int
++-qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb)
++-{
++-	u32 reg[3];
++-	int ret;
++-
++-	/* load the ARL table into an array */
++-	ret = qca8k_bulk_read(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
++-	if (ret)
++-		return ret;
++-
++-	/* vid - 83:72 */
++-	fdb->vid = FIELD_GET(QCA8K_ATU_VID_MASK, reg[2]);
++-	/* aging - 67:64 */
++-	fdb->aging = FIELD_GET(QCA8K_ATU_STATUS_MASK, reg[2]);
++-	/* portmask - 54:48 */
++-	fdb->port_mask = FIELD_GET(QCA8K_ATU_PORT_MASK, reg[1]);
++-	/* mac - 47:0 */
++-	fdb->mac[0] = FIELD_GET(QCA8K_ATU_ADDR0_MASK, reg[1]);
++-	fdb->mac[1] = FIELD_GET(QCA8K_ATU_ADDR1_MASK, reg[1]);
++-	fdb->mac[2] = FIELD_GET(QCA8K_ATU_ADDR2_MASK, reg[0]);
++-	fdb->mac[3] = FIELD_GET(QCA8K_ATU_ADDR3_MASK, reg[0]);
++-	fdb->mac[4] = FIELD_GET(QCA8K_ATU_ADDR4_MASK, reg[0]);
++-	fdb->mac[5] = FIELD_GET(QCA8K_ATU_ADDR5_MASK, reg[0]);
++-
++-	return 0;
++-}
++-
++-static void
++-qca8k_fdb_write(struct qca8k_priv *priv, u16 vid, u8 port_mask, const u8 *mac,
++-		u8 aging)
++-{
++-	u32 reg[3] = { 0 };
++-
++-	/* vid - 83:72 */
++-	reg[2] = FIELD_PREP(QCA8K_ATU_VID_MASK, vid);
++-	/* aging - 67:64 */
++-	reg[2] |= FIELD_PREP(QCA8K_ATU_STATUS_MASK, aging);
++-	/* portmask - 54:48 */
++-	reg[1] = FIELD_PREP(QCA8K_ATU_PORT_MASK, port_mask);
++-	/* mac - 47:0 */
++-	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR0_MASK, mac[0]);
++-	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR1_MASK, mac[1]);
++-	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR2_MASK, mac[2]);
++-	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR3_MASK, mac[3]);
++-	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR4_MASK, mac[4]);
++-	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR5_MASK, mac[5]);
++-
++-	/* load the array into the ARL table */
++-	qca8k_bulk_write(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
++-}
++-
++-static int
++-qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd, int port)
++-{
++-	u32 reg;
++-	int ret;
++-
++-	/* Set the command and FDB index */
++-	reg = QCA8K_ATU_FUNC_BUSY;
++-	reg |= cmd;
++-	if (port >= 0) {
++-		reg |= QCA8K_ATU_FUNC_PORT_EN;
++-		reg |= FIELD_PREP(QCA8K_ATU_FUNC_PORT_MASK, port);
++-	}
++-
++-	/* Write the function register triggering the table access */
++-	ret = qca8k_write(priv, QCA8K_REG_ATU_FUNC, reg);
++-	if (ret)
++-		return ret;
++-
++-	/* wait for completion */
++-	ret = qca8k_busy_wait(priv, QCA8K_REG_ATU_FUNC, QCA8K_ATU_FUNC_BUSY);
++-	if (ret)
++-		return ret;
++-
++-	/* Check for table full violation when adding an entry */
++-	if (cmd == QCA8K_FDB_LOAD) {
++-		ret = qca8k_read(priv, QCA8K_REG_ATU_FUNC, &reg);
++-		if (ret < 0)
++-			return ret;
++-		if (reg & QCA8K_ATU_FUNC_FULL)
++-			return -1;
++-	}
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb, int port)
++-{
++-	int ret;
++-
++-	qca8k_fdb_write(priv, fdb->vid, fdb->port_mask, fdb->mac, fdb->aging);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_NEXT, port);
++-	if (ret < 0)
++-		return ret;
++-
++-	return qca8k_fdb_read(priv, fdb);
++-}
++-
++-static int
++-qca8k_fdb_add(struct qca8k_priv *priv, const u8 *mac, u16 port_mask,
++-	      u16 vid, u8 aging)
++-{
++-	int ret;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	qca8k_fdb_write(priv, vid, port_mask, mac, aging);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
++-	mutex_unlock(&priv->reg_mutex);
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac, u16 port_mask, u16 vid)
++-{
++-	int ret;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	qca8k_fdb_write(priv, vid, port_mask, mac, 0);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
++-	mutex_unlock(&priv->reg_mutex);
++-
++-	return ret;
++-}
++-
++-static void
++-qca8k_fdb_flush(struct qca8k_priv *priv)
++-{
++-	mutex_lock(&priv->reg_mutex);
++-	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH, -1);
++-	mutex_unlock(&priv->reg_mutex);
++-}
++-
++-static int
++-qca8k_fdb_search_and_insert(struct qca8k_priv *priv, u8 port_mask,
++-			    const u8 *mac, u16 vid)
++-{
++-	struct qca8k_fdb fdb = { 0 };
++-	int ret;
++-
++-	mutex_lock(&priv->reg_mutex);
++-
++-	qca8k_fdb_write(priv, vid, 0, mac, 0);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
++-	if (ret < 0)
++-		goto exit;
++-
++-	ret = qca8k_fdb_read(priv, &fdb);
++-	if (ret < 0)
++-		goto exit;
++-
++-	/* Rule exist. Delete first */
++-	if (!fdb.aging) {
++-		ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
++-		if (ret)
++-			goto exit;
++-	}
++-
++-	/* Add port to fdb portmask */
++-	fdb.port_mask |= port_mask;
++-
++-	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
++-
++-exit:
++-	mutex_unlock(&priv->reg_mutex);
++-	return ret;
++-}
++-
++-static int
++-qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask,
++-			 const u8 *mac, u16 vid)
++-{
++-	struct qca8k_fdb fdb = { 0 };
++-	int ret;
++-
++-	mutex_lock(&priv->reg_mutex);
++-
++-	qca8k_fdb_write(priv, vid, 0, mac, 0);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
++-	if (ret < 0)
++-		goto exit;
++-
++-	/* Rule doesn't exist. Why delete? */
++-	if (!fdb.aging) {
++-		ret = -EINVAL;
++-		goto exit;
++-	}
++-
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
++-	if (ret)
++-		goto exit;
++-
++-	/* Only port in the rule is this port. Don't re insert */
++-	if (fdb.port_mask == port_mask)
++-		goto exit;
++-
++-	/* Remove port from port mask */
++-	fdb.port_mask &= ~port_mask;
++-
++-	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
++-
++-exit:
++-	mutex_unlock(&priv->reg_mutex);
++-	return ret;
++-}
++-
++-static int
++-qca8k_vlan_access(struct qca8k_priv *priv, enum qca8k_vlan_cmd cmd, u16 vid)
++-{
++-	u32 reg;
++-	int ret;
++-
++-	/* Set the command and VLAN index */
++-	reg = QCA8K_VTU_FUNC1_BUSY;
++-	reg |= cmd;
++-	reg |= FIELD_PREP(QCA8K_VTU_FUNC1_VID_MASK, vid);
++-
++-	/* Write the function register triggering the table access */
++-	ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC1, reg);
++-	if (ret)
++-		return ret;
++-
++-	/* wait for completion */
++-	ret = qca8k_busy_wait(priv, QCA8K_REG_VTU_FUNC1, QCA8K_VTU_FUNC1_BUSY);
++-	if (ret)
++-		return ret;
++-
++-	/* Check for table full violation when adding an entry */
++-	if (cmd == QCA8K_VLAN_LOAD) {
++-		ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC1, &reg);
++-		if (ret < 0)
++-			return ret;
++-		if (reg & QCA8K_VTU_FUNC1_FULL)
++-			return -ENOMEM;
++-	}
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid, bool untagged)
++-{
++-	u32 reg;
++-	int ret;
++-
++-	/*
++-	   We do the right thing with VLAN 0 and treat it as untagged while
++-	   preserving the tag on egress.
++-	 */
++-	if (vid == 0)
++-		return 0;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid);
++-	if (ret < 0)
++-		goto out;
++-
++-	ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, &reg);
++-	if (ret < 0)
++-		goto out;
++-	reg |= QCA8K_VTU_FUNC0_VALID | QCA8K_VTU_FUNC0_IVL_EN;
++-	reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port);
++-	if (untagged)
++-		reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_UNTAG(port);
++-	else
++-		reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_TAG(port);
++-
++-	ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
++-	if (ret)
++-		goto out;
++-	ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid);
++-
++-out:
++-	mutex_unlock(&priv->reg_mutex);
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid)
++-{
++-	u32 reg, mask;
++-	int ret, i;
++-	bool del;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid);
++-	if (ret < 0)
++-		goto out;
++-
++-	ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, &reg);
++-	if (ret < 0)
++-		goto out;
++-	reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port);
++-	reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(port);
++-
++-	/* Check if we're the last member to be removed */
++-	del = true;
++-	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++-		mask = QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(i);
++-
++-		if ((reg & mask) != mask) {
++-			del = false;
++-			break;
++-		}
++-	}
++-
++-	if (del) {
++-		ret = qca8k_vlan_access(priv, QCA8K_VLAN_PURGE, vid);
++-	} else {
++-		ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
++-		if (ret)
++-			goto out;
++-		ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid);
++-	}
++-
++-out:
++-	mutex_unlock(&priv->reg_mutex);
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_mib_init(struct qca8k_priv *priv)
++-{
++-	int ret;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB,
++-				 QCA8K_MIB_FUNC | QCA8K_MIB_BUSY,
++-				 FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_FLUSH) |
++-				 QCA8K_MIB_BUSY);
++-	if (ret)
++-		goto exit;
++-
++-	ret = qca8k_busy_wait(priv, QCA8K_REG_MIB, QCA8K_MIB_BUSY);
++-	if (ret)
++-		goto exit;
++-
++-	ret = regmap_set_bits(priv->regmap, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP);
++-	if (ret)
++-		goto exit;
++-
++-	ret = qca8k_write(priv, QCA8K_REG_MODULE_EN, QCA8K_MODULE_EN_MIB);
++-
++-exit:
++-	mutex_unlock(&priv->reg_mutex);
++-	return ret;
++-}
++-
++-static void
++-qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable)
++-{
++-	u32 mask = QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
++-
++-	/* Port 0 and 6 have no internal PHY */
++-	if (port > 0 && port < 6)
++-		mask |= QCA8K_PORT_STATUS_LINK_AUTO;
++-
++-	if (enable)
++-		regmap_set_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
++-	else
++-		regmap_clear_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
++-}
++-
++-static int
++-qca8k_phy_eth_busy_wait(struct qca8k_mgmt_eth_data *mgmt_eth_data,
++-			struct sk_buff *read_skb, u32 *val)
++-{
++-	struct sk_buff *skb = skb_copy(read_skb, GFP_KERNEL);
++-	bool ack;
++-	int ret;
++-
++-	reinit_completion(&mgmt_eth_data->rw_done);
++-
++-	/* Increment seq_num and set it in the copy pkt */
++-	mgmt_eth_data->seq++;
++-	qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
++-	mgmt_eth_data->ack = false;
++-
++-	dev_queue_xmit(skb);
++-
++-	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++-					  QCA8K_ETHERNET_TIMEOUT);
++-
++-	ack = mgmt_eth_data->ack;
++-
++-	if (ret <= 0)
++-		return -ETIMEDOUT;
++-
++-	if (!ack)
++-		return -EINVAL;
++-
++-	*val = mgmt_eth_data->data[0];
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_phy_eth_command(struct qca8k_priv *priv, bool read, int phy,
++-		      int regnum, u16 data)
++-{
++-	struct sk_buff *write_skb, *clear_skb, *read_skb;
++-	struct qca8k_mgmt_eth_data *mgmt_eth_data;
++-	u32 write_val, clear_val = 0, val;
++-	struct net_device *mgmt_master;
++-	int ret, ret1;
++-	bool ack;
++-
++-	if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
++-		return -EINVAL;
++-
++-	mgmt_eth_data = &priv->mgmt_eth_data;
++-
++-	write_val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
++-		    QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
++-		    QCA8K_MDIO_MASTER_REG_ADDR(regnum);
++-
++-	if (read) {
++-		write_val |= QCA8K_MDIO_MASTER_READ;
++-	} else {
++-		write_val |= QCA8K_MDIO_MASTER_WRITE;
++-		write_val |= QCA8K_MDIO_MASTER_DATA(data);
++-	}
++-
++-	/* Prealloc all the needed skb before the lock */
++-	write_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL, &write_val,
++-					    QCA8K_ETHERNET_PHY_PRIORITY, sizeof(write_val));
++-	if (!write_skb)
++-		return -ENOMEM;
++-
++-	clear_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL, &clear_val,
++-					    QCA8K_ETHERNET_PHY_PRIORITY, sizeof(clear_val));
++-	if (!clear_skb) {
++-		ret = -ENOMEM;
++-		goto err_clear_skb;
++-	}
++-
++-	read_skb = qca8k_alloc_mdio_header(MDIO_READ, QCA8K_MDIO_MASTER_CTRL, &clear_val,
++-					   QCA8K_ETHERNET_PHY_PRIORITY, sizeof(clear_val));
++-	if (!read_skb) {
++-		ret = -ENOMEM;
++-		goto err_read_skb;
++-	}
++-
++-	/* Actually start the request:
++-	 * 1. Send mdio master packet
++-	 * 2. Busy Wait for mdio master command
++-	 * 3. Get the data if we are reading
++-	 * 4. Reset the mdio master (even with error)
++-	 */
++-	mutex_lock(&mgmt_eth_data->mutex);
++-
++-	/* Check if mgmt_master is operational */
++-	mgmt_master = priv->mgmt_master;
++-	if (!mgmt_master) {
++-		mutex_unlock(&mgmt_eth_data->mutex);
++-		ret = -EINVAL;
++-		goto err_mgmt_master;
++-	}
++-
++-	read_skb->dev = mgmt_master;
++-	clear_skb->dev = mgmt_master;
++-	write_skb->dev = mgmt_master;
++-
++-	reinit_completion(&mgmt_eth_data->rw_done);
++-
++-	/* Increment seq_num and set it in the write pkt */
++-	mgmt_eth_data->seq++;
++-	qca8k_mdio_header_fill_seq_num(write_skb, mgmt_eth_data->seq);
++-	mgmt_eth_data->ack = false;
++-
++-	dev_queue_xmit(write_skb);
++-
++-	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++-					  QCA8K_ETHERNET_TIMEOUT);
++-
++-	ack = mgmt_eth_data->ack;
++-
++-	if (ret <= 0) {
++-		ret = -ETIMEDOUT;
++-		kfree_skb(read_skb);
++-		goto exit;
++-	}
++-
++-	if (!ack) {
++-		ret = -EINVAL;
++-		kfree_skb(read_skb);
++-		goto exit;
++-	}
++-
++-	ret = read_poll_timeout(qca8k_phy_eth_busy_wait, ret1,
++-				!(val & QCA8K_MDIO_MASTER_BUSY), 0,
++-				QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
++-				mgmt_eth_data, read_skb, &val);
++-
++-	if (ret < 0 && ret1 < 0) {
++-		ret = ret1;
++-		goto exit;
++-	}
++-
++-	if (read) {
++-		reinit_completion(&mgmt_eth_data->rw_done);
++-
++-		/* Increment seq_num and set it in the read pkt */
++-		mgmt_eth_data->seq++;
++-		qca8k_mdio_header_fill_seq_num(read_skb, mgmt_eth_data->seq);
++-		mgmt_eth_data->ack = false;
++-
++-		dev_queue_xmit(read_skb);
++-
++-		ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++-						  QCA8K_ETHERNET_TIMEOUT);
++-
++-		ack = mgmt_eth_data->ack;
++-
++-		if (ret <= 0) {
++-			ret = -ETIMEDOUT;
++-			goto exit;
++-		}
++-
++-		if (!ack) {
++-			ret = -EINVAL;
++-			goto exit;
++-		}
++-
++-		ret = mgmt_eth_data->data[0] & QCA8K_MDIO_MASTER_DATA_MASK;
++-	} else {
++-		kfree_skb(read_skb);
++-	}
++-exit:
++-	reinit_completion(&mgmt_eth_data->rw_done);
++-
++-	/* Increment seq_num and set it in the clear pkt */
++-	mgmt_eth_data->seq++;
++-	qca8k_mdio_header_fill_seq_num(clear_skb, mgmt_eth_data->seq);
++-	mgmt_eth_data->ack = false;
++-
++-	dev_queue_xmit(clear_skb);
++-
++-	wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++-				    QCA8K_ETHERNET_TIMEOUT);
++-
++-	mutex_unlock(&mgmt_eth_data->mutex);
++-
++-	return ret;
++-
++-	/* Error handling before lock */
++-err_mgmt_master:
++-	kfree_skb(read_skb);
++-err_read_skb:
++-	kfree_skb(clear_skb);
++-err_clear_skb:
++-	kfree_skb(write_skb);
++-
++-	return ret;
++-}
++-
++-static u32
++-qca8k_port_to_phy(int port)
++-{
++-	/* From Andrew Lunn:
++-	 * Port 0 has no internal phy.
++-	 * Port 1 has an internal PHY at MDIO address 0.
++-	 * Port 2 has an internal PHY at MDIO address 1.
++-	 * ...
++-	 * Port 5 has an internal PHY at MDIO address 4.
++-	 * Port 6 has no internal PHY.
++-	 */
++-
++-	return port - 1;
++-}
++-
++-static int
++-qca8k_mdio_busy_wait(struct mii_bus *bus, u32 reg, u32 mask)
++-{
++-	u16 r1, r2, page;
++-	u32 val;
++-	int ret, ret1;
++-
++-	qca8k_split_addr(reg, &r1, &r2, &page);
++-
++-	ret = read_poll_timeout(qca8k_mii_read32, ret1, !(val & mask), 0,
++-				QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
++-				bus, 0x10 | r2, r1, &val);
++-
++-	/* Check if qca8k_read has failed for a different reason
++-	 * before returnting -ETIMEDOUT
++-	 */
++-	if (ret < 0 && ret1 < 0)
++-		return ret1;
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_mdio_write(struct qca8k_priv *priv, int phy, int regnum, u16 data)
++-{
++-	struct mii_bus *bus = priv->bus;
++-	u16 r1, r2, page;
++-	u32 val;
++-	int ret;
++-
++-	if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
++-		return -EINVAL;
++-
++-	val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
++-	      QCA8K_MDIO_MASTER_WRITE | QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
++-	      QCA8K_MDIO_MASTER_REG_ADDR(regnum) |
++-	      QCA8K_MDIO_MASTER_DATA(data);
++-
++-	qca8k_split_addr(QCA8K_MDIO_MASTER_CTRL, &r1, &r2, &page);
++-
++-	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++-
++-	ret = qca8k_set_page(priv, page);
++-	if (ret)
++-		goto exit;
++-
++-	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
++-
++-	ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
++-				   QCA8K_MDIO_MASTER_BUSY);
++-
++-exit:
++-	/* even if the busy_wait timeouts try to clear the MASTER_EN */
++-	qca8k_mii_write32(priv, 0x10 | r2, r1, 0);
++-
++-	mutex_unlock(&bus->mdio_lock);
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_mdio_read(struct qca8k_priv *priv, int phy, int regnum)
++-{
++-	struct mii_bus *bus = priv->bus;
++-	u16 r1, r2, page;
++-	u32 val;
++-	int ret;
++-
++-	if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
++-		return -EINVAL;
++-
++-	val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
++-	      QCA8K_MDIO_MASTER_READ | QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
++-	      QCA8K_MDIO_MASTER_REG_ADDR(regnum);
++-
++-	qca8k_split_addr(QCA8K_MDIO_MASTER_CTRL, &r1, &r2, &page);
++-
++-	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++-
++-	ret = qca8k_set_page(priv, page);
++-	if (ret)
++-		goto exit;
++-
++-	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
++-
++-	ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
++-				   QCA8K_MDIO_MASTER_BUSY);
++-	if (ret)
++-		goto exit;
++-
++-	ret = qca8k_mii_read32(bus, 0x10 | r2, r1, &val);
++-
++-exit:
++-	/* even if the busy_wait timeouts try to clear the MASTER_EN */
++-	qca8k_mii_write32(priv, 0x10 | r2, r1, 0);
++-
++-	mutex_unlock(&bus->mdio_lock);
++-
++-	if (ret >= 0)
++-		ret = val & QCA8K_MDIO_MASTER_DATA_MASK;
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_internal_mdio_write(struct mii_bus *slave_bus, int phy, int regnum, u16 data)
++-{
++-	struct qca8k_priv *priv = slave_bus->priv;
++-	int ret;
++-
++-	/* Use mdio Ethernet when available, fallback to legacy one on error */
++-	ret = qca8k_phy_eth_command(priv, false, phy, regnum, data);
++-	if (!ret)
++-		return 0;
++-
++-	return qca8k_mdio_write(priv, phy, regnum, data);
++-}
++-
++-static int
++-qca8k_internal_mdio_read(struct mii_bus *slave_bus, int phy, int regnum)
++-{
++-	struct qca8k_priv *priv = slave_bus->priv;
++-	int ret;
++-
++-	/* Use mdio Ethernet when available, fallback to legacy one on error */
++-	ret = qca8k_phy_eth_command(priv, true, phy, regnum, 0);
++-	if (ret >= 0)
++-		return ret;
++-
++-	ret = qca8k_mdio_read(priv, phy, regnum);
++-
++-	if (ret < 0)
++-		return 0xffff;
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_legacy_mdio_write(struct mii_bus *slave_bus, int port, int regnum, u16 data)
++-{
++-	port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
++-
++-	return qca8k_internal_mdio_write(slave_bus, port, regnum, data);
++-}
++-
++-static int
++-qca8k_legacy_mdio_read(struct mii_bus *slave_bus, int port, int regnum)
++-{
++-	port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
++-
++-	return qca8k_internal_mdio_read(slave_bus, port, regnum);
++-}
++-
++-static int
++-qca8k_mdio_register(struct qca8k_priv *priv)
++-{
++-	struct dsa_switch *ds = priv->ds;
++-	struct device_node *mdio;
++-	struct mii_bus *bus;
++-
++-	bus = devm_mdiobus_alloc(ds->dev);
++-	if (!bus)
++-		return -ENOMEM;
++-
++-	bus->priv = (void *)priv;
++-	snprintf(bus->id, MII_BUS_ID_SIZE, "qca8k-%d.%d",
++-		 ds->dst->index, ds->index);
++-	bus->parent = ds->dev;
++-	bus->phy_mask = ~ds->phys_mii_mask;
++-	ds->slave_mii_bus = bus;
++-
++-	/* Check if the devicetree declare the port:phy mapping */
++-	mdio = of_get_child_by_name(priv->dev->of_node, "mdio");
++-	if (of_device_is_available(mdio)) {
++-		bus->name = "qca8k slave mii";
++-		bus->read = qca8k_internal_mdio_read;
++-		bus->write = qca8k_internal_mdio_write;
++-		return devm_of_mdiobus_register(priv->dev, bus, mdio);
++-	}
++-
++-	/* If a mapping can't be found the legacy mapping is used,
++-	 * using the qca8k_port_to_phy function
++-	 */
++-	bus->name = "qca8k-legacy slave mii";
++-	bus->read = qca8k_legacy_mdio_read;
++-	bus->write = qca8k_legacy_mdio_write;
++-	return devm_mdiobus_register(priv->dev, bus);
++-}
++-
++-static int
++-qca8k_setup_mdio_bus(struct qca8k_priv *priv)
++-{
++-	u32 internal_mdio_mask = 0, external_mdio_mask = 0, reg;
++-	struct device_node *ports, *port;
++-	phy_interface_t mode;
++-	int err;
++-
++-	ports = of_get_child_by_name(priv->dev->of_node, "ports");
++-	if (!ports)
++-		ports = of_get_child_by_name(priv->dev->of_node, "ethernet-ports");
++-
++-	if (!ports)
++-		return -EINVAL;
++-
++-	for_each_available_child_of_node(ports, port) {
++-		err = of_property_read_u32(port, "reg", &reg);
++-		if (err) {
++-			of_node_put(port);
++-			of_node_put(ports);
++-			return err;
++-		}
++-
++-		if (!dsa_is_user_port(priv->ds, reg))
++-			continue;
++-
++-		of_get_phy_mode(port, &mode);
++-
++-		if (of_property_read_bool(port, "phy-handle") &&
++-		    mode != PHY_INTERFACE_MODE_INTERNAL)
++-			external_mdio_mask |= BIT(reg);
++-		else
++-			internal_mdio_mask |= BIT(reg);
++-	}
++-
++-	of_node_put(ports);
++-	if (!external_mdio_mask && !internal_mdio_mask) {
++-		dev_err(priv->dev, "no PHYs are defined.\n");
++-		return -EINVAL;
++-	}
++-
++-	/* The QCA8K_MDIO_MASTER_EN Bit, which grants access to PHYs through
++-	 * the MDIO_MASTER register also _disconnects_ the external MDC
++-	 * passthrough to the internal PHYs. It's not possible to use both
++-	 * configurations at the same time!
++-	 *
++-	 * Because this came up during the review process:
++-	 * If the external mdio-bus driver is capable magically disabling
++-	 * the QCA8K_MDIO_MASTER_EN and mutex/spin-locking out the qca8k's
++-	 * accessors for the time being, it would be possible to pull this
++-	 * off.
++-	 */
++-	if (!!external_mdio_mask && !!internal_mdio_mask) {
++-		dev_err(priv->dev, "either internal or external mdio bus configuration is supported.\n");
++-		return -EINVAL;
++-	}
++-
++-	if (external_mdio_mask) {
++-		/* Make sure to disable the internal mdio bus in cases
++-		 * a dt-overlay and driver reload changed the configuration
++-		 */
++-
++-		return regmap_clear_bits(priv->regmap, QCA8K_MDIO_MASTER_CTRL,
++-					 QCA8K_MDIO_MASTER_EN);
++-	}
++-
++-	return qca8k_mdio_register(priv);
++-}
++-
++-static int
++-qca8k_setup_mac_pwr_sel(struct qca8k_priv *priv)
++-{
++-	u32 mask = 0;
++-	int ret = 0;
++-
++-	/* SoC specific settings for ipq8064.
++-	 * If more device require this consider adding
++-	 * a dedicated binding.
++-	 */
++-	if (of_machine_is_compatible("qcom,ipq8064"))
++-		mask |= QCA8K_MAC_PWR_RGMII0_1_8V;
++-
++-	/* SoC specific settings for ipq8065 */
++-	if (of_machine_is_compatible("qcom,ipq8065"))
++-		mask |= QCA8K_MAC_PWR_RGMII1_1_8V;
++-
++-	if (mask) {
++-		ret = qca8k_rmw(priv, QCA8K_REG_MAC_PWR_SEL,
++-				QCA8K_MAC_PWR_RGMII0_1_8V |
++-				QCA8K_MAC_PWR_RGMII1_1_8V,
++-				mask);
++-	}
++-
++-	return ret;
++-}
++-
++-static int qca8k_find_cpu_port(struct dsa_switch *ds)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-
++-	/* Find the connected cpu port. Valid port are 0 or 6 */
++-	if (dsa_is_cpu_port(ds, 0))
++-		return 0;
++-
++-	dev_dbg(priv->dev, "port 0 is not the CPU port. Checking port 6");
++-
++-	if (dsa_is_cpu_port(ds, 6))
++-		return 6;
++-
++-	return -EINVAL;
++-}
++-
++-static int
++-qca8k_setup_of_pws_reg(struct qca8k_priv *priv)
++-{
++-	const struct qca8k_match_data *data = priv->info;
++-	struct device_node *node = priv->dev->of_node;
++-	u32 val = 0;
++-	int ret;
++-
++-	/* QCA8327 require to set to the correct mode.
++-	 * His bigger brother QCA8328 have the 172 pin layout.
++-	 * Should be applied by default but we set this just to make sure.
++-	 */
++-	if (priv->switch_id == QCA8K_ID_QCA8327) {
++-		/* Set the correct package of 148 pin for QCA8327 */
++-		if (data->reduced_package)
++-			val |= QCA8327_PWS_PACKAGE148_EN;
++-
++-		ret = qca8k_rmw(priv, QCA8K_REG_PWS, QCA8327_PWS_PACKAGE148_EN,
++-				val);
++-		if (ret)
++-			return ret;
++-	}
++-
++-	if (of_property_read_bool(node, "qca,ignore-power-on-sel"))
++-		val |= QCA8K_PWS_POWER_ON_SEL;
++-
++-	if (of_property_read_bool(node, "qca,led-open-drain")) {
++-		if (!(val & QCA8K_PWS_POWER_ON_SEL)) {
++-			dev_err(priv->dev, "qca,led-open-drain require qca,ignore-power-on-sel to be set.");
++-			return -EINVAL;
++-		}
++-
++-		val |= QCA8K_PWS_LED_OPEN_EN_CSR;
++-	}
++-
++-	return qca8k_rmw(priv, QCA8K_REG_PWS,
++-			QCA8K_PWS_LED_OPEN_EN_CSR | QCA8K_PWS_POWER_ON_SEL,
++-			val);
++-}
++-
++-static int
++-qca8k_parse_port_config(struct qca8k_priv *priv)
++-{
++-	int port, cpu_port_index = -1, ret;
++-	struct device_node *port_dn;
++-	phy_interface_t mode;
++-	struct dsa_port *dp;
++-	u32 delay;
++-
++-	/* We have 2 CPU port. Check them */
++-	for (port = 0; port < QCA8K_NUM_PORTS; port++) {
++-		/* Skip every other port */
++-		if (port != 0 && port != 6)
++-			continue;
++-
++-		dp = dsa_to_port(priv->ds, port);
++-		port_dn = dp->dn;
++-		cpu_port_index++;
++-
++-		if (!of_device_is_available(port_dn))
++-			continue;
++-
++-		ret = of_get_phy_mode(port_dn, &mode);
++-		if (ret)
++-			continue;
++-
++-		switch (mode) {
++-		case PHY_INTERFACE_MODE_RGMII:
++-		case PHY_INTERFACE_MODE_RGMII_ID:
++-		case PHY_INTERFACE_MODE_RGMII_TXID:
++-		case PHY_INTERFACE_MODE_RGMII_RXID:
++-		case PHY_INTERFACE_MODE_SGMII:
++-			delay = 0;
++-
++-			if (!of_property_read_u32(port_dn, "tx-internal-delay-ps", &delay))
++-				/* Switch regs accept value in ns, convert ps to ns */
++-				delay = delay / 1000;
++-			else if (mode == PHY_INTERFACE_MODE_RGMII_ID ||
++-				 mode == PHY_INTERFACE_MODE_RGMII_TXID)
++-				delay = 1;
++-
++-			if (!FIELD_FIT(QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK, delay)) {
++-				dev_err(priv->dev, "rgmii tx delay is limited to a max value of 3ns, setting to the max value");
++-				delay = 3;
++-			}
++-
++-			priv->ports_config.rgmii_tx_delay[cpu_port_index] = delay;
++-
++-			delay = 0;
++-
++-			if (!of_property_read_u32(port_dn, "rx-internal-delay-ps", &delay))
++-				/* Switch regs accept value in ns, convert ps to ns */
++-				delay = delay / 1000;
++-			else if (mode == PHY_INTERFACE_MODE_RGMII_ID ||
++-				 mode == PHY_INTERFACE_MODE_RGMII_RXID)
++-				delay = 2;
++-
++-			if (!FIELD_FIT(QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK, delay)) {
++-				dev_err(priv->dev, "rgmii rx delay is limited to a max value of 3ns, setting to the max value");
++-				delay = 3;
++-			}
++-
++-			priv->ports_config.rgmii_rx_delay[cpu_port_index] = delay;
++-
++-			/* Skip sgmii parsing for rgmii* mode */
++-			if (mode == PHY_INTERFACE_MODE_RGMII ||
++-			    mode == PHY_INTERFACE_MODE_RGMII_ID ||
++-			    mode == PHY_INTERFACE_MODE_RGMII_TXID ||
++-			    mode == PHY_INTERFACE_MODE_RGMII_RXID)
++-				break;
++-
++-			if (of_property_read_bool(port_dn, "qca,sgmii-txclk-falling-edge"))
++-				priv->ports_config.sgmii_tx_clk_falling_edge = true;
++-
++-			if (of_property_read_bool(port_dn, "qca,sgmii-rxclk-falling-edge"))
++-				priv->ports_config.sgmii_rx_clk_falling_edge = true;
++-
++-			if (of_property_read_bool(port_dn, "qca,sgmii-enable-pll")) {
++-				priv->ports_config.sgmii_enable_pll = true;
++-
++-				if (priv->switch_id == QCA8K_ID_QCA8327) {
++-					dev_err(priv->dev, "SGMII PLL should NOT be enabled for qca8327. Aborting enabling");
++-					priv->ports_config.sgmii_enable_pll = false;
++-				}
++-
++-				if (priv->switch_revision < 2)
++-					dev_warn(priv->dev, "SGMII PLL should NOT be enabled for qca8337 with revision 2 or more.");
++-			}
++-
++-			break;
++-		default:
++-			continue;
++-		}
++-	}
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_setup(struct dsa_switch *ds)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	int cpu_port, ret, i;
++-	u32 mask;
++-
++-	cpu_port = qca8k_find_cpu_port(ds);
++-	if (cpu_port < 0) {
++-		dev_err(priv->dev, "No cpu port configured in both cpu port0 and port6");
++-		return cpu_port;
++-	}
++-
++-	/* Parse CPU port config to be later used in phy_link mac_config */
++-	ret = qca8k_parse_port_config(priv);
++-	if (ret)
++-		return ret;
++-
++-	ret = qca8k_setup_mdio_bus(priv);
++-	if (ret)
++-		return ret;
++-
++-	ret = qca8k_setup_of_pws_reg(priv);
++-	if (ret)
++-		return ret;
++-
++-	ret = qca8k_setup_mac_pwr_sel(priv);
++-	if (ret)
++-		return ret;
++-
++-	/* Make sure MAC06 is disabled */
++-	ret = regmap_clear_bits(priv->regmap, QCA8K_REG_PORT0_PAD_CTRL,
++-				QCA8K_PORT0_PAD_MAC06_EXCHANGE_EN);
++-	if (ret) {
++-		dev_err(priv->dev, "failed disabling MAC06 exchange");
++-		return ret;
++-	}
++-
++-	/* Enable CPU Port */
++-	ret = regmap_set_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
++-			      QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
++-	if (ret) {
++-		dev_err(priv->dev, "failed enabling CPU port");
++-		return ret;
++-	}
++-
++-	/* Enable MIB counters */
++-	ret = qca8k_mib_init(priv);
++-	if (ret)
++-		dev_warn(priv->dev, "mib init failed");
++-
++-	/* Initial setup of all ports */
++-	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++-		/* Disable forwarding by default on all ports */
++-		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
++-				QCA8K_PORT_LOOKUP_MEMBER, 0);
++-		if (ret)
++-			return ret;
++-
++-		/* Enable QCA header mode on all cpu ports */
++-		if (dsa_is_cpu_port(ds, i)) {
++-			ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(i),
++-					  FIELD_PREP(QCA8K_PORT_HDR_CTRL_TX_MASK, QCA8K_PORT_HDR_CTRL_ALL) |
++-					  FIELD_PREP(QCA8K_PORT_HDR_CTRL_RX_MASK, QCA8K_PORT_HDR_CTRL_ALL));
++-			if (ret) {
++-				dev_err(priv->dev, "failed enabling QCA header mode");
++-				return ret;
++-			}
++-		}
++-
++-		/* Disable MAC by default on all user ports */
++-		if (dsa_is_user_port(ds, i))
++-			qca8k_port_set_status(priv, i, 0);
++-	}
++-
++-	/* Forward all unknown frames to CPU port for Linux processing
++-	 * Notice that in multi-cpu config only one port should be set
++-	 * for igmp, unknown, multicast and broadcast packet
++-	 */
++-	ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1,
++-			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK, BIT(cpu_port)) |
++-			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK, BIT(cpu_port)) |
++-			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_MC_DP_MASK, BIT(cpu_port)) |
++-			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_UC_DP_MASK, BIT(cpu_port)));
++-	if (ret)
++-		return ret;
++-
++-	/* Setup connection between CPU port & user ports
++-	 * Configure specific switch configuration for ports
++-	 */
++-	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++-		/* CPU port gets connected to all user ports of the switch */
++-		if (dsa_is_cpu_port(ds, i)) {
++-			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
++-					QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds));
++-			if (ret)
++-				return ret;
++-		}
++-
++-		/* Individual user ports get connected to CPU port only */
++-		if (dsa_is_user_port(ds, i)) {
++-			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
++-					QCA8K_PORT_LOOKUP_MEMBER,
++-					BIT(cpu_port));
++-			if (ret)
++-				return ret;
++-
++-			/* Enable ARP Auto-learning by default */
++-			ret = regmap_set_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(i),
++-					      QCA8K_PORT_LOOKUP_LEARN);
++-			if (ret)
++-				return ret;
++-
++-			/* For port based vlans to work we need to set the
++-			 * default egress vid
++-			 */
++-			ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(i),
++-					QCA8K_EGREES_VLAN_PORT_MASK(i),
++-					QCA8K_EGREES_VLAN_PORT(i, QCA8K_PORT_VID_DEF));
++-			if (ret)
++-				return ret;
++-
++-			ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(i),
++-					  QCA8K_PORT_VLAN_CVID(QCA8K_PORT_VID_DEF) |
++-					  QCA8K_PORT_VLAN_SVID(QCA8K_PORT_VID_DEF));
++-			if (ret)
++-				return ret;
++-		}
++-
++-		/* The port 5 of the qca8337 have some problem in flood condition. The
++-		 * original legacy driver had some specific buffer and priority settings
++-		 * for the different port suggested by the QCA switch team. Add this
++-		 * missing settings to improve switch stability under load condition.
++-		 * This problem is limited to qca8337 and other qca8k switch are not affected.
++-		 */
++-		if (priv->switch_id == QCA8K_ID_QCA8337) {
++-			switch (i) {
++-			/* The 2 CPU port and port 5 requires some different
++-			 * priority than any other ports.
++-			 */
++-			case 0:
++-			case 5:
++-			case 6:
++-				mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x4) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x4) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PRI4(0x6) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PRI5(0x8) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PORT(0x1e);
++-				break;
++-			default:
++-				mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x6) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x8) |
++-					QCA8K_PORT_HOL_CTRL0_EG_PORT(0x19);
++-			}
++-			qca8k_write(priv, QCA8K_REG_PORT_HOL_CTRL0(i), mask);
++-
++-			mask = QCA8K_PORT_HOL_CTRL1_ING(0x6) |
++-			QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
++-			QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
++-			QCA8K_PORT_HOL_CTRL1_WRED_EN;
++-			qca8k_rmw(priv, QCA8K_REG_PORT_HOL_CTRL1(i),
++-				  QCA8K_PORT_HOL_CTRL1_ING_BUF_MASK |
++-				  QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
++-				  QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
++-				  QCA8K_PORT_HOL_CTRL1_WRED_EN,
++-				  mask);
++-		}
++-	}
++-
++-	/* Special GLOBAL_FC_THRESH value are needed for ar8327 switch */
++-	if (priv->switch_id == QCA8K_ID_QCA8327) {
++-		mask = QCA8K_GLOBAL_FC_GOL_XON_THRES(288) |
++-		       QCA8K_GLOBAL_FC_GOL_XOFF_THRES(496);
++-		qca8k_rmw(priv, QCA8K_REG_GLOBAL_FC_THRESH,
++-			  QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK |
++-			  QCA8K_GLOBAL_FC_GOL_XOFF_THRES_MASK,
++-			  mask);
++-	}
++-
++-	/* Setup our port MTUs to match power on defaults */
++-	ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, ETH_FRAME_LEN + ETH_FCS_LEN);
++-	if (ret)
++-		dev_warn(priv->dev, "failed setting MTU settings");
++-
++-	/* Flush the FDB table */
++-	qca8k_fdb_flush(priv);
++-
++-	/* We don't have interrupts for link changes, so we need to poll */
++-	ds->pcs_poll = true;
++-
++-	/* Set min a max ageing value supported */
++-	ds->ageing_time_min = 7000;
++-	ds->ageing_time_max = 458745000;
++-
++-	/* Set max number of LAGs supported */
++-	ds->num_lag_ids = QCA8K_NUM_LAGS;
++-
++-	return 0;
++-}
++-
++-static void
++-qca8k_mac_config_setup_internal_delay(struct qca8k_priv *priv, int cpu_port_index,
++-				      u32 reg)
++-{
++-	u32 delay, val = 0;
++-	int ret;
++-
++-	/* Delay can be declared in 3 different way.
++-	 * Mode to rgmii and internal-delay standard binding defined
++-	 * rgmii-id or rgmii-tx/rx phy mode set.
++-	 * The parse logic set a delay different than 0 only when one
++-	 * of the 3 different way is used. In all other case delay is
++-	 * not enabled. With ID or TX/RXID delay is enabled and set
++-	 * to the default and recommended value.
++-	 */
++-	if (priv->ports_config.rgmii_tx_delay[cpu_port_index]) {
++-		delay = priv->ports_config.rgmii_tx_delay[cpu_port_index];
++-
++-		val |= QCA8K_PORT_PAD_RGMII_TX_DELAY(delay) |
++-			QCA8K_PORT_PAD_RGMII_TX_DELAY_EN;
++-	}
++-
++-	if (priv->ports_config.rgmii_rx_delay[cpu_port_index]) {
++-		delay = priv->ports_config.rgmii_rx_delay[cpu_port_index];
++-
++-		val |= QCA8K_PORT_PAD_RGMII_RX_DELAY(delay) |
++-			QCA8K_PORT_PAD_RGMII_RX_DELAY_EN;
++-	}
++-
++-	/* Set RGMII delay based on the selected values */
++-	ret = qca8k_rmw(priv, reg,
++-			QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK |
++-			QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK |
++-			QCA8K_PORT_PAD_RGMII_TX_DELAY_EN |
++-			QCA8K_PORT_PAD_RGMII_RX_DELAY_EN,
++-			val);
++-	if (ret)
++-		dev_err(priv->dev, "Failed to set internal delay for CPU port%d",
++-			cpu_port_index == QCA8K_CPU_PORT0 ? 0 : 6);
++-}
++-
++-static void
++-qca8k_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode,
++-			 const struct phylink_link_state *state)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int cpu_port_index, ret;
++-	u32 reg, val;
++-
++-	switch (port) {
++-	case 0: /* 1st CPU port */
++-		if (state->interface != PHY_INTERFACE_MODE_RGMII &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
++-		    state->interface != PHY_INTERFACE_MODE_SGMII)
++-			return;
++-
++-		reg = QCA8K_REG_PORT0_PAD_CTRL;
++-		cpu_port_index = QCA8K_CPU_PORT0;
++-		break;
++-	case 1:
++-	case 2:
++-	case 3:
++-	case 4:
++-	case 5:
++-		/* Internal PHY, nothing to do */
++-		return;
++-	case 6: /* 2nd CPU port / external PHY */
++-		if (state->interface != PHY_INTERFACE_MODE_RGMII &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
++-		    state->interface != PHY_INTERFACE_MODE_SGMII &&
++-		    state->interface != PHY_INTERFACE_MODE_1000BASEX)
++-			return;
++-
++-		reg = QCA8K_REG_PORT6_PAD_CTRL;
++-		cpu_port_index = QCA8K_CPU_PORT6;
++-		break;
++-	default:
++-		dev_err(ds->dev, "%s: unsupported port: %i\n", __func__, port);
++-		return;
++-	}
++-
++-	if (port != 6 && phylink_autoneg_inband(mode)) {
++-		dev_err(ds->dev, "%s: in-band negotiation unsupported\n",
++-			__func__);
++-		return;
++-	}
++-
++-	switch (state->interface) {
++-	case PHY_INTERFACE_MODE_RGMII:
++-	case PHY_INTERFACE_MODE_RGMII_ID:
++-	case PHY_INTERFACE_MODE_RGMII_TXID:
++-	case PHY_INTERFACE_MODE_RGMII_RXID:
++-		qca8k_write(priv, reg, QCA8K_PORT_PAD_RGMII_EN);
++-
++-		/* Configure rgmii delay */
++-		qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg);
++-
++-		/* QCA8337 requires to set rgmii rx delay for all ports.
++-		 * This is enabled through PORT5_PAD_CTRL for all ports,
++-		 * rather than individual port registers.
++-		 */
++-		if (priv->switch_id == QCA8K_ID_QCA8337)
++-			qca8k_write(priv, QCA8K_REG_PORT5_PAD_CTRL,
++-				    QCA8K_PORT_PAD_RGMII_RX_DELAY_EN);
++-		break;
++-	case PHY_INTERFACE_MODE_SGMII:
++-	case PHY_INTERFACE_MODE_1000BASEX:
++-		/* Enable SGMII on the port */
++-		qca8k_write(priv, reg, QCA8K_PORT_PAD_SGMII_EN);
++-
++-		/* Enable/disable SerDes auto-negotiation as necessary */
++-		ret = qca8k_read(priv, QCA8K_REG_PWS, &val);
++-		if (ret)
++-			return;
++-		if (phylink_autoneg_inband(mode))
++-			val &= ~QCA8K_PWS_SERDES_AEN_DIS;
++-		else
++-			val |= QCA8K_PWS_SERDES_AEN_DIS;
++-		qca8k_write(priv, QCA8K_REG_PWS, val);
++-
++-		/* Configure the SGMII parameters */
++-		ret = qca8k_read(priv, QCA8K_REG_SGMII_CTRL, &val);
++-		if (ret)
++-			return;
++-
++-		val |= QCA8K_SGMII_EN_SD;
++-
++-		if (priv->ports_config.sgmii_enable_pll)
++-			val |= QCA8K_SGMII_EN_PLL | QCA8K_SGMII_EN_RX |
++-			       QCA8K_SGMII_EN_TX;
++-
++-		if (dsa_is_cpu_port(ds, port)) {
++-			/* CPU port, we're talking to the CPU MAC, be a PHY */
++-			val &= ~QCA8K_SGMII_MODE_CTRL_MASK;
++-			val |= QCA8K_SGMII_MODE_CTRL_PHY;
++-		} else if (state->interface == PHY_INTERFACE_MODE_SGMII) {
++-			val &= ~QCA8K_SGMII_MODE_CTRL_MASK;
++-			val |= QCA8K_SGMII_MODE_CTRL_MAC;
++-		} else if (state->interface == PHY_INTERFACE_MODE_1000BASEX) {
++-			val &= ~QCA8K_SGMII_MODE_CTRL_MASK;
++-			val |= QCA8K_SGMII_MODE_CTRL_BASEX;
++-		}
++-
++-		qca8k_write(priv, QCA8K_REG_SGMII_CTRL, val);
++-
++-		/* From original code is reported port instability as SGMII also
++-		 * require delay set. Apply advised values here or take them from DT.
++-		 */
++-		if (state->interface == PHY_INTERFACE_MODE_SGMII)
++-			qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg);
++-
++-		/* For qca8327/qca8328/qca8334/qca8338 sgmii is unique and
++-		 * falling edge is set writing in the PORT0 PAD reg
++-		 */
++-		if (priv->switch_id == QCA8K_ID_QCA8327 ||
++-		    priv->switch_id == QCA8K_ID_QCA8337)
++-			reg = QCA8K_REG_PORT0_PAD_CTRL;
++-
++-		val = 0;
++-
++-		/* SGMII Clock phase configuration */
++-		if (priv->ports_config.sgmii_rx_clk_falling_edge)
++-			val |= QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE;
++-
++-		if (priv->ports_config.sgmii_tx_clk_falling_edge)
++-			val |= QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE;
++-
++-		if (val)
++-			ret = qca8k_rmw(priv, reg,
++-					QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE |
++-					QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE,
++-					val);
++-
++-		break;
++-	default:
++-		dev_err(ds->dev, "xMII mode %s not supported for port %d\n",
++-			phy_modes(state->interface), port);
++-		return;
++-	}
++-}
++-
++-static void
++-qca8k_phylink_validate(struct dsa_switch *ds, int port,
++-		       unsigned long *supported,
++-		       struct phylink_link_state *state)
++-{
++-	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
++-
++-	switch (port) {
++-	case 0: /* 1st CPU port */
++-		if (state->interface != PHY_INTERFACE_MODE_NA &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
++-		    state->interface != PHY_INTERFACE_MODE_SGMII)
++-			goto unsupported;
++-		break;
++-	case 1:
++-	case 2:
++-	case 3:
++-	case 4:
++-	case 5:
++-		/* Internal PHY */
++-		if (state->interface != PHY_INTERFACE_MODE_NA &&
++-		    state->interface != PHY_INTERFACE_MODE_GMII &&
++-		    state->interface != PHY_INTERFACE_MODE_INTERNAL)
++-			goto unsupported;
++-		break;
++-	case 6: /* 2nd CPU port / external PHY */
++-		if (state->interface != PHY_INTERFACE_MODE_NA &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
++-		    state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
++-		    state->interface != PHY_INTERFACE_MODE_SGMII &&
++-		    state->interface != PHY_INTERFACE_MODE_1000BASEX)
++-			goto unsupported;
++-		break;
++-	default:
++-unsupported:
++-		linkmode_zero(supported);
++-		return;
++-	}
++-
++-	phylink_set_port_modes(mask);
++-	phylink_set(mask, Autoneg);
++-
++-	phylink_set(mask, 1000baseT_Full);
++-	phylink_set(mask, 10baseT_Half);
++-	phylink_set(mask, 10baseT_Full);
++-	phylink_set(mask, 100baseT_Half);
++-	phylink_set(mask, 100baseT_Full);
++-
++-	if (state->interface == PHY_INTERFACE_MODE_1000BASEX)
++-		phylink_set(mask, 1000baseX_Full);
++-
++-	phylink_set(mask, Pause);
++-	phylink_set(mask, Asym_Pause);
++-
++-	linkmode_and(supported, supported, mask);
++-	linkmode_and(state->advertising, state->advertising, mask);
++-}
++-
++-static int
++-qca8k_phylink_mac_link_state(struct dsa_switch *ds, int port,
++-			     struct phylink_link_state *state)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	u32 reg;
++-	int ret;
++-
++-	ret = qca8k_read(priv, QCA8K_REG_PORT_STATUS(port), &reg);
++-	if (ret < 0)
++-		return ret;
++-
++-	state->link = !!(reg & QCA8K_PORT_STATUS_LINK_UP);
++-	state->an_complete = state->link;
++-	state->an_enabled = !!(reg & QCA8K_PORT_STATUS_LINK_AUTO);
++-	state->duplex = (reg & QCA8K_PORT_STATUS_DUPLEX) ? DUPLEX_FULL :
++-							   DUPLEX_HALF;
++-
++-	switch (reg & QCA8K_PORT_STATUS_SPEED) {
++-	case QCA8K_PORT_STATUS_SPEED_10:
++-		state->speed = SPEED_10;
++-		break;
++-	case QCA8K_PORT_STATUS_SPEED_100:
++-		state->speed = SPEED_100;
++-		break;
++-	case QCA8K_PORT_STATUS_SPEED_1000:
++-		state->speed = SPEED_1000;
++-		break;
++-	default:
++-		state->speed = SPEED_UNKNOWN;
++-		break;
++-	}
++-
++-	state->pause = MLO_PAUSE_NONE;
++-	if (reg & QCA8K_PORT_STATUS_RXFLOW)
++-		state->pause |= MLO_PAUSE_RX;
++-	if (reg & QCA8K_PORT_STATUS_TXFLOW)
++-		state->pause |= MLO_PAUSE_TX;
++-
++-	return 1;
++-}
++-
++-static void
++-qca8k_phylink_mac_link_down(struct dsa_switch *ds, int port, unsigned int mode,
++-			    phy_interface_t interface)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-
++-	qca8k_port_set_status(priv, port, 0);
++-}
++-
++-static void
++-qca8k_phylink_mac_link_up(struct dsa_switch *ds, int port, unsigned int mode,
++-			  phy_interface_t interface, struct phy_device *phydev,
++-			  int speed, int duplex, bool tx_pause, bool rx_pause)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	u32 reg;
++-
++-	if (phylink_autoneg_inband(mode)) {
++-		reg = QCA8K_PORT_STATUS_LINK_AUTO;
++-	} else {
++-		switch (speed) {
++-		case SPEED_10:
++-			reg = QCA8K_PORT_STATUS_SPEED_10;
++-			break;
++-		case SPEED_100:
++-			reg = QCA8K_PORT_STATUS_SPEED_100;
++-			break;
++-		case SPEED_1000:
++-			reg = QCA8K_PORT_STATUS_SPEED_1000;
++-			break;
++-		default:
++-			reg = QCA8K_PORT_STATUS_LINK_AUTO;
++-			break;
++-		}
++-
++-		if (duplex == DUPLEX_FULL)
++-			reg |= QCA8K_PORT_STATUS_DUPLEX;
++-
++-		if (rx_pause || dsa_is_cpu_port(ds, port))
++-			reg |= QCA8K_PORT_STATUS_RXFLOW;
++-
++-		if (tx_pause || dsa_is_cpu_port(ds, port))
++-			reg |= QCA8K_PORT_STATUS_TXFLOW;
++-	}
++-
++-	reg |= QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
++-
++-	qca8k_write(priv, QCA8K_REG_PORT_STATUS(port), reg);
++-}
++-
++-static void
++-qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int i;
++-
++-	if (stringset != ETH_SS_STATS)
++-		return;
++-
++-	for (i = 0; i < priv->info->mib_count; i++)
++-		strncpy(data + i * ETH_GSTRING_LEN, ar8327_mib[i].name,
++-			ETH_GSTRING_LEN);
++-}
++-
++-static void qca8k_mib_autocast_handler(struct dsa_switch *ds, struct sk_buff *skb)
++-{
++-	struct qca8k_mib_eth_data *mib_eth_data;
++-	struct qca8k_priv *priv = ds->priv;
++-	const struct qca8k_mib_desc *mib;
++-	struct mib_ethhdr *mib_ethhdr;
++-	int i, mib_len, offset = 0;
++-	u64 *data;
++-	u8 port;
++-
++-	mib_ethhdr = (struct mib_ethhdr *)skb_mac_header(skb);
++-	mib_eth_data = &priv->mib_eth_data;
++-
++-	/* The switch autocast every port. Ignore other packet and
++-	 * parse only the requested one.
++-	 */
++-	port = FIELD_GET(QCA_HDR_RECV_SOURCE_PORT, ntohs(mib_ethhdr->hdr));
++-	if (port != mib_eth_data->req_port)
++-		goto exit;
++-
++-	data = mib_eth_data->data;
++-
++-	for (i = 0; i < priv->info->mib_count; i++) {
++-		mib = &ar8327_mib[i];
++-
++-		/* First 3 mib are present in the skb head */
++-		if (i < 3) {
++-			data[i] = mib_ethhdr->data[i];
++-			continue;
++-		}
++-
++-		mib_len = sizeof(uint32_t);
++-
++-		/* Some mib are 64 bit wide */
++-		if (mib->size == 2)
++-			mib_len = sizeof(uint64_t);
++-
++-		/* Copy the mib value from packet to the */
++-		memcpy(data + i, skb->data + offset, mib_len);
++-
++-		/* Set the offset for the next mib */
++-		offset += mib_len;
++-	}
++-
++-exit:
++-	/* Complete on receiving all the mib packet */
++-	if (refcount_dec_and_test(&mib_eth_data->port_parsed))
++-		complete(&mib_eth_data->rw_done);
++-}
++-
++-static int
++-qca8k_get_ethtool_stats_eth(struct dsa_switch *ds, int port, u64 *data)
++-{
++-	struct dsa_port *dp = dsa_to_port(ds, port);
++-	struct qca8k_mib_eth_data *mib_eth_data;
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret;
++-
++-	mib_eth_data = &priv->mib_eth_data;
++-
++-	mutex_lock(&mib_eth_data->mutex);
++-
++-	reinit_completion(&mib_eth_data->rw_done);
++-
++-	mib_eth_data->req_port = dp->index;
++-	mib_eth_data->data = data;
++-	refcount_set(&mib_eth_data->port_parsed, QCA8K_NUM_PORTS);
++-
++-	mutex_lock(&priv->reg_mutex);
++-
++-	/* Send mib autocast request */
++-	ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB,
++-				 QCA8K_MIB_FUNC | QCA8K_MIB_BUSY,
++-				 FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_CAST) |
++-				 QCA8K_MIB_BUSY);
++-
++-	mutex_unlock(&priv->reg_mutex);
++-
++-	if (ret)
++-		goto exit;
++-
++-	ret = wait_for_completion_timeout(&mib_eth_data->rw_done, QCA8K_ETHERNET_TIMEOUT);
++-
++-exit:
++-	mutex_unlock(&mib_eth_data->mutex);
++-
++-	return ret;
++-}
++-
++-static void
++-qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
++-			uint64_t *data)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	const struct qca8k_mib_desc *mib;
++-	u32 reg, i, val;
++-	u32 hi = 0;
++-	int ret;
++-
++-	if (priv->mgmt_master && priv->info->ops->autocast_mib &&
++-	    priv->info->ops->autocast_mib(ds, port, data) > 0)
++-		return;
++-
++-	for (i = 0; i < priv->info->mib_count; i++) {
++-		mib = &ar8327_mib[i];
++-		reg = QCA8K_PORT_MIB_COUNTER(port) + mib->offset;
++-
++-		ret = qca8k_read(priv, reg, &val);
++-		if (ret < 0)
++-			continue;
++-
++-		if (mib->size == 2) {
++-			ret = qca8k_read(priv, reg + 4, &hi);
++-			if (ret < 0)
++-				continue;
++-		}
++-
++-		data[i] = val;
++-		if (mib->size == 2)
++-			data[i] |= (u64)hi << 32;
++-	}
++-}
++-
++-static int
++-qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-
++-	if (sset != ETH_SS_STATS)
++-		return 0;
++-
++-	return priv->info->mib_count;
++-}
++-
++-static int
++-qca8k_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *eee)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	u32 lpi_en = QCA8K_REG_EEE_CTRL_LPI_EN(port);
++-	u32 reg;
++-	int ret;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	ret = qca8k_read(priv, QCA8K_REG_EEE_CTRL, &reg);
++-	if (ret < 0)
++-		goto exit;
++-
++-	if (eee->eee_enabled)
++-		reg |= lpi_en;
++-	else
++-		reg &= ~lpi_en;
++-	ret = qca8k_write(priv, QCA8K_REG_EEE_CTRL, reg);
++-
++-exit:
++-	mutex_unlock(&priv->reg_mutex);
++-	return ret;
++-}
++-
++-static int
++-qca8k_get_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e)
++-{
++-	/* Nothing to do on the port's MAC */
++-	return 0;
++-}
++-
++-static void
++-qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	u32 stp_state;
++-
++-	switch (state) {
++-	case BR_STATE_DISABLED:
++-		stp_state = QCA8K_PORT_LOOKUP_STATE_DISABLED;
++-		break;
++-	case BR_STATE_BLOCKING:
++-		stp_state = QCA8K_PORT_LOOKUP_STATE_BLOCKING;
++-		break;
++-	case BR_STATE_LISTENING:
++-		stp_state = QCA8K_PORT_LOOKUP_STATE_LISTENING;
++-		break;
++-	case BR_STATE_LEARNING:
++-		stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING;
++-		break;
++-	case BR_STATE_FORWARDING:
++-	default:
++-		stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD;
++-		break;
++-	}
++-
++-	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++-		  QCA8K_PORT_LOOKUP_STATE_MASK, stp_state);
++-}
++-
++-static int
++-qca8k_port_bridge_join(struct dsa_switch *ds, int port, struct net_device *br)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	int port_mask, cpu_port;
++-	int i, ret;
++-
++-	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
++-	port_mask = BIT(cpu_port);
++-
++-	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++-		if (dsa_is_cpu_port(ds, i))
++-			continue;
++-		if (dsa_to_port(ds, i)->bridge_dev != br)
++-			continue;
++-		/* Add this port to the portvlan mask of the other ports
++-		 * in the bridge
++-		 */
++-		ret = regmap_set_bits(priv->regmap,
++-				      QCA8K_PORT_LOOKUP_CTRL(i),
++-				      BIT(port));
++-		if (ret)
++-			return ret;
++-		if (i != port)
++-			port_mask |= BIT(i);
++-	}
++-
++-	/* Add all other ports to this ports portvlan mask */
++-	ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++-			QCA8K_PORT_LOOKUP_MEMBER, port_mask);
++-
++-	return ret;
++-}
++-
++-static void
++-qca8k_port_bridge_leave(struct dsa_switch *ds, int port, struct net_device *br)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	int cpu_port, i;
++-
++-	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
++-
++-	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++-		if (dsa_is_cpu_port(ds, i))
++-			continue;
++-		if (dsa_to_port(ds, i)->bridge_dev != br)
++-			continue;
++-		/* Remove this port to the portvlan mask of the other ports
++-		 * in the bridge
++-		 */
++-		regmap_clear_bits(priv->regmap,
++-				  QCA8K_PORT_LOOKUP_CTRL(i),
++-				  BIT(port));
++-	}
++-
++-	/* Set the cpu port to be the only one in the portvlan mask of
++-	 * this port
++-	 */
++-	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++-		  QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port));
++-}
++-
++-static void
++-qca8k_port_fast_age(struct dsa_switch *ds, int port)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port);
++-	mutex_unlock(&priv->reg_mutex);
++-}
++-
++-static int
++-qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	unsigned int secs = msecs / 1000;
++-	u32 val;
++-
++-	/* AGE_TIME reg is set in 7s step */
++-	val = secs / 7;
++-
++-	/* Handle case with 0 as val to NOT disable
++-	 * learning
++-	 */
++-	if (!val)
++-		val = 1;
++-
++-	return regmap_update_bits(priv->regmap, QCA8K_REG_ATU_CTRL, QCA8K_ATU_AGE_TIME_MASK,
++-				  QCA8K_ATU_AGE_TIME(val));
++-}
++-
++-static int
++-qca8k_port_enable(struct dsa_switch *ds, int port,
++-		  struct phy_device *phy)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-
++-	qca8k_port_set_status(priv, port, 1);
++-	priv->port_enabled_map |= BIT(port);
++-
++-	if (dsa_is_user_port(ds, port))
++-		phy_support_asym_pause(phy);
++-
++-	return 0;
++-}
++-
++-static void
++-qca8k_port_disable(struct dsa_switch *ds, int port)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-
++-	qca8k_port_set_status(priv, port, 0);
++-	priv->port_enabled_map &= ~BIT(port);
++-}
++-
++-static int
++-qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret;
++-
++-	/* We have only have a general MTU setting.
++-	 * DSA always set the CPU port's MTU to the largest MTU of the slave
++-	 * ports.
++-	 * Setting MTU just for the CPU port is sufficient to correctly set a
++-	 * value for every port.
++-	 */
++-	if (!dsa_is_cpu_port(ds, port))
++-		return 0;
++-
++-	/* To change the MAX_FRAME_SIZE the cpu ports must be off or
++-	 * the switch panics.
++-	 * Turn off both cpu ports before applying the new value to prevent
++-	 * this.
++-	 */
++-	if (priv->port_enabled_map & BIT(0))
++-		qca8k_port_set_status(priv, 0, 0);
++-
++-	if (priv->port_enabled_map & BIT(6))
++-		qca8k_port_set_status(priv, 6, 0);
++-
++-	/* Include L2 header / FCS length */
++-	ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, new_mtu + ETH_HLEN + ETH_FCS_LEN);
++-
++-	if (priv->port_enabled_map & BIT(0))
++-		qca8k_port_set_status(priv, 0, 1);
++-
++-	if (priv->port_enabled_map & BIT(6))
++-		qca8k_port_set_status(priv, 6, 1);
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_port_max_mtu(struct dsa_switch *ds, int port)
++-{
++-	return QCA8K_MAX_MTU;
++-}
++-
++-static int
++-qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
++-		      u16 port_mask, u16 vid)
++-{
++-	/* Set the vid to the port vlan id if no vid is set */
++-	if (!vid)
++-		vid = QCA8K_PORT_VID_DEF;
++-
++-	return qca8k_fdb_add(priv, addr, port_mask, vid,
++-			     QCA8K_ATU_STATUS_STATIC);
++-}
++-
++-static int
++-qca8k_port_fdb_add(struct dsa_switch *ds, int port,
++-		   const unsigned char *addr, u16 vid)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	u16 port_mask = BIT(port);
++-
++-	return qca8k_port_fdb_insert(priv, addr, port_mask, vid);
++-}
++-
++-static int
++-qca8k_port_fdb_del(struct dsa_switch *ds, int port,
++-		   const unsigned char *addr, u16 vid)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	u16 port_mask = BIT(port);
++-
++-	if (!vid)
++-		vid = QCA8K_PORT_VID_DEF;
++-
++-	return qca8k_fdb_del(priv, addr, port_mask, vid);
++-}
++-
++-static int
++-qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
++-		    dsa_fdb_dump_cb_t *cb, void *data)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	struct qca8k_fdb _fdb = { 0 };
++-	int cnt = QCA8K_NUM_FDB_RECORDS;
++-	bool is_static;
++-	int ret = 0;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port)) {
++-		if (!_fdb.aging)
++-			break;
++-		is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC);
++-		ret = cb(_fdb.mac, _fdb.vid, is_static, data);
++-		if (ret)
++-			break;
++-	}
++-	mutex_unlock(&priv->reg_mutex);
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_port_mdb_add(struct dsa_switch *ds, int port,
++-		   const struct switchdev_obj_port_mdb *mdb)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	const u8 *addr = mdb->addr;
++-	u16 vid = mdb->vid;
++-
++-	return qca8k_fdb_search_and_insert(priv, BIT(port), addr, vid);
++-}
++-
++-static int
++-qca8k_port_mdb_del(struct dsa_switch *ds, int port,
++-		   const struct switchdev_obj_port_mdb *mdb)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	const u8 *addr = mdb->addr;
++-	u16 vid = mdb->vid;
++-
++-	return qca8k_fdb_search_and_del(priv, BIT(port), addr, vid);
++-}
++-
++-static int
++-qca8k_port_mirror_add(struct dsa_switch *ds, int port,
++-		      struct dsa_mall_mirror_tc_entry *mirror,
++-		      bool ingress)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int monitor_port, ret;
++-	u32 reg, val;
++-
++-	/* Check for existent entry */
++-	if ((ingress ? priv->mirror_rx : priv->mirror_tx) & BIT(port))
++-		return -EEXIST;
++-
++-	ret = regmap_read(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0, &val);
++-	if (ret)
++-		return ret;
++-
++-	/* QCA83xx can have only one port set to mirror mode.
++-	 * Check that the correct port is requested and return error otherwise.
++-	 * When no mirror port is set, the values is set to 0xF
++-	 */
++-	monitor_port = FIELD_GET(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
++-	if (monitor_port != 0xF && monitor_port != mirror->to_local_port)
++-		return -EEXIST;
++-
++-	/* Set the monitor port */
++-	val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM,
++-			 mirror->to_local_port);
++-	ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
++-				 QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
++-	if (ret)
++-		return ret;
++-
++-	if (ingress) {
++-		reg = QCA8K_PORT_LOOKUP_CTRL(port);
++-		val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN;
++-	} else {
++-		reg = QCA8K_REG_PORT_HOL_CTRL1(port);
++-		val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN;
++-	}
++-
++-	ret = regmap_update_bits(priv->regmap, reg, val, val);
++-	if (ret)
++-		return ret;
++-
++-	/* Track mirror port for tx and rx to decide when the
++-	 * mirror port has to be disabled.
++-	 */
++-	if (ingress)
++-		priv->mirror_rx |= BIT(port);
++-	else
++-		priv->mirror_tx |= BIT(port);
++-
++-	return 0;
++-}
++-
++-static void
++-qca8k_port_mirror_del(struct dsa_switch *ds, int port,
++-		      struct dsa_mall_mirror_tc_entry *mirror)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	u32 reg, val;
++-	int ret;
++-
++-	if (mirror->ingress) {
++-		reg = QCA8K_PORT_LOOKUP_CTRL(port);
++-		val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN;
++-	} else {
++-		reg = QCA8K_REG_PORT_HOL_CTRL1(port);
++-		val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN;
++-	}
++-
++-	ret = regmap_clear_bits(priv->regmap, reg, val);
++-	if (ret)
++-		goto err;
++-
++-	if (mirror->ingress)
++-		priv->mirror_rx &= ~BIT(port);
++-	else
++-		priv->mirror_tx &= ~BIT(port);
++-
++-	/* No port set to send packet to mirror port. Disable mirror port */
++-	if (!priv->mirror_rx && !priv->mirror_tx) {
++-		val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, 0xF);
++-		ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
++-					 QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
++-		if (ret)
++-			goto err;
++-	}
++-err:
++-	dev_err(priv->dev, "Failed to del mirror port from %d", port);
++-}
++-
++-static int
++-qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
++-			  struct netlink_ext_ack *extack)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret;
++-
++-	if (vlan_filtering) {
++-		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++-				QCA8K_PORT_LOOKUP_VLAN_MODE_MASK,
++-				QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE);
++-	} else {
++-		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++-				QCA8K_PORT_LOOKUP_VLAN_MODE_MASK,
++-				QCA8K_PORT_LOOKUP_VLAN_MODE_NONE);
++-	}
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_port_vlan_add(struct dsa_switch *ds, int port,
++-		    const struct switchdev_obj_port_vlan *vlan,
++-		    struct netlink_ext_ack *extack)
++-{
++-	bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
++-	bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret;
++-
++-	ret = qca8k_vlan_add(priv, port, vlan->vid, untagged);
++-	if (ret) {
++-		dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port, ret);
++-		return ret;
++-	}
++-
++-	if (pvid) {
++-		ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port),
++-				QCA8K_EGREES_VLAN_PORT_MASK(port),
++-				QCA8K_EGREES_VLAN_PORT(port, vlan->vid));
++-		if (ret)
++-			return ret;
++-
++-		ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port),
++-				  QCA8K_PORT_VLAN_CVID(vlan->vid) |
++-				  QCA8K_PORT_VLAN_SVID(vlan->vid));
++-	}
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_port_vlan_del(struct dsa_switch *ds, int port,
++-		    const struct switchdev_obj_port_vlan *vlan)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret;
++-
++-	ret = qca8k_vlan_del(priv, port, vlan->vid);
++-	if (ret)
++-		dev_err(priv->dev, "Failed to delete VLAN from port %d (%d)", port, ret);
++-
++-	return ret;
++-}
++-
++-static u32 qca8k_get_phy_flags(struct dsa_switch *ds, int port)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-
++-	/* Communicate to the phy internal driver the switch revision.
++-	 * Based on the switch revision different values needs to be
++-	 * set to the dbg and mmd reg on the phy.
++-	 * The first 2 bit are used to communicate the switch revision
++-	 * to the phy driver.
++-	 */
++-	if (port > 0 && port < 6)
++-		return priv->switch_revision;
++-
++-	return 0;
++-}
++-
++-static enum dsa_tag_protocol
++-qca8k_get_tag_protocol(struct dsa_switch *ds, int port,
++-		       enum dsa_tag_protocol mp)
++-{
++-	return DSA_TAG_PROTO_QCA;
++-}
++-
++-static bool
++-qca8k_lag_can_offload(struct dsa_switch *ds,
++-		      struct net_device *lag,
++-		      struct netdev_lag_upper_info *info)
++-{
++-	struct dsa_port *dp;
++-	int id, members = 0;
++-
++-	id = dsa_lag_id(ds->dst, lag);
++-	if (id < 0 || id >= ds->num_lag_ids)
++-		return false;
++-
++-	dsa_lag_foreach_port(dp, ds->dst, lag)
++-		/* Includes the port joining the LAG */
++-		members++;
++-
++-	if (members > QCA8K_NUM_PORTS_FOR_LAG)
++-		return false;
++-
++-	if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH)
++-		return false;
++-
++-	if (info->hash_type != NETDEV_LAG_HASH_L2 &&
++-	    info->hash_type != NETDEV_LAG_HASH_L23)
++-		return false;
++-
++-	return true;
++-}
++-
++-static int
++-qca8k_lag_setup_hash(struct dsa_switch *ds,
++-		     struct net_device *lag,
++-		     struct netdev_lag_upper_info *info)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	bool unique_lag = true;
++-	u32 hash = 0;
++-	int i, id;
++-
++-	id = dsa_lag_id(ds->dst, lag);
++-
++-	switch (info->hash_type) {
++-	case NETDEV_LAG_HASH_L23:
++-		hash |= QCA8K_TRUNK_HASH_SIP_EN;
++-		hash |= QCA8K_TRUNK_HASH_DIP_EN;
++-		fallthrough;
++-	case NETDEV_LAG_HASH_L2:
++-		hash |= QCA8K_TRUNK_HASH_SA_EN;
++-		hash |= QCA8K_TRUNK_HASH_DA_EN;
++-		break;
++-	default: /* We should NEVER reach this */
++-		return -EOPNOTSUPP;
++-	}
++-
++-	/* Check if we are the unique configured LAG */
++-	dsa_lags_foreach_id(i, ds->dst)
++-		if (i != id && dsa_lag_dev(ds->dst, i)) {
++-			unique_lag = false;
++-			break;
++-		}
++-
++-	/* Hash Mode is global. Make sure the same Hash Mode
++-	 * is set to all the 4 possible lag.
++-	 * If we are the unique LAG we can set whatever hash
++-	 * mode we want.
++-	 * To change hash mode it's needed to remove all LAG
++-	 * and change the mode with the latest.
++-	 */
++-	if (unique_lag) {
++-		priv->lag_hash_mode = hash;
++-	} else if (priv->lag_hash_mode != hash) {
++-		netdev_err(lag, "Error: Mismateched Hash Mode across different lag is not supported\n");
++-		return -EOPNOTSUPP;
++-	}
++-
++-	return regmap_update_bits(priv->regmap, QCA8K_TRUNK_HASH_EN_CTRL,
++-				  QCA8K_TRUNK_HASH_MASK, hash);
++-}
++-
++-static int
++-qca8k_lag_refresh_portmap(struct dsa_switch *ds, int port,
++-			  struct net_device *lag, bool delete)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret, id, i;
++-	u32 val;
++-
++-	id = dsa_lag_id(ds->dst, lag);
++-
++-	/* Read current port member */
++-	ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0, &val);
++-	if (ret)
++-		return ret;
++-
++-	/* Shift val to the correct trunk */
++-	val >>= QCA8K_REG_GOL_TRUNK_SHIFT(id);
++-	val &= QCA8K_REG_GOL_TRUNK_MEMBER_MASK;
++-	if (delete)
++-		val &= ~BIT(port);
++-	else
++-		val |= BIT(port);
++-
++-	/* Update port member. With empty portmap disable trunk */
++-	ret = regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0,
++-				 QCA8K_REG_GOL_TRUNK_MEMBER(id) |
++-				 QCA8K_REG_GOL_TRUNK_EN(id),
++-				 !val << QCA8K_REG_GOL_TRUNK_SHIFT(id) |
++-				 val << QCA8K_REG_GOL_TRUNK_SHIFT(id));
++-
++-	/* Search empty member if adding or port on deleting */
++-	for (i = 0; i < QCA8K_NUM_PORTS_FOR_LAG; i++) {
++-		ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id), &val);
++-		if (ret)
++-			return ret;
++-
++-		val >>= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i);
++-		val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_MASK;
++-
++-		if (delete) {
++-			/* If port flagged to be disabled assume this member is
++-			 * empty
++-			 */
++-			if (val != QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
++-				continue;
++-
++-			val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK;
++-			if (val != port)
++-				continue;
++-		} else {
++-			/* If port flagged to be enabled assume this member is
++-			 * already set
++-			 */
++-			if (val == QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
++-				continue;
++-		}
++-
++-		/* We have found the member to add/remove */
++-		break;
++-	}
++-
++-	/* Set port in the correct port mask or disable port if in delete mode */
++-	return regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id),
++-				  QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN(id, i) |
++-				  QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT(id, i),
++-				  !delete << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i) |
++-				  port << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i));
++-}
++-
++-static int
++-qca8k_port_lag_join(struct dsa_switch *ds, int port,
++-		    struct net_device *lag,
++-		    struct netdev_lag_upper_info *info)
++-{
++-	int ret;
++-
++-	if (!qca8k_lag_can_offload(ds, lag, info))
++-		return -EOPNOTSUPP;
++-
++-	ret = qca8k_lag_setup_hash(ds, lag, info);
++-	if (ret)
++-		return ret;
++-
++-	return qca8k_lag_refresh_portmap(ds, port, lag, false);
++-}
++-
++-static int
++-qca8k_port_lag_leave(struct dsa_switch *ds, int port,
++-		     struct net_device *lag)
++-{
++-	return qca8k_lag_refresh_portmap(ds, port, lag, true);
++-}
++-
++-static void
++-qca8k_master_change(struct dsa_switch *ds, const struct net_device *master,
++-		    bool operational)
++-{
++-	struct dsa_port *dp = master->dsa_ptr;
++-	struct qca8k_priv *priv = ds->priv;
++-
++-	/* Ethernet MIB/MDIO is only supported for CPU port 0 */
++-	if (dp->index != 0)
++-		return;
++-
++-	mutex_lock(&priv->mgmt_eth_data.mutex);
++-	mutex_lock(&priv->mib_eth_data.mutex);
++-
++-	priv->mgmt_master = operational ? (struct net_device *)master : NULL;
++-
++-	mutex_unlock(&priv->mib_eth_data.mutex);
++-	mutex_unlock(&priv->mgmt_eth_data.mutex);
++-}
++-
++-static int qca8k_connect_tag_protocol(struct dsa_switch *ds,
++-				      enum dsa_tag_protocol proto)
++-{
++-	struct qca_tagger_data *tagger_data;
++-
++-	switch (proto) {
++-	case DSA_TAG_PROTO_QCA:
++-		tagger_data = ds->tagger_data;
++-
++-		tagger_data->rw_reg_ack_handler = qca8k_rw_reg_ack_handler;
++-		tagger_data->mib_autocast_handler = qca8k_mib_autocast_handler;
++-
++-		break;
++-	default:
++-		return -EOPNOTSUPP;
++-	}
++-
++-	return 0;
++-}
++-
++-static const struct dsa_switch_ops qca8k_switch_ops = {
++-	.get_tag_protocol	= qca8k_get_tag_protocol,
++-	.setup			= qca8k_setup,
++-	.get_strings		= qca8k_get_strings,
++-	.get_ethtool_stats	= qca8k_get_ethtool_stats,
++-	.get_sset_count		= qca8k_get_sset_count,
++-	.set_ageing_time	= qca8k_set_ageing_time,
++-	.get_mac_eee		= qca8k_get_mac_eee,
++-	.set_mac_eee		= qca8k_set_mac_eee,
++-	.port_enable		= qca8k_port_enable,
++-	.port_disable		= qca8k_port_disable,
++-	.port_change_mtu	= qca8k_port_change_mtu,
++-	.port_max_mtu		= qca8k_port_max_mtu,
++-	.port_stp_state_set	= qca8k_port_stp_state_set,
++-	.port_bridge_join	= qca8k_port_bridge_join,
++-	.port_bridge_leave	= qca8k_port_bridge_leave,
++-	.port_fast_age		= qca8k_port_fast_age,
++-	.port_fdb_add		= qca8k_port_fdb_add,
++-	.port_fdb_del		= qca8k_port_fdb_del,
++-	.port_fdb_dump		= qca8k_port_fdb_dump,
++-	.port_mdb_add		= qca8k_port_mdb_add,
++-	.port_mdb_del		= qca8k_port_mdb_del,
++-	.port_mirror_add	= qca8k_port_mirror_add,
++-	.port_mirror_del	= qca8k_port_mirror_del,
++-	.port_vlan_filtering	= qca8k_port_vlan_filtering,
++-	.port_vlan_add		= qca8k_port_vlan_add,
++-	.port_vlan_del		= qca8k_port_vlan_del,
++-	.phylink_validate	= qca8k_phylink_validate,
++-	.phylink_mac_link_state	= qca8k_phylink_mac_link_state,
++-	.phylink_mac_config	= qca8k_phylink_mac_config,
++-	.phylink_mac_link_down	= qca8k_phylink_mac_link_down,
++-	.phylink_mac_link_up	= qca8k_phylink_mac_link_up,
++-	.get_phy_flags		= qca8k_get_phy_flags,
++-	.port_lag_join		= qca8k_port_lag_join,
++-	.port_lag_leave		= qca8k_port_lag_leave,
++-	.master_state_change	= qca8k_master_change,
++-	.connect_tag_protocol	= qca8k_connect_tag_protocol,
++-};
++-
++-static int qca8k_read_switch_id(struct qca8k_priv *priv)
++-{
++-	u32 val;
++-	u8 id;
++-	int ret;
++-
++-	if (!priv->info)
++-		return -ENODEV;
++-
++-	ret = qca8k_read(priv, QCA8K_REG_MASK_CTRL, &val);
++-	if (ret < 0)
++-		return -ENODEV;
++-
++-	id = QCA8K_MASK_CTRL_DEVICE_ID(val);
++-	if (id != priv->info->id) {
++-		dev_err(priv->dev,
++-			"Switch id detected %x but expected %x",
++-			id, priv->info->id);
++-		return -ENODEV;
++-	}
++-
++-	priv->switch_id = id;
++-
++-	/* Save revision to communicate to the internal PHY driver */
++-	priv->switch_revision = QCA8K_MASK_CTRL_REV_ID(val);
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_sw_probe(struct mdio_device *mdiodev)
++-{
++-	struct qca8k_priv *priv;
++-	int ret;
++-
++-	/* allocate the private data struct so that we can probe the switches
++-	 * ID register
++-	 */
++-	priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL);
++-	if (!priv)
++-		return -ENOMEM;
++-
++-	priv->info = of_device_get_match_data(priv->dev);
++-	priv->bus = mdiodev->bus;
++-	priv->dev = &mdiodev->dev;
++-
++-	priv->reset_gpio = devm_gpiod_get_optional(priv->dev, "reset",
++-						   GPIOD_ASIS);
++-	if (IS_ERR(priv->reset_gpio))
++-		return PTR_ERR(priv->reset_gpio);
++-
++-	if (priv->reset_gpio) {
++-		gpiod_set_value_cansleep(priv->reset_gpio, 1);
++-		/* The active low duration must be greater than 10 ms
++-		 * and checkpatch.pl wants 20 ms.
++-		 */
++-		msleep(20);
++-		gpiod_set_value_cansleep(priv->reset_gpio, 0);
++-	}
++-
++-	/* Start by setting up the register mapping */
++-	priv->regmap = devm_regmap_init(&mdiodev->dev, NULL, priv,
++-					&qca8k_regmap_config);
++-	if (IS_ERR(priv->regmap)) {
++-		dev_err(priv->dev, "regmap initialization failed");
++-		return PTR_ERR(priv->regmap);
++-	}
++-
++-	priv->mdio_cache.page = 0xffff;
++-	priv->mdio_cache.lo = 0xffff;
++-	priv->mdio_cache.hi = 0xffff;
++-
++-	/* Check the detected switch id */
++-	ret = qca8k_read_switch_id(priv);
++-	if (ret)
++-		return ret;
++-
++-	priv->ds = devm_kzalloc(&mdiodev->dev, sizeof(*priv->ds), GFP_KERNEL);
++-	if (!priv->ds)
++-		return -ENOMEM;
++-
++-	mutex_init(&priv->mgmt_eth_data.mutex);
++-	init_completion(&priv->mgmt_eth_data.rw_done);
++-
++-	mutex_init(&priv->mib_eth_data.mutex);
++-	init_completion(&priv->mib_eth_data.rw_done);
++-
++-	priv->ds->dev = &mdiodev->dev;
++-	priv->ds->num_ports = QCA8K_NUM_PORTS;
++-	priv->ds->priv = priv;
++-	priv->ds->ops = &qca8k_switch_ops;
++-	mutex_init(&priv->reg_mutex);
++-	dev_set_drvdata(&mdiodev->dev, priv);
++-
++-	return dsa_register_switch(priv->ds);
++-}
++-
++-static void
++-qca8k_sw_remove(struct mdio_device *mdiodev)
++-{
++-	struct qca8k_priv *priv = dev_get_drvdata(&mdiodev->dev);
++-	int i;
++-
++-	if (!priv)
++-		return;
++-
++-	for (i = 0; i < QCA8K_NUM_PORTS; i++)
++-		qca8k_port_set_status(priv, i, 0);
++-
++-	dsa_unregister_switch(priv->ds);
++-
++-	dev_set_drvdata(&mdiodev->dev, NULL);
++-}
++-
++-static void qca8k_sw_shutdown(struct mdio_device *mdiodev)
++-{
++-	struct qca8k_priv *priv = dev_get_drvdata(&mdiodev->dev);
++-
++-	if (!priv)
++-		return;
++-
++-	dsa_switch_shutdown(priv->ds);
++-
++-	dev_set_drvdata(&mdiodev->dev, NULL);
++-}
++-
++-#ifdef CONFIG_PM_SLEEP
++-static void
++-qca8k_set_pm(struct qca8k_priv *priv, int enable)
++-{
++-	int port;
++-
++-	for (port = 0; port < QCA8K_NUM_PORTS; port++) {
++-		/* Do not enable on resume if the port was
++-		 * disabled before.
++-		 */
++-		if (!(priv->port_enabled_map & BIT(port)))
++-			continue;
++-
++-		qca8k_port_set_status(priv, port, enable);
++-	}
++-}
++-
++-static int qca8k_suspend(struct device *dev)
++-{
++-	struct qca8k_priv *priv = dev_get_drvdata(dev);
++-
++-	qca8k_set_pm(priv, 0);
++-
++-	return dsa_switch_suspend(priv->ds);
++-}
++-
++-static int qca8k_resume(struct device *dev)
++-{
++-	struct qca8k_priv *priv = dev_get_drvdata(dev);
++-
++-	qca8k_set_pm(priv, 1);
++-
++-	return dsa_switch_resume(priv->ds);
++-}
++-#endif /* CONFIG_PM_SLEEP */
++-
++-static SIMPLE_DEV_PM_OPS(qca8k_pm_ops,
++-			 qca8k_suspend, qca8k_resume);
++-
++-static const struct qca8k_info_ops qca8xxx_ops = {
++-	.autocast_mib = qca8k_get_ethtool_stats_eth,
++-};
++-
++-static const struct qca8k_match_data qca8327 = {
++-	.id = QCA8K_ID_QCA8327,
++-	.reduced_package = true,
++-	.mib_count = QCA8K_QCA832X_MIB_COUNT,
++-	.ops = &qca8xxx_ops,
++-};
++-
++-static const struct qca8k_match_data qca8328 = {
++-	.id = QCA8K_ID_QCA8327,
++-	.mib_count = QCA8K_QCA832X_MIB_COUNT,
++-	.ops = &qca8xxx_ops,
++-};
++-
++-static const struct qca8k_match_data qca833x = {
++-	.id = QCA8K_ID_QCA8337,
++-	.mib_count = QCA8K_QCA833X_MIB_COUNT,
++-	.ops = &qca8xxx_ops,
++-};
++-
++-static const struct of_device_id qca8k_of_match[] = {
++-	{ .compatible = "qca,qca8327", .data = &qca8327 },
++-	{ .compatible = "qca,qca8328", .data = &qca8328 },
++-	{ .compatible = "qca,qca8334", .data = &qca833x },
++-	{ .compatible = "qca,qca8337", .data = &qca833x },
++-	{ /* sentinel */ },
++-};
++-
++-static struct mdio_driver qca8kmdio_driver = {
++-	.probe  = qca8k_sw_probe,
++-	.remove = qca8k_sw_remove,
++-	.shutdown = qca8k_sw_shutdown,
++-	.mdiodrv.driver = {
++-		.name = "qca8k",
++-		.of_match_table = qca8k_of_match,
++-		.pm = &qca8k_pm_ops,
++-	},
++-};
++-
++-mdio_module_driver(qca8kmdio_driver);
++-
++-MODULE_AUTHOR("Mathieu Olivari, John Crispin <john@phrozen.org>");
++-MODULE_DESCRIPTION("Driver for QCA8K ethernet switch family");
++-MODULE_LICENSE("GPL v2");
++-MODULE_ALIAS("platform:qca8k");
++--- /dev/null
+++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
++@@ -0,0 +1,3186 @@
+++// SPDX-License-Identifier: GPL-2.0
+++/*
+++ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+++ * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
+++ * Copyright (c) 2015, 2019, The Linux Foundation. All rights reserved.
+++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
+++ */
+++
+++#include <linux/module.h>
+++#include <linux/phy.h>
+++#include <linux/netdevice.h>
+++#include <linux/bitfield.h>
+++#include <linux/regmap.h>
+++#include <net/dsa.h>
+++#include <linux/of_net.h>
+++#include <linux/of_mdio.h>
+++#include <linux/of_platform.h>
+++#include <linux/if_bridge.h>
+++#include <linux/mdio.h>
+++#include <linux/phylink.h>
+++#include <linux/gpio/consumer.h>
+++#include <linux/etherdevice.h>
+++#include <linux/dsa/tag_qca.h>
+++
+++#include "qca8k.h"
+++
+++static void
+++qca8k_split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
+++{
+++	regaddr >>= 1;
+++	*r1 = regaddr & 0x1e;
+++
+++	regaddr >>= 5;
+++	*r2 = regaddr & 0x7;
+++
+++	regaddr >>= 3;
+++	*page = regaddr & 0x3ff;
+++}
+++
+++static int
+++qca8k_set_lo(struct qca8k_priv *priv, int phy_id, u32 regnum, u16 lo)
+++{
+++	u16 *cached_lo = &priv->mdio_cache.lo;
+++	struct mii_bus *bus = priv->bus;
+++	int ret;
+++
+++	if (lo == *cached_lo)
+++		return 0;
+++
+++	ret = bus->write(bus, phy_id, regnum, lo);
+++	if (ret < 0)
+++		dev_err_ratelimited(&bus->dev,
+++				    "failed to write qca8k 32bit lo register\n");
+++
+++	*cached_lo = lo;
+++	return 0;
+++}
+++
+++static int
+++qca8k_set_hi(struct qca8k_priv *priv, int phy_id, u32 regnum, u16 hi)
+++{
+++	u16 *cached_hi = &priv->mdio_cache.hi;
+++	struct mii_bus *bus = priv->bus;
+++	int ret;
+++
+++	if (hi == *cached_hi)
+++		return 0;
+++
+++	ret = bus->write(bus, phy_id, regnum, hi);
+++	if (ret < 0)
+++		dev_err_ratelimited(&bus->dev,
+++				    "failed to write qca8k 32bit hi register\n");
+++
+++	*cached_hi = hi;
+++	return 0;
+++}
+++
+++static int
+++qca8k_mii_read32(struct mii_bus *bus, int phy_id, u32 regnum, u32 *val)
+++{
+++	int ret;
+++
+++	ret = bus->read(bus, phy_id, regnum);
+++	if (ret >= 0) {
+++		*val = ret;
+++		ret = bus->read(bus, phy_id, regnum + 1);
+++		*val |= ret << 16;
+++	}
+++
+++	if (ret < 0) {
+++		dev_err_ratelimited(&bus->dev,
+++				    "failed to read qca8k 32bit register\n");
+++		*val = 0;
+++		return ret;
+++	}
+++
+++	return 0;
+++}
+++
+++static void
+++qca8k_mii_write32(struct qca8k_priv *priv, int phy_id, u32 regnum, u32 val)
+++{
+++	u16 lo, hi;
+++	int ret;
+++
+++	lo = val & 0xffff;
+++	hi = (u16)(val >> 16);
+++
+++	ret = qca8k_set_lo(priv, phy_id, regnum, lo);
+++	if (ret >= 0)
+++		ret = qca8k_set_hi(priv, phy_id, regnum + 1, hi);
+++}
+++
+++static int
+++qca8k_set_page(struct qca8k_priv *priv, u16 page)
+++{
+++	u16 *cached_page = &priv->mdio_cache.page;
+++	struct mii_bus *bus = priv->bus;
+++	int ret;
+++
+++	if (page == *cached_page)
+++		return 0;
+++
+++	ret = bus->write(bus, 0x18, 0, page);
+++	if (ret < 0) {
+++		dev_err_ratelimited(&bus->dev,
+++				    "failed to set qca8k page\n");
+++		return ret;
+++	}
+++
+++	*cached_page = page;
+++	usleep_range(1000, 2000);
+++	return 0;
+++}
+++
+++static int
+++qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val)
+++{
+++	return regmap_read(priv->regmap, reg, val);
+++}
+++
+++static int
+++qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val)
+++{
+++	return regmap_write(priv->regmap, reg, val);
+++}
+++
+++static int
+++qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
+++{
+++	return regmap_update_bits(priv->regmap, reg, mask, write_val);
+++}
+++
+++static void qca8k_rw_reg_ack_handler(struct dsa_switch *ds, struct sk_buff *skb)
+++{
+++	struct qca8k_mgmt_eth_data *mgmt_eth_data;
+++	struct qca8k_priv *priv = ds->priv;
+++	struct qca_mgmt_ethhdr *mgmt_ethhdr;
+++	u8 len, cmd;
+++
+++	mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb_mac_header(skb);
+++	mgmt_eth_data = &priv->mgmt_eth_data;
+++
+++	cmd = FIELD_GET(QCA_HDR_MGMT_CMD, mgmt_ethhdr->command);
+++	len = FIELD_GET(QCA_HDR_MGMT_LENGTH, mgmt_ethhdr->command);
+++
+++	/* Make sure the seq match the requested packet */
+++	if (mgmt_ethhdr->seq == mgmt_eth_data->seq)
+++		mgmt_eth_data->ack = true;
+++
+++	if (cmd == MDIO_READ) {
+++		mgmt_eth_data->data[0] = mgmt_ethhdr->mdio_data;
+++
+++		/* Get the rest of the 12 byte of data.
+++		 * The read/write function will extract the requested data.
+++		 */
+++		if (len > QCA_HDR_MGMT_DATA1_LEN)
+++			memcpy(mgmt_eth_data->data + 1, skb->data,
+++			       QCA_HDR_MGMT_DATA2_LEN);
+++	}
+++
+++	complete(&mgmt_eth_data->rw_done);
+++}
+++
+++static struct sk_buff *qca8k_alloc_mdio_header(enum mdio_cmd cmd, u32 reg, u32 *val,
+++					       int priority, unsigned int len)
+++{
+++	struct qca_mgmt_ethhdr *mgmt_ethhdr;
+++	unsigned int real_len;
+++	struct sk_buff *skb;
+++	u32 *data2;
+++	u16 hdr;
+++
+++	skb = dev_alloc_skb(QCA_HDR_MGMT_PKT_LEN);
+++	if (!skb)
+++		return NULL;
+++
+++	/* Max value for len reg is 15 (0xf) but the switch actually return 16 byte
+++	 * Actually for some reason the steps are:
+++	 * 0: nothing
+++	 * 1-4: first 4 byte
+++	 * 5-6: first 12 byte
+++	 * 7-15: all 16 byte
+++	 */
+++	if (len == 16)
+++		real_len = 15;
+++	else
+++		real_len = len;
+++
+++	skb_reset_mac_header(skb);
+++	skb_set_network_header(skb, skb->len);
+++
+++	mgmt_ethhdr = skb_push(skb, QCA_HDR_MGMT_HEADER_LEN + QCA_HDR_LEN);
+++
+++	hdr = FIELD_PREP(QCA_HDR_XMIT_VERSION, QCA_HDR_VERSION);
+++	hdr |= FIELD_PREP(QCA_HDR_XMIT_PRIORITY, priority);
+++	hdr |= QCA_HDR_XMIT_FROM_CPU;
+++	hdr |= FIELD_PREP(QCA_HDR_XMIT_DP_BIT, BIT(0));
+++	hdr |= FIELD_PREP(QCA_HDR_XMIT_CONTROL, QCA_HDR_XMIT_TYPE_RW_REG);
+++
+++	mgmt_ethhdr->command = FIELD_PREP(QCA_HDR_MGMT_ADDR, reg);
+++	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_LENGTH, real_len);
+++	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CMD, cmd);
+++	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CHECK_CODE,
+++					   QCA_HDR_MGMT_CHECK_CODE_VAL);
+++
+++	if (cmd == MDIO_WRITE)
+++		mgmt_ethhdr->mdio_data = *val;
+++
+++	mgmt_ethhdr->hdr = htons(hdr);
+++
+++	data2 = skb_put_zero(skb, QCA_HDR_MGMT_DATA2_LEN + QCA_HDR_MGMT_PADDING_LEN);
+++	if (cmd == MDIO_WRITE && len > QCA_HDR_MGMT_DATA1_LEN)
+++		memcpy(data2, val + 1, len - QCA_HDR_MGMT_DATA1_LEN);
+++
+++	return skb;
+++}
+++
+++static void qca8k_mdio_header_fill_seq_num(struct sk_buff *skb, u32 seq_num)
+++{
+++	struct qca_mgmt_ethhdr *mgmt_ethhdr;
+++
+++	mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb->data;
+++	mgmt_ethhdr->seq = FIELD_PREP(QCA_HDR_MGMT_SEQ_NUM, seq_num);
+++}
+++
+++static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
+++{
+++	struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
+++	struct sk_buff *skb;
+++	bool ack;
+++	int ret;
+++
+++	skb = qca8k_alloc_mdio_header(MDIO_READ, reg, NULL,
+++				      QCA8K_ETHERNET_MDIO_PRIORITY, len);
+++	if (!skb)
+++		return -ENOMEM;
+++
+++	mutex_lock(&mgmt_eth_data->mutex);
+++
+++	/* Check mgmt_master if is operational */
+++	if (!priv->mgmt_master) {
+++		kfree_skb(skb);
+++		mutex_unlock(&mgmt_eth_data->mutex);
+++		return -EINVAL;
+++	}
+++
+++	skb->dev = priv->mgmt_master;
+++
+++	reinit_completion(&mgmt_eth_data->rw_done);
+++
+++	/* Increment seq_num and set it in the mdio pkt */
+++	mgmt_eth_data->seq++;
+++	qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
+++	mgmt_eth_data->ack = false;
+++
+++	dev_queue_xmit(skb);
+++
+++	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
+++					  msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
+++
+++	*val = mgmt_eth_data->data[0];
+++	if (len > QCA_HDR_MGMT_DATA1_LEN)
+++		memcpy(val + 1, mgmt_eth_data->data + 1, len - QCA_HDR_MGMT_DATA1_LEN);
+++
+++	ack = mgmt_eth_data->ack;
+++
+++	mutex_unlock(&mgmt_eth_data->mutex);
+++
+++	if (ret <= 0)
+++		return -ETIMEDOUT;
+++
+++	if (!ack)
+++		return -EINVAL;
+++
+++	return 0;
+++}
+++
+++static int qca8k_write_eth(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
+++{
+++	struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
+++	struct sk_buff *skb;
+++	bool ack;
+++	int ret;
+++
+++	skb = qca8k_alloc_mdio_header(MDIO_WRITE, reg, val,
+++				      QCA8K_ETHERNET_MDIO_PRIORITY, len);
+++	if (!skb)
+++		return -ENOMEM;
+++
+++	mutex_lock(&mgmt_eth_data->mutex);
+++
+++	/* Check mgmt_master if is operational */
+++	if (!priv->mgmt_master) {
+++		kfree_skb(skb);
+++		mutex_unlock(&mgmt_eth_data->mutex);
+++		return -EINVAL;
+++	}
+++
+++	skb->dev = priv->mgmt_master;
+++
+++	reinit_completion(&mgmt_eth_data->rw_done);
+++
+++	/* Increment seq_num and set it in the mdio pkt */
+++	mgmt_eth_data->seq++;
+++	qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
+++	mgmt_eth_data->ack = false;
+++
+++	dev_queue_xmit(skb);
+++
+++	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
+++					  msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
+++
+++	ack = mgmt_eth_data->ack;
+++
+++	mutex_unlock(&mgmt_eth_data->mutex);
+++
+++	if (ret <= 0)
+++		return -ETIMEDOUT;
+++
+++	if (!ack)
+++		return -EINVAL;
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_regmap_update_bits_eth(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
+++{
+++	u32 val = 0;
+++	int ret;
+++
+++	ret = qca8k_read_eth(priv, reg, &val, sizeof(val));
+++	if (ret)
+++		return ret;
+++
+++	val &= ~mask;
+++	val |= write_val;
+++
+++	return qca8k_write_eth(priv, reg, &val, sizeof(val));
+++}
+++
+++static int
+++qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
+++{
+++	int i, count = len / sizeof(u32), ret;
+++
+++	if (priv->mgmt_master && !qca8k_read_eth(priv, reg, val, len))
+++		return 0;
+++
+++	for (i = 0; i < count; i++) {
+++		ret = regmap_read(priv->regmap, reg + (i * 4), val + i);
+++		if (ret < 0)
+++			return ret;
+++	}
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
+++{
+++	int i, count = len / sizeof(u32), ret;
+++	u32 tmp;
+++
+++	if (priv->mgmt_master && !qca8k_write_eth(priv, reg, val, len))
+++		return 0;
+++
+++	for (i = 0; i < count; i++) {
+++		tmp = val[i];
+++
+++		ret = regmap_write(priv->regmap, reg + (i * 4), tmp);
+++		if (ret < 0)
+++			return ret;
+++	}
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
+++	struct mii_bus *bus = priv->bus;
+++	u16 r1, r2, page;
+++	int ret;
+++
+++	if (!qca8k_read_eth(priv, reg, val, sizeof(*val)))
+++		return 0;
+++
+++	qca8k_split_addr(reg, &r1, &r2, &page);
+++
+++	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+++
+++	ret = qca8k_set_page(priv, page);
+++	if (ret < 0)
+++		goto exit;
+++
+++	ret = qca8k_mii_read32(bus, 0x10 | r2, r1, val);
+++
+++exit:
+++	mutex_unlock(&bus->mdio_lock);
+++	return ret;
+++}
+++
+++static int
+++qca8k_regmap_write(void *ctx, uint32_t reg, uint32_t val)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
+++	struct mii_bus *bus = priv->bus;
+++	u16 r1, r2, page;
+++	int ret;
+++
+++	if (!qca8k_write_eth(priv, reg, &val, sizeof(val)))
+++		return 0;
+++
+++	qca8k_split_addr(reg, &r1, &r2, &page);
+++
+++	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+++
+++	ret = qca8k_set_page(priv, page);
+++	if (ret < 0)
+++		goto exit;
+++
+++	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
+++
+++exit:
+++	mutex_unlock(&bus->mdio_lock);
+++	return ret;
+++}
+++
+++static int
+++qca8k_regmap_update_bits(void *ctx, uint32_t reg, uint32_t mask, uint32_t write_val)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
+++	struct mii_bus *bus = priv->bus;
+++	u16 r1, r2, page;
+++	u32 val;
+++	int ret;
+++
+++	if (!qca8k_regmap_update_bits_eth(priv, reg, mask, write_val))
+++		return 0;
+++
+++	qca8k_split_addr(reg, &r1, &r2, &page);
+++
+++	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+++
+++	ret = qca8k_set_page(priv, page);
+++	if (ret < 0)
+++		goto exit;
+++
+++	ret = qca8k_mii_read32(bus, 0x10 | r2, r1, &val);
+++	if (ret < 0)
+++		goto exit;
+++
+++	val &= ~mask;
+++	val |= write_val;
+++	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
+++
+++exit:
+++	mutex_unlock(&bus->mdio_lock);
+++
+++	return ret;
+++}
+++
+++static const struct regmap_range qca8k_readable_ranges[] = {
+++	regmap_reg_range(0x0000, 0x00e4), /* Global control */
+++	regmap_reg_range(0x0100, 0x0168), /* EEE control */
+++	regmap_reg_range(0x0200, 0x0270), /* Parser control */
+++	regmap_reg_range(0x0400, 0x0454), /* ACL */
+++	regmap_reg_range(0x0600, 0x0718), /* Lookup */
+++	regmap_reg_range(0x0800, 0x0b70), /* QM */
+++	regmap_reg_range(0x0c00, 0x0c80), /* PKT */
+++	regmap_reg_range(0x0e00, 0x0e98), /* L3 */
+++	regmap_reg_range(0x1000, 0x10ac), /* MIB - Port0 */
+++	regmap_reg_range(0x1100, 0x11ac), /* MIB - Port1 */
+++	regmap_reg_range(0x1200, 0x12ac), /* MIB - Port2 */
+++	regmap_reg_range(0x1300, 0x13ac), /* MIB - Port3 */
+++	regmap_reg_range(0x1400, 0x14ac), /* MIB - Port4 */
+++	regmap_reg_range(0x1500, 0x15ac), /* MIB - Port5 */
+++	regmap_reg_range(0x1600, 0x16ac), /* MIB - Port6 */
+++
+++};
+++
+++static const struct regmap_access_table qca8k_readable_table = {
+++	.yes_ranges = qca8k_readable_ranges,
+++	.n_yes_ranges = ARRAY_SIZE(qca8k_readable_ranges),
+++};
+++
+++static struct regmap_config qca8k_regmap_config = {
+++	.reg_bits = 16,
+++	.val_bits = 32,
+++	.reg_stride = 4,
+++	.max_register = 0x16ac, /* end MIB - Port6 range */
+++	.reg_read = qca8k_regmap_read,
+++	.reg_write = qca8k_regmap_write,
+++	.reg_update_bits = qca8k_regmap_update_bits,
+++	.rd_table = &qca8k_readable_table,
+++	.disable_locking = true, /* Locking is handled by qca8k read/write */
+++	.cache_type = REGCACHE_NONE, /* Explicitly disable CACHE */
+++};
+++
+++static int
+++qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask)
+++{
+++	u32 val;
+++
+++	return regmap_read_poll_timeout(priv->regmap, reg, val, !(val & mask), 0,
+++				       QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC);
+++}
+++
+++static int
+++qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb)
+++{
+++	u32 reg[3];
+++	int ret;
+++
+++	/* load the ARL table into an array */
+++	ret = qca8k_bulk_read(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
+++	if (ret)
+++		return ret;
+++
+++	/* vid - 83:72 */
+++	fdb->vid = FIELD_GET(QCA8K_ATU_VID_MASK, reg[2]);
+++	/* aging - 67:64 */
+++	fdb->aging = FIELD_GET(QCA8K_ATU_STATUS_MASK, reg[2]);
+++	/* portmask - 54:48 */
+++	fdb->port_mask = FIELD_GET(QCA8K_ATU_PORT_MASK, reg[1]);
+++	/* mac - 47:0 */
+++	fdb->mac[0] = FIELD_GET(QCA8K_ATU_ADDR0_MASK, reg[1]);
+++	fdb->mac[1] = FIELD_GET(QCA8K_ATU_ADDR1_MASK, reg[1]);
+++	fdb->mac[2] = FIELD_GET(QCA8K_ATU_ADDR2_MASK, reg[0]);
+++	fdb->mac[3] = FIELD_GET(QCA8K_ATU_ADDR3_MASK, reg[0]);
+++	fdb->mac[4] = FIELD_GET(QCA8K_ATU_ADDR4_MASK, reg[0]);
+++	fdb->mac[5] = FIELD_GET(QCA8K_ATU_ADDR5_MASK, reg[0]);
+++
+++	return 0;
+++}
+++
+++static void
+++qca8k_fdb_write(struct qca8k_priv *priv, u16 vid, u8 port_mask, const u8 *mac,
+++		u8 aging)
+++{
+++	u32 reg[3] = { 0 };
+++
+++	/* vid - 83:72 */
+++	reg[2] = FIELD_PREP(QCA8K_ATU_VID_MASK, vid);
+++	/* aging - 67:64 */
+++	reg[2] |= FIELD_PREP(QCA8K_ATU_STATUS_MASK, aging);
+++	/* portmask - 54:48 */
+++	reg[1] = FIELD_PREP(QCA8K_ATU_PORT_MASK, port_mask);
+++	/* mac - 47:0 */
+++	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR0_MASK, mac[0]);
+++	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR1_MASK, mac[1]);
+++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR2_MASK, mac[2]);
+++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR3_MASK, mac[3]);
+++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR4_MASK, mac[4]);
+++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR5_MASK, mac[5]);
+++
+++	/* load the array into the ARL table */
+++	qca8k_bulk_write(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
+++}
+++
+++static int
+++qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd, int port)
+++{
+++	u32 reg;
+++	int ret;
+++
+++	/* Set the command and FDB index */
+++	reg = QCA8K_ATU_FUNC_BUSY;
+++	reg |= cmd;
+++	if (port >= 0) {
+++		reg |= QCA8K_ATU_FUNC_PORT_EN;
+++		reg |= FIELD_PREP(QCA8K_ATU_FUNC_PORT_MASK, port);
+++	}
+++
+++	/* Write the function register triggering the table access */
+++	ret = qca8k_write(priv, QCA8K_REG_ATU_FUNC, reg);
+++	if (ret)
+++		return ret;
+++
+++	/* wait for completion */
+++	ret = qca8k_busy_wait(priv, QCA8K_REG_ATU_FUNC, QCA8K_ATU_FUNC_BUSY);
+++	if (ret)
+++		return ret;
+++
+++	/* Check for table full violation when adding an entry */
+++	if (cmd == QCA8K_FDB_LOAD) {
+++		ret = qca8k_read(priv, QCA8K_REG_ATU_FUNC, &reg);
+++		if (ret < 0)
+++			return ret;
+++		if (reg & QCA8K_ATU_FUNC_FULL)
+++			return -1;
+++	}
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb, int port)
+++{
+++	int ret;
+++
+++	qca8k_fdb_write(priv, fdb->vid, fdb->port_mask, fdb->mac, fdb->aging);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_NEXT, port);
+++	if (ret < 0)
+++		return ret;
+++
+++	return qca8k_fdb_read(priv, fdb);
+++}
+++
+++static int
+++qca8k_fdb_add(struct qca8k_priv *priv, const u8 *mac, u16 port_mask,
+++	      u16 vid, u8 aging)
+++{
+++	int ret;
+++
+++	mutex_lock(&priv->reg_mutex);
+++	qca8k_fdb_write(priv, vid, port_mask, mac, aging);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
+++	mutex_unlock(&priv->reg_mutex);
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac, u16 port_mask, u16 vid)
+++{
+++	int ret;
+++
+++	mutex_lock(&priv->reg_mutex);
+++	qca8k_fdb_write(priv, vid, port_mask, mac, 0);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
+++	mutex_unlock(&priv->reg_mutex);
+++
+++	return ret;
+++}
+++
+++static void
+++qca8k_fdb_flush(struct qca8k_priv *priv)
+++{
+++	mutex_lock(&priv->reg_mutex);
+++	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH, -1);
+++	mutex_unlock(&priv->reg_mutex);
+++}
+++
+++static int
+++qca8k_fdb_search_and_insert(struct qca8k_priv *priv, u8 port_mask,
+++			    const u8 *mac, u16 vid)
+++{
+++	struct qca8k_fdb fdb = { 0 };
+++	int ret;
+++
+++	mutex_lock(&priv->reg_mutex);
+++
+++	qca8k_fdb_write(priv, vid, 0, mac, 0);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
+++	if (ret < 0)
+++		goto exit;
+++
+++	ret = qca8k_fdb_read(priv, &fdb);
+++	if (ret < 0)
+++		goto exit;
+++
+++	/* Rule exist. Delete first */
+++	if (!fdb.aging) {
+++		ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
+++		if (ret)
+++			goto exit;
+++	}
+++
+++	/* Add port to fdb portmask */
+++	fdb.port_mask |= port_mask;
+++
+++	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
+++
+++exit:
+++	mutex_unlock(&priv->reg_mutex);
+++	return ret;
+++}
+++
+++static int
+++qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask,
+++			 const u8 *mac, u16 vid)
+++{
+++	struct qca8k_fdb fdb = { 0 };
+++	int ret;
+++
+++	mutex_lock(&priv->reg_mutex);
+++
+++	qca8k_fdb_write(priv, vid, 0, mac, 0);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
+++	if (ret < 0)
+++		goto exit;
+++
+++	/* Rule doesn't exist. Why delete? */
+++	if (!fdb.aging) {
+++		ret = -EINVAL;
+++		goto exit;
+++	}
+++
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
+++	if (ret)
+++		goto exit;
+++
+++	/* Only port in the rule is this port. Don't re insert */
+++	if (fdb.port_mask == port_mask)
+++		goto exit;
+++
+++	/* Remove port from port mask */
+++	fdb.port_mask &= ~port_mask;
+++
+++	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
+++
+++exit:
+++	mutex_unlock(&priv->reg_mutex);
+++	return ret;
+++}
+++
+++static int
+++qca8k_vlan_access(struct qca8k_priv *priv, enum qca8k_vlan_cmd cmd, u16 vid)
+++{
+++	u32 reg;
+++	int ret;
+++
+++	/* Set the command and VLAN index */
+++	reg = QCA8K_VTU_FUNC1_BUSY;
+++	reg |= cmd;
+++	reg |= FIELD_PREP(QCA8K_VTU_FUNC1_VID_MASK, vid);
+++
+++	/* Write the function register triggering the table access */
+++	ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC1, reg);
+++	if (ret)
+++		return ret;
+++
+++	/* wait for completion */
+++	ret = qca8k_busy_wait(priv, QCA8K_REG_VTU_FUNC1, QCA8K_VTU_FUNC1_BUSY);
+++	if (ret)
+++		return ret;
+++
+++	/* Check for table full violation when adding an entry */
+++	if (cmd == QCA8K_VLAN_LOAD) {
+++		ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC1, &reg);
+++		if (ret < 0)
+++			return ret;
+++		if (reg & QCA8K_VTU_FUNC1_FULL)
+++			return -ENOMEM;
+++	}
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid, bool untagged)
+++{
+++	u32 reg;
+++	int ret;
+++
+++	/*
+++	   We do the right thing with VLAN 0 and treat it as untagged while
+++	   preserving the tag on egress.
+++	 */
+++	if (vid == 0)
+++		return 0;
+++
+++	mutex_lock(&priv->reg_mutex);
+++	ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid);
+++	if (ret < 0)
+++		goto out;
+++
+++	ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, &reg);
+++	if (ret < 0)
+++		goto out;
+++	reg |= QCA8K_VTU_FUNC0_VALID | QCA8K_VTU_FUNC0_IVL_EN;
+++	reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port);
+++	if (untagged)
+++		reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_UNTAG(port);
+++	else
+++		reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_TAG(port);
+++
+++	ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
+++	if (ret)
+++		goto out;
+++	ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid);
+++
+++out:
+++	mutex_unlock(&priv->reg_mutex);
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid)
+++{
+++	u32 reg, mask;
+++	int ret, i;
+++	bool del;
+++
+++	mutex_lock(&priv->reg_mutex);
+++	ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid);
+++	if (ret < 0)
+++		goto out;
+++
+++	ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, &reg);
+++	if (ret < 0)
+++		goto out;
+++	reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port);
+++	reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(port);
+++
+++	/* Check if we're the last member to be removed */
+++	del = true;
+++	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+++		mask = QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(i);
+++
+++		if ((reg & mask) != mask) {
+++			del = false;
+++			break;
+++		}
+++	}
+++
+++	if (del) {
+++		ret = qca8k_vlan_access(priv, QCA8K_VLAN_PURGE, vid);
+++	} else {
+++		ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
+++		if (ret)
+++			goto out;
+++		ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid);
+++	}
+++
+++out:
+++	mutex_unlock(&priv->reg_mutex);
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_mib_init(struct qca8k_priv *priv)
+++{
+++	int ret;
+++
+++	mutex_lock(&priv->reg_mutex);
+++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB,
+++				 QCA8K_MIB_FUNC | QCA8K_MIB_BUSY,
+++				 FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_FLUSH) |
+++				 QCA8K_MIB_BUSY);
+++	if (ret)
+++		goto exit;
+++
+++	ret = qca8k_busy_wait(priv, QCA8K_REG_MIB, QCA8K_MIB_BUSY);
+++	if (ret)
+++		goto exit;
+++
+++	ret = regmap_set_bits(priv->regmap, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP);
+++	if (ret)
+++		goto exit;
+++
+++	ret = qca8k_write(priv, QCA8K_REG_MODULE_EN, QCA8K_MODULE_EN_MIB);
+++
+++exit:
+++	mutex_unlock(&priv->reg_mutex);
+++	return ret;
+++}
+++
+++static void
+++qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable)
+++{
+++	u32 mask = QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
+++
+++	/* Port 0 and 6 have no internal PHY */
+++	if (port > 0 && port < 6)
+++		mask |= QCA8K_PORT_STATUS_LINK_AUTO;
+++
+++	if (enable)
+++		regmap_set_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
+++	else
+++		regmap_clear_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
+++}
+++
+++static int
+++qca8k_phy_eth_busy_wait(struct qca8k_mgmt_eth_data *mgmt_eth_data,
+++			struct sk_buff *read_skb, u32 *val)
+++{
+++	struct sk_buff *skb = skb_copy(read_skb, GFP_KERNEL);
+++	bool ack;
+++	int ret;
+++
+++	reinit_completion(&mgmt_eth_data->rw_done);
+++
+++	/* Increment seq_num and set it in the copy pkt */
+++	mgmt_eth_data->seq++;
+++	qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
+++	mgmt_eth_data->ack = false;
+++
+++	dev_queue_xmit(skb);
+++
+++	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
+++					  QCA8K_ETHERNET_TIMEOUT);
+++
+++	ack = mgmt_eth_data->ack;
+++
+++	if (ret <= 0)
+++		return -ETIMEDOUT;
+++
+++	if (!ack)
+++		return -EINVAL;
+++
+++	*val = mgmt_eth_data->data[0];
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_phy_eth_command(struct qca8k_priv *priv, bool read, int phy,
+++		      int regnum, u16 data)
+++{
+++	struct sk_buff *write_skb, *clear_skb, *read_skb;
+++	struct qca8k_mgmt_eth_data *mgmt_eth_data;
+++	u32 write_val, clear_val = 0, val;
+++	struct net_device *mgmt_master;
+++	int ret, ret1;
+++	bool ack;
+++
+++	if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
+++		return -EINVAL;
+++
+++	mgmt_eth_data = &priv->mgmt_eth_data;
+++
+++	write_val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
+++		    QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
+++		    QCA8K_MDIO_MASTER_REG_ADDR(regnum);
+++
+++	if (read) {
+++		write_val |= QCA8K_MDIO_MASTER_READ;
+++	} else {
+++		write_val |= QCA8K_MDIO_MASTER_WRITE;
+++		write_val |= QCA8K_MDIO_MASTER_DATA(data);
+++	}
+++
+++	/* Prealloc all the needed skb before the lock */
+++	write_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL, &write_val,
+++					    QCA8K_ETHERNET_PHY_PRIORITY, sizeof(write_val));
+++	if (!write_skb)
+++		return -ENOMEM;
+++
+++	clear_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL, &clear_val,
+++					    QCA8K_ETHERNET_PHY_PRIORITY, sizeof(clear_val));
+++	if (!clear_skb) {
+++		ret = -ENOMEM;
+++		goto err_clear_skb;
+++	}
+++
+++	read_skb = qca8k_alloc_mdio_header(MDIO_READ, QCA8K_MDIO_MASTER_CTRL, &clear_val,
+++					   QCA8K_ETHERNET_PHY_PRIORITY, sizeof(clear_val));
+++	if (!read_skb) {
+++		ret = -ENOMEM;
+++		goto err_read_skb;
+++	}
+++
+++	/* Actually start the request:
+++	 * 1. Send mdio master packet
+++	 * 2. Busy Wait for mdio master command
+++	 * 3. Get the data if we are reading
+++	 * 4. Reset the mdio master (even with error)
+++	 */
+++	mutex_lock(&mgmt_eth_data->mutex);
+++
+++	/* Check if mgmt_master is operational */
+++	mgmt_master = priv->mgmt_master;
+++	if (!mgmt_master) {
+++		mutex_unlock(&mgmt_eth_data->mutex);
+++		ret = -EINVAL;
+++		goto err_mgmt_master;
+++	}
+++
+++	read_skb->dev = mgmt_master;
+++	clear_skb->dev = mgmt_master;
+++	write_skb->dev = mgmt_master;
+++
+++	reinit_completion(&mgmt_eth_data->rw_done);
+++
+++	/* Increment seq_num and set it in the write pkt */
+++	mgmt_eth_data->seq++;
+++	qca8k_mdio_header_fill_seq_num(write_skb, mgmt_eth_data->seq);
+++	mgmt_eth_data->ack = false;
+++
+++	dev_queue_xmit(write_skb);
+++
+++	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
+++					  QCA8K_ETHERNET_TIMEOUT);
+++
+++	ack = mgmt_eth_data->ack;
+++
+++	if (ret <= 0) {
+++		ret = -ETIMEDOUT;
+++		kfree_skb(read_skb);
+++		goto exit;
+++	}
+++
+++	if (!ack) {
+++		ret = -EINVAL;
+++		kfree_skb(read_skb);
+++		goto exit;
+++	}
+++
+++	ret = read_poll_timeout(qca8k_phy_eth_busy_wait, ret1,
+++				!(val & QCA8K_MDIO_MASTER_BUSY), 0,
+++				QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
+++				mgmt_eth_data, read_skb, &val);
+++
+++	if (ret < 0 && ret1 < 0) {
+++		ret = ret1;
+++		goto exit;
+++	}
+++
+++	if (read) {
+++		reinit_completion(&mgmt_eth_data->rw_done);
+++
+++		/* Increment seq_num and set it in the read pkt */
+++		mgmt_eth_data->seq++;
+++		qca8k_mdio_header_fill_seq_num(read_skb, mgmt_eth_data->seq);
+++		mgmt_eth_data->ack = false;
+++
+++		dev_queue_xmit(read_skb);
+++
+++		ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
+++						  QCA8K_ETHERNET_TIMEOUT);
+++
+++		ack = mgmt_eth_data->ack;
+++
+++		if (ret <= 0) {
+++			ret = -ETIMEDOUT;
+++			goto exit;
+++		}
+++
+++		if (!ack) {
+++			ret = -EINVAL;
+++			goto exit;
+++		}
+++
+++		ret = mgmt_eth_data->data[0] & QCA8K_MDIO_MASTER_DATA_MASK;
+++	} else {
+++		kfree_skb(read_skb);
+++	}
+++exit:
+++	reinit_completion(&mgmt_eth_data->rw_done);
+++
+++	/* Increment seq_num and set it in the clear pkt */
+++	mgmt_eth_data->seq++;
+++	qca8k_mdio_header_fill_seq_num(clear_skb, mgmt_eth_data->seq);
+++	mgmt_eth_data->ack = false;
+++
+++	dev_queue_xmit(clear_skb);
+++
+++	wait_for_completion_timeout(&mgmt_eth_data->rw_done,
+++				    QCA8K_ETHERNET_TIMEOUT);
+++
+++	mutex_unlock(&mgmt_eth_data->mutex);
+++
+++	return ret;
+++
+++	/* Error handling before lock */
+++err_mgmt_master:
+++	kfree_skb(read_skb);
+++err_read_skb:
+++	kfree_skb(clear_skb);
+++err_clear_skb:
+++	kfree_skb(write_skb);
+++
+++	return ret;
+++}
+++
+++static u32
+++qca8k_port_to_phy(int port)
+++{
+++	/* From Andrew Lunn:
+++	 * Port 0 has no internal phy.
+++	 * Port 1 has an internal PHY at MDIO address 0.
+++	 * Port 2 has an internal PHY at MDIO address 1.
+++	 * ...
+++	 * Port 5 has an internal PHY at MDIO address 4.
+++	 * Port 6 has no internal PHY.
+++	 */
+++
+++	return port - 1;
+++}
+++
+++static int
+++qca8k_mdio_busy_wait(struct mii_bus *bus, u32 reg, u32 mask)
+++{
+++	u16 r1, r2, page;
+++	u32 val;
+++	int ret, ret1;
+++
+++	qca8k_split_addr(reg, &r1, &r2, &page);
+++
+++	ret = read_poll_timeout(qca8k_mii_read32, ret1, !(val & mask), 0,
+++				QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
+++				bus, 0x10 | r2, r1, &val);
+++
+++	/* Check if qca8k_read has failed for a different reason
+++	 * before returnting -ETIMEDOUT
+++	 */
+++	if (ret < 0 && ret1 < 0)
+++		return ret1;
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_mdio_write(struct qca8k_priv *priv, int phy, int regnum, u16 data)
+++{
+++	struct mii_bus *bus = priv->bus;
+++	u16 r1, r2, page;
+++	u32 val;
+++	int ret;
+++
+++	if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
+++		return -EINVAL;
+++
+++	val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
+++	      QCA8K_MDIO_MASTER_WRITE | QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
+++	      QCA8K_MDIO_MASTER_REG_ADDR(regnum) |
+++	      QCA8K_MDIO_MASTER_DATA(data);
+++
+++	qca8k_split_addr(QCA8K_MDIO_MASTER_CTRL, &r1, &r2, &page);
+++
+++	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+++
+++	ret = qca8k_set_page(priv, page);
+++	if (ret)
+++		goto exit;
+++
+++	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
+++
+++	ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
+++				   QCA8K_MDIO_MASTER_BUSY);
+++
+++exit:
+++	/* even if the busy_wait timeouts try to clear the MASTER_EN */
+++	qca8k_mii_write32(priv, 0x10 | r2, r1, 0);
+++
+++	mutex_unlock(&bus->mdio_lock);
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_mdio_read(struct qca8k_priv *priv, int phy, int regnum)
+++{
+++	struct mii_bus *bus = priv->bus;
+++	u16 r1, r2, page;
+++	u32 val;
+++	int ret;
+++
+++	if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
+++		return -EINVAL;
+++
+++	val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
+++	      QCA8K_MDIO_MASTER_READ | QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
+++	      QCA8K_MDIO_MASTER_REG_ADDR(regnum);
+++
+++	qca8k_split_addr(QCA8K_MDIO_MASTER_CTRL, &r1, &r2, &page);
+++
+++	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+++
+++	ret = qca8k_set_page(priv, page);
+++	if (ret)
+++		goto exit;
+++
+++	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
+++
+++	ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
+++				   QCA8K_MDIO_MASTER_BUSY);
+++	if (ret)
+++		goto exit;
+++
+++	ret = qca8k_mii_read32(bus, 0x10 | r2, r1, &val);
+++
+++exit:
+++	/* even if the busy_wait timeouts try to clear the MASTER_EN */
+++	qca8k_mii_write32(priv, 0x10 | r2, r1, 0);
+++
+++	mutex_unlock(&bus->mdio_lock);
+++
+++	if (ret >= 0)
+++		ret = val & QCA8K_MDIO_MASTER_DATA_MASK;
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_internal_mdio_write(struct mii_bus *slave_bus, int phy, int regnum, u16 data)
+++{
+++	struct qca8k_priv *priv = slave_bus->priv;
+++	int ret;
+++
+++	/* Use mdio Ethernet when available, fallback to legacy one on error */
+++	ret = qca8k_phy_eth_command(priv, false, phy, regnum, data);
+++	if (!ret)
+++		return 0;
+++
+++	return qca8k_mdio_write(priv, phy, regnum, data);
+++}
+++
+++static int
+++qca8k_internal_mdio_read(struct mii_bus *slave_bus, int phy, int regnum)
+++{
+++	struct qca8k_priv *priv = slave_bus->priv;
+++	int ret;
+++
+++	/* Use mdio Ethernet when available, fallback to legacy one on error */
+++	ret = qca8k_phy_eth_command(priv, true, phy, regnum, 0);
+++	if (ret >= 0)
+++		return ret;
+++
+++	ret = qca8k_mdio_read(priv, phy, regnum);
+++
+++	if (ret < 0)
+++		return 0xffff;
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_legacy_mdio_write(struct mii_bus *slave_bus, int port, int regnum, u16 data)
+++{
+++	port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
+++
+++	return qca8k_internal_mdio_write(slave_bus, port, regnum, data);
+++}
+++
+++static int
+++qca8k_legacy_mdio_read(struct mii_bus *slave_bus, int port, int regnum)
+++{
+++	port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
+++
+++	return qca8k_internal_mdio_read(slave_bus, port, regnum);
+++}
+++
+++static int
+++qca8k_mdio_register(struct qca8k_priv *priv)
+++{
+++	struct dsa_switch *ds = priv->ds;
+++	struct device_node *mdio;
+++	struct mii_bus *bus;
+++
+++	bus = devm_mdiobus_alloc(ds->dev);
+++	if (!bus)
+++		return -ENOMEM;
+++
+++	bus->priv = (void *)priv;
+++	snprintf(bus->id, MII_BUS_ID_SIZE, "qca8k-%d.%d",
+++		 ds->dst->index, ds->index);
+++	bus->parent = ds->dev;
+++	bus->phy_mask = ~ds->phys_mii_mask;
+++	ds->slave_mii_bus = bus;
+++
+++	/* Check if the devicetree declare the port:phy mapping */
+++	mdio = of_get_child_by_name(priv->dev->of_node, "mdio");
+++	if (of_device_is_available(mdio)) {
+++		bus->name = "qca8k slave mii";
+++		bus->read = qca8k_internal_mdio_read;
+++		bus->write = qca8k_internal_mdio_write;
+++		return devm_of_mdiobus_register(priv->dev, bus, mdio);
+++	}
+++
+++	/* If a mapping can't be found the legacy mapping is used,
+++	 * using the qca8k_port_to_phy function
+++	 */
+++	bus->name = "qca8k-legacy slave mii";
+++	bus->read = qca8k_legacy_mdio_read;
+++	bus->write = qca8k_legacy_mdio_write;
+++	return devm_mdiobus_register(priv->dev, bus);
+++}
+++
+++static int
+++qca8k_setup_mdio_bus(struct qca8k_priv *priv)
+++{
+++	u32 internal_mdio_mask = 0, external_mdio_mask = 0, reg;
+++	struct device_node *ports, *port;
+++	phy_interface_t mode;
+++	int err;
+++
+++	ports = of_get_child_by_name(priv->dev->of_node, "ports");
+++	if (!ports)
+++		ports = of_get_child_by_name(priv->dev->of_node, "ethernet-ports");
+++
+++	if (!ports)
+++		return -EINVAL;
+++
+++	for_each_available_child_of_node(ports, port) {
+++		err = of_property_read_u32(port, "reg", &reg);
+++		if (err) {
+++			of_node_put(port);
+++			of_node_put(ports);
+++			return err;
+++		}
+++
+++		if (!dsa_is_user_port(priv->ds, reg))
+++			continue;
+++
+++		of_get_phy_mode(port, &mode);
+++
+++		if (of_property_read_bool(port, "phy-handle") &&
+++		    mode != PHY_INTERFACE_MODE_INTERNAL)
+++			external_mdio_mask |= BIT(reg);
+++		else
+++			internal_mdio_mask |= BIT(reg);
+++	}
+++
+++	of_node_put(ports);
+++	if (!external_mdio_mask && !internal_mdio_mask) {
+++		dev_err(priv->dev, "no PHYs are defined.\n");
+++		return -EINVAL;
+++	}
+++
+++	/* The QCA8K_MDIO_MASTER_EN Bit, which grants access to PHYs through
+++	 * the MDIO_MASTER register also _disconnects_ the external MDC
+++	 * passthrough to the internal PHYs. It's not possible to use both
+++	 * configurations at the same time!
+++	 *
+++	 * Because this came up during the review process:
+++	 * If the external mdio-bus driver is capable magically disabling
+++	 * the QCA8K_MDIO_MASTER_EN and mutex/spin-locking out the qca8k's
+++	 * accessors for the time being, it would be possible to pull this
+++	 * off.
+++	 */
+++	if (!!external_mdio_mask && !!internal_mdio_mask) {
+++		dev_err(priv->dev, "either internal or external mdio bus configuration is supported.\n");
+++		return -EINVAL;
+++	}
+++
+++	if (external_mdio_mask) {
+++		/* Make sure to disable the internal mdio bus in cases
+++		 * a dt-overlay and driver reload changed the configuration
+++		 */
+++
+++		return regmap_clear_bits(priv->regmap, QCA8K_MDIO_MASTER_CTRL,
+++					 QCA8K_MDIO_MASTER_EN);
+++	}
+++
+++	return qca8k_mdio_register(priv);
+++}
+++
+++static int
+++qca8k_setup_mac_pwr_sel(struct qca8k_priv *priv)
+++{
+++	u32 mask = 0;
+++	int ret = 0;
+++
+++	/* SoC specific settings for ipq8064.
+++	 * If more device require this consider adding
+++	 * a dedicated binding.
+++	 */
+++	if (of_machine_is_compatible("qcom,ipq8064"))
+++		mask |= QCA8K_MAC_PWR_RGMII0_1_8V;
+++
+++	/* SoC specific settings for ipq8065 */
+++	if (of_machine_is_compatible("qcom,ipq8065"))
+++		mask |= QCA8K_MAC_PWR_RGMII1_1_8V;
+++
+++	if (mask) {
+++		ret = qca8k_rmw(priv, QCA8K_REG_MAC_PWR_SEL,
+++				QCA8K_MAC_PWR_RGMII0_1_8V |
+++				QCA8K_MAC_PWR_RGMII1_1_8V,
+++				mask);
+++	}
+++
+++	return ret;
+++}
+++
+++static int qca8k_find_cpu_port(struct dsa_switch *ds)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++
+++	/* Find the connected cpu port. Valid port are 0 or 6 */
+++	if (dsa_is_cpu_port(ds, 0))
+++		return 0;
+++
+++	dev_dbg(priv->dev, "port 0 is not the CPU port. Checking port 6");
+++
+++	if (dsa_is_cpu_port(ds, 6))
+++		return 6;
+++
+++	return -EINVAL;
+++}
+++
+++static int
+++qca8k_setup_of_pws_reg(struct qca8k_priv *priv)
+++{
+++	const struct qca8k_match_data *data = priv->info;
+++	struct device_node *node = priv->dev->of_node;
+++	u32 val = 0;
+++	int ret;
+++
+++	/* QCA8327 require to set to the correct mode.
+++	 * His bigger brother QCA8328 have the 172 pin layout.
+++	 * Should be applied by default but we set this just to make sure.
+++	 */
+++	if (priv->switch_id == QCA8K_ID_QCA8327) {
+++		/* Set the correct package of 148 pin for QCA8327 */
+++		if (data->reduced_package)
+++			val |= QCA8327_PWS_PACKAGE148_EN;
+++
+++		ret = qca8k_rmw(priv, QCA8K_REG_PWS, QCA8327_PWS_PACKAGE148_EN,
+++				val);
+++		if (ret)
+++			return ret;
+++	}
+++
+++	if (of_property_read_bool(node, "qca,ignore-power-on-sel"))
+++		val |= QCA8K_PWS_POWER_ON_SEL;
+++
+++	if (of_property_read_bool(node, "qca,led-open-drain")) {
+++		if (!(val & QCA8K_PWS_POWER_ON_SEL)) {
+++			dev_err(priv->dev, "qca,led-open-drain require qca,ignore-power-on-sel to be set.");
+++			return -EINVAL;
+++		}
+++
+++		val |= QCA8K_PWS_LED_OPEN_EN_CSR;
+++	}
+++
+++	return qca8k_rmw(priv, QCA8K_REG_PWS,
+++			QCA8K_PWS_LED_OPEN_EN_CSR | QCA8K_PWS_POWER_ON_SEL,
+++			val);
+++}
+++
+++static int
+++qca8k_parse_port_config(struct qca8k_priv *priv)
+++{
+++	int port, cpu_port_index = -1, ret;
+++	struct device_node *port_dn;
+++	phy_interface_t mode;
+++	struct dsa_port *dp;
+++	u32 delay;
+++
+++	/* We have 2 CPU port. Check them */
+++	for (port = 0; port < QCA8K_NUM_PORTS; port++) {
+++		/* Skip every other port */
+++		if (port != 0 && port != 6)
+++			continue;
+++
+++		dp = dsa_to_port(priv->ds, port);
+++		port_dn = dp->dn;
+++		cpu_port_index++;
+++
+++		if (!of_device_is_available(port_dn))
+++			continue;
+++
+++		ret = of_get_phy_mode(port_dn, &mode);
+++		if (ret)
+++			continue;
+++
+++		switch (mode) {
+++		case PHY_INTERFACE_MODE_RGMII:
+++		case PHY_INTERFACE_MODE_RGMII_ID:
+++		case PHY_INTERFACE_MODE_RGMII_TXID:
+++		case PHY_INTERFACE_MODE_RGMII_RXID:
+++		case PHY_INTERFACE_MODE_SGMII:
+++			delay = 0;
+++
+++			if (!of_property_read_u32(port_dn, "tx-internal-delay-ps", &delay))
+++				/* Switch regs accept value in ns, convert ps to ns */
+++				delay = delay / 1000;
+++			else if (mode == PHY_INTERFACE_MODE_RGMII_ID ||
+++				 mode == PHY_INTERFACE_MODE_RGMII_TXID)
+++				delay = 1;
+++
+++			if (!FIELD_FIT(QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK, delay)) {
+++				dev_err(priv->dev, "rgmii tx delay is limited to a max value of 3ns, setting to the max value");
+++				delay = 3;
+++			}
+++
+++			priv->ports_config.rgmii_tx_delay[cpu_port_index] = delay;
+++
+++			delay = 0;
+++
+++			if (!of_property_read_u32(port_dn, "rx-internal-delay-ps", &delay))
+++				/* Switch regs accept value in ns, convert ps to ns */
+++				delay = delay / 1000;
+++			else if (mode == PHY_INTERFACE_MODE_RGMII_ID ||
+++				 mode == PHY_INTERFACE_MODE_RGMII_RXID)
+++				delay = 2;
+++
+++			if (!FIELD_FIT(QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK, delay)) {
+++				dev_err(priv->dev, "rgmii rx delay is limited to a max value of 3ns, setting to the max value");
+++				delay = 3;
+++			}
+++
+++			priv->ports_config.rgmii_rx_delay[cpu_port_index] = delay;
+++
+++			/* Skip sgmii parsing for rgmii* mode */
+++			if (mode == PHY_INTERFACE_MODE_RGMII ||
+++			    mode == PHY_INTERFACE_MODE_RGMII_ID ||
+++			    mode == PHY_INTERFACE_MODE_RGMII_TXID ||
+++			    mode == PHY_INTERFACE_MODE_RGMII_RXID)
+++				break;
+++
+++			if (of_property_read_bool(port_dn, "qca,sgmii-txclk-falling-edge"))
+++				priv->ports_config.sgmii_tx_clk_falling_edge = true;
+++
+++			if (of_property_read_bool(port_dn, "qca,sgmii-rxclk-falling-edge"))
+++				priv->ports_config.sgmii_rx_clk_falling_edge = true;
+++
+++			if (of_property_read_bool(port_dn, "qca,sgmii-enable-pll")) {
+++				priv->ports_config.sgmii_enable_pll = true;
+++
+++				if (priv->switch_id == QCA8K_ID_QCA8327) {
+++					dev_err(priv->dev, "SGMII PLL should NOT be enabled for qca8327. Aborting enabling");
+++					priv->ports_config.sgmii_enable_pll = false;
+++				}
+++
+++				if (priv->switch_revision < 2)
+++					dev_warn(priv->dev, "SGMII PLL should NOT be enabled for qca8337 with revision 2 or more.");
+++			}
+++
+++			break;
+++		default:
+++			continue;
+++		}
+++	}
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_setup(struct dsa_switch *ds)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	int cpu_port, ret, i;
+++	u32 mask;
+++
+++	cpu_port = qca8k_find_cpu_port(ds);
+++	if (cpu_port < 0) {
+++		dev_err(priv->dev, "No cpu port configured in both cpu port0 and port6");
+++		return cpu_port;
+++	}
+++
+++	/* Parse CPU port config to be later used in phy_link mac_config */
+++	ret = qca8k_parse_port_config(priv);
+++	if (ret)
+++		return ret;
+++
+++	ret = qca8k_setup_mdio_bus(priv);
+++	if (ret)
+++		return ret;
+++
+++	ret = qca8k_setup_of_pws_reg(priv);
+++	if (ret)
+++		return ret;
+++
+++	ret = qca8k_setup_mac_pwr_sel(priv);
+++	if (ret)
+++		return ret;
+++
+++	/* Make sure MAC06 is disabled */
+++	ret = regmap_clear_bits(priv->regmap, QCA8K_REG_PORT0_PAD_CTRL,
+++				QCA8K_PORT0_PAD_MAC06_EXCHANGE_EN);
+++	if (ret) {
+++		dev_err(priv->dev, "failed disabling MAC06 exchange");
+++		return ret;
+++	}
+++
+++	/* Enable CPU Port */
+++	ret = regmap_set_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
+++			      QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
+++	if (ret) {
+++		dev_err(priv->dev, "failed enabling CPU port");
+++		return ret;
+++	}
+++
+++	/* Enable MIB counters */
+++	ret = qca8k_mib_init(priv);
+++	if (ret)
+++		dev_warn(priv->dev, "mib init failed");
+++
+++	/* Initial setup of all ports */
+++	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+++		/* Disable forwarding by default on all ports */
+++		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
+++				QCA8K_PORT_LOOKUP_MEMBER, 0);
+++		if (ret)
+++			return ret;
+++
+++		/* Enable QCA header mode on all cpu ports */
+++		if (dsa_is_cpu_port(ds, i)) {
+++			ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(i),
+++					  FIELD_PREP(QCA8K_PORT_HDR_CTRL_TX_MASK, QCA8K_PORT_HDR_CTRL_ALL) |
+++					  FIELD_PREP(QCA8K_PORT_HDR_CTRL_RX_MASK, QCA8K_PORT_HDR_CTRL_ALL));
+++			if (ret) {
+++				dev_err(priv->dev, "failed enabling QCA header mode");
+++				return ret;
+++			}
+++		}
+++
+++		/* Disable MAC by default on all user ports */
+++		if (dsa_is_user_port(ds, i))
+++			qca8k_port_set_status(priv, i, 0);
+++	}
+++
+++	/* Forward all unknown frames to CPU port for Linux processing
+++	 * Notice that in multi-cpu config only one port should be set
+++	 * for igmp, unknown, multicast and broadcast packet
+++	 */
+++	ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1,
+++			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK, BIT(cpu_port)) |
+++			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK, BIT(cpu_port)) |
+++			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_MC_DP_MASK, BIT(cpu_port)) |
+++			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_UC_DP_MASK, BIT(cpu_port)));
+++	if (ret)
+++		return ret;
+++
+++	/* Setup connection between CPU port & user ports
+++	 * Configure specific switch configuration for ports
+++	 */
+++	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+++		/* CPU port gets connected to all user ports of the switch */
+++		if (dsa_is_cpu_port(ds, i)) {
+++			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
+++					QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds));
+++			if (ret)
+++				return ret;
+++		}
+++
+++		/* Individual user ports get connected to CPU port only */
+++		if (dsa_is_user_port(ds, i)) {
+++			ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
+++					QCA8K_PORT_LOOKUP_MEMBER,
+++					BIT(cpu_port));
+++			if (ret)
+++				return ret;
+++
+++			/* Enable ARP Auto-learning by default */
+++			ret = regmap_set_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(i),
+++					      QCA8K_PORT_LOOKUP_LEARN);
+++			if (ret)
+++				return ret;
+++
+++			/* For port based vlans to work we need to set the
+++			 * default egress vid
+++			 */
+++			ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(i),
+++					QCA8K_EGREES_VLAN_PORT_MASK(i),
+++					QCA8K_EGREES_VLAN_PORT(i, QCA8K_PORT_VID_DEF));
+++			if (ret)
+++				return ret;
+++
+++			ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(i),
+++					  QCA8K_PORT_VLAN_CVID(QCA8K_PORT_VID_DEF) |
+++					  QCA8K_PORT_VLAN_SVID(QCA8K_PORT_VID_DEF));
+++			if (ret)
+++				return ret;
+++		}
+++
+++		/* The port 5 of the qca8337 have some problem in flood condition. The
+++		 * original legacy driver had some specific buffer and priority settings
+++		 * for the different port suggested by the QCA switch team. Add this
+++		 * missing settings to improve switch stability under load condition.
+++		 * This problem is limited to qca8337 and other qca8k switch are not affected.
+++		 */
+++		if (priv->switch_id == QCA8K_ID_QCA8337) {
+++			switch (i) {
+++			/* The 2 CPU port and port 5 requires some different
+++			 * priority than any other ports.
+++			 */
+++			case 0:
+++			case 5:
+++			case 6:
+++				mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x4) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x4) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PRI4(0x6) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PRI5(0x8) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PORT(0x1e);
+++				break;
+++			default:
+++				mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x6) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x8) |
+++					QCA8K_PORT_HOL_CTRL0_EG_PORT(0x19);
+++			}
+++			qca8k_write(priv, QCA8K_REG_PORT_HOL_CTRL0(i), mask);
+++
+++			mask = QCA8K_PORT_HOL_CTRL1_ING(0x6) |
+++			QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
+++			QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
+++			QCA8K_PORT_HOL_CTRL1_WRED_EN;
+++			qca8k_rmw(priv, QCA8K_REG_PORT_HOL_CTRL1(i),
+++				  QCA8K_PORT_HOL_CTRL1_ING_BUF_MASK |
+++				  QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
+++				  QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
+++				  QCA8K_PORT_HOL_CTRL1_WRED_EN,
+++				  mask);
+++		}
+++	}
+++
+++	/* Special GLOBAL_FC_THRESH value are needed for ar8327 switch */
+++	if (priv->switch_id == QCA8K_ID_QCA8327) {
+++		mask = QCA8K_GLOBAL_FC_GOL_XON_THRES(288) |
+++		       QCA8K_GLOBAL_FC_GOL_XOFF_THRES(496);
+++		qca8k_rmw(priv, QCA8K_REG_GLOBAL_FC_THRESH,
+++			  QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK |
+++			  QCA8K_GLOBAL_FC_GOL_XOFF_THRES_MASK,
+++			  mask);
+++	}
+++
+++	/* Setup our port MTUs to match power on defaults */
+++	ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, ETH_FRAME_LEN + ETH_FCS_LEN);
+++	if (ret)
+++		dev_warn(priv->dev, "failed setting MTU settings");
+++
+++	/* Flush the FDB table */
+++	qca8k_fdb_flush(priv);
+++
+++	/* We don't have interrupts for link changes, so we need to poll */
+++	ds->pcs_poll = true;
+++
+++	/* Set min a max ageing value supported */
+++	ds->ageing_time_min = 7000;
+++	ds->ageing_time_max = 458745000;
+++
+++	/* Set max number of LAGs supported */
+++	ds->num_lag_ids = QCA8K_NUM_LAGS;
+++
+++	return 0;
+++}
+++
+++static void
+++qca8k_mac_config_setup_internal_delay(struct qca8k_priv *priv, int cpu_port_index,
+++				      u32 reg)
+++{
+++	u32 delay, val = 0;
+++	int ret;
+++
+++	/* Delay can be declared in 3 different way.
+++	 * Mode to rgmii and internal-delay standard binding defined
+++	 * rgmii-id or rgmii-tx/rx phy mode set.
+++	 * The parse logic set a delay different than 0 only when one
+++	 * of the 3 different way is used. In all other case delay is
+++	 * not enabled. With ID or TX/RXID delay is enabled and set
+++	 * to the default and recommended value.
+++	 */
+++	if (priv->ports_config.rgmii_tx_delay[cpu_port_index]) {
+++		delay = priv->ports_config.rgmii_tx_delay[cpu_port_index];
+++
+++		val |= QCA8K_PORT_PAD_RGMII_TX_DELAY(delay) |
+++			QCA8K_PORT_PAD_RGMII_TX_DELAY_EN;
+++	}
+++
+++	if (priv->ports_config.rgmii_rx_delay[cpu_port_index]) {
+++		delay = priv->ports_config.rgmii_rx_delay[cpu_port_index];
+++
+++		val |= QCA8K_PORT_PAD_RGMII_RX_DELAY(delay) |
+++			QCA8K_PORT_PAD_RGMII_RX_DELAY_EN;
+++	}
+++
+++	/* Set RGMII delay based on the selected values */
+++	ret = qca8k_rmw(priv, reg,
+++			QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK |
+++			QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK |
+++			QCA8K_PORT_PAD_RGMII_TX_DELAY_EN |
+++			QCA8K_PORT_PAD_RGMII_RX_DELAY_EN,
+++			val);
+++	if (ret)
+++		dev_err(priv->dev, "Failed to set internal delay for CPU port%d",
+++			cpu_port_index == QCA8K_CPU_PORT0 ? 0 : 6);
+++}
+++
+++static void
+++qca8k_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode,
+++			 const struct phylink_link_state *state)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int cpu_port_index, ret;
+++	u32 reg, val;
+++
+++	switch (port) {
+++	case 0: /* 1st CPU port */
+++		if (state->interface != PHY_INTERFACE_MODE_RGMII &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
+++		    state->interface != PHY_INTERFACE_MODE_SGMII)
+++			return;
+++
+++		reg = QCA8K_REG_PORT0_PAD_CTRL;
+++		cpu_port_index = QCA8K_CPU_PORT0;
+++		break;
+++	case 1:
+++	case 2:
+++	case 3:
+++	case 4:
+++	case 5:
+++		/* Internal PHY, nothing to do */
+++		return;
+++	case 6: /* 2nd CPU port / external PHY */
+++		if (state->interface != PHY_INTERFACE_MODE_RGMII &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
+++		    state->interface != PHY_INTERFACE_MODE_SGMII &&
+++		    state->interface != PHY_INTERFACE_MODE_1000BASEX)
+++			return;
+++
+++		reg = QCA8K_REG_PORT6_PAD_CTRL;
+++		cpu_port_index = QCA8K_CPU_PORT6;
+++		break;
+++	default:
+++		dev_err(ds->dev, "%s: unsupported port: %i\n", __func__, port);
+++		return;
+++	}
+++
+++	if (port != 6 && phylink_autoneg_inband(mode)) {
+++		dev_err(ds->dev, "%s: in-band negotiation unsupported\n",
+++			__func__);
+++		return;
+++	}
+++
+++	switch (state->interface) {
+++	case PHY_INTERFACE_MODE_RGMII:
+++	case PHY_INTERFACE_MODE_RGMII_ID:
+++	case PHY_INTERFACE_MODE_RGMII_TXID:
+++	case PHY_INTERFACE_MODE_RGMII_RXID:
+++		qca8k_write(priv, reg, QCA8K_PORT_PAD_RGMII_EN);
+++
+++		/* Configure rgmii delay */
+++		qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg);
+++
+++		/* QCA8337 requires to set rgmii rx delay for all ports.
+++		 * This is enabled through PORT5_PAD_CTRL for all ports,
+++		 * rather than individual port registers.
+++		 */
+++		if (priv->switch_id == QCA8K_ID_QCA8337)
+++			qca8k_write(priv, QCA8K_REG_PORT5_PAD_CTRL,
+++				    QCA8K_PORT_PAD_RGMII_RX_DELAY_EN);
+++		break;
+++	case PHY_INTERFACE_MODE_SGMII:
+++	case PHY_INTERFACE_MODE_1000BASEX:
+++		/* Enable SGMII on the port */
+++		qca8k_write(priv, reg, QCA8K_PORT_PAD_SGMII_EN);
+++
+++		/* Enable/disable SerDes auto-negotiation as necessary */
+++		ret = qca8k_read(priv, QCA8K_REG_PWS, &val);
+++		if (ret)
+++			return;
+++		if (phylink_autoneg_inband(mode))
+++			val &= ~QCA8K_PWS_SERDES_AEN_DIS;
+++		else
+++			val |= QCA8K_PWS_SERDES_AEN_DIS;
+++		qca8k_write(priv, QCA8K_REG_PWS, val);
+++
+++		/* Configure the SGMII parameters */
+++		ret = qca8k_read(priv, QCA8K_REG_SGMII_CTRL, &val);
+++		if (ret)
+++			return;
+++
+++		val |= QCA8K_SGMII_EN_SD;
+++
+++		if (priv->ports_config.sgmii_enable_pll)
+++			val |= QCA8K_SGMII_EN_PLL | QCA8K_SGMII_EN_RX |
+++			       QCA8K_SGMII_EN_TX;
+++
+++		if (dsa_is_cpu_port(ds, port)) {
+++			/* CPU port, we're talking to the CPU MAC, be a PHY */
+++			val &= ~QCA8K_SGMII_MODE_CTRL_MASK;
+++			val |= QCA8K_SGMII_MODE_CTRL_PHY;
+++		} else if (state->interface == PHY_INTERFACE_MODE_SGMII) {
+++			val &= ~QCA8K_SGMII_MODE_CTRL_MASK;
+++			val |= QCA8K_SGMII_MODE_CTRL_MAC;
+++		} else if (state->interface == PHY_INTERFACE_MODE_1000BASEX) {
+++			val &= ~QCA8K_SGMII_MODE_CTRL_MASK;
+++			val |= QCA8K_SGMII_MODE_CTRL_BASEX;
+++		}
+++
+++		qca8k_write(priv, QCA8K_REG_SGMII_CTRL, val);
+++
+++		/* From original code is reported port instability as SGMII also
+++		 * require delay set. Apply advised values here or take them from DT.
+++		 */
+++		if (state->interface == PHY_INTERFACE_MODE_SGMII)
+++			qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg);
+++
+++		/* For qca8327/qca8328/qca8334/qca8338 sgmii is unique and
+++		 * falling edge is set writing in the PORT0 PAD reg
+++		 */
+++		if (priv->switch_id == QCA8K_ID_QCA8327 ||
+++		    priv->switch_id == QCA8K_ID_QCA8337)
+++			reg = QCA8K_REG_PORT0_PAD_CTRL;
+++
+++		val = 0;
+++
+++		/* SGMII Clock phase configuration */
+++		if (priv->ports_config.sgmii_rx_clk_falling_edge)
+++			val |= QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE;
+++
+++		if (priv->ports_config.sgmii_tx_clk_falling_edge)
+++			val |= QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE;
+++
+++		if (val)
+++			ret = qca8k_rmw(priv, reg,
+++					QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE |
+++					QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE,
+++					val);
+++
+++		break;
+++	default:
+++		dev_err(ds->dev, "xMII mode %s not supported for port %d\n",
+++			phy_modes(state->interface), port);
+++		return;
+++	}
+++}
+++
+++static void
+++qca8k_phylink_validate(struct dsa_switch *ds, int port,
+++		       unsigned long *supported,
+++		       struct phylink_link_state *state)
+++{
+++	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
+++
+++	switch (port) {
+++	case 0: /* 1st CPU port */
+++		if (state->interface != PHY_INTERFACE_MODE_NA &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
+++		    state->interface != PHY_INTERFACE_MODE_SGMII)
+++			goto unsupported;
+++		break;
+++	case 1:
+++	case 2:
+++	case 3:
+++	case 4:
+++	case 5:
+++		/* Internal PHY */
+++		if (state->interface != PHY_INTERFACE_MODE_NA &&
+++		    state->interface != PHY_INTERFACE_MODE_GMII &&
+++		    state->interface != PHY_INTERFACE_MODE_INTERNAL)
+++			goto unsupported;
+++		break;
+++	case 6: /* 2nd CPU port / external PHY */
+++		if (state->interface != PHY_INTERFACE_MODE_NA &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
+++		    state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
+++		    state->interface != PHY_INTERFACE_MODE_SGMII &&
+++		    state->interface != PHY_INTERFACE_MODE_1000BASEX)
+++			goto unsupported;
+++		break;
+++	default:
+++unsupported:
+++		linkmode_zero(supported);
+++		return;
+++	}
+++
+++	phylink_set_port_modes(mask);
+++	phylink_set(mask, Autoneg);
+++
+++	phylink_set(mask, 1000baseT_Full);
+++	phylink_set(mask, 10baseT_Half);
+++	phylink_set(mask, 10baseT_Full);
+++	phylink_set(mask, 100baseT_Half);
+++	phylink_set(mask, 100baseT_Full);
+++
+++	if (state->interface == PHY_INTERFACE_MODE_1000BASEX)
+++		phylink_set(mask, 1000baseX_Full);
+++
+++	phylink_set(mask, Pause);
+++	phylink_set(mask, Asym_Pause);
+++
+++	linkmode_and(supported, supported, mask);
+++	linkmode_and(state->advertising, state->advertising, mask);
+++}
+++
+++static int
+++qca8k_phylink_mac_link_state(struct dsa_switch *ds, int port,
+++			     struct phylink_link_state *state)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	u32 reg;
+++	int ret;
+++
+++	ret = qca8k_read(priv, QCA8K_REG_PORT_STATUS(port), &reg);
+++	if (ret < 0)
+++		return ret;
+++
+++	state->link = !!(reg & QCA8K_PORT_STATUS_LINK_UP);
+++	state->an_complete = state->link;
+++	state->an_enabled = !!(reg & QCA8K_PORT_STATUS_LINK_AUTO);
+++	state->duplex = (reg & QCA8K_PORT_STATUS_DUPLEX) ? DUPLEX_FULL :
+++							   DUPLEX_HALF;
+++
+++	switch (reg & QCA8K_PORT_STATUS_SPEED) {
+++	case QCA8K_PORT_STATUS_SPEED_10:
+++		state->speed = SPEED_10;
+++		break;
+++	case QCA8K_PORT_STATUS_SPEED_100:
+++		state->speed = SPEED_100;
+++		break;
+++	case QCA8K_PORT_STATUS_SPEED_1000:
+++		state->speed = SPEED_1000;
+++		break;
+++	default:
+++		state->speed = SPEED_UNKNOWN;
+++		break;
+++	}
+++
+++	state->pause = MLO_PAUSE_NONE;
+++	if (reg & QCA8K_PORT_STATUS_RXFLOW)
+++		state->pause |= MLO_PAUSE_RX;
+++	if (reg & QCA8K_PORT_STATUS_TXFLOW)
+++		state->pause |= MLO_PAUSE_TX;
+++
+++	return 1;
+++}
+++
+++static void
+++qca8k_phylink_mac_link_down(struct dsa_switch *ds, int port, unsigned int mode,
+++			    phy_interface_t interface)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++
+++	qca8k_port_set_status(priv, port, 0);
+++}
+++
+++static void
+++qca8k_phylink_mac_link_up(struct dsa_switch *ds, int port, unsigned int mode,
+++			  phy_interface_t interface, struct phy_device *phydev,
+++			  int speed, int duplex, bool tx_pause, bool rx_pause)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	u32 reg;
+++
+++	if (phylink_autoneg_inband(mode)) {
+++		reg = QCA8K_PORT_STATUS_LINK_AUTO;
+++	} else {
+++		switch (speed) {
+++		case SPEED_10:
+++			reg = QCA8K_PORT_STATUS_SPEED_10;
+++			break;
+++		case SPEED_100:
+++			reg = QCA8K_PORT_STATUS_SPEED_100;
+++			break;
+++		case SPEED_1000:
+++			reg = QCA8K_PORT_STATUS_SPEED_1000;
+++			break;
+++		default:
+++			reg = QCA8K_PORT_STATUS_LINK_AUTO;
+++			break;
+++		}
+++
+++		if (duplex == DUPLEX_FULL)
+++			reg |= QCA8K_PORT_STATUS_DUPLEX;
+++
+++		if (rx_pause || dsa_is_cpu_port(ds, port))
+++			reg |= QCA8K_PORT_STATUS_RXFLOW;
+++
+++		if (tx_pause || dsa_is_cpu_port(ds, port))
+++			reg |= QCA8K_PORT_STATUS_TXFLOW;
+++	}
+++
+++	reg |= QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
+++
+++	qca8k_write(priv, QCA8K_REG_PORT_STATUS(port), reg);
+++}
+++
+++static void
+++qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int i;
+++
+++	if (stringset != ETH_SS_STATS)
+++		return;
+++
+++	for (i = 0; i < priv->info->mib_count; i++)
+++		strncpy(data + i * ETH_GSTRING_LEN, ar8327_mib[i].name,
+++			ETH_GSTRING_LEN);
+++}
+++
+++static void qca8k_mib_autocast_handler(struct dsa_switch *ds, struct sk_buff *skb)
+++{
+++	struct qca8k_mib_eth_data *mib_eth_data;
+++	struct qca8k_priv *priv = ds->priv;
+++	const struct qca8k_mib_desc *mib;
+++	struct mib_ethhdr *mib_ethhdr;
+++	int i, mib_len, offset = 0;
+++	u64 *data;
+++	u8 port;
+++
+++	mib_ethhdr = (struct mib_ethhdr *)skb_mac_header(skb);
+++	mib_eth_data = &priv->mib_eth_data;
+++
+++	/* The switch autocast every port. Ignore other packet and
+++	 * parse only the requested one.
+++	 */
+++	port = FIELD_GET(QCA_HDR_RECV_SOURCE_PORT, ntohs(mib_ethhdr->hdr));
+++	if (port != mib_eth_data->req_port)
+++		goto exit;
+++
+++	data = mib_eth_data->data;
+++
+++	for (i = 0; i < priv->info->mib_count; i++) {
+++		mib = &ar8327_mib[i];
+++
+++		/* First 3 mib are present in the skb head */
+++		if (i < 3) {
+++			data[i] = mib_ethhdr->data[i];
+++			continue;
+++		}
+++
+++		mib_len = sizeof(uint32_t);
+++
+++		/* Some mib are 64 bit wide */
+++		if (mib->size == 2)
+++			mib_len = sizeof(uint64_t);
+++
+++		/* Copy the mib value from packet to the */
+++		memcpy(data + i, skb->data + offset, mib_len);
+++
+++		/* Set the offset for the next mib */
+++		offset += mib_len;
+++	}
+++
+++exit:
+++	/* Complete on receiving all the mib packet */
+++	if (refcount_dec_and_test(&mib_eth_data->port_parsed))
+++		complete(&mib_eth_data->rw_done);
+++}
+++
+++static int
+++qca8k_get_ethtool_stats_eth(struct dsa_switch *ds, int port, u64 *data)
+++{
+++	struct dsa_port *dp = dsa_to_port(ds, port);
+++	struct qca8k_mib_eth_data *mib_eth_data;
+++	struct qca8k_priv *priv = ds->priv;
+++	int ret;
+++
+++	mib_eth_data = &priv->mib_eth_data;
+++
+++	mutex_lock(&mib_eth_data->mutex);
+++
+++	reinit_completion(&mib_eth_data->rw_done);
+++
+++	mib_eth_data->req_port = dp->index;
+++	mib_eth_data->data = data;
+++	refcount_set(&mib_eth_data->port_parsed, QCA8K_NUM_PORTS);
+++
+++	mutex_lock(&priv->reg_mutex);
+++
+++	/* Send mib autocast request */
+++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB,
+++				 QCA8K_MIB_FUNC | QCA8K_MIB_BUSY,
+++				 FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_CAST) |
+++				 QCA8K_MIB_BUSY);
+++
+++	mutex_unlock(&priv->reg_mutex);
+++
+++	if (ret)
+++		goto exit;
+++
+++	ret = wait_for_completion_timeout(&mib_eth_data->rw_done, QCA8K_ETHERNET_TIMEOUT);
+++
+++exit:
+++	mutex_unlock(&mib_eth_data->mutex);
+++
+++	return ret;
+++}
+++
+++static void
+++qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
+++			uint64_t *data)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	const struct qca8k_mib_desc *mib;
+++	u32 reg, i, val;
+++	u32 hi = 0;
+++	int ret;
+++
+++	if (priv->mgmt_master && priv->info->ops->autocast_mib &&
+++	    priv->info->ops->autocast_mib(ds, port, data) > 0)
+++		return;
+++
+++	for (i = 0; i < priv->info->mib_count; i++) {
+++		mib = &ar8327_mib[i];
+++		reg = QCA8K_PORT_MIB_COUNTER(port) + mib->offset;
+++
+++		ret = qca8k_read(priv, reg, &val);
+++		if (ret < 0)
+++			continue;
+++
+++		if (mib->size == 2) {
+++			ret = qca8k_read(priv, reg + 4, &hi);
+++			if (ret < 0)
+++				continue;
+++		}
+++
+++		data[i] = val;
+++		if (mib->size == 2)
+++			data[i] |= (u64)hi << 32;
+++	}
+++}
+++
+++static int
+++qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++
+++	if (sset != ETH_SS_STATS)
+++		return 0;
+++
+++	return priv->info->mib_count;
+++}
+++
+++static int
+++qca8k_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *eee)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	u32 lpi_en = QCA8K_REG_EEE_CTRL_LPI_EN(port);
+++	u32 reg;
+++	int ret;
+++
+++	mutex_lock(&priv->reg_mutex);
+++	ret = qca8k_read(priv, QCA8K_REG_EEE_CTRL, &reg);
+++	if (ret < 0)
+++		goto exit;
+++
+++	if (eee->eee_enabled)
+++		reg |= lpi_en;
+++	else
+++		reg &= ~lpi_en;
+++	ret = qca8k_write(priv, QCA8K_REG_EEE_CTRL, reg);
+++
+++exit:
+++	mutex_unlock(&priv->reg_mutex);
+++	return ret;
+++}
+++
+++static int
+++qca8k_get_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e)
+++{
+++	/* Nothing to do on the port's MAC */
+++	return 0;
+++}
+++
+++static void
+++qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	u32 stp_state;
+++
+++	switch (state) {
+++	case BR_STATE_DISABLED:
+++		stp_state = QCA8K_PORT_LOOKUP_STATE_DISABLED;
+++		break;
+++	case BR_STATE_BLOCKING:
+++		stp_state = QCA8K_PORT_LOOKUP_STATE_BLOCKING;
+++		break;
+++	case BR_STATE_LISTENING:
+++		stp_state = QCA8K_PORT_LOOKUP_STATE_LISTENING;
+++		break;
+++	case BR_STATE_LEARNING:
+++		stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING;
+++		break;
+++	case BR_STATE_FORWARDING:
+++	default:
+++		stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD;
+++		break;
+++	}
+++
+++	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+++		  QCA8K_PORT_LOOKUP_STATE_MASK, stp_state);
+++}
+++
+++static int
+++qca8k_port_bridge_join(struct dsa_switch *ds, int port, struct net_device *br)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	int port_mask, cpu_port;
+++	int i, ret;
+++
+++	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
+++	port_mask = BIT(cpu_port);
+++
+++	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+++		if (dsa_is_cpu_port(ds, i))
+++			continue;
+++		if (dsa_to_port(ds, i)->bridge_dev != br)
+++			continue;
+++		/* Add this port to the portvlan mask of the other ports
+++		 * in the bridge
+++		 */
+++		ret = regmap_set_bits(priv->regmap,
+++				      QCA8K_PORT_LOOKUP_CTRL(i),
+++				      BIT(port));
+++		if (ret)
+++			return ret;
+++		if (i != port)
+++			port_mask |= BIT(i);
+++	}
+++
+++	/* Add all other ports to this ports portvlan mask */
+++	ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+++			QCA8K_PORT_LOOKUP_MEMBER, port_mask);
+++
+++	return ret;
+++}
+++
+++static void
+++qca8k_port_bridge_leave(struct dsa_switch *ds, int port, struct net_device *br)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	int cpu_port, i;
+++
+++	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
+++
+++	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+++		if (dsa_is_cpu_port(ds, i))
+++			continue;
+++		if (dsa_to_port(ds, i)->bridge_dev != br)
+++			continue;
+++		/* Remove this port to the portvlan mask of the other ports
+++		 * in the bridge
+++		 */
+++		regmap_clear_bits(priv->regmap,
+++				  QCA8K_PORT_LOOKUP_CTRL(i),
+++				  BIT(port));
+++	}
+++
+++	/* Set the cpu port to be the only one in the portvlan mask of
+++	 * this port
+++	 */
+++	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+++		  QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port));
+++}
+++
+++static void
+++qca8k_port_fast_age(struct dsa_switch *ds, int port)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++
+++	mutex_lock(&priv->reg_mutex);
+++	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port);
+++	mutex_unlock(&priv->reg_mutex);
+++}
+++
+++static int
+++qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	unsigned int secs = msecs / 1000;
+++	u32 val;
+++
+++	/* AGE_TIME reg is set in 7s step */
+++	val = secs / 7;
+++
+++	/* Handle case with 0 as val to NOT disable
+++	 * learning
+++	 */
+++	if (!val)
+++		val = 1;
+++
+++	return regmap_update_bits(priv->regmap, QCA8K_REG_ATU_CTRL, QCA8K_ATU_AGE_TIME_MASK,
+++				  QCA8K_ATU_AGE_TIME(val));
+++}
+++
+++static int
+++qca8k_port_enable(struct dsa_switch *ds, int port,
+++		  struct phy_device *phy)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++
+++	qca8k_port_set_status(priv, port, 1);
+++	priv->port_enabled_map |= BIT(port);
+++
+++	if (dsa_is_user_port(ds, port))
+++		phy_support_asym_pause(phy);
+++
+++	return 0;
+++}
+++
+++static void
+++qca8k_port_disable(struct dsa_switch *ds, int port)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++
+++	qca8k_port_set_status(priv, port, 0);
+++	priv->port_enabled_map &= ~BIT(port);
+++}
+++
+++static int
+++qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int ret;
+++
+++	/* We have only have a general MTU setting.
+++	 * DSA always set the CPU port's MTU to the largest MTU of the slave
+++	 * ports.
+++	 * Setting MTU just for the CPU port is sufficient to correctly set a
+++	 * value for every port.
+++	 */
+++	if (!dsa_is_cpu_port(ds, port))
+++		return 0;
+++
+++	/* To change the MAX_FRAME_SIZE the cpu ports must be off or
+++	 * the switch panics.
+++	 * Turn off both cpu ports before applying the new value to prevent
+++	 * this.
+++	 */
+++	if (priv->port_enabled_map & BIT(0))
+++		qca8k_port_set_status(priv, 0, 0);
+++
+++	if (priv->port_enabled_map & BIT(6))
+++		qca8k_port_set_status(priv, 6, 0);
+++
+++	/* Include L2 header / FCS length */
+++	ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, new_mtu + ETH_HLEN + ETH_FCS_LEN);
+++
+++	if (priv->port_enabled_map & BIT(0))
+++		qca8k_port_set_status(priv, 0, 1);
+++
+++	if (priv->port_enabled_map & BIT(6))
+++		qca8k_port_set_status(priv, 6, 1);
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_port_max_mtu(struct dsa_switch *ds, int port)
+++{
+++	return QCA8K_MAX_MTU;
+++}
+++
+++static int
+++qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
+++		      u16 port_mask, u16 vid)
+++{
+++	/* Set the vid to the port vlan id if no vid is set */
+++	if (!vid)
+++		vid = QCA8K_PORT_VID_DEF;
+++
+++	return qca8k_fdb_add(priv, addr, port_mask, vid,
+++			     QCA8K_ATU_STATUS_STATIC);
+++}
+++
+++static int
+++qca8k_port_fdb_add(struct dsa_switch *ds, int port,
+++		   const unsigned char *addr, u16 vid)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	u16 port_mask = BIT(port);
+++
+++	return qca8k_port_fdb_insert(priv, addr, port_mask, vid);
+++}
+++
+++static int
+++qca8k_port_fdb_del(struct dsa_switch *ds, int port,
+++		   const unsigned char *addr, u16 vid)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	u16 port_mask = BIT(port);
+++
+++	if (!vid)
+++		vid = QCA8K_PORT_VID_DEF;
+++
+++	return qca8k_fdb_del(priv, addr, port_mask, vid);
+++}
+++
+++static int
+++qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
+++		    dsa_fdb_dump_cb_t *cb, void *data)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	struct qca8k_fdb _fdb = { 0 };
+++	int cnt = QCA8K_NUM_FDB_RECORDS;
+++	bool is_static;
+++	int ret = 0;
+++
+++	mutex_lock(&priv->reg_mutex);
+++	while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port)) {
+++		if (!_fdb.aging)
+++			break;
+++		is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC);
+++		ret = cb(_fdb.mac, _fdb.vid, is_static, data);
+++		if (ret)
+++			break;
+++	}
+++	mutex_unlock(&priv->reg_mutex);
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_port_mdb_add(struct dsa_switch *ds, int port,
+++		   const struct switchdev_obj_port_mdb *mdb)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	const u8 *addr = mdb->addr;
+++	u16 vid = mdb->vid;
+++
+++	return qca8k_fdb_search_and_insert(priv, BIT(port), addr, vid);
+++}
+++
+++static int
+++qca8k_port_mdb_del(struct dsa_switch *ds, int port,
+++		   const struct switchdev_obj_port_mdb *mdb)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	const u8 *addr = mdb->addr;
+++	u16 vid = mdb->vid;
+++
+++	return qca8k_fdb_search_and_del(priv, BIT(port), addr, vid);
+++}
+++
+++static int
+++qca8k_port_mirror_add(struct dsa_switch *ds, int port,
+++		      struct dsa_mall_mirror_tc_entry *mirror,
+++		      bool ingress)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int monitor_port, ret;
+++	u32 reg, val;
+++
+++	/* Check for existent entry */
+++	if ((ingress ? priv->mirror_rx : priv->mirror_tx) & BIT(port))
+++		return -EEXIST;
+++
+++	ret = regmap_read(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0, &val);
+++	if (ret)
+++		return ret;
+++
+++	/* QCA83xx can have only one port set to mirror mode.
+++	 * Check that the correct port is requested and return error otherwise.
+++	 * When no mirror port is set, the values is set to 0xF
+++	 */
+++	monitor_port = FIELD_GET(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
+++	if (monitor_port != 0xF && monitor_port != mirror->to_local_port)
+++		return -EEXIST;
+++
+++	/* Set the monitor port */
+++	val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM,
+++			 mirror->to_local_port);
+++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
+++				 QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
+++	if (ret)
+++		return ret;
+++
+++	if (ingress) {
+++		reg = QCA8K_PORT_LOOKUP_CTRL(port);
+++		val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN;
+++	} else {
+++		reg = QCA8K_REG_PORT_HOL_CTRL1(port);
+++		val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN;
+++	}
+++
+++	ret = regmap_update_bits(priv->regmap, reg, val, val);
+++	if (ret)
+++		return ret;
+++
+++	/* Track mirror port for tx and rx to decide when the
+++	 * mirror port has to be disabled.
+++	 */
+++	if (ingress)
+++		priv->mirror_rx |= BIT(port);
+++	else
+++		priv->mirror_tx |= BIT(port);
+++
+++	return 0;
+++}
+++
+++static void
+++qca8k_port_mirror_del(struct dsa_switch *ds, int port,
+++		      struct dsa_mall_mirror_tc_entry *mirror)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	u32 reg, val;
+++	int ret;
+++
+++	if (mirror->ingress) {
+++		reg = QCA8K_PORT_LOOKUP_CTRL(port);
+++		val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN;
+++	} else {
+++		reg = QCA8K_REG_PORT_HOL_CTRL1(port);
+++		val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN;
+++	}
+++
+++	ret = regmap_clear_bits(priv->regmap, reg, val);
+++	if (ret)
+++		goto err;
+++
+++	if (mirror->ingress)
+++		priv->mirror_rx &= ~BIT(port);
+++	else
+++		priv->mirror_tx &= ~BIT(port);
+++
+++	/* No port set to send packet to mirror port. Disable mirror port */
+++	if (!priv->mirror_rx && !priv->mirror_tx) {
+++		val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, 0xF);
+++		ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
+++					 QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
+++		if (ret)
+++			goto err;
+++	}
+++err:
+++	dev_err(priv->dev, "Failed to del mirror port from %d", port);
+++}
+++
+++static int
+++qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
+++			  struct netlink_ext_ack *extack)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int ret;
+++
+++	if (vlan_filtering) {
+++		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+++				QCA8K_PORT_LOOKUP_VLAN_MODE_MASK,
+++				QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE);
+++	} else {
+++		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+++				QCA8K_PORT_LOOKUP_VLAN_MODE_MASK,
+++				QCA8K_PORT_LOOKUP_VLAN_MODE_NONE);
+++	}
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_port_vlan_add(struct dsa_switch *ds, int port,
+++		    const struct switchdev_obj_port_vlan *vlan,
+++		    struct netlink_ext_ack *extack)
+++{
+++	bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
+++	bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
+++	struct qca8k_priv *priv = ds->priv;
+++	int ret;
+++
+++	ret = qca8k_vlan_add(priv, port, vlan->vid, untagged);
+++	if (ret) {
+++		dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port, ret);
+++		return ret;
+++	}
+++
+++	if (pvid) {
+++		ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port),
+++				QCA8K_EGREES_VLAN_PORT_MASK(port),
+++				QCA8K_EGREES_VLAN_PORT(port, vlan->vid));
+++		if (ret)
+++			return ret;
+++
+++		ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port),
+++				  QCA8K_PORT_VLAN_CVID(vlan->vid) |
+++				  QCA8K_PORT_VLAN_SVID(vlan->vid));
+++	}
+++
+++	return ret;
+++}
+++
+++static int
+++qca8k_port_vlan_del(struct dsa_switch *ds, int port,
+++		    const struct switchdev_obj_port_vlan *vlan)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int ret;
+++
+++	ret = qca8k_vlan_del(priv, port, vlan->vid);
+++	if (ret)
+++		dev_err(priv->dev, "Failed to delete VLAN from port %d (%d)", port, ret);
+++
+++	return ret;
+++}
+++
+++static u32 qca8k_get_phy_flags(struct dsa_switch *ds, int port)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++
+++	/* Communicate to the phy internal driver the switch revision.
+++	 * Based on the switch revision different values needs to be
+++	 * set to the dbg and mmd reg on the phy.
+++	 * The first 2 bit are used to communicate the switch revision
+++	 * to the phy driver.
+++	 */
+++	if (port > 0 && port < 6)
+++		return priv->switch_revision;
+++
+++	return 0;
+++}
+++
+++static enum dsa_tag_protocol
+++qca8k_get_tag_protocol(struct dsa_switch *ds, int port,
+++		       enum dsa_tag_protocol mp)
+++{
+++	return DSA_TAG_PROTO_QCA;
+++}
+++
+++static bool
+++qca8k_lag_can_offload(struct dsa_switch *ds,
+++		      struct net_device *lag,
+++		      struct netdev_lag_upper_info *info)
+++{
+++	struct dsa_port *dp;
+++	int id, members = 0;
+++
+++	id = dsa_lag_id(ds->dst, lag);
+++	if (id < 0 || id >= ds->num_lag_ids)
+++		return false;
+++
+++	dsa_lag_foreach_port(dp, ds->dst, lag)
+++		/* Includes the port joining the LAG */
+++		members++;
+++
+++	if (members > QCA8K_NUM_PORTS_FOR_LAG)
+++		return false;
+++
+++	if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH)
+++		return false;
+++
+++	if (info->hash_type != NETDEV_LAG_HASH_L2 &&
+++	    info->hash_type != NETDEV_LAG_HASH_L23)
+++		return false;
+++
+++	return true;
+++}
+++
+++static int
+++qca8k_lag_setup_hash(struct dsa_switch *ds,
+++		     struct net_device *lag,
+++		     struct netdev_lag_upper_info *info)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	bool unique_lag = true;
+++	u32 hash = 0;
+++	int i, id;
+++
+++	id = dsa_lag_id(ds->dst, lag);
+++
+++	switch (info->hash_type) {
+++	case NETDEV_LAG_HASH_L23:
+++		hash |= QCA8K_TRUNK_HASH_SIP_EN;
+++		hash |= QCA8K_TRUNK_HASH_DIP_EN;
+++		fallthrough;
+++	case NETDEV_LAG_HASH_L2:
+++		hash |= QCA8K_TRUNK_HASH_SA_EN;
+++		hash |= QCA8K_TRUNK_HASH_DA_EN;
+++		break;
+++	default: /* We should NEVER reach this */
+++		return -EOPNOTSUPP;
+++	}
+++
+++	/* Check if we are the unique configured LAG */
+++	dsa_lags_foreach_id(i, ds->dst)
+++		if (i != id && dsa_lag_dev(ds->dst, i)) {
+++			unique_lag = false;
+++			break;
+++		}
+++
+++	/* Hash Mode is global. Make sure the same Hash Mode
+++	 * is set to all the 4 possible lag.
+++	 * If we are the unique LAG we can set whatever hash
+++	 * mode we want.
+++	 * To change hash mode it's needed to remove all LAG
+++	 * and change the mode with the latest.
+++	 */
+++	if (unique_lag) {
+++		priv->lag_hash_mode = hash;
+++	} else if (priv->lag_hash_mode != hash) {
+++		netdev_err(lag, "Error: Mismateched Hash Mode across different lag is not supported\n");
+++		return -EOPNOTSUPP;
+++	}
+++
+++	return regmap_update_bits(priv->regmap, QCA8K_TRUNK_HASH_EN_CTRL,
+++				  QCA8K_TRUNK_HASH_MASK, hash);
+++}
+++
+++static int
+++qca8k_lag_refresh_portmap(struct dsa_switch *ds, int port,
+++			  struct net_device *lag, bool delete)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int ret, id, i;
+++	u32 val;
+++
+++	id = dsa_lag_id(ds->dst, lag);
+++
+++	/* Read current port member */
+++	ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0, &val);
+++	if (ret)
+++		return ret;
+++
+++	/* Shift val to the correct trunk */
+++	val >>= QCA8K_REG_GOL_TRUNK_SHIFT(id);
+++	val &= QCA8K_REG_GOL_TRUNK_MEMBER_MASK;
+++	if (delete)
+++		val &= ~BIT(port);
+++	else
+++		val |= BIT(port);
+++
+++	/* Update port member. With empty portmap disable trunk */
+++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0,
+++				 QCA8K_REG_GOL_TRUNK_MEMBER(id) |
+++				 QCA8K_REG_GOL_TRUNK_EN(id),
+++				 !val << QCA8K_REG_GOL_TRUNK_SHIFT(id) |
+++				 val << QCA8K_REG_GOL_TRUNK_SHIFT(id));
+++
+++	/* Search empty member if adding or port on deleting */
+++	for (i = 0; i < QCA8K_NUM_PORTS_FOR_LAG; i++) {
+++		ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id), &val);
+++		if (ret)
+++			return ret;
+++
+++		val >>= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i);
+++		val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_MASK;
+++
+++		if (delete) {
+++			/* If port flagged to be disabled assume this member is
+++			 * empty
+++			 */
+++			if (val != QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
+++				continue;
+++
+++			val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK;
+++			if (val != port)
+++				continue;
+++		} else {
+++			/* If port flagged to be enabled assume this member is
+++			 * already set
+++			 */
+++			if (val == QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
+++				continue;
+++		}
+++
+++		/* We have found the member to add/remove */
+++		break;
+++	}
+++
+++	/* Set port in the correct port mask or disable port if in delete mode */
+++	return regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id),
+++				  QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN(id, i) |
+++				  QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT(id, i),
+++				  !delete << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i) |
+++				  port << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i));
+++}
+++
+++static int
+++qca8k_port_lag_join(struct dsa_switch *ds, int port,
+++		    struct net_device *lag,
+++		    struct netdev_lag_upper_info *info)
+++{
+++	int ret;
+++
+++	if (!qca8k_lag_can_offload(ds, lag, info))
+++		return -EOPNOTSUPP;
+++
+++	ret = qca8k_lag_setup_hash(ds, lag, info);
+++	if (ret)
+++		return ret;
+++
+++	return qca8k_lag_refresh_portmap(ds, port, lag, false);
+++}
+++
+++static int
+++qca8k_port_lag_leave(struct dsa_switch *ds, int port,
+++		     struct net_device *lag)
+++{
+++	return qca8k_lag_refresh_portmap(ds, port, lag, true);
+++}
+++
+++static void
+++qca8k_master_change(struct dsa_switch *ds, const struct net_device *master,
+++		    bool operational)
+++{
+++	struct dsa_port *dp = master->dsa_ptr;
+++	struct qca8k_priv *priv = ds->priv;
+++
+++	/* Ethernet MIB/MDIO is only supported for CPU port 0 */
+++	if (dp->index != 0)
+++		return;
+++
+++	mutex_lock(&priv->mgmt_eth_data.mutex);
+++	mutex_lock(&priv->mib_eth_data.mutex);
+++
+++	priv->mgmt_master = operational ? (struct net_device *)master : NULL;
+++
+++	mutex_unlock(&priv->mib_eth_data.mutex);
+++	mutex_unlock(&priv->mgmt_eth_data.mutex);
+++}
+++
+++static int qca8k_connect_tag_protocol(struct dsa_switch *ds,
+++				      enum dsa_tag_protocol proto)
+++{
+++	struct qca_tagger_data *tagger_data;
+++
+++	switch (proto) {
+++	case DSA_TAG_PROTO_QCA:
+++		tagger_data = ds->tagger_data;
+++
+++		tagger_data->rw_reg_ack_handler = qca8k_rw_reg_ack_handler;
+++		tagger_data->mib_autocast_handler = qca8k_mib_autocast_handler;
+++
+++		break;
+++	default:
+++		return -EOPNOTSUPP;
+++	}
+++
+++	return 0;
+++}
+++
+++static const struct dsa_switch_ops qca8k_switch_ops = {
+++	.get_tag_protocol	= qca8k_get_tag_protocol,
+++	.setup			= qca8k_setup,
+++	.get_strings		= qca8k_get_strings,
+++	.get_ethtool_stats	= qca8k_get_ethtool_stats,
+++	.get_sset_count		= qca8k_get_sset_count,
+++	.set_ageing_time	= qca8k_set_ageing_time,
+++	.get_mac_eee		= qca8k_get_mac_eee,
+++	.set_mac_eee		= qca8k_set_mac_eee,
+++	.port_enable		= qca8k_port_enable,
+++	.port_disable		= qca8k_port_disable,
+++	.port_change_mtu	= qca8k_port_change_mtu,
+++	.port_max_mtu		= qca8k_port_max_mtu,
+++	.port_stp_state_set	= qca8k_port_stp_state_set,
+++	.port_bridge_join	= qca8k_port_bridge_join,
+++	.port_bridge_leave	= qca8k_port_bridge_leave,
+++	.port_fast_age		= qca8k_port_fast_age,
+++	.port_fdb_add		= qca8k_port_fdb_add,
+++	.port_fdb_del		= qca8k_port_fdb_del,
+++	.port_fdb_dump		= qca8k_port_fdb_dump,
+++	.port_mdb_add		= qca8k_port_mdb_add,
+++	.port_mdb_del		= qca8k_port_mdb_del,
+++	.port_mirror_add	= qca8k_port_mirror_add,
+++	.port_mirror_del	= qca8k_port_mirror_del,
+++	.port_vlan_filtering	= qca8k_port_vlan_filtering,
+++	.port_vlan_add		= qca8k_port_vlan_add,
+++	.port_vlan_del		= qca8k_port_vlan_del,
+++	.phylink_validate	= qca8k_phylink_validate,
+++	.phylink_mac_link_state	= qca8k_phylink_mac_link_state,
+++	.phylink_mac_config	= qca8k_phylink_mac_config,
+++	.phylink_mac_link_down	= qca8k_phylink_mac_link_down,
+++	.phylink_mac_link_up	= qca8k_phylink_mac_link_up,
+++	.get_phy_flags		= qca8k_get_phy_flags,
+++	.port_lag_join		= qca8k_port_lag_join,
+++	.port_lag_leave		= qca8k_port_lag_leave,
+++	.master_state_change	= qca8k_master_change,
+++	.connect_tag_protocol	= qca8k_connect_tag_protocol,
+++};
+++
+++static int qca8k_read_switch_id(struct qca8k_priv *priv)
+++{
+++	u32 val;
+++	u8 id;
+++	int ret;
+++
+++	if (!priv->info)
+++		return -ENODEV;
+++
+++	ret = qca8k_read(priv, QCA8K_REG_MASK_CTRL, &val);
+++	if (ret < 0)
+++		return -ENODEV;
+++
+++	id = QCA8K_MASK_CTRL_DEVICE_ID(val);
+++	if (id != priv->info->id) {
+++		dev_err(priv->dev,
+++			"Switch id detected %x but expected %x",
+++			id, priv->info->id);
+++		return -ENODEV;
+++	}
+++
+++	priv->switch_id = id;
+++
+++	/* Save revision to communicate to the internal PHY driver */
+++	priv->switch_revision = QCA8K_MASK_CTRL_REV_ID(val);
+++
+++	return 0;
+++}
+++
+++static int
+++qca8k_sw_probe(struct mdio_device *mdiodev)
+++{
+++	struct qca8k_priv *priv;
+++	int ret;
+++
+++	/* allocate the private data struct so that we can probe the switches
+++	 * ID register
+++	 */
+++	priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL);
+++	if (!priv)
+++		return -ENOMEM;
+++
+++	priv->info = of_device_get_match_data(priv->dev);
+++	priv->bus = mdiodev->bus;
+++	priv->dev = &mdiodev->dev;
+++
+++	priv->reset_gpio = devm_gpiod_get_optional(priv->dev, "reset",
+++						   GPIOD_ASIS);
+++	if (IS_ERR(priv->reset_gpio))
+++		return PTR_ERR(priv->reset_gpio);
+++
+++	if (priv->reset_gpio) {
+++		gpiod_set_value_cansleep(priv->reset_gpio, 1);
+++		/* The active low duration must be greater than 10 ms
+++		 * and checkpatch.pl wants 20 ms.
+++		 */
+++		msleep(20);
+++		gpiod_set_value_cansleep(priv->reset_gpio, 0);
+++	}
+++
 ++	/* Start by setting up the register mapping */
 ++	priv->regmap = devm_regmap_init(&mdiodev->dev, NULL, priv,
 ++					&qca8k_regmap_config);
@@ -18214,5339 +59906,8409 @@ index 0000000000..f873b70d0f
 ++		return PTR_ERR(priv->regmap);
 ++	}
 ++
-+ 	/* Check the detected switch id */
-+ 	ret = qca8k_read_switch_id(priv);
-+ 	if (ret)
-diff --git a/target/linux/generic/backport-5.15/757-net-next-net-dsa-qca8k-initial-conversion-to-regmap-heper.patch b/target/linux/generic/backport-5.15/757-net-next-net-dsa-qca8k-initial-conversion-to-regmap-heper.patch
-new file mode 100644
-index 0000000000..4ca9c8ba41
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/757-net-next-net-dsa-qca8k-initial-conversion-to-regmap-heper.patch
-@@ -0,0 +1,249 @@
-+From 8b5f3f29a81a71934d004e21a1292c1148b05926 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Mon, 22 Nov 2021 16:23:44 +0100
-+Subject: net: dsa: qca8k: initial conversion to regmap helper
-+
-+Convert any qca8k set/clear/pool to regmap helper and add
-+missing config to regmap_config struct.
-+Read/write/rmw operation are reworked to use the regmap helper
-+internally to keep the delta of this patch low. These additional
-+function will then be dropped when the code split will be proposed.
-+
-+Ipq40xx SoC have the internal switch based on the qca8k regmap but use
-+mmio for read/write/rmw operation instead of mdio.
-+In preparation for the support of this internal switch, convert the
-+driver to regmap API to later split the driver to common and specific
-+code. The overhead introduced by the use of regamp API is marginal as the
-+internal mdio will bypass it by using its direct access and regmap will be
-+used only by configuration functions or fdb access.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 107 +++++++++++++++++++++---------------------------
-+ 1 file changed, 47 insertions(+), 60 deletions(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -10,6 +10,7 @@
-+ #include <linux/phy.h>
-+ #include <linux/netdevice.h>
-+ #include <linux/bitfield.h>
-++#include <linux/regmap.h>
-+ #include <net/dsa.h>
-+ #include <linux/of_net.h>
-+ #include <linux/of_mdio.h>
-+@@ -152,6 +153,25 @@ qca8k_set_page(struct mii_bus *bus, u16
-+ static int
-+ qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val)
-+ {
-++	return regmap_read(priv->regmap, reg, val);
+++	priv->mdio_cache.page = 0xffff;
+++	priv->mdio_cache.lo = 0xffff;
+++	priv->mdio_cache.hi = 0xffff;
+++
+++	/* Check the detected switch id */
+++	ret = qca8k_read_switch_id(priv);
+++	if (ret)
+++		return ret;
+++
+++	priv->ds = devm_kzalloc(&mdiodev->dev, sizeof(*priv->ds), GFP_KERNEL);
+++	if (!priv->ds)
+++		return -ENOMEM;
+++
+++	mutex_init(&priv->mgmt_eth_data.mutex);
+++	init_completion(&priv->mgmt_eth_data.rw_done);
+++
+++	mutex_init(&priv->mib_eth_data.mutex);
+++	init_completion(&priv->mib_eth_data.rw_done);
+++
+++	priv->ds->dev = &mdiodev->dev;
+++	priv->ds->num_ports = QCA8K_NUM_PORTS;
+++	priv->ds->priv = priv;
+++	priv->ds->ops = &qca8k_switch_ops;
+++	mutex_init(&priv->reg_mutex);
+++	dev_set_drvdata(&mdiodev->dev, priv);
+++
+++	return dsa_register_switch(priv->ds);
 ++}
 ++
-++static int
-++qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val)
+++static void
+++qca8k_sw_remove(struct mdio_device *mdiodev)
 ++{
-++	return regmap_write(priv->regmap, reg, val);
+++	struct qca8k_priv *priv = dev_get_drvdata(&mdiodev->dev);
+++	int i;
+++
+++	if (!priv)
+++		return;
+++
+++	for (i = 0; i < QCA8K_NUM_PORTS; i++)
+++		qca8k_port_set_status(priv, i, 0);
+++
+++	dsa_unregister_switch(priv->ds);
+++
+++	dev_set_drvdata(&mdiodev->dev, NULL);
 ++}
 ++
-++static int
-++qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
+++static void qca8k_sw_shutdown(struct mdio_device *mdiodev)
 ++{
-++	return regmap_update_bits(priv->regmap, reg, mask, write_val);
+++	struct qca8k_priv *priv = dev_get_drvdata(&mdiodev->dev);
+++
+++	if (!priv)
+++		return;
+++
+++	dsa_switch_shutdown(priv->ds);
+++
+++	dev_set_drvdata(&mdiodev->dev, NULL);
 ++}
 ++
-++static int
-++qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
+++#ifdef CONFIG_PM_SLEEP
+++static void
+++qca8k_set_pm(struct qca8k_priv *priv, int enable)
 ++{
-++	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
-+ 	struct mii_bus *bus = priv->bus;
-+ 	u16 r1, r2, page;
-+ 	int ret;
-+@@ -172,8 +192,9 @@ exit:
-+ }
-+ 
-+ static int
-+-qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val)
-++qca8k_regmap_write(void *ctx, uint32_t reg, uint32_t val)
-+ {
-++	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
-+ 	struct mii_bus *bus = priv->bus;
-+ 	u16 r1, r2, page;
-+ 	int ret;
-+@@ -194,8 +215,9 @@ exit:
-+ }
+++	int port;
+++
+++	for (port = 0; port < QCA8K_NUM_PORTS; port++) {
+++		/* Do not enable on resume if the port was
+++		 * disabled before.
+++		 */
+++		if (!(priv->port_enabled_map & BIT(port)))
+++			continue;
+++
+++		qca8k_port_set_status(priv, port, enable);
+++	}
+++}
+++
+++static int qca8k_suspend(struct device *dev)
+++{
+++	struct qca8k_priv *priv = dev_get_drvdata(dev);
+++
+++	qca8k_set_pm(priv, 0);
+++
+++	return dsa_switch_suspend(priv->ds);
+++}
+++
+++static int qca8k_resume(struct device *dev)
+++{
+++	struct qca8k_priv *priv = dev_get_drvdata(dev);
+++
+++	qca8k_set_pm(priv, 1);
+++
+++	return dsa_switch_resume(priv->ds);
+++}
+++#endif /* CONFIG_PM_SLEEP */
+++
+++static SIMPLE_DEV_PM_OPS(qca8k_pm_ops,
+++			 qca8k_suspend, qca8k_resume);
+++
+++static const struct qca8k_info_ops qca8xxx_ops = {
+++	.autocast_mib = qca8k_get_ethtool_stats_eth,
+++};
+++
+++static const struct qca8k_match_data qca8327 = {
+++	.id = QCA8K_ID_QCA8327,
+++	.reduced_package = true,
+++	.mib_count = QCA8K_QCA832X_MIB_COUNT,
+++	.ops = &qca8xxx_ops,
+++};
+++
+++static const struct qca8k_match_data qca8328 = {
+++	.id = QCA8K_ID_QCA8327,
+++	.mib_count = QCA8K_QCA832X_MIB_COUNT,
+++	.ops = &qca8xxx_ops,
+++};
+++
+++static const struct qca8k_match_data qca833x = {
+++	.id = QCA8K_ID_QCA8337,
+++	.mib_count = QCA8K_QCA833X_MIB_COUNT,
+++	.ops = &qca8xxx_ops,
+++};
+++
+++static const struct of_device_id qca8k_of_match[] = {
+++	{ .compatible = "qca,qca8327", .data = &qca8327 },
+++	{ .compatible = "qca,qca8328", .data = &qca8328 },
+++	{ .compatible = "qca,qca8334", .data = &qca833x },
+++	{ .compatible = "qca,qca8337", .data = &qca833x },
+++	{ /* sentinel */ },
+++};
+++
+++static struct mdio_driver qca8kmdio_driver = {
+++	.probe  = qca8k_sw_probe,
+++	.remove = qca8k_sw_remove,
+++	.shutdown = qca8k_sw_shutdown,
+++	.mdiodrv.driver = {
+++		.name = "qca8k",
+++		.of_match_table = qca8k_of_match,
+++		.pm = &qca8k_pm_ops,
+++	},
+++};
+++
+++mdio_module_driver(qca8kmdio_driver);
+++
+++MODULE_AUTHOR("Mathieu Olivari, John Crispin <john@phrozen.org>");
+++MODULE_DESCRIPTION("Driver for QCA8K ethernet switch family");
+++MODULE_LICENSE("GPL v2");
+++MODULE_ALIAS("platform:qca8k");
++--- /dev/null
+++++ b/drivers/net/dsa/qca/qca8k-common.c
++@@ -0,0 +1,63 @@
+++// SPDX-License-Identifier: GPL-2.0
+++/*
+++ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+++ * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
+++ * Copyright (c) 2015, 2019, The Linux Foundation. All rights reserved.
+++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
+++ */
+++
+++#include <linux/netdevice.h>
+++#include <net/dsa.h>
+++
+++#include "qca8k.h"
+++
+++#define MIB_DESC(_s, _o, _n)	\
+++	{			\
+++		.size = (_s),	\
+++		.offset = (_o),	\
+++		.name = (_n),	\
+++	}
+++
+++const struct qca8k_mib_desc ar8327_mib[] = {
+++	MIB_DESC(1, 0x00, "RxBroad"),
+++	MIB_DESC(1, 0x04, "RxPause"),
+++	MIB_DESC(1, 0x08, "RxMulti"),
+++	MIB_DESC(1, 0x0c, "RxFcsErr"),
+++	MIB_DESC(1, 0x10, "RxAlignErr"),
+++	MIB_DESC(1, 0x14, "RxRunt"),
+++	MIB_DESC(1, 0x18, "RxFragment"),
+++	MIB_DESC(1, 0x1c, "Rx64Byte"),
+++	MIB_DESC(1, 0x20, "Rx128Byte"),
+++	MIB_DESC(1, 0x24, "Rx256Byte"),
+++	MIB_DESC(1, 0x28, "Rx512Byte"),
+++	MIB_DESC(1, 0x2c, "Rx1024Byte"),
+++	MIB_DESC(1, 0x30, "Rx1518Byte"),
+++	MIB_DESC(1, 0x34, "RxMaxByte"),
+++	MIB_DESC(1, 0x38, "RxTooLong"),
+++	MIB_DESC(2, 0x3c, "RxGoodByte"),
+++	MIB_DESC(2, 0x44, "RxBadByte"),
+++	MIB_DESC(1, 0x4c, "RxOverFlow"),
+++	MIB_DESC(1, 0x50, "Filtered"),
+++	MIB_DESC(1, 0x54, "TxBroad"),
+++	MIB_DESC(1, 0x58, "TxPause"),
+++	MIB_DESC(1, 0x5c, "TxMulti"),
+++	MIB_DESC(1, 0x60, "TxUnderRun"),
+++	MIB_DESC(1, 0x64, "Tx64Byte"),
+++	MIB_DESC(1, 0x68, "Tx128Byte"),
+++	MIB_DESC(1, 0x6c, "Tx256Byte"),
+++	MIB_DESC(1, 0x70, "Tx512Byte"),
+++	MIB_DESC(1, 0x74, "Tx1024Byte"),
+++	MIB_DESC(1, 0x78, "Tx1518Byte"),
+++	MIB_DESC(1, 0x7c, "TxMaxByte"),
+++	MIB_DESC(1, 0x80, "TxOverSize"),
+++	MIB_DESC(2, 0x84, "TxByte"),
+++	MIB_DESC(1, 0x8c, "TxCollision"),
+++	MIB_DESC(1, 0x90, "TxAbortCol"),
+++	MIB_DESC(1, 0x94, "TxMultiCol"),
+++	MIB_DESC(1, 0x98, "TxSingleCol"),
+++	MIB_DESC(1, 0x9c, "TxExcDefer"),
+++	MIB_DESC(1, 0xa0, "TxDefer"),
+++	MIB_DESC(1, 0xa4, "TxLateCol"),
+++	MIB_DESC(1, 0xa8, "RXUnicast"),
+++	MIB_DESC(1, 0xac, "TXUnicast"),
+++};
++--- a/drivers/net/dsa/qca/qca8k.h
+++++ b/drivers/net/dsa/qca/qca8k.h
++@@ -414,4 +414,7 @@ struct qca8k_fdb {
++ 	u8 mac[6];
++ };
 + 
-+ static int
-+-qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
-++qca8k_regmap_update_bits(void *ctx, uint32_t reg, uint32_t mask, uint32_t write_val)
-+ {
-++	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
-+ 	struct mii_bus *bus = priv->bus;
-+ 	u16 r1, r2, page;
-+ 	u32 val;
-+@@ -223,34 +245,6 @@ exit:
-+ 	return ret;
+++/* Common setup function */
+++extern const struct qca8k_mib_desc ar8327_mib[];
+++
++ #endif /* __QCA8K_H */
+diff --git a/target/linux/generic/backport-5.15/771-v6.0-04-net-dsa-qca8k-move-qca8k-read-write-rmw-and-reg-tabl.patch b/target/linux/generic/backport-5.15/771-v6.0-04-net-dsa-qca8k-move-qca8k-read-write-rmw-and-reg-tabl.patch
+new file mode 100644
+index 0000000000..012ab85474
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/771-v6.0-04-net-dsa-qca8k-move-qca8k-read-write-rmw-and-reg-tabl.patch
+@@ -0,0 +1,135 @@
++From d5f901eab2e9dfed1095995dfc98f231f4fd2971 Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Wed, 27 Jul 2022 13:35:13 +0200
++Subject: [PATCH 04/14] net: dsa: qca8k: move qca8k read/write/rmw and reg
++ table to common code
++
++The same reg table and read/write/rmw function are used by drivers
++based on qca8k family switch.
++Move them to common code to make it accessible also by other drivers.
++
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/dsa/qca/qca8k-8xxx.c   | 42 ------------------------------
++ drivers/net/dsa/qca/qca8k-common.c | 38 +++++++++++++++++++++++++++
++ drivers/net/dsa/qca/qca8k.h        |  6 +++++
++ 3 files changed, 44 insertions(+), 42 deletions(-)
++
++--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
++@@ -133,24 +133,6 @@ qca8k_set_page(struct qca8k_priv *priv,
++ 	return 0;
 + }
 + 
 +-static int
-+-qca8k_reg_set(struct qca8k_priv *priv, u32 reg, u32 val)
-+-{
-+-	return qca8k_rmw(priv, reg, 0, val);
-+-}
-+-
-+-static int
-+-qca8k_reg_clear(struct qca8k_priv *priv, u32 reg, u32 val)
++-qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val)
 +-{
-+-	return qca8k_rmw(priv, reg, val, 0);
++-	return regmap_read(priv->regmap, reg, val);
 +-}
 +-
 +-static int
-+-qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
++-qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val)
 +-{
-+-	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
-+-
-+-	return qca8k_read(priv, reg, val);
++-	return regmap_write(priv->regmap, reg, val);
 +-}
 +-
 +-static int
-+-qca8k_regmap_write(void *ctx, uint32_t reg, uint32_t val)
++-qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
 +-{
-+-	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
-+-
-+-	return qca8k_write(priv, reg, val);
++-	return regmap_update_bits(priv->regmap, reg, mask, write_val);
 +-}
 +-
-+ static const struct regmap_range qca8k_readable_ranges[] = {
-+ 	regmap_reg_range(0x0000, 0x00e4), /* Global control */
-+ 	regmap_reg_range(0x0100, 0x0168), /* EEE control */
-+@@ -282,26 +276,19 @@ static struct regmap_config qca8k_regmap
-+ 	.max_register = 0x16ac, /* end MIB - Port6 range */
-+ 	.reg_read = qca8k_regmap_read,
-+ 	.reg_write = qca8k_regmap_write,
-++	.reg_update_bits = qca8k_regmap_update_bits,
-+ 	.rd_table = &qca8k_readable_table,
-++	.disable_locking = true, /* Locking is handled by qca8k read/write */
-++	.cache_type = REGCACHE_NONE, /* Explicitly disable CACHE */
-+ };
-+ 
-+ static int
-+ qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask)
++ static void qca8k_rw_reg_ack_handler(struct dsa_switch *ds, struct sk_buff *skb)
 + {
-+-	int ret, ret1;
-+ 	u32 val;
++ 	struct qca8k_mgmt_eth_data *mgmt_eth_data;
++@@ -483,30 +465,6 @@ exit:
++ 	return ret;
++ }
 + 
-+-	ret = read_poll_timeout(qca8k_read, ret1, !(val & mask),
-+-				0, QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
-+-				priv, reg, &val);
++-static const struct regmap_range qca8k_readable_ranges[] = {
++-	regmap_reg_range(0x0000, 0x00e4), /* Global control */
++-	regmap_reg_range(0x0100, 0x0168), /* EEE control */
++-	regmap_reg_range(0x0200, 0x0270), /* Parser control */
++-	regmap_reg_range(0x0400, 0x0454), /* ACL */
++-	regmap_reg_range(0x0600, 0x0718), /* Lookup */
++-	regmap_reg_range(0x0800, 0x0b70), /* QM */
++-	regmap_reg_range(0x0c00, 0x0c80), /* PKT */
++-	regmap_reg_range(0x0e00, 0x0e98), /* L3 */
++-	regmap_reg_range(0x1000, 0x10ac), /* MIB - Port0 */
++-	regmap_reg_range(0x1100, 0x11ac), /* MIB - Port1 */
++-	regmap_reg_range(0x1200, 0x12ac), /* MIB - Port2 */
++-	regmap_reg_range(0x1300, 0x13ac), /* MIB - Port3 */
++-	regmap_reg_range(0x1400, 0x14ac), /* MIB - Port4 */
++-	regmap_reg_range(0x1500, 0x15ac), /* MIB - Port5 */
++-	regmap_reg_range(0x1600, 0x16ac), /* MIB - Port6 */
 +-
-+-	/* Check if qca8k_read has failed for a different reason
-+-	 * before returning -ETIMEDOUT
-+-	 */
-+-	if (ret < 0 && ret1 < 0)
-+-		return ret1;
++-};
 +-
-+-	return ret;
-++	return regmap_read_poll_timeout(priv->regmap, reg, val, !(val & mask), 0,
-++				       QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC);
-+ }
-+ 
-+ static int
-+@@ -568,7 +555,7 @@ qca8k_mib_init(struct qca8k_priv *priv)
-+ 	int ret;
-+ 
-+ 	mutex_lock(&priv->reg_mutex);
-+-	ret = qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_FLUSH | QCA8K_MIB_BUSY);
-++	ret = regmap_set_bits(priv->regmap, QCA8K_REG_MIB, QCA8K_MIB_FLUSH | QCA8K_MIB_BUSY);
-+ 	if (ret)
-+ 		goto exit;
-+ 
-+@@ -576,7 +563,7 @@ qca8k_mib_init(struct qca8k_priv *priv)
-+ 	if (ret)
-+ 		goto exit;
-+ 
-+-	ret = qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP);
-++	ret = regmap_set_bits(priv->regmap, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP);
-+ 	if (ret)
-+ 		goto exit;
-+ 
-+@@ -597,9 +584,9 @@ qca8k_port_set_status(struct qca8k_priv
-+ 		mask |= QCA8K_PORT_STATUS_LINK_AUTO;
-+ 
-+ 	if (enable)
-+-		qca8k_reg_set(priv, QCA8K_REG_PORT_STATUS(port), mask);
-++		regmap_set_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
-+ 	else
-+-		qca8k_reg_clear(priv, QCA8K_REG_PORT_STATUS(port), mask);
-++		regmap_clear_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
-+ }
-+ 
-+ static u32
-+@@ -861,8 +848,8 @@ qca8k_setup_mdio_bus(struct qca8k_priv *
-+ 		 * a dt-overlay and driver reload changed the configuration
-+ 		 */
-+ 
-+-		return qca8k_reg_clear(priv, QCA8K_MDIO_MASTER_CTRL,
-+-				       QCA8K_MDIO_MASTER_EN);
-++		return regmap_clear_bits(priv->regmap, QCA8K_MDIO_MASTER_CTRL,
-++					 QCA8K_MDIO_MASTER_EN);
-+ 	}
-+ 
-+ 	/* Check if the devicetree declare the port:phy mapping */
-+@@ -1099,16 +1086,16 @@ qca8k_setup(struct dsa_switch *ds)
-+ 		return ret;
-+ 
-+ 	/* Make sure MAC06 is disabled */
-+-	ret = qca8k_reg_clear(priv, QCA8K_REG_PORT0_PAD_CTRL,
-+-			      QCA8K_PORT0_PAD_MAC06_EXCHANGE_EN);
-++	ret = regmap_clear_bits(priv->regmap, QCA8K_REG_PORT0_PAD_CTRL,
-++				QCA8K_PORT0_PAD_MAC06_EXCHANGE_EN);
-+ 	if (ret) {
-+ 		dev_err(priv->dev, "failed disabling MAC06 exchange");
-+ 		return ret;
-+ 	}
-+ 
-+ 	/* Enable CPU Port */
-+-	ret = qca8k_reg_set(priv, QCA8K_REG_GLOBAL_FW_CTRL0,
-+-			    QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
-++	ret = regmap_set_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
-++			      QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
-+ 	if (ret) {
-+ 		dev_err(priv->dev, "failed enabling CPU port");
-+ 		return ret;
-+@@ -1176,8 +1163,8 @@ qca8k_setup(struct dsa_switch *ds)
-+ 				return ret;
-+ 
-+ 			/* Enable ARP Auto-learning by default */
-+-			ret = qca8k_reg_set(priv, QCA8K_PORT_LOOKUP_CTRL(i),
-+-					    QCA8K_PORT_LOOKUP_LEARN);
-++			ret = regmap_set_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(i),
-++					      QCA8K_PORT_LOOKUP_LEARN);
-+ 			if (ret)
-+ 				return ret;
++-static const struct regmap_access_table qca8k_readable_table = {
++-	.yes_ranges = qca8k_readable_ranges,
++-	.n_yes_ranges = ARRAY_SIZE(qca8k_readable_ranges),
++-};
++-
++ static struct regmap_config qca8k_regmap_config = {
++ 	.reg_bits = 16,
++ 	.val_bits = 32,
++--- a/drivers/net/dsa/qca/qca8k-common.c
+++++ b/drivers/net/dsa/qca/qca8k-common.c
++@@ -61,3 +61,41 @@ const struct qca8k_mib_desc ar8327_mib[]
++ 	MIB_DESC(1, 0xa8, "RXUnicast"),
++ 	MIB_DESC(1, 0xac, "TXUnicast"),
++ };
+++
+++int qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val)
+++{
+++	return regmap_read(priv->regmap, reg, val);
+++}
+++
+++int qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val)
+++{
+++	return regmap_write(priv->regmap, reg, val);
+++}
+++
+++int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
+++{
+++	return regmap_update_bits(priv->regmap, reg, mask, write_val);
+++}
+++
+++static const struct regmap_range qca8k_readable_ranges[] = {
+++	regmap_reg_range(0x0000, 0x00e4), /* Global control */
+++	regmap_reg_range(0x0100, 0x0168), /* EEE control */
+++	regmap_reg_range(0x0200, 0x0270), /* Parser control */
+++	regmap_reg_range(0x0400, 0x0454), /* ACL */
+++	regmap_reg_range(0x0600, 0x0718), /* Lookup */
+++	regmap_reg_range(0x0800, 0x0b70), /* QM */
+++	regmap_reg_range(0x0c00, 0x0c80), /* PKT */
+++	regmap_reg_range(0x0e00, 0x0e98), /* L3 */
+++	regmap_reg_range(0x1000, 0x10ac), /* MIB - Port0 */
+++	regmap_reg_range(0x1100, 0x11ac), /* MIB - Port1 */
+++	regmap_reg_range(0x1200, 0x12ac), /* MIB - Port2 */
+++	regmap_reg_range(0x1300, 0x13ac), /* MIB - Port3 */
+++	regmap_reg_range(0x1400, 0x14ac), /* MIB - Port4 */
+++	regmap_reg_range(0x1500, 0x15ac), /* MIB - Port5 */
+++	regmap_reg_range(0x1600, 0x16ac), /* MIB - Port6 */
+++};
+++
+++const struct regmap_access_table qca8k_readable_table = {
+++	.yes_ranges = qca8k_readable_ranges,
+++	.n_yes_ranges = ARRAY_SIZE(qca8k_readable_ranges),
+++};
++--- a/drivers/net/dsa/qca/qca8k.h
+++++ b/drivers/net/dsa/qca/qca8k.h
++@@ -416,5 +416,11 @@ struct qca8k_fdb {
 + 
-+@@ -1745,9 +1732,9 @@ qca8k_port_bridge_join(struct dsa_switch
-+ 		/* Add this port to the portvlan mask of the other ports
-+ 		 * in the bridge
-+ 		 */
-+-		ret = qca8k_reg_set(priv,
-+-				    QCA8K_PORT_LOOKUP_CTRL(i),
-+-				    BIT(port));
-++		ret = regmap_set_bits(priv->regmap,
-++				      QCA8K_PORT_LOOKUP_CTRL(i),
-++				      BIT(port));
-+ 		if (ret)
-+ 			return ret;
-+ 		if (i != port)
-+@@ -1777,9 +1764,9 @@ qca8k_port_bridge_leave(struct dsa_switc
-+ 		/* Remove this port to the portvlan mask of the other ports
-+ 		 * in the bridge
-+ 		 */
-+-		qca8k_reg_clear(priv,
-+-				QCA8K_PORT_LOOKUP_CTRL(i),
-+-				BIT(port));
-++		regmap_clear_bits(priv->regmap,
-++				  QCA8K_PORT_LOOKUP_CTRL(i),
-++				  BIT(port));
-+ 	}
++ /* Common setup function */
++ extern const struct qca8k_mib_desc ar8327_mib[];
+++extern const struct regmap_access_table qca8k_readable_table;
+++
+++/* Common read/write/rmw function */
+++int qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val);
+++int qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val);
+++int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val);
 + 
-+ 	/* Set the cpu port to be the only one in the portvlan mask of
-diff --git a/target/linux/generic/backport-5.15/758-net-next-net-dsa-qca8k-add-additional-MIB-counter-and-.patch b/target/linux/generic/backport-5.15/758-net-next-net-dsa-qca8k-add-additional-MIB-counter-and-.patch
++ #endif /* __QCA8K_H */
+diff --git a/target/linux/generic/backport-5.15/771-v6.0-05-net-dsa-qca8k-move-qca8k-bulk-read-write-helper-to-c.patch b/target/linux/generic/backport-5.15/771-v6.0-05-net-dsa-qca8k-move-qca8k-bulk-read-write-helper-to-c.patch
 new file mode 100644
-index 0000000000..1465d1f35d
+index 0000000000..0ed7ed41fb
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/758-net-next-net-dsa-qca8k-add-additional-MIB-counter-and-.patch
-@@ -0,0 +1,120 @@
-+From c126f118b330ccf0db0dda4a4bd6c729865a205f Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Mon, 22 Nov 2021 16:23:45 +0100
-+Subject: net: dsa: qca8k: add additional MIB counter and make it dynamic
++++ b/target/linux/generic/backport-5.15/771-v6.0-05-net-dsa-qca8k-move-qca8k-bulk-read-write-helper-to-c.patch
+@@ -0,0 +1,145 @@
++From 910746444313dc463396cd63024cdf54ef04ef39 Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Wed, 27 Jul 2022 13:35:14 +0200
++Subject: [PATCH 05/14] net: dsa: qca8k: move qca8k bulk read/write helper to
++ common code
 +
-+We are currently missing 2 additionals MIB counter present in QCA833x
-+switch.
-+QC832x switch have 39 MIB counter and QCA833X have 41 MIB counter.
-+Add the additional MIB counter and rework the MIB function to print the
-+correct supported counter from the match_data struct.
++The same ATU function are used by drivers based on qca8k family switch.
++Move the bulk read/write helper to common code to declare these shared
++ATU functions in common code.
++These helper will be dropped when regmap correctly support bulk
++read/write.
 +
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
 +Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 +---
-+ drivers/net/dsa/qca8k.c | 23 ++++++++++++++++++++---
-+ drivers/net/dsa/qca8k.h |  4 ++++
-+ 2 files changed, 24 insertions(+), 3 deletions(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -70,6 +70,8 @@ static const struct qca8k_mib_desc ar832
-+ 	MIB_DESC(1, 0x9c, "TxExcDefer"),
-+ 	MIB_DESC(1, 0xa0, "TxDefer"),
-+ 	MIB_DESC(1, 0xa4, "TxLateCol"),
-++	MIB_DESC(1, 0xa8, "RXUnicast"),
-++	MIB_DESC(1, 0xac, "TXUnicast"),
-+ };
-+ 
-+ /* The 32bit switch registers are accessed indirectly. To achieve this we need
-+@@ -1605,12 +1607,16 @@ qca8k_phylink_mac_link_up(struct dsa_swi
-+ static void
-+ qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data)
-+ {
-++	const struct qca8k_match_data *match_data;
-++	struct qca8k_priv *priv = ds->priv;
-+ 	int i;
-+ 
-+ 	if (stringset != ETH_SS_STATS)
-+ 		return;
-+ 
-+-	for (i = 0; i < ARRAY_SIZE(ar8327_mib); i++)
-++	match_data = of_device_get_match_data(priv->dev);
-++
-++	for (i = 0; i < match_data->mib_count; i++)
-+ 		strncpy(data + i * ETH_GSTRING_LEN, ar8327_mib[i].name,
-+ 			ETH_GSTRING_LEN);
++ drivers/net/dsa/qca/qca8k-8xxx.c   | 39 ++----------------------------
++ drivers/net/dsa/qca/qca8k-common.c | 39 ++++++++++++++++++++++++++++++
++ drivers/net/dsa/qca/qca8k.h        |  8 ++++++
++ 3 files changed, 49 insertions(+), 37 deletions(-)
++
++--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
++@@ -343,43 +343,6 @@ qca8k_regmap_update_bits_eth(struct qca8
 + }
-+@@ -1620,12 +1626,15 @@ qca8k_get_ethtool_stats(struct dsa_switc
-+ 			uint64_t *data)
-+ {
-+ 	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
-++	const struct qca8k_match_data *match_data;
-+ 	const struct qca8k_mib_desc *mib;
-+ 	u32 reg, i, val;
-+ 	u32 hi = 0;
-+ 	int ret;
-+ 
-+-	for (i = 0; i < ARRAY_SIZE(ar8327_mib); i++) {
-++	match_data = of_device_get_match_data(priv->dev);
-++
-++	for (i = 0; i < match_data->mib_count; i++) {
-+ 		mib = &ar8327_mib[i];
-+ 		reg = QCA8K_PORT_MIB_COUNTER(port) + mib->offset;
 + 
-+@@ -1648,10 +1657,15 @@ qca8k_get_ethtool_stats(struct dsa_switc
 + static int
-+ qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset)
-+ {
-++	const struct qca8k_match_data *match_data;
-++	struct qca8k_priv *priv = ds->priv;
-++
-+ 	if (sset != ETH_SS_STATS)
-+ 		return 0;
-+ 
-+-	return ARRAY_SIZE(ar8327_mib);
-++	match_data = of_device_get_match_data(priv->dev);
-++
-++	return match_data->mib_count;
-+ }
++-qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++-{
++-	int i, count = len / sizeof(u32), ret;
++-
++-	if (priv->mgmt_master && !qca8k_read_eth(priv, reg, val, len))
++-		return 0;
++-
++-	for (i = 0; i < count; i++) {
++-		ret = regmap_read(priv->regmap, reg + (i * 4), val + i);
++-		if (ret < 0)
++-			return ret;
++-	}
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++-{
++-	int i, count = len / sizeof(u32), ret;
++-	u32 tmp;
++-
++-	if (priv->mgmt_master && !qca8k_write_eth(priv, reg, val, len))
++-		return 0;
++-
++-	for (i = 0; i < count; i++) {
++-		tmp = val[i];
++-
++-		ret = regmap_write(priv->regmap, reg + (i * 4), tmp);
++-		if (ret < 0)
++-			return ret;
++-	}
++-
++-	return 0;
++-}
++-
++-static int
++ qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
++ {
++ 	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
++@@ -3096,6 +3059,8 @@ static SIMPLE_DEV_PM_OPS(qca8k_pm_ops,
 + 
-+ static int
-+@@ -2154,14 +2168,17 @@ static SIMPLE_DEV_PM_OPS(qca8k_pm_ops,
-+ static const struct qca8k_match_data qca8327 = {
-+ 	.id = QCA8K_ID_QCA8327,
-+ 	.reduced_package = true,
-++	.mib_count = QCA8K_QCA832X_MIB_COUNT,
++ static const struct qca8k_info_ops qca8xxx_ops = {
++ 	.autocast_mib = qca8k_get_ethtool_stats_eth,
+++	.read_eth = qca8k_read_eth,
+++	.write_eth = qca8k_write_eth,
 + };
 + 
-+ static const struct qca8k_match_data qca8328 = {
-+ 	.id = QCA8K_ID_QCA8327,
-++	.mib_count = QCA8K_QCA832X_MIB_COUNT,
++ static const struct qca8k_match_data qca8327 = {
++--- a/drivers/net/dsa/qca/qca8k-common.c
+++++ b/drivers/net/dsa/qca/qca8k-common.c
++@@ -99,3 +99,42 @@ const struct regmap_access_table qca8k_r
++ 	.yes_ranges = qca8k_readable_ranges,
++ 	.n_yes_ranges = ARRAY_SIZE(qca8k_readable_ranges),
 + };
-+ 
-+ static const struct qca8k_match_data qca833x = {
-+ 	.id = QCA8K_ID_QCA8337,
-++	.mib_count = QCA8K_QCA833X_MIB_COUNT,
+++
+++/* TODO: remove these extra ops when we can support regmap bulk read/write */
+++int qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
+++{
+++	int i, count = len / sizeof(u32), ret;
+++
+++	if (priv->mgmt_master && priv->info->ops->read_eth &&
+++	    !priv->info->ops->read_eth(priv, reg, val, len))
+++		return 0;
+++
+++	for (i = 0; i < count; i++) {
+++		ret = regmap_read(priv->regmap, reg + (i * 4), val + i);
+++		if (ret < 0)
+++			return ret;
+++	}
+++
+++	return 0;
+++}
+++
+++/* TODO: remove these extra ops when we can support regmap bulk read/write */
+++int qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
+++{
+++	int i, count = len / sizeof(u32), ret;
+++	u32 tmp;
+++
+++	if (priv->mgmt_master && priv->info->ops->write_eth &&
+++	    !priv->info->ops->write_eth(priv, reg, val, len))
+++		return 0;
+++
+++	for (i = 0; i < count; i++) {
+++		tmp = val[i];
+++
+++		ret = regmap_write(priv->regmap, reg + (i * 4), tmp);
+++		if (ret < 0)
+++			return ret;
+++	}
+++
+++	return 0;
+++}
++--- a/drivers/net/dsa/qca/qca8k.h
+++++ b/drivers/net/dsa/qca/qca8k.h
++@@ -324,8 +324,13 @@ enum qca8k_mid_cmd {
++ 	QCA8K_MIB_CAST = 3,
 + };
 + 
-+ static const struct of_device_id qca8k_of_match[] = {
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -21,6 +21,9 @@
-+ #define PHY_ID_QCA8337					0x004dd036
-+ #define QCA8K_ID_QCA8337				0x13
-+ 
-++#define QCA8K_QCA832X_MIB_COUNT				39
-++#define QCA8K_QCA833X_MIB_COUNT				41
+++struct qca8k_priv;
 ++
-+ #define QCA8K_BUSY_WAIT_TIMEOUT				2000
++ struct qca8k_info_ops {
++ 	int (*autocast_mib)(struct dsa_switch *ds, int port, u64 *data);
+++	/* TODO: remove these extra ops when we can support regmap bulk read/write */
+++	int (*read_eth)(struct qca8k_priv *priv, u32 reg, u32 *val, int len);
+++	int (*write_eth)(struct qca8k_priv *priv, u32 reg, u32 *val, int len);
++ };
 + 
-+ #define QCA8K_NUM_FDB_RECORDS				2048
-+@@ -279,6 +282,7 @@ struct ar8xxx_port_status {
 + struct qca8k_match_data {
-+ 	u8 id;
-+ 	bool reduced_package;
-++	u8 mib_count;
-+ };
++@@ -423,4 +428,7 @@ int qca8k_read(struct qca8k_priv *priv,
++ int qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val);
++ int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val);
 + 
-+ enum {
-diff --git a/target/linux/generic/backport-5.15/759-net-next-net-dsa-qca8k-add-support-for-port-fast-aging.patch b/target/linux/generic/backport-5.15/759-net-next-net-dsa-qca8k-add-support-for-port-fast-aging.patch
+++int qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len);
+++int qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len);
+++
++ #endif /* __QCA8K_H */
+diff --git a/target/linux/generic/backport-5.15/771-v6.0-06-net-dsa-qca8k-move-mib-init-function-to-common-code.patch b/target/linux/generic/backport-5.15/771-v6.0-06-net-dsa-qca8k-move-mib-init-function-to-common-code.patch
 new file mode 100644
-index 0000000000..973446ec57
+index 0000000000..a39a55b89b
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/759-net-next-net-dsa-qca8k-add-support-for-port-fast-aging.patch
-@@ -0,0 +1,53 @@
-+From 4592538bfb0d5d3c3c8a1d7071724d081412ac91 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Mon, 22 Nov 2021 16:23:46 +0100
-+Subject: net: dsa: qca8k: add support for port fast aging
++++ b/target/linux/generic/backport-5.15/771-v6.0-06-net-dsa-qca8k-move-mib-init-function-to-common-code.patch
+@@ -0,0 +1,137 @@
++From fce1ec0c4e2d03d9c62ffc615a42bdba78eb4c14 Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Wed, 27 Jul 2022 13:35:15 +0200
++Subject: [PATCH 06/14] net: dsa: qca8k: move mib init function to common code
 +
-+The switch supports fast aging by flushing any rule in the ARL
-+table for a specific port.
++The same mib function is used by drivers based on qca8k family switch.
++Move it to common code to make it accessible also by other drivers.
 +
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
 +Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 +---
-+ drivers/net/dsa/qca8k.c | 11 +++++++++++
-+ drivers/net/dsa/qca8k.h |  1 +
-+ 2 files changed, 12 insertions(+)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -1790,6 +1790,16 @@ qca8k_port_bridge_leave(struct dsa_switc
-+ 		  QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port));
++ drivers/net/dsa/qca/qca8k-8xxx.c   | 37 ------------------------------
++ drivers/net/dsa/qca/qca8k-common.c | 35 ++++++++++++++++++++++++++++
++ drivers/net/dsa/qca/qca8k.h        |  4 ++++
++ 3 files changed, 39 insertions(+), 37 deletions(-)
++
++--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
++@@ -442,15 +442,6 @@ static struct regmap_config qca8k_regmap
++ };
++ 
++ static int
++-qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask)
++-{
++-	u32 val;
++-
++-	return regmap_read_poll_timeout(priv->regmap, reg, val, !(val & mask), 0,
++-				       QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC);
++-}
++-
++-static int
++ qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb)
++ {
++ 	u32 reg[3];
++@@ -777,34 +768,6 @@ out:
++ 	return ret;
 + }
 + 
-++static void
-++qca8k_port_fast_age(struct dsa_switch *ds, int port)
++-static int
++-qca8k_mib_init(struct qca8k_priv *priv)
++-{
++-	int ret;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB,
++-				 QCA8K_MIB_FUNC | QCA8K_MIB_BUSY,
++-				 FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_FLUSH) |
++-				 QCA8K_MIB_BUSY);
++-	if (ret)
++-		goto exit;
++-
++-	ret = qca8k_busy_wait(priv, QCA8K_REG_MIB, QCA8K_MIB_BUSY);
++-	if (ret)
++-		goto exit;
++-
++-	ret = regmap_set_bits(priv->regmap, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP);
++-	if (ret)
++-		goto exit;
++-
++-	ret = qca8k_write(priv, QCA8K_REG_MODULE_EN, QCA8K_MODULE_EN_MIB);
++-
++-exit:
++-	mutex_unlock(&priv->reg_mutex);
++-	return ret;
++-}
++-
++ static void
++ qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable)
++ {
++--- a/drivers/net/dsa/qca/qca8k-common.c
+++++ b/drivers/net/dsa/qca/qca8k-common.c
++@@ -7,6 +7,7 @@
++  */
++ 
++ #include <linux/netdevice.h>
+++#include <linux/bitfield.h>
++ #include <net/dsa.h>
++ 
++ #include "qca8k.h"
++@@ -138,3 +139,38 @@ int qca8k_bulk_write(struct qca8k_priv *
++ 
++ 	return 0;
++ }
+++
+++int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask)
 ++{
-++	struct qca8k_priv *priv = ds->priv;
+++	u32 val;
+++
+++	return regmap_read_poll_timeout(priv->regmap, reg, val, !(val & mask), 0,
+++				       QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC);
+++}
+++
+++int qca8k_mib_init(struct qca8k_priv *priv)
+++{
+++	int ret;
 ++
 ++	mutex_lock(&priv->reg_mutex);
-++	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port);
+++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB,
+++				 QCA8K_MIB_FUNC | QCA8K_MIB_BUSY,
+++				 FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_FLUSH) |
+++				 QCA8K_MIB_BUSY);
+++	if (ret)
+++		goto exit;
+++
+++	ret = qca8k_busy_wait(priv, QCA8K_REG_MIB, QCA8K_MIB_BUSY);
+++	if (ret)
+++		goto exit;
+++
+++	ret = regmap_set_bits(priv->regmap, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP);
+++	if (ret)
+++		goto exit;
+++
+++	ret = qca8k_write(priv, QCA8K_REG_MODULE_EN, QCA8K_MODULE_EN_MIB);
+++
+++exit:
 ++	mutex_unlock(&priv->reg_mutex);
+++	return ret;
 ++}
-++
-+ static int
-+ qca8k_port_enable(struct dsa_switch *ds, int port,
-+ 		  struct phy_device *phy)
-+@@ -1998,6 +2008,7 @@ static const struct dsa_switch_ops qca8k
-+ 	.port_stp_state_set	= qca8k_port_stp_state_set,
-+ 	.port_bridge_join	= qca8k_port_bridge_join,
-+ 	.port_bridge_leave	= qca8k_port_bridge_leave,
-++	.port_fast_age		= qca8k_port_fast_age,
-+ 	.port_fdb_add		= qca8k_port_fdb_add,
-+ 	.port_fdb_del		= qca8k_port_fdb_del,
-+ 	.port_fdb_dump		= qca8k_port_fdb_dump,
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -262,6 +262,7 @@ enum qca8k_fdb_cmd {
-+ 	QCA8K_FDB_FLUSH	= 1,
-+ 	QCA8K_FDB_LOAD = 2,
-+ 	QCA8K_FDB_PURGE = 3,
-++	QCA8K_FDB_FLUSH_PORT = 5,
-+ 	QCA8K_FDB_NEXT = 6,
-+ 	QCA8K_FDB_SEARCH = 7,
-+ };
-diff --git a/target/linux/generic/backport-5.15/760-net-next-net-dsa-qca8k-add-set_ageing_time-support.patch b/target/linux/generic/backport-5.15/760-net-next-net-dsa-qca8k-add-set_ageing_time-support.patch
++--- a/drivers/net/dsa/qca/qca8k.h
+++++ b/drivers/net/dsa/qca/qca8k.h
++@@ -422,6 +422,7 @@ struct qca8k_fdb {
++ /* Common setup function */
++ extern const struct qca8k_mib_desc ar8327_mib[];
++ extern const struct regmap_access_table qca8k_readable_table;
+++int qca8k_mib_init(struct qca8k_priv *priv);
++ 
++ /* Common read/write/rmw function */
++ int qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val);
++@@ -431,4 +432,7 @@ int qca8k_rmw(struct qca8k_priv *priv, u
++ int qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len);
++ int qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len);
++ 
+++/* Common ops function */
+++int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask);
+++
++ #endif /* __QCA8K_H */
+diff --git a/target/linux/generic/backport-5.15/771-v6.0-07-net-dsa-qca8k-move-port-set-status-eee-ethtool-stats.patch b/target/linux/generic/backport-5.15/771-v6.0-07-net-dsa-qca8k-move-port-set-status-eee-ethtool-stats.patch
 new file mode 100644
-index 0000000000..29530065a1
+index 0000000000..6fd1c66b0a
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/760-net-next-net-dsa-qca8k-add-set_ageing_time-support.patch
-@@ -0,0 +1,78 @@
-+From 6a3bdc5209f45d2af83aa92433ab6e5cf2297aa4 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Mon, 22 Nov 2021 16:23:47 +0100
-+Subject: net: dsa: qca8k: add set_ageing_time support
++++ b/target/linux/generic/backport-5.15/771-v6.0-07-net-dsa-qca8k-move-port-set-status-eee-ethtool-stats.patch
+@@ -0,0 +1,281 @@
++From 472fcea160f27a5d9b7526093d9d8d89ba0b6137 Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Wed, 27 Jul 2022 13:35:16 +0200
++Subject: [PATCH 07/14] net: dsa: qca8k: move port set status/eee/ethtool stats
++ function to common code
 +
-+qca8k support setting ageing time in step of 7s. Add support for it and
-+set the max value accepted of 7645m.
-+Documentation talks about support for 10000m but that values doesn't
-+make sense as the value doesn't match the max value in the reg.
++The same logic to disable/enable port, set eee and get ethtool stats is
++used by drivers based on qca8k family switch.
++Move it to common code to make it accessible also by other drivers.
++While at it also drop unnecessary qca8k_priv cast for void pointers.
 +
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
 +Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 +---
-+ drivers/net/dsa/qca8k.c | 25 +++++++++++++++++++++++++
-+ drivers/net/dsa/qca8k.h |  3 +++
-+ 2 files changed, 28 insertions(+)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -1261,6 +1261,10 @@ qca8k_setup(struct dsa_switch *ds)
-+ 	/* We don't have interrupts for link changes, so we need to poll */
-+ 	ds->pcs_poll = true;
++ drivers/net/dsa/qca/qca8k-8xxx.c   | 105 -----------------------------
++ drivers/net/dsa/qca/qca8k-common.c | 102 ++++++++++++++++++++++++++++
++ drivers/net/dsa/qca/qca8k.h        |  11 +++
++ 3 files changed, 113 insertions(+), 105 deletions(-)
++
++--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
++@@ -768,21 +768,6 @@ out:
++ 	return ret;
++ }
 + 
-++	/* Set min a max ageing value supported */
-++	ds->ageing_time_min = 7000;
-++	ds->ageing_time_max = 458745000;
-++
-+ 	return 0;
++-static void
++-qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable)
++-{
++-	u32 mask = QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
++-
++-	/* Port 0 and 6 have no internal PHY */
++-	if (port > 0 && port < 6)
++-		mask |= QCA8K_PORT_STATUS_LINK_AUTO;
++-
++-	if (enable)
++-		regmap_set_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
++-	else
++-		regmap_clear_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
++-}
++-
++ static int
++ qca8k_phy_eth_busy_wait(struct qca8k_mgmt_eth_data *mgmt_eth_data,
++ 			struct sk_buff *read_skb, u32 *val)
++@@ -1974,20 +1959,6 @@ qca8k_phylink_mac_link_up(struct dsa_swi
++ 	qca8k_write(priv, QCA8K_REG_PORT_STATUS(port), reg);
 + }
 + 
-+@@ -1801,6 +1805,26 @@ qca8k_port_fast_age(struct dsa_switch *d
++-static void
++-qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int i;
++-
++-	if (stringset != ETH_SS_STATS)
++-		return;
++-
++-	for (i = 0; i < priv->info->mib_count; i++)
++-		strncpy(data + i * ETH_GSTRING_LEN, ar8327_mib[i].name,
++-			ETH_GSTRING_LEN);
++-}
++-
++ static void qca8k_mib_autocast_handler(struct dsa_switch *ds, struct sk_buff *skb)
++ {
++ 	struct qca8k_mib_eth_data *mib_eth_data;
++@@ -2078,82 +2049,6 @@ exit:
 + }
 + 
-+ static int
-++qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
++ static void
++-qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
++-			uint64_t *data)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	const struct qca8k_mib_desc *mib;
++-	u32 reg, i, val;
++-	u32 hi = 0;
++-	int ret;
++-
++-	if (priv->mgmt_master && priv->info->ops->autocast_mib &&
++-	    priv->info->ops->autocast_mib(ds, port, data) > 0)
++-		return;
++-
++-	for (i = 0; i < priv->info->mib_count; i++) {
++-		mib = &ar8327_mib[i];
++-		reg = QCA8K_PORT_MIB_COUNTER(port) + mib->offset;
++-
++-		ret = qca8k_read(priv, reg, &val);
++-		if (ret < 0)
++-			continue;
++-
++-		if (mib->size == 2) {
++-			ret = qca8k_read(priv, reg + 4, &hi);
++-			if (ret < 0)
++-				continue;
++-		}
++-
++-		data[i] = val;
++-		if (mib->size == 2)
++-			data[i] |= (u64)hi << 32;
++-	}
++-}
++-
++-static int
++-qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-
++-	if (sset != ETH_SS_STATS)
++-		return 0;
++-
++-	return priv->info->mib_count;
++-}
++-
++-static int
++-qca8k_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *eee)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	u32 lpi_en = QCA8K_REG_EEE_CTRL_LPI_EN(port);
++-	u32 reg;
++-	int ret;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	ret = qca8k_read(priv, QCA8K_REG_EEE_CTRL, &reg);
++-	if (ret < 0)
++-		goto exit;
++-
++-	if (eee->eee_enabled)
++-		reg |= lpi_en;
++-	else
++-		reg &= ~lpi_en;
++-	ret = qca8k_write(priv, QCA8K_REG_EEE_CTRL, reg);
++-
++-exit:
++-	mutex_unlock(&priv->reg_mutex);
++-	return ret;
++-}
++-
++-static int
++-qca8k_get_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e)
++-{
++-	/* Nothing to do on the port's MAC */
++-	return 0;
++-}
++-
++-static void
++ qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
++ {
++ 	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++--- a/drivers/net/dsa/qca/qca8k-common.c
+++++ b/drivers/net/dsa/qca/qca8k-common.c
++@@ -174,3 +174,105 @@ exit:
++ 	mutex_unlock(&priv->reg_mutex);
++ 	return ret;
++ }
+++
+++void qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable)
 ++{
-++	struct qca8k_priv *priv = ds->priv;
-++	unsigned int secs = msecs / 1000;
-++	u32 val;
+++	u32 mask = QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
 ++
-++	/* AGE_TIME reg is set in 7s step */
-++	val = secs / 7;
+++	/* Port 0 and 6 have no internal PHY */
+++	if (port > 0 && port < 6)
+++		mask |= QCA8K_PORT_STATUS_LINK_AUTO;
 ++
-++	/* Handle case with 0 as val to NOT disable
-++	 * learning
-++	 */
-++	if (!val)
-++		val = 1;
+++	if (enable)
+++		regmap_set_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
+++	else
+++		regmap_clear_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
+++}
 ++
-++	return regmap_update_bits(priv->regmap, QCA8K_REG_ATU_CTRL, QCA8K_ATU_AGE_TIME_MASK,
-++				  QCA8K_ATU_AGE_TIME(val));
+++void qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset,
+++		       uint8_t *data)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int i;
+++
+++	if (stringset != ETH_SS_STATS)
+++		return;
+++
+++	for (i = 0; i < priv->info->mib_count; i++)
+++		strncpy(data + i * ETH_GSTRING_LEN, ar8327_mib[i].name,
+++			ETH_GSTRING_LEN);
 ++}
 ++
-++static int
-+ qca8k_port_enable(struct dsa_switch *ds, int port,
-+ 		  struct phy_device *phy)
-+ {
-+@@ -1999,6 +2023,7 @@ static const struct dsa_switch_ops qca8k
-+ 	.get_strings		= qca8k_get_strings,
-+ 	.get_ethtool_stats	= qca8k_get_ethtool_stats,
-+ 	.get_sset_count		= qca8k_get_sset_count,
-++	.set_ageing_time	= qca8k_set_ageing_time,
-+ 	.get_mac_eee		= qca8k_get_mac_eee,
-+ 	.set_mac_eee		= qca8k_set_mac_eee,
-+ 	.port_enable		= qca8k_port_enable,
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -175,6 +175,9 @@
-+ #define   QCA8K_VTU_FUNC1_BUSY				BIT(31)
-+ #define   QCA8K_VTU_FUNC1_VID_MASK			GENMASK(27, 16)
-+ #define   QCA8K_VTU_FUNC1_FULL				BIT(4)
-++#define QCA8K_REG_ATU_CTRL				0x618
-++#define   QCA8K_ATU_AGE_TIME_MASK			GENMASK(15, 0)
-++#define   QCA8K_ATU_AGE_TIME(x)				FIELD_PREP(QCA8K_ATU_AGE_TIME_MASK, (x))
-+ #define QCA8K_REG_GLOBAL_FW_CTRL0			0x620
-+ #define   QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN		BIT(10)
-+ #define QCA8K_REG_GLOBAL_FW_CTRL1			0x624
-diff --git a/target/linux/generic/backport-5.15/761-net-next-net-dsa-qca8k-add-support-for-mdb_add-del.patch b/target/linux/generic/backport-5.15/761-net-next-net-dsa-qca8k-add-support-for-mdb_add-del.patch
-new file mode 100644
-index 0000000000..fa022d7ae6
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/761-net-next-net-dsa-qca8k-add-support-for-mdb_add-del.patch
-@@ -0,0 +1,142 @@
-+From ba8f870dfa635113ce6e8095a5eb1835ecde2e9e Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Mon, 22 Nov 2021 16:23:48 +0100
-+Subject: net: dsa: qca8k: add support for mdb_add/del
-+
-+Add support for mdb add/del function. The ARL table is used to insert
-+the rule. The rule will be searched, deleted and reinserted with the
-+port mask updated. The function will check if the rule has to be updated
-+or insert directly with no deletion of the old rule.
-+If every port is removed from the port mask, the rule is removed.
-+The rule is set STATIC in the ARL table (aka it doesn't age) to not be
-+flushed by fast age function.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++
-+ 1 file changed, 99 insertions(+)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -436,6 +436,81 @@ qca8k_fdb_flush(struct qca8k_priv *priv)
-+ }
-+ 
-+ static int
-++qca8k_fdb_search_and_insert(struct qca8k_priv *priv, u8 port_mask,
-++			    const u8 *mac, u16 vid)
+++void qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
+++			     uint64_t *data)
 ++{
-++	struct qca8k_fdb fdb = { 0 };
+++	struct qca8k_priv *priv = ds->priv;
+++	const struct qca8k_mib_desc *mib;
+++	u32 reg, i, val;
+++	u32 hi = 0;
 ++	int ret;
 ++
-++	mutex_lock(&priv->reg_mutex);
+++	if (priv->mgmt_master && priv->info->ops->autocast_mib &&
+++	    priv->info->ops->autocast_mib(ds, port, data) > 0)
+++		return;
 ++
-++	qca8k_fdb_write(priv, vid, 0, mac, 0);
-++	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
-++	if (ret < 0)
-++		goto exit;
+++	for (i = 0; i < priv->info->mib_count; i++) {
+++		mib = &ar8327_mib[i];
+++		reg = QCA8K_PORT_MIB_COUNTER(port) + mib->offset;
 ++
-++	ret = qca8k_fdb_read(priv, &fdb);
-++	if (ret < 0)
-++		goto exit;
+++		ret = qca8k_read(priv, reg, &val);
+++		if (ret < 0)
+++			continue;
 ++
-++	/* Rule exist. Delete first */
-++	if (!fdb.aging) {
-++		ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
-++		if (ret)
-++			goto exit;
+++		if (mib->size == 2) {
+++			ret = qca8k_read(priv, reg + 4, &hi);
+++			if (ret < 0)
+++				continue;
+++		}
+++
+++		data[i] = val;
+++		if (mib->size == 2)
+++			data[i] |= (u64)hi << 32;
 ++	}
+++}
 ++
-++	/* Add port to fdb portmask */
-++	fdb.port_mask |= port_mask;
+++int qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset)
+++{
+++	struct qca8k_priv *priv = ds->priv;
 ++
-++	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
-++	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
+++	if (sset != ETH_SS_STATS)
+++		return 0;
 ++
-++exit:
-++	mutex_unlock(&priv->reg_mutex);
-++	return ret;
+++	return priv->info->mib_count;
 ++}
 ++
-++static int
-++qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask,
-++			 const u8 *mac, u16 vid)
+++int qca8k_set_mac_eee(struct dsa_switch *ds, int port,
+++		      struct ethtool_eee *eee)
 ++{
-++	struct qca8k_fdb fdb = { 0 };
+++	u32 lpi_en = QCA8K_REG_EEE_CTRL_LPI_EN(port);
+++	struct qca8k_priv *priv = ds->priv;
+++	u32 reg;
 ++	int ret;
 ++
 ++	mutex_lock(&priv->reg_mutex);
-++
-++	qca8k_fdb_write(priv, vid, 0, mac, 0);
-++	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
+++	ret = qca8k_read(priv, QCA8K_REG_EEE_CTRL, &reg);
 ++	if (ret < 0)
 ++		goto exit;
 ++
-++	/* Rule doesn't exist. Why delete? */
-++	if (!fdb.aging) {
-++		ret = -EINVAL;
-++		goto exit;
-++	}
-++
-++	ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
-++	if (ret)
-++		goto exit;
-++
-++	/* Only port in the rule is this port. Don't re insert */
-++	if (fdb.port_mask == port_mask)
-++		goto exit;
-++
-++	/* Remove port from port mask */
-++	fdb.port_mask &= ~port_mask;
-++
-++	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
-++	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
+++	if (eee->eee_enabled)
+++		reg |= lpi_en;
+++	else
+++		reg &= ~lpi_en;
+++	ret = qca8k_write(priv, QCA8K_REG_EEE_CTRL, reg);
 ++
 ++exit:
 ++	mutex_unlock(&priv->reg_mutex);
 ++	return ret;
 ++}
 ++
-++static int
-+ qca8k_vlan_access(struct qca8k_priv *priv, enum qca8k_vlan_cmd cmd, u16 vid)
-+ {
-+ 	u32 reg;
-+@@ -1930,6 +2005,28 @@ qca8k_port_fdb_dump(struct dsa_switch *d
+++int qca8k_get_mac_eee(struct dsa_switch *ds, int port,
+++		      struct ethtool_eee *e)
+++{
+++	/* Nothing to do on the port's MAC */
+++	return 0;
+++}
++--- a/drivers/net/dsa/qca/qca8k.h
+++++ b/drivers/net/dsa/qca/qca8k.h
++@@ -423,6 +423,7 @@ struct qca8k_fdb {
++ extern const struct qca8k_mib_desc ar8327_mib[];
++ extern const struct regmap_access_table qca8k_readable_table;
++ int qca8k_mib_init(struct qca8k_priv *priv);
+++void qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable);
++ 
++ /* Common read/write/rmw function */
++ int qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val);
++@@ -435,4 +436,14 @@ int qca8k_bulk_write(struct qca8k_priv *
++ /* Common ops function */
++ int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask);
++ 
+++/* Common ethtool stats function */
+++void qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data);
+++void qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
+++			     uint64_t *data);
+++int qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset);
+++
+++/* Common eee function */
+++int qca8k_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *eee);
+++int qca8k_get_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e);
+++
++ #endif /* __QCA8K_H */
+diff --git a/target/linux/generic/backport-5.15/771-v6.0-08-net-dsa-qca8k-move-bridge-functions-to-common-code.patch b/target/linux/generic/backport-5.15/771-v6.0-08-net-dsa-qca8k-move-bridge-functions-to-common-code.patch
+new file mode 100644
+index 0000000000..3ca682d72c
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/771-v6.0-08-net-dsa-qca8k-move-bridge-functions-to-common-code.patch
+@@ -0,0 +1,237 @@
++From fd3cae2f3ac190d06e48f43739237e02f9dc51ff Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Wed, 27 Jul 2022 13:35:17 +0200
++Subject: [PATCH 08/14] net: dsa: qca8k: move bridge functions to common code
++
++The same bridge functions are used by drivers based on qca8k family
++switch. Move them to common code to make them accessible also by other
++drivers.
++While at it also drop unnecessary qca8k_priv cast for void pointers.
++
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/dsa/qca/qca8k-8xxx.c   | 93 ------------------------------
++ drivers/net/dsa/qca/qca8k-common.c | 93 ++++++++++++++++++++++++++++++
++ drivers/net/dsa/qca/qca8k.h        |  9 +++
++ 3 files changed, 102 insertions(+), 93 deletions(-)
++
++--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
++@@ -2049,97 +2049,6 @@ exit:
 + }
 + 
-+ static int
-++qca8k_port_mdb_add(struct dsa_switch *ds, int port,
-++		   const struct switchdev_obj_port_mdb *mdb)
++ static void
++-qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	u32 stp_state;
++-
++-	switch (state) {
++-	case BR_STATE_DISABLED:
++-		stp_state = QCA8K_PORT_LOOKUP_STATE_DISABLED;
++-		break;
++-	case BR_STATE_BLOCKING:
++-		stp_state = QCA8K_PORT_LOOKUP_STATE_BLOCKING;
++-		break;
++-	case BR_STATE_LISTENING:
++-		stp_state = QCA8K_PORT_LOOKUP_STATE_LISTENING;
++-		break;
++-	case BR_STATE_LEARNING:
++-		stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING;
++-		break;
++-	case BR_STATE_FORWARDING:
++-	default:
++-		stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD;
++-		break;
++-	}
++-
++-	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++-		  QCA8K_PORT_LOOKUP_STATE_MASK, stp_state);
++-}
++-
++-static int
++-qca8k_port_bridge_join(struct dsa_switch *ds, int port, struct net_device *br)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	int port_mask, cpu_port;
++-	int i, ret;
++-
++-	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
++-	port_mask = BIT(cpu_port);
++-
++-	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++-		if (dsa_is_cpu_port(ds, i))
++-			continue;
++-		if (dsa_to_port(ds, i)->bridge_dev != br)
++-			continue;
++-		/* Add this port to the portvlan mask of the other ports
++-		 * in the bridge
++-		 */
++-		ret = regmap_set_bits(priv->regmap,
++-				      QCA8K_PORT_LOOKUP_CTRL(i),
++-				      BIT(port));
++-		if (ret)
++-			return ret;
++-		if (i != port)
++-			port_mask |= BIT(i);
++-	}
++-
++-	/* Add all other ports to this ports portvlan mask */
++-	ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++-			QCA8K_PORT_LOOKUP_MEMBER, port_mask);
++-
++-	return ret;
++-}
++-
++-static void
++-qca8k_port_bridge_leave(struct dsa_switch *ds, int port, struct net_device *br)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	int cpu_port, i;
++-
++-	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
++-
++-	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++-		if (dsa_is_cpu_port(ds, i))
++-			continue;
++-		if (dsa_to_port(ds, i)->bridge_dev != br)
++-			continue;
++-		/* Remove this port to the portvlan mask of the other ports
++-		 * in the bridge
++-		 */
++-		regmap_clear_bits(priv->regmap,
++-				  QCA8K_PORT_LOOKUP_CTRL(i),
++-				  BIT(port));
++-	}
++-
++-	/* Set the cpu port to be the only one in the portvlan mask of
++-	 * this port
++-	 */
++-	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++-		  QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port));
++-}
++-
++-static void
++ qca8k_port_fast_age(struct dsa_switch *ds, int port)
++ {
++ 	struct qca8k_priv *priv = ds->priv;
++--- a/drivers/net/dsa/qca/qca8k-common.c
+++++ b/drivers/net/dsa/qca/qca8k-common.c
++@@ -9,6 +9,7 @@
++ #include <linux/netdevice.h>
++ #include <linux/bitfield.h>
++ #include <net/dsa.h>
+++#include <linux/if_bridge.h>
++ 
++ #include "qca8k.h"
++ 
++@@ -276,3 +277,93 @@ int qca8k_get_mac_eee(struct dsa_switch
++ 	/* Nothing to do on the port's MAC */
++ 	return 0;
++ }
+++
+++void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
 ++{
 ++	struct qca8k_priv *priv = ds->priv;
-++	const u8 *addr = mdb->addr;
-++	u16 vid = mdb->vid;
+++	u32 stp_state;
+++
+++	switch (state) {
+++	case BR_STATE_DISABLED:
+++		stp_state = QCA8K_PORT_LOOKUP_STATE_DISABLED;
+++		break;
+++	case BR_STATE_BLOCKING:
+++		stp_state = QCA8K_PORT_LOOKUP_STATE_BLOCKING;
+++		break;
+++	case BR_STATE_LISTENING:
+++		stp_state = QCA8K_PORT_LOOKUP_STATE_LISTENING;
+++		break;
+++	case BR_STATE_LEARNING:
+++		stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING;
+++		break;
+++	case BR_STATE_FORWARDING:
+++	default:
+++		stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD;
+++		break;
+++	}
+++
+++	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+++		  QCA8K_PORT_LOOKUP_STATE_MASK, stp_state);
+++}
+++
+++int qca8k_port_bridge_join(struct dsa_switch *ds, int port,
+++			   struct net_device *br)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int port_mask, cpu_port;
+++	int i, ret;
+++
+++	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
+++	port_mask = BIT(cpu_port);
+++
+++	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+++		if (dsa_is_cpu_port(ds, i))
+++			continue;
+++		if (dsa_to_port(ds, i)->bridge_dev != br)
+++			continue;
+++		/* Add this port to the portvlan mask of the other ports
+++		 * in the bridge
+++		 */
+++		ret = regmap_set_bits(priv->regmap,
+++				      QCA8K_PORT_LOOKUP_CTRL(i),
+++				      BIT(port));
+++		if (ret)
+++			return ret;
+++		if (i != port)
+++			port_mask |= BIT(i);
+++	}
 ++
-++	return qca8k_fdb_search_and_insert(priv, BIT(port), addr, vid);
+++	/* Add all other ports to this ports portvlan mask */
+++	ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+++			QCA8K_PORT_LOOKUP_MEMBER, port_mask);
+++
+++	return ret;
 ++}
 ++
-++static int
-++qca8k_port_mdb_del(struct dsa_switch *ds, int port,
-++		   const struct switchdev_obj_port_mdb *mdb)
+++void qca8k_port_bridge_leave(struct dsa_switch *ds, int port,
+++			     struct net_device *br)
 ++{
 ++	struct qca8k_priv *priv = ds->priv;
-++	const u8 *addr = mdb->addr;
-++	u16 vid = mdb->vid;
+++	int cpu_port, i;
 ++
-++	return qca8k_fdb_search_and_del(priv, BIT(port), addr, vid);
-++}
+++	cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
 ++
-++static int
-+ qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
-+ 			  struct netlink_ext_ack *extack)
-+ {
-+@@ -2037,6 +2134,8 @@ static const struct dsa_switch_ops qca8k
-+ 	.port_fdb_add		= qca8k_port_fdb_add,
-+ 	.port_fdb_del		= qca8k_port_fdb_del,
-+ 	.port_fdb_dump		= qca8k_port_fdb_dump,
-++	.port_mdb_add		= qca8k_port_mdb_add,
-++	.port_mdb_del		= qca8k_port_mdb_del,
-+ 	.port_vlan_filtering	= qca8k_port_vlan_filtering,
-+ 	.port_vlan_add		= qca8k_port_vlan_add,
-+ 	.port_vlan_del		= qca8k_port_vlan_del,
-diff --git a/target/linux/generic/backport-5.15/762-net-next-net-dsa-qca8k-add-support-for-mirror-mode.patch b/target/linux/generic/backport-5.15/762-net-next-net-dsa-qca8k-add-support-for-mirror-mode.patch
+++	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+++		if (dsa_is_cpu_port(ds, i))
+++			continue;
+++		if (dsa_to_port(ds, i)->bridge_dev != br)
+++			continue;
+++		/* Remove this port to the portvlan mask of the other ports
+++		 * in the bridge
+++		 */
+++		regmap_clear_bits(priv->regmap,
+++				  QCA8K_PORT_LOOKUP_CTRL(i),
+++				  BIT(port));
+++	}
+++
+++	/* Set the cpu port to be the only one in the portvlan mask of
+++	 * this port
+++	 */
+++	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+++		  QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port));
+++}
++--- a/drivers/net/dsa/qca/qca8k.h
+++++ b/drivers/net/dsa/qca/qca8k.h
++@@ -446,4 +446,11 @@ int qca8k_get_sset_count(struct dsa_swit
++ int qca8k_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *eee);
++ int qca8k_get_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e);
++ 
+++/* Common bridge function */
+++void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state);
+++int qca8k_port_bridge_join(struct dsa_switch *ds, int port,
+++			   struct net_device *br);
+++void qca8k_port_bridge_leave(struct dsa_switch *ds, int port,
+++			     struct net_device *br);
+++
++ #endif /* __QCA8K_H */
+diff --git a/target/linux/generic/backport-5.15/771-v6.0-09-net-dsa-qca8k-move-set-age-MTU-port-enable-disable-f.patch b/target/linux/generic/backport-5.15/771-v6.0-09-net-dsa-qca8k-move-set-age-MTU-port-enable-disable-f.patch
 new file mode 100644
-index 0000000000..69e9b381c2
+index 0000000000..e3414408d6
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/762-net-next-net-dsa-qca8k-add-support-for-mirror-mode.patch
-@@ -0,0 +1,155 @@
-+From 2c1bdbc7e7560d7de754cad277d968d56bb1899e Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Tue, 23 Nov 2021 03:59:10 +0100
-+Subject: net: dsa: qca8k: add support for mirror mode
++++ b/target/linux/generic/backport-5.15/771-v6.0-09-net-dsa-qca8k-move-set-age-MTU-port-enable-disable-f.patch
+@@ -0,0 +1,227 @@
++From b3a302b171f73425b41de8d3357fae3fa7057322 Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Wed, 27 Jul 2022 13:35:18 +0200
++Subject: [PATCH 09/14] net: dsa: qca8k: move set age/MTU/port enable/disable
++ functions to common code
 +
-+The switch supports mirror mode. Only one port can set as mirror port and
-+every other port can set to both ingress and egress mode. The mirror
-+port is disabled and reverted to normal operation once every port is
-+removed from sending packet to it.
++The same set age, MTU and port enable/disable function are used by
++driver based on qca8k family switch.
++Move them to common code to make them accessible also by other drivers.
++While at it also drop unnecessary qca8k_priv cast for void pointers.
 +
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 +---
-+ drivers/net/dsa/qca8k.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++++
-+ drivers/net/dsa/qca8k.h |  4 +++
-+ 2 files changed, 99 insertions(+)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -2027,6 +2027,99 @@ qca8k_port_mdb_del(struct dsa_switch *ds
++ drivers/net/dsa/qca/qca8k-8xxx.c   | 88 ------------------------------
++ drivers/net/dsa/qca/qca8k-common.c | 85 +++++++++++++++++++++++++++++
++ drivers/net/dsa/qca/qca8k.h        | 12 ++++
++ 3 files changed, 97 insertions(+), 88 deletions(-)
++
++--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
++@@ -2059,94 +2059,6 @@ qca8k_port_fast_age(struct dsa_switch *d
 + }
 + 
 + static int
-++qca8k_port_mirror_add(struct dsa_switch *ds, int port,
-++		      struct dsa_mall_mirror_tc_entry *mirror,
-++		      bool ingress)
++-qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	unsigned int secs = msecs / 1000;
++-	u32 val;
++-
++-	/* AGE_TIME reg is set in 7s step */
++-	val = secs / 7;
++-
++-	/* Handle case with 0 as val to NOT disable
++-	 * learning
++-	 */
++-	if (!val)
++-		val = 1;
++-
++-	return regmap_update_bits(priv->regmap, QCA8K_REG_ATU_CTRL, QCA8K_ATU_AGE_TIME_MASK,
++-				  QCA8K_ATU_AGE_TIME(val));
++-}
++-
++-static int
++-qca8k_port_enable(struct dsa_switch *ds, int port,
++-		  struct phy_device *phy)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-
++-	qca8k_port_set_status(priv, port, 1);
++-	priv->port_enabled_map |= BIT(port);
++-
++-	if (dsa_is_user_port(ds, port))
++-		phy_support_asym_pause(phy);
++-
++-	return 0;
++-}
++-
++-static void
++-qca8k_port_disable(struct dsa_switch *ds, int port)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-
++-	qca8k_port_set_status(priv, port, 0);
++-	priv->port_enabled_map &= ~BIT(port);
++-}
++-
++-static int
++-qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret;
++-
++-	/* We have only have a general MTU setting.
++-	 * DSA always set the CPU port's MTU to the largest MTU of the slave
++-	 * ports.
++-	 * Setting MTU just for the CPU port is sufficient to correctly set a
++-	 * value for every port.
++-	 */
++-	if (!dsa_is_cpu_port(ds, port))
++-		return 0;
++-
++-	/* To change the MAX_FRAME_SIZE the cpu ports must be off or
++-	 * the switch panics.
++-	 * Turn off both cpu ports before applying the new value to prevent
++-	 * this.
++-	 */
++-	if (priv->port_enabled_map & BIT(0))
++-		qca8k_port_set_status(priv, 0, 0);
++-
++-	if (priv->port_enabled_map & BIT(6))
++-		qca8k_port_set_status(priv, 6, 0);
++-
++-	/* Include L2 header / FCS length */
++-	ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, new_mtu + ETH_HLEN + ETH_FCS_LEN);
++-
++-	if (priv->port_enabled_map & BIT(0))
++-		qca8k_port_set_status(priv, 0, 1);
++-
++-	if (priv->port_enabled_map & BIT(6))
++-		qca8k_port_set_status(priv, 6, 1);
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_port_max_mtu(struct dsa_switch *ds, int port)
++-{
++-	return QCA8K_MAX_MTU;
++-}
++-
++-static int
++ qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
++ 		      u16 port_mask, u16 vid)
++ {
++--- a/drivers/net/dsa/qca/qca8k-common.c
+++++ b/drivers/net/dsa/qca/qca8k-common.c
++@@ -367,3 +367,88 @@ void qca8k_port_bridge_leave(struct dsa_
++ 	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++ 		  QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port));
++ }
+++
+++int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
 ++{
 ++	struct qca8k_priv *priv = ds->priv;
-++	int monitor_port, ret;
-++	u32 reg, val;
-++
-++	/* Check for existent entry */
-++	if ((ingress ? priv->mirror_rx : priv->mirror_tx) & BIT(port))
-++		return -EEXIST;
+++	unsigned int secs = msecs / 1000;
+++	u32 val;
 ++
-++	ret = regmap_read(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0, &val);
-++	if (ret)
-++		return ret;
+++	/* AGE_TIME reg is set in 7s step */
+++	val = secs / 7;
 ++
-++	/* QCA83xx can have only one port set to mirror mode.
-++	 * Check that the correct port is requested and return error otherwise.
-++	 * When no mirror port is set, the values is set to 0xF
+++	/* Handle case with 0 as val to NOT disable
+++	 * learning
 ++	 */
-++	monitor_port = FIELD_GET(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
-++	if (monitor_port != 0xF && monitor_port != mirror->to_local_port)
-++		return -EEXIST;
+++	if (!val)
+++		val = 1;
 ++
-++	/* Set the monitor port */
-++	val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM,
-++			 mirror->to_local_port);
-++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
-++				 QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
-++	if (ret)
-++		return ret;
+++	return regmap_update_bits(priv->regmap, QCA8K_REG_ATU_CTRL,
+++				  QCA8K_ATU_AGE_TIME_MASK,
+++				  QCA8K_ATU_AGE_TIME(val));
+++}
 ++
-++	if (ingress) {
-++		reg = QCA8K_PORT_LOOKUP_CTRL(port);
-++		val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN;
-++	} else {
-++		reg = QCA8K_REG_PORT_HOL_CTRL1(port);
-++		val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN;
-++	}
+++int qca8k_port_enable(struct dsa_switch *ds, int port,
+++		      struct phy_device *phy)
+++{
+++	struct qca8k_priv *priv = ds->priv;
 ++
-++	ret = regmap_update_bits(priv->regmap, reg, val, val);
-++	if (ret)
-++		return ret;
+++	qca8k_port_set_status(priv, port, 1);
+++	priv->port_enabled_map |= BIT(port);
 ++
-++	/* Track mirror port for tx and rx to decide when the
-++	 * mirror port has to be disabled.
-++	 */
-++	if (ingress)
-++		priv->mirror_rx |= BIT(port);
-++	else
-++		priv->mirror_tx |= BIT(port);
+++	if (dsa_is_user_port(ds, port))
+++		phy_support_asym_pause(phy);
 ++
 ++	return 0;
 ++}
 ++
-++static void
-++qca8k_port_mirror_del(struct dsa_switch *ds, int port,
-++		      struct dsa_mall_mirror_tc_entry *mirror)
+++void qca8k_port_disable(struct dsa_switch *ds, int port)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++
+++	qca8k_port_set_status(priv, port, 0);
+++	priv->port_enabled_map &= ~BIT(port);
+++}
+++
+++int qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
 ++{
 ++	struct qca8k_priv *priv = ds->priv;
-++	u32 reg, val;
 ++	int ret;
 ++
-++	if (mirror->ingress) {
-++		reg = QCA8K_PORT_LOOKUP_CTRL(port);
-++		val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN;
-++	} else {
-++		reg = QCA8K_REG_PORT_HOL_CTRL1(port);
-++		val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN;
-++	}
+++	/* We have only have a general MTU setting.
+++	 * DSA always set the CPU port's MTU to the largest MTU of the slave
+++	 * ports.
+++	 * Setting MTU just for the CPU port is sufficient to correctly set a
+++	 * value for every port.
+++	 */
+++	if (!dsa_is_cpu_port(ds, port))
+++		return 0;
 ++
-++	ret = regmap_clear_bits(priv->regmap, reg, val);
-++	if (ret)
-++		goto err;
+++	/* To change the MAX_FRAME_SIZE the cpu ports must be off or
+++	 * the switch panics.
+++	 * Turn off both cpu ports before applying the new value to prevent
+++	 * this.
+++	 */
+++	if (priv->port_enabled_map & BIT(0))
+++		qca8k_port_set_status(priv, 0, 0);
 ++
-++	if (mirror->ingress)
-++		priv->mirror_rx &= ~BIT(port);
-++	else
-++		priv->mirror_tx &= ~BIT(port);
+++	if (priv->port_enabled_map & BIT(6))
+++		qca8k_port_set_status(priv, 6, 0);
 ++
-++	/* No port set to send packet to mirror port. Disable mirror port */
-++	if (!priv->mirror_rx && !priv->mirror_tx) {
-++		val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, 0xF);
-++		ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
-++					 QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
-++		if (ret)
-++			goto err;
-++	}
-++err:
-++	dev_err(priv->dev, "Failed to del mirror port from %d", port);
+++	/* Include L2 header / FCS length */
+++	ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, new_mtu +
+++			  ETH_HLEN + ETH_FCS_LEN);
+++
+++	if (priv->port_enabled_map & BIT(0))
+++		qca8k_port_set_status(priv, 0, 1);
+++
+++	if (priv->port_enabled_map & BIT(6))
+++		qca8k_port_set_status(priv, 6, 1);
+++
+++	return ret;
 ++}
 ++
-++static int
-+ qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
-+ 			  struct netlink_ext_ack *extack)
-+ {
-+@@ -2136,6 +2229,8 @@ static const struct dsa_switch_ops qca8k
-+ 	.port_fdb_dump		= qca8k_port_fdb_dump,
-+ 	.port_mdb_add		= qca8k_port_mdb_add,
-+ 	.port_mdb_del		= qca8k_port_mdb_del,
-++	.port_mirror_add	= qca8k_port_mirror_add,
-++	.port_mirror_del	= qca8k_port_mirror_del,
-+ 	.port_vlan_filtering	= qca8k_port_vlan_filtering,
-+ 	.port_vlan_add		= qca8k_port_vlan_add,
-+ 	.port_vlan_del		= qca8k_port_vlan_del,
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -180,6 +180,7 @@
-+ #define   QCA8K_ATU_AGE_TIME(x)				FIELD_PREP(QCA8K_ATU_AGE_TIME_MASK, (x))
-+ #define QCA8K_REG_GLOBAL_FW_CTRL0			0x620
-+ #define   QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN		BIT(10)
-++#define   QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM		GENMASK(7, 4)
-+ #define QCA8K_REG_GLOBAL_FW_CTRL1			0x624
-+ #define   QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK		GENMASK(30, 24)
-+ #define   QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK		GENMASK(22, 16)
-+@@ -201,6 +202,7 @@
-+ #define   QCA8K_PORT_LOOKUP_STATE_LEARNING		QCA8K_PORT_LOOKUP_STATE(0x3)
-+ #define   QCA8K_PORT_LOOKUP_STATE_FORWARD		QCA8K_PORT_LOOKUP_STATE(0x4)
-+ #define   QCA8K_PORT_LOOKUP_LEARN			BIT(20)
-++#define   QCA8K_PORT_LOOKUP_ING_MIRROR_EN		BIT(25)
-+ 
-+ #define QCA8K_REG_GLOBAL_FC_THRESH			0x800
-+ #define   QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK		GENMASK(24, 16)
-+@@ -305,6 +307,8 @@ struct qca8k_ports_config {
-+ struct qca8k_priv {
-+ 	u8 switch_id;
-+ 	u8 switch_revision;
-++	u8 mirror_rx;
-++	u8 mirror_tx;
-+ 	bool legacy_phy_port_mapping;
-+ 	struct qca8k_ports_config ports_config;
-+ 	struct regmap *regmap;
-diff --git a/target/linux/generic/backport-5.15/763-net-next-net-dsa-qca8k-add-LAG-support.patch b/target/linux/generic/backport-5.15/763-net-next-net-dsa-qca8k-add-LAG-support.patch
+++int qca8k_port_max_mtu(struct dsa_switch *ds, int port)
+++{
+++	return QCA8K_MAX_MTU;
+++}
++--- a/drivers/net/dsa/qca/qca8k.h
+++++ b/drivers/net/dsa/qca/qca8k.h
++@@ -453,4 +453,16 @@ int qca8k_port_bridge_join(struct dsa_sw
++ void qca8k_port_bridge_leave(struct dsa_switch *ds, int port,
++ 			     struct net_device *br);
++ 
+++/* Common port enable/disable function */
+++int qca8k_port_enable(struct dsa_switch *ds, int port,
+++		      struct phy_device *phy);
+++void qca8k_port_disable(struct dsa_switch *ds, int port);
+++
+++/* Common MTU function */
+++int qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu);
+++int qca8k_port_max_mtu(struct dsa_switch *ds, int port);
+++
+++/* Common fast age function */
+++int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs);
+++
++ #endif /* __QCA8K_H */
+diff --git a/target/linux/generic/backport-5.15/771-v6.0-10-net-dsa-qca8k-move-port-FDB-MDB-function-to-common-c.patch b/target/linux/generic/backport-5.15/771-v6.0-10-net-dsa-qca8k-move-port-FDB-MDB-function-to-common-c.patch
 new file mode 100644
-index 0000000000..bfc77db184
+index 0000000000..96468ae74e
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/763-net-next-net-dsa-qca8k-add-LAG-support.patch
-@@ -0,0 +1,288 @@
-+From def975307c01191b6f0170048c3724b0ed3348af Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Tue, 23 Nov 2021 03:59:11 +0100
-+Subject: net: dsa: qca8k: add LAG support
++++ b/target/linux/generic/backport-5.15/771-v6.0-10-net-dsa-qca8k-move-port-FDB-MDB-function-to-common-c.patch
+@@ -0,0 +1,704 @@
++From 2e5bd96eea86a246b4de3bf756f7a11b43e6187d Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Wed, 27 Jul 2022 13:35:19 +0200
++Subject: [PATCH 10/14] net: dsa: qca8k: move port FDB/MDB function to common
++ code
 +
-+Add LAG support to this switch. In Documentation this is described as
-+trunk mode. A max of 4 LAGs are supported and each can support up to 4
-+port. The current tx mode supported is Hash mode with both L2 and L2+3
-+mode.
-+When no port are present in the trunk, the trunk is disabled in the
-+switch.
-+When a port is disconnected, the traffic is redirected to the other
-+available port.
-+The hash mode is global and each LAG require to have the same hash mode
-+set. To change the hash mode when multiple LAG are configured, it's
-+required to remove each LAG and set the desired hash mode to the last.
-+An error is printed when it's asked to set a not supported hadh mode.
++The same port FDB/MDB function are used by drivers based on qca8k family
++switch. Move them to common code to make them accessible also by other
++drivers.
++Also drop bulk read/write functions and make them static
 +
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 +---
-+ drivers/net/dsa/qca8k.c | 177 ++++++++++++++++++++++++++++++++++++++++++++++++
-+ drivers/net/dsa/qca8k.h |  33 +++++++++
-+ 2 files changed, 210 insertions(+)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -1340,6 +1340,9 @@ qca8k_setup(struct dsa_switch *ds)
-+ 	ds->ageing_time_min = 7000;
-+ 	ds->ageing_time_max = 458745000;
++ drivers/net/dsa/qca/qca8k-8xxx.c   | 306 -----------------------------
++ drivers/net/dsa/qca/qca8k-common.c | 297 +++++++++++++++++++++++++++-
++ drivers/net/dsa/qca/qca8k.h        |  25 ++-
++ 3 files changed, 317 insertions(+), 311 deletions(-)
++
++--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
++@@ -442,217 +442,6 @@ static struct regmap_config qca8k_regmap
++ };
 + 
-++	/* Set max number of LAGs supported */
-++	ds->num_lag_ids = QCA8K_NUM_LAGS;
-++
-+ 	return 0;
++ static int
++-qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb)
++-{
++-	u32 reg[3];
++-	int ret;
++-
++-	/* load the ARL table into an array */
++-	ret = qca8k_bulk_read(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
++-	if (ret)
++-		return ret;
++-
++-	/* vid - 83:72 */
++-	fdb->vid = FIELD_GET(QCA8K_ATU_VID_MASK, reg[2]);
++-	/* aging - 67:64 */
++-	fdb->aging = FIELD_GET(QCA8K_ATU_STATUS_MASK, reg[2]);
++-	/* portmask - 54:48 */
++-	fdb->port_mask = FIELD_GET(QCA8K_ATU_PORT_MASK, reg[1]);
++-	/* mac - 47:0 */
++-	fdb->mac[0] = FIELD_GET(QCA8K_ATU_ADDR0_MASK, reg[1]);
++-	fdb->mac[1] = FIELD_GET(QCA8K_ATU_ADDR1_MASK, reg[1]);
++-	fdb->mac[2] = FIELD_GET(QCA8K_ATU_ADDR2_MASK, reg[0]);
++-	fdb->mac[3] = FIELD_GET(QCA8K_ATU_ADDR3_MASK, reg[0]);
++-	fdb->mac[4] = FIELD_GET(QCA8K_ATU_ADDR4_MASK, reg[0]);
++-	fdb->mac[5] = FIELD_GET(QCA8K_ATU_ADDR5_MASK, reg[0]);
++-
++-	return 0;
++-}
++-
++-static void
++-qca8k_fdb_write(struct qca8k_priv *priv, u16 vid, u8 port_mask, const u8 *mac,
++-		u8 aging)
++-{
++-	u32 reg[3] = { 0 };
++-
++-	/* vid - 83:72 */
++-	reg[2] = FIELD_PREP(QCA8K_ATU_VID_MASK, vid);
++-	/* aging - 67:64 */
++-	reg[2] |= FIELD_PREP(QCA8K_ATU_STATUS_MASK, aging);
++-	/* portmask - 54:48 */
++-	reg[1] = FIELD_PREP(QCA8K_ATU_PORT_MASK, port_mask);
++-	/* mac - 47:0 */
++-	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR0_MASK, mac[0]);
++-	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR1_MASK, mac[1]);
++-	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR2_MASK, mac[2]);
++-	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR3_MASK, mac[3]);
++-	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR4_MASK, mac[4]);
++-	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR5_MASK, mac[5]);
++-
++-	/* load the array into the ARL table */
++-	qca8k_bulk_write(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
++-}
++-
++-static int
++-qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd, int port)
++-{
++-	u32 reg;
++-	int ret;
++-
++-	/* Set the command and FDB index */
++-	reg = QCA8K_ATU_FUNC_BUSY;
++-	reg |= cmd;
++-	if (port >= 0) {
++-		reg |= QCA8K_ATU_FUNC_PORT_EN;
++-		reg |= FIELD_PREP(QCA8K_ATU_FUNC_PORT_MASK, port);
++-	}
++-
++-	/* Write the function register triggering the table access */
++-	ret = qca8k_write(priv, QCA8K_REG_ATU_FUNC, reg);
++-	if (ret)
++-		return ret;
++-
++-	/* wait for completion */
++-	ret = qca8k_busy_wait(priv, QCA8K_REG_ATU_FUNC, QCA8K_ATU_FUNC_BUSY);
++-	if (ret)
++-		return ret;
++-
++-	/* Check for table full violation when adding an entry */
++-	if (cmd == QCA8K_FDB_LOAD) {
++-		ret = qca8k_read(priv, QCA8K_REG_ATU_FUNC, &reg);
++-		if (ret < 0)
++-			return ret;
++-		if (reg & QCA8K_ATU_FUNC_FULL)
++-			return -1;
++-	}
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb, int port)
++-{
++-	int ret;
++-
++-	qca8k_fdb_write(priv, fdb->vid, fdb->port_mask, fdb->mac, fdb->aging);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_NEXT, port);
++-	if (ret < 0)
++-		return ret;
++-
++-	return qca8k_fdb_read(priv, fdb);
++-}
++-
++-static int
++-qca8k_fdb_add(struct qca8k_priv *priv, const u8 *mac, u16 port_mask,
++-	      u16 vid, u8 aging)
++-{
++-	int ret;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	qca8k_fdb_write(priv, vid, port_mask, mac, aging);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
++-	mutex_unlock(&priv->reg_mutex);
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac, u16 port_mask, u16 vid)
++-{
++-	int ret;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	qca8k_fdb_write(priv, vid, port_mask, mac, 0);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
++-	mutex_unlock(&priv->reg_mutex);
++-
++-	return ret;
++-}
++-
++-static void
++-qca8k_fdb_flush(struct qca8k_priv *priv)
++-{
++-	mutex_lock(&priv->reg_mutex);
++-	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH, -1);
++-	mutex_unlock(&priv->reg_mutex);
++-}
++-
++-static int
++-qca8k_fdb_search_and_insert(struct qca8k_priv *priv, u8 port_mask,
++-			    const u8 *mac, u16 vid)
++-{
++-	struct qca8k_fdb fdb = { 0 };
++-	int ret;
++-
++-	mutex_lock(&priv->reg_mutex);
++-
++-	qca8k_fdb_write(priv, vid, 0, mac, 0);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
++-	if (ret < 0)
++-		goto exit;
++-
++-	ret = qca8k_fdb_read(priv, &fdb);
++-	if (ret < 0)
++-		goto exit;
++-
++-	/* Rule exist. Delete first */
++-	if (!fdb.aging) {
++-		ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
++-		if (ret)
++-			goto exit;
++-	}
++-
++-	/* Add port to fdb portmask */
++-	fdb.port_mask |= port_mask;
++-
++-	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
++-
++-exit:
++-	mutex_unlock(&priv->reg_mutex);
++-	return ret;
++-}
++-
++-static int
++-qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask,
++-			 const u8 *mac, u16 vid)
++-{
++-	struct qca8k_fdb fdb = { 0 };
++-	int ret;
++-
++-	mutex_lock(&priv->reg_mutex);
++-
++-	qca8k_fdb_write(priv, vid, 0, mac, 0);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
++-	if (ret < 0)
++-		goto exit;
++-
++-	/* Rule doesn't exist. Why delete? */
++-	if (!fdb.aging) {
++-		ret = -EINVAL;
++-		goto exit;
++-	}
++-
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
++-	if (ret)
++-		goto exit;
++-
++-	/* Only port in the rule is this port. Don't re insert */
++-	if (fdb.port_mask == port_mask)
++-		goto exit;
++-
++-	/* Remove port from port mask */
++-	fdb.port_mask &= ~port_mask;
++-
++-	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
++-	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
++-
++-exit:
++-	mutex_unlock(&priv->reg_mutex);
++-	return ret;
++-}
++-
++-static int
++ qca8k_vlan_access(struct qca8k_priv *priv, enum qca8k_vlan_cmd cmd, u16 vid)
++ {
++ 	u32 reg;
++@@ -2048,97 +1837,6 @@ exit:
++ 	return ret;
 + }
 + 
-+@@ -2207,6 +2210,178 @@ qca8k_get_tag_protocol(struct dsa_switch
-+ 	return DSA_TAG_PROTO_QCA;
++-static void
++-qca8k_port_fast_age(struct dsa_switch *ds, int port)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port);
++-	mutex_unlock(&priv->reg_mutex);
++-}
++-
++-static int
++-qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
++-		      u16 port_mask, u16 vid)
++-{
++-	/* Set the vid to the port vlan id if no vid is set */
++-	if (!vid)
++-		vid = QCA8K_PORT_VID_DEF;
++-
++-	return qca8k_fdb_add(priv, addr, port_mask, vid,
++-			     QCA8K_ATU_STATUS_STATIC);
++-}
++-
++-static int
++-qca8k_port_fdb_add(struct dsa_switch *ds, int port,
++-		   const unsigned char *addr, u16 vid)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	u16 port_mask = BIT(port);
++-
++-	return qca8k_port_fdb_insert(priv, addr, port_mask, vid);
++-}
++-
++-static int
++-qca8k_port_fdb_del(struct dsa_switch *ds, int port,
++-		   const unsigned char *addr, u16 vid)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	u16 port_mask = BIT(port);
++-
++-	if (!vid)
++-		vid = QCA8K_PORT_VID_DEF;
++-
++-	return qca8k_fdb_del(priv, addr, port_mask, vid);
++-}
++-
++-static int
++-qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
++-		    dsa_fdb_dump_cb_t *cb, void *data)
++-{
++-	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++-	struct qca8k_fdb _fdb = { 0 };
++-	int cnt = QCA8K_NUM_FDB_RECORDS;
++-	bool is_static;
++-	int ret = 0;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port)) {
++-		if (!_fdb.aging)
++-			break;
++-		is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC);
++-		ret = cb(_fdb.mac, _fdb.vid, is_static, data);
++-		if (ret)
++-			break;
++-	}
++-	mutex_unlock(&priv->reg_mutex);
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_port_mdb_add(struct dsa_switch *ds, int port,
++-		   const struct switchdev_obj_port_mdb *mdb)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	const u8 *addr = mdb->addr;
++-	u16 vid = mdb->vid;
++-
++-	return qca8k_fdb_search_and_insert(priv, BIT(port), addr, vid);
++-}
++-
++-static int
++-qca8k_port_mdb_del(struct dsa_switch *ds, int port,
++-		   const struct switchdev_obj_port_mdb *mdb)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	const u8 *addr = mdb->addr;
++-	u16 vid = mdb->vid;
++-
++-	return qca8k_fdb_search_and_del(priv, BIT(port), addr, vid);
++-}
++-
++ static int
++ qca8k_port_mirror_add(struct dsa_switch *ds, int port,
++ 		      struct dsa_mall_mirror_tc_entry *mirror,
++--- a/drivers/net/dsa/qca/qca8k-common.c
+++++ b/drivers/net/dsa/qca/qca8k-common.c
++@@ -103,7 +103,7 @@ const struct regmap_access_table qca8k_r
++ };
++ 
++ /* TODO: remove these extra ops when we can support regmap bulk read/write */
++-int qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
+++static int qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++ {
++ 	int i, count = len / sizeof(u32), ret;
++ 
++@@ -121,7 +121,7 @@ int qca8k_bulk_read(struct qca8k_priv *p
 + }
 + 
-++static bool
-++qca8k_lag_can_offload(struct dsa_switch *ds,
-++		      struct net_device *lag,
-++		      struct netdev_lag_upper_info *info)
++ /* TODO: remove these extra ops when we can support regmap bulk read/write */
++-int qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
+++static int qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++ {
++ 	int i, count = len / sizeof(u32), ret;
++ 	u32 tmp;
++@@ -149,6 +149,211 @@ int qca8k_busy_wait(struct qca8k_priv *p
++ 				       QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC);
++ }
++ 
+++static int qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb)
 ++{
-++	struct dsa_port *dp;
-++	int id, members = 0;
+++	u32 reg[3];
+++	int ret;
 ++
-++	id = dsa_lag_id(ds->dst, lag);
-++	if (id < 0 || id >= ds->num_lag_ids)
-++		return false;
+++	/* load the ARL table into an array */
+++	ret = qca8k_bulk_read(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
+++	if (ret)
+++		return ret;
 ++
-++	dsa_lag_foreach_port(dp, ds->dst, lag)
-++		/* Includes the port joining the LAG */
-++		members++;
+++	/* vid - 83:72 */
+++	fdb->vid = FIELD_GET(QCA8K_ATU_VID_MASK, reg[2]);
+++	/* aging - 67:64 */
+++	fdb->aging = FIELD_GET(QCA8K_ATU_STATUS_MASK, reg[2]);
+++	/* portmask - 54:48 */
+++	fdb->port_mask = FIELD_GET(QCA8K_ATU_PORT_MASK, reg[1]);
+++	/* mac - 47:0 */
+++	fdb->mac[0] = FIELD_GET(QCA8K_ATU_ADDR0_MASK, reg[1]);
+++	fdb->mac[1] = FIELD_GET(QCA8K_ATU_ADDR1_MASK, reg[1]);
+++	fdb->mac[2] = FIELD_GET(QCA8K_ATU_ADDR2_MASK, reg[0]);
+++	fdb->mac[3] = FIELD_GET(QCA8K_ATU_ADDR3_MASK, reg[0]);
+++	fdb->mac[4] = FIELD_GET(QCA8K_ATU_ADDR4_MASK, reg[0]);
+++	fdb->mac[5] = FIELD_GET(QCA8K_ATU_ADDR5_MASK, reg[0]);
 ++
-++	if (members > QCA8K_NUM_PORTS_FOR_LAG)
-++		return false;
+++	return 0;
+++}
 ++
-++	if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH)
-++		return false;
+++static void qca8k_fdb_write(struct qca8k_priv *priv, u16 vid, u8 port_mask,
+++			    const u8 *mac, u8 aging)
+++{
+++	u32 reg[3] = { 0 };
 ++
-++	if (info->hash_type != NETDEV_LAG_HASH_L2 ||
-++	    info->hash_type != NETDEV_LAG_HASH_L23)
-++		return false;
+++	/* vid - 83:72 */
+++	reg[2] = FIELD_PREP(QCA8K_ATU_VID_MASK, vid);
+++	/* aging - 67:64 */
+++	reg[2] |= FIELD_PREP(QCA8K_ATU_STATUS_MASK, aging);
+++	/* portmask - 54:48 */
+++	reg[1] = FIELD_PREP(QCA8K_ATU_PORT_MASK, port_mask);
+++	/* mac - 47:0 */
+++	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR0_MASK, mac[0]);
+++	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR1_MASK, mac[1]);
+++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR2_MASK, mac[2]);
+++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR3_MASK, mac[3]);
+++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR4_MASK, mac[4]);
+++	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR5_MASK, mac[5]);
 ++
-++	return true;
+++	/* load the array into the ARL table */
+++	qca8k_bulk_write(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
 ++}
 ++
-++static int
-++qca8k_lag_setup_hash(struct dsa_switch *ds,
-++		     struct net_device *lag,
-++		     struct netdev_lag_upper_info *info)
+++static int qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd,
+++			    int port)
 ++{
-++	struct qca8k_priv *priv = ds->priv;
-++	bool unique_lag = true;
-++	int i, id;
-++	u32 hash;
+++	u32 reg;
+++	int ret;
 ++
-++	id = dsa_lag_id(ds->dst, lag);
+++	/* Set the command and FDB index */
+++	reg = QCA8K_ATU_FUNC_BUSY;
+++	reg |= cmd;
+++	if (port >= 0) {
+++		reg |= QCA8K_ATU_FUNC_PORT_EN;
+++		reg |= FIELD_PREP(QCA8K_ATU_FUNC_PORT_MASK, port);
+++	}
 ++
-++	switch (info->hash_type) {
-++	case NETDEV_LAG_HASH_L23:
-++		hash |= QCA8K_TRUNK_HASH_SIP_EN;
-++		hash |= QCA8K_TRUNK_HASH_DIP_EN;
-++		fallthrough;
-++	case NETDEV_LAG_HASH_L2:
-++		hash |= QCA8K_TRUNK_HASH_SA_EN;
-++		hash |= QCA8K_TRUNK_HASH_DA_EN;
-++		break;
-++	default: /* We should NEVER reach this */
-++		return -EOPNOTSUPP;
+++	/* Write the function register triggering the table access */
+++	ret = qca8k_write(priv, QCA8K_REG_ATU_FUNC, reg);
+++	if (ret)
+++		return ret;
+++
+++	/* wait for completion */
+++	ret = qca8k_busy_wait(priv, QCA8K_REG_ATU_FUNC, QCA8K_ATU_FUNC_BUSY);
+++	if (ret)
+++		return ret;
+++
+++	/* Check for table full violation when adding an entry */
+++	if (cmd == QCA8K_FDB_LOAD) {
+++		ret = qca8k_read(priv, QCA8K_REG_ATU_FUNC, &reg);
+++		if (ret < 0)
+++			return ret;
+++		if (reg & QCA8K_ATU_FUNC_FULL)
+++			return -1;
 ++	}
 ++
-++	/* Check if we are the unique configured LAG */
-++	dsa_lags_foreach_id(i, ds->dst)
-++		if (i != id && dsa_lag_dev(ds->dst, i)) {
-++			unique_lag = false;
-++			break;
-++		}
+++	return 0;
+++}
+++
+++static int qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb,
+++			  int port)
+++{
+++	int ret;
 ++
-++	/* Hash Mode is global. Make sure the same Hash Mode
-++	 * is set to all the 4 possible lag.
-++	 * If we are the unique LAG we can set whatever hash
-++	 * mode we want.
-++	 * To change hash mode it's needed to remove all LAG
-++	 * and change the mode with the latest.
-++	 */
-++	if (unique_lag) {
-++		priv->lag_hash_mode = hash;
-++	} else if (priv->lag_hash_mode != hash) {
-++		netdev_err(lag, "Error: Mismateched Hash Mode across different lag is not supported\n");
-++		return -EOPNOTSUPP;
-++	}
+++	qca8k_fdb_write(priv, fdb->vid, fdb->port_mask, fdb->mac, fdb->aging);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_NEXT, port);
+++	if (ret < 0)
+++		return ret;
 ++
-++	return regmap_update_bits(priv->regmap, QCA8K_TRUNK_HASH_EN_CTRL,
-++				  QCA8K_TRUNK_HASH_MASK, hash);
+++	return qca8k_fdb_read(priv, fdb);
 ++}
 ++
-++static int
-++qca8k_lag_refresh_portmap(struct dsa_switch *ds, int port,
-++			  struct net_device *lag, bool delete)
+++static int qca8k_fdb_add(struct qca8k_priv *priv, const u8 *mac,
+++			 u16 port_mask, u16 vid, u8 aging)
 ++{
-++	struct qca8k_priv *priv = ds->priv;
-++	int ret, id, i;
-++	u32 val;
+++	int ret;
 ++
-++	id = dsa_lag_id(ds->dst, lag);
+++	mutex_lock(&priv->reg_mutex);
+++	qca8k_fdb_write(priv, vid, port_mask, mac, aging);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
+++	mutex_unlock(&priv->reg_mutex);
 ++
-++	/* Read current port member */
-++	ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0, &val);
-++	if (ret)
-++		return ret;
+++	return ret;
+++}
 ++
-++	/* Shift val to the correct trunk */
-++	val >>= QCA8K_REG_GOL_TRUNK_SHIFT(id);
-++	val &= QCA8K_REG_GOL_TRUNK_MEMBER_MASK;
-++	if (delete)
-++		val &= ~BIT(port);
-++	else
-++		val |= BIT(port);
+++static int qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac,
+++			 u16 port_mask, u16 vid)
+++{
+++	int ret;
 ++
-++	/* Update port member. With empty portmap disable trunk */
-++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0,
-++				 QCA8K_REG_GOL_TRUNK_MEMBER(id) |
-++				 QCA8K_REG_GOL_TRUNK_EN(id),
-++				 !val << QCA8K_REG_GOL_TRUNK_SHIFT(id) |
-++				 val << QCA8K_REG_GOL_TRUNK_SHIFT(id));
+++	mutex_lock(&priv->reg_mutex);
+++	qca8k_fdb_write(priv, vid, port_mask, mac, 0);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
+++	mutex_unlock(&priv->reg_mutex);
 ++
-++	/* Search empty member if adding or port on deleting */
-++	for (i = 0; i < QCA8K_NUM_PORTS_FOR_LAG; i++) {
-++		ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id), &val);
-++		if (ret)
-++			return ret;
+++	return ret;
+++}
 ++
-++		val >>= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i);
-++		val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_MASK;
+++void qca8k_fdb_flush(struct qca8k_priv *priv)
+++{
+++	mutex_lock(&priv->reg_mutex);
+++	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH, -1);
+++	mutex_unlock(&priv->reg_mutex);
+++}
 ++
-++		if (delete) {
-++			/* If port flagged to be disabled assume this member is
-++			 * empty
-++			 */
-++			if (val != QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
-++				continue;
+++static int qca8k_fdb_search_and_insert(struct qca8k_priv *priv, u8 port_mask,
+++				       const u8 *mac, u16 vid)
+++{
+++	struct qca8k_fdb fdb = { 0 };
+++	int ret;
 ++
-++			val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK;
-++			if (val != port)
-++				continue;
-++		} else {
-++			/* If port flagged to be enabled assume this member is
-++			 * already set
-++			 */
-++			if (val == QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
-++				continue;
-++		}
+++	mutex_lock(&priv->reg_mutex);
 ++
-++		/* We have found the member to add/remove */
-++		break;
+++	qca8k_fdb_write(priv, vid, 0, mac, 0);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
+++	if (ret < 0)
+++		goto exit;
+++
+++	ret = qca8k_fdb_read(priv, &fdb);
+++	if (ret < 0)
+++		goto exit;
+++
+++	/* Rule exist. Delete first */
+++	if (!fdb.aging) {
+++		ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
+++		if (ret)
+++			goto exit;
 ++	}
 ++
-++	/* Set port in the correct port mask or disable port if in delete mode */
-++	return regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id),
-++				  QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN(id, i) |
-++				  QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT(id, i),
-++				  !delete << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i) |
-++				  port << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i));
+++	/* Add port to fdb portmask */
+++	fdb.port_mask |= port_mask;
+++
+++	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
+++
+++exit:
+++	mutex_unlock(&priv->reg_mutex);
+++	return ret;
 ++}
 ++
-++static int
-++qca8k_port_lag_join(struct dsa_switch *ds, int port,
-++		    struct net_device *lag,
-++		    struct netdev_lag_upper_info *info)
+++static int qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask,
+++				    const u8 *mac, u16 vid)
 ++{
+++	struct qca8k_fdb fdb = { 0 };
 ++	int ret;
 ++
-++	if (!qca8k_lag_can_offload(ds, lag, info))
-++		return -EOPNOTSUPP;
+++	mutex_lock(&priv->reg_mutex);
 ++
-++	ret = qca8k_lag_setup_hash(ds, lag, info);
+++	qca8k_fdb_write(priv, vid, 0, mac, 0);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
+++	if (ret < 0)
+++		goto exit;
+++
+++	/* Rule doesn't exist. Why delete? */
+++	if (!fdb.aging) {
+++		ret = -EINVAL;
+++		goto exit;
+++	}
+++
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
 ++	if (ret)
-++		return ret;
+++		goto exit;
 ++
-++	return qca8k_lag_refresh_portmap(ds, port, lag, false);
-++}
+++	/* Only port in the rule is this port. Don't re insert */
+++	if (fdb.port_mask == port_mask)
+++		goto exit;
 ++
-++static int
-++qca8k_port_lag_leave(struct dsa_switch *ds, int port,
-++		     struct net_device *lag)
-++{
-++	return qca8k_lag_refresh_portmap(ds, port, lag, true);
+++	/* Remove port from port mask */
+++	fdb.port_mask &= ~port_mask;
+++
+++	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
+++	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
+++
+++exit:
+++	mutex_unlock(&priv->reg_mutex);
+++	return ret;
 ++}
 ++
-+ static const struct dsa_switch_ops qca8k_switch_ops = {
-+ 	.get_tag_protocol	= qca8k_get_tag_protocol,
-+ 	.setup			= qca8k_setup,
-+@@ -2240,6 +2415,8 @@ static const struct dsa_switch_ops qca8k
-+ 	.phylink_mac_link_down	= qca8k_phylink_mac_link_down,
-+ 	.phylink_mac_link_up	= qca8k_phylink_mac_link_up,
-+ 	.get_phy_flags		= qca8k_get_phy_flags,
-++	.port_lag_join		= qca8k_port_lag_join,
-++	.port_lag_leave		= qca8k_port_lag_leave,
-+ };
-+ 
-+ static int qca8k_read_switch_id(struct qca8k_priv *priv)
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -15,6 +15,8 @@
-+ #define QCA8K_NUM_PORTS					7
-+ #define QCA8K_NUM_CPU_PORTS				2
-+ #define QCA8K_MAX_MTU					9000
-++#define QCA8K_NUM_LAGS					4
-++#define QCA8K_NUM_PORTS_FOR_LAG				4
-+ 
-+ #define PHY_ID_QCA8327					0x004dd034
-+ #define QCA8K_ID_QCA8327				0x12
-+@@ -122,6 +124,14 @@
-+ #define QCA8K_REG_EEE_CTRL				0x100
-+ #define  QCA8K_REG_EEE_CTRL_LPI_EN(_i)			((_i + 1) * 2)
++ int qca8k_mib_init(struct qca8k_priv *priv)
++ {
++ 	int ret;
++@@ -368,6 +573,15 @@ void qca8k_port_bridge_leave(struct dsa_
++ 		  QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port));
++ }
 + 
-++/* TRUNK_HASH_EN registers */
-++#define QCA8K_TRUNK_HASH_EN_CTRL			0x270
-++#define   QCA8K_TRUNK_HASH_SIP_EN			BIT(3)
-++#define   QCA8K_TRUNK_HASH_DIP_EN			BIT(2)
-++#define   QCA8K_TRUNK_HASH_SA_EN			BIT(1)
-++#define   QCA8K_TRUNK_HASH_DA_EN			BIT(0)
-++#define   QCA8K_TRUNK_HASH_MASK				GENMASK(3, 0)
+++void qca8k_port_fast_age(struct dsa_switch *ds, int port)
+++{
+++	struct qca8k_priv *priv = ds->priv;
 ++
-+ /* ACL registers */
-+ #define QCA8K_REG_PORT_VLAN_CTRL0(_i)			(0x420 + (_i * 8))
-+ #define   QCA8K_PORT_VLAN_CVID_MASK			GENMASK(27, 16)
-+@@ -204,6 +214,28 @@
-+ #define   QCA8K_PORT_LOOKUP_LEARN			BIT(20)
-+ #define   QCA8K_PORT_LOOKUP_ING_MIRROR_EN		BIT(25)
-+ 
-++#define QCA8K_REG_GOL_TRUNK_CTRL0			0x700
-++/* 4 max trunk first
-++ * first 6 bit for member bitmap
-++ * 7th bit is to enable trunk port
-++ */
-++#define QCA8K_REG_GOL_TRUNK_SHIFT(_i)			((_i) * 8)
-++#define QCA8K_REG_GOL_TRUNK_EN_MASK			BIT(7)
-++#define QCA8K_REG_GOL_TRUNK_EN(_i)			(QCA8K_REG_GOL_TRUNK_EN_MASK << QCA8K_REG_GOL_TRUNK_SHIFT(_i))
-++#define QCA8K_REG_GOL_TRUNK_MEMBER_MASK			GENMASK(6, 0)
-++#define QCA8K_REG_GOL_TRUNK_MEMBER(_i)			(QCA8K_REG_GOL_TRUNK_MEMBER_MASK << QCA8K_REG_GOL_TRUNK_SHIFT(_i))
-++/* 0x704 for TRUNK 0-1 --- 0x708 for TRUNK 2-3 */
-++#define QCA8K_REG_GOL_TRUNK_CTRL(_i)			(0x704 + (((_i) / 2) * 4))
-++#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_MASK		GENMASK(3, 0)
-++#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK		BIT(3)
-++#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK		GENMASK(2, 0)
-++#define QCA8K_REG_GOL_TRUNK_ID_SHIFT(_i)		(((_i) / 2) * 16)
-++#define QCA8K_REG_GOL_MEM_ID_SHIFT(_i)			((_i) * 4)
-++/* Complex shift: FIRST shift for port THEN shift for trunk */
-++#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(_i, _j)	(QCA8K_REG_GOL_MEM_ID_SHIFT(_j) + QCA8K_REG_GOL_TRUNK_ID_SHIFT(_i))
-++#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN(_i, _j)	(QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(_i, _j))
-++#define QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT(_i, _j)	(QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(_i, _j))
+++	mutex_lock(&priv->reg_mutex);
+++	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port);
+++	mutex_unlock(&priv->reg_mutex);
+++}
 ++
-+ #define QCA8K_REG_GLOBAL_FC_THRESH			0x800
-+ #define   QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK		GENMASK(24, 16)
-+ #define   QCA8K_GLOBAL_FC_GOL_XON_THRES(x)		FIELD_PREP(QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK, x)
-+@@ -309,6 +341,7 @@ struct qca8k_priv {
-+ 	u8 switch_revision;
-+ 	u8 mirror_rx;
-+ 	u8 mirror_tx;
-++	u8 lag_hash_mode;
-+ 	bool legacy_phy_port_mapping;
-+ 	struct qca8k_ports_config ports_config;
-+ 	struct regmap *regmap;
-diff --git a/target/linux/generic/backport-5.15/764-net-next-net-dsa-qca8k-fix-warning-in-LAG-feature.patch b/target/linux/generic/backport-5.15/764-net-next-net-dsa-qca8k-fix-warning-in-LAG-feature.patch
-new file mode 100644
-index 0000000000..8c0a990b0d
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/764-net-next-net-dsa-qca8k-fix-warning-in-LAG-feature.patch
-@@ -0,0 +1,40 @@
-+From 0898ca67b86e14207d4feb3f3fea8b87cec5aab1 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Tue, 23 Nov 2021 16:44:46 +0100
-+Subject: net: dsa: qca8k: fix warning in LAG feature
-+
-+Fix warning reported by bot.
-+Make sure hash is init to 0 and fix wrong logic for hash_type in
-+qca8k_lag_can_offload.
-+
-+Reported-by: kernel test robot <lkp@intel.com>
-+Fixes: def975307c01 ("net: dsa: qca8k: add LAG support")
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Link: https://lore.kernel.org/r/20211123154446.31019-1-ansuelsmth@gmail.com
-+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-+---
-+ drivers/net/dsa/qca8k.c | 4 ++--
-+ 1 file changed, 2 insertions(+), 2 deletions(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -2232,7 +2232,7 @@ qca8k_lag_can_offload(struct dsa_switch
-+ 	if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH)
-+ 		return false;
-+ 
-+-	if (info->hash_type != NETDEV_LAG_HASH_L2 ||
-++	if (info->hash_type != NETDEV_LAG_HASH_L2 &&
-+ 	    info->hash_type != NETDEV_LAG_HASH_L23)
-+ 		return false;
-+ 
-+@@ -2246,8 +2246,8 @@ qca8k_lag_setup_hash(struct dsa_switch *
++ int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
 + {
 + 	struct qca8k_priv *priv = ds->priv;
-+ 	bool unique_lag = true;
-++	u32 hash = 0;
-+ 	int i, id;
-+-	u32 hash;
-+ 
-+ 	id = dsa_lag_id(ds->dst, lag);
-+ 
-diff --git a/target/linux/generic/backport-5.15/765-1-net-next-net-dsa-reorder-PHY-initialization-with-MTU-setup-in.patch b/target/linux/generic/backport-5.15/765-1-net-next-net-dsa-reorder-PHY-initialization-with-MTU-setup-in.patch
-new file mode 100644
-index 0000000000..1786bf0345
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/765-1-net-next-net-dsa-reorder-PHY-initialization-with-MTU-setup-in.patch
-@@ -0,0 +1,52 @@
-+From 904e112ad431492b34f235f59738e8312802bbf9 Mon Sep 17 00:00:00 2001
-+From: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Date: Thu, 6 Jan 2022 01:11:12 +0200
-+Subject: [PATCH 1/6] net: dsa: reorder PHY initialization with MTU setup in
-+ slave.c
-+
-+In dsa_slave_create() there are 2 sections that take rtnl_lock():
-+MTU change and netdev registration. They are separated by PHY
-+initialization.
-+
-+There isn't any strict ordering requirement except for the fact that
-+netdev registration should be last. Therefore, we can perform the MTU
-+change a bit later, after the PHY setup. A future change will then be
-+able to merge the two rtnl_lock sections into one.
-+
-+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ net/dsa/slave.c | 14 +++++++-------
-+ 1 file changed, 7 insertions(+), 7 deletions(-)
-+
-+--- a/net/dsa/slave.c
-++++ b/net/dsa/slave.c
-+@@ -1986,13 +1986,6 @@ int dsa_slave_create(struct dsa_port *po
-+ 	port->slave = slave_dev;
-+ 	dsa_slave_setup_tagger(slave_dev);
-+ 
-+-	rtnl_lock();
-+-	ret = dsa_slave_change_mtu(slave_dev, ETH_DATA_LEN);
-+-	rtnl_unlock();
-+-	if (ret && ret != -EOPNOTSUPP)
-+-		dev_warn(ds->dev, "nonfatal error %d setting MTU to %d on port %d\n",
-+-			 ret, ETH_DATA_LEN, port->index);
-+-
-+ 	netif_carrier_off(slave_dev);
-+ 
-+ 	ret = dsa_slave_phy_setup(slave_dev);
-+@@ -2004,6 +1997,13 @@ int dsa_slave_create(struct dsa_port *po
-+ 	}
-+ 
-+ 	rtnl_lock();
-++	ret = dsa_slave_change_mtu(slave_dev, ETH_DATA_LEN);
-++	rtnl_unlock();
-++	if (ret && ret != -EOPNOTSUPP)
-++		dev_warn(ds->dev, "nonfatal error %d setting MTU to %d on port %d\n",
-++			 ret, ETH_DATA_LEN, port->index);
++@@ -452,3 +666,78 @@ int qca8k_port_max_mtu(struct dsa_switch
++ {
++ 	return QCA8K_MAX_MTU;
++ }
 ++
-++	rtnl_lock();
-+ 
-+ 	ret = register_netdevice(slave_dev);
-+ 	if (ret) {
-diff --git a/target/linux/generic/backport-5.15/765-2-net-next-net-dsa-merge-rtnl_lock-sections-in-dsa_slave_create.patch b/target/linux/generic/backport-5.15/765-2-net-next-net-dsa-merge-rtnl_lock-sections-in-dsa_slave_create.patch
-new file mode 100644
-index 0000000000..c2493a08fd
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/765-2-net-next-net-dsa-merge-rtnl_lock-sections-in-dsa_slave_create.patch
-@@ -0,0 +1,34 @@
-+From e31dbd3b6aba585231cd84a87adeb22e7c6a8c19 Mon Sep 17 00:00:00 2001
-+From: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Date: Thu, 6 Jan 2022 01:11:13 +0200
-+Subject: [PATCH 2/6] net: dsa: merge rtnl_lock sections in dsa_slave_create
-+
-+Currently dsa_slave_create() has two sequences of rtnl_lock/rtnl_unlock
-+in a row. Remove the rtnl_unlock() and rtnl_lock() in between, such that
-+the operation can execute slighly faster.
-+
-+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ net/dsa/slave.c | 4 +---
-+ 1 file changed, 1 insertion(+), 3 deletions(-)
-+
-+--- a/net/dsa/slave.c
-++++ b/net/dsa/slave.c
-+@@ -1997,14 +1997,12 @@ int dsa_slave_create(struct dsa_port *po
-+ 	}
-+ 
-+ 	rtnl_lock();
+++int qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
+++			  u16 port_mask, u16 vid)
+++{
+++	/* Set the vid to the port vlan id if no vid is set */
+++	if (!vid)
+++		vid = QCA8K_PORT_VID_DEF;
 ++
-+ 	ret = dsa_slave_change_mtu(slave_dev, ETH_DATA_LEN);
-+-	rtnl_unlock();
-+ 	if (ret && ret != -EOPNOTSUPP)
-+ 		dev_warn(ds->dev, "nonfatal error %d setting MTU to %d on port %d\n",
-+ 			 ret, ETH_DATA_LEN, port->index);
-+ 
-+-	rtnl_lock();
-+-
-+ 	ret = register_netdevice(slave_dev);
-+ 	if (ret) {
-+ 		netdev_err(master, "error %d registering interface %s\n",
-diff --git a/target/linux/generic/backport-5.15/765-3-net-next-net-dsa-stop-updating-master-MTU-from-master.c.patch b/target/linux/generic/backport-5.15/765-3-net-next-net-dsa-stop-updating-master-MTU-from-master.c.patch
-new file mode 100644
-index 0000000000..d1126de5dd
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/765-3-net-next-net-dsa-stop-updating-master-MTU-from-master.c.patch
-@@ -0,0 +1,91 @@
-+From a1ff94c2973c43bc1e2677ac63ebb15b1d1ff846 Mon Sep 17 00:00:00 2001
-+From: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Date: Thu, 6 Jan 2022 01:11:14 +0200
-+Subject: [PATCH 3/6] net: dsa: stop updating master MTU from master.c
-+
-+At present there are two paths for changing the MTU of the DSA master.
-+
-+The first is:
-+
-+dsa_tree_setup
-+-> dsa_tree_setup_ports
-+   -> dsa_port_setup
-+      -> dsa_slave_create
-+         -> dsa_slave_change_mtu
-+            -> dev_set_mtu(master)
-+
-+The second is:
-+
-+dsa_tree_setup
-+-> dsa_tree_setup_master
-+   -> dsa_master_setup
-+      -> dev_set_mtu(dev)
-+
-+So the dev_set_mtu() call from dsa_master_setup() has been effectively
-+superseded by the dsa_slave_change_mtu(slave_dev, ETH_DATA_LEN) that is
-+done from dsa_slave_create() for each user port. The later function also
-+updates the master MTU according to the largest user port MTU from the
-+tree. Therefore, updating the master MTU through a separate code path
-+isn't needed.
-+
-+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ net/dsa/master.c | 25 +------------------------
-+ 1 file changed, 1 insertion(+), 24 deletions(-)
-+
-+--- a/net/dsa/master.c
-++++ b/net/dsa/master.c
-+@@ -330,28 +330,13 @@ static const struct attribute_group dsa_
-+ 	.attrs	= dsa_slave_attrs,
-+ };
-+ 
-+-static void dsa_master_reset_mtu(struct net_device *dev)
-+-{
-+-	int err;
-+-
-+-	rtnl_lock();
-+-	err = dev_set_mtu(dev, ETH_DATA_LEN);
-+-	if (err)
-+-		netdev_dbg(dev,
-+-			   "Unable to reset MTU to exclude DSA overheads\n");
-+-	rtnl_unlock();
-+-}
-+-
-+ static struct lock_class_key dsa_master_addr_list_lock_key;
-+ 
-+ int dsa_master_setup(struct net_device *dev, struct dsa_port *cpu_dp)
-+ {
-+-	const struct dsa_device_ops *tag_ops = cpu_dp->tag_ops;
-+ 	struct dsa_switch *ds = cpu_dp->ds;
-+ 	struct device_link *consumer_link;
-+-	int mtu, ret;
-+-
-+-	mtu = ETH_DATA_LEN + dsa_tag_protocol_overhead(tag_ops);
-++	int ret;
-+ 
-+ 	/* The DSA master must use SET_NETDEV_DEV for this to work. */
-+ 	consumer_link = device_link_add(ds->dev, dev->dev.parent,
-+@@ -361,13 +346,6 @@ int dsa_master_setup(struct net_device *
-+ 			   "Failed to create a device link to DSA switch %s\n",
-+ 			   dev_name(ds->dev));
-+ 
-+-	rtnl_lock();
-+-	ret = dev_set_mtu(dev, mtu);
-+-	rtnl_unlock();
-+-	if (ret)
-+-		netdev_warn(dev, "error %d setting MTU to %d to include DSA overhead\n",
-+-			    ret, mtu);
-+-
-+ 	/* If we use a tagging format that doesn't have an ethertype
-+ 	 * field, make sure that all packets from this point on get
-+ 	 * sent to the tag format's receive function.
-+@@ -405,7 +383,6 @@ void dsa_master_teardown(struct net_devi
-+ 	sysfs_remove_group(&dev->dev.kobj, &dsa_group);
-+ 	dsa_netdev_ops_set(dev, NULL);
-+ 	dsa_master_ethtool_teardown(dev);
-+-	dsa_master_reset_mtu(dev);
-+ 	dsa_master_set_promiscuity(dev, -1);
-+ 
-+ 	dev->dsa_ptr = NULL;
-diff --git a/target/linux/generic/backport-5.15/765-4-net-next-net-dsa-hold-rtnl_mutex-when-calling-dsa_master_-set.patch b/target/linux/generic/backport-5.15/765-4-net-next-net-dsa-hold-rtnl_mutex-when-calling-dsa_master_-set.patch
-new file mode 100644
-index 0000000000..67d434006b
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/765-4-net-next-net-dsa-hold-rtnl_mutex-when-calling-dsa_master_-set.patch
-@@ -0,0 +1,78 @@
-+From c146f9bc195a9dc3ad7fd000a14540e7c9df952d Mon Sep 17 00:00:00 2001
-+From: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Date: Thu, 6 Jan 2022 01:11:15 +0200
-+Subject: [PATCH 4/6] net: dsa: hold rtnl_mutex when calling
-+ dsa_master_{setup,teardown}
-+
-+DSA needs to simulate master tracking events when a binding is first
-+with a DSA master established and torn down, in order to give drivers
-+the simplifying guarantee that ->master_state_change calls are made
-+only when the master's readiness state to pass traffic changes.
-+master_state_change() provide a operational bool that DSA driver can use
-+to understand if DSA master is operational or not.
-+To avoid races, we need to block the reception of
-+NETDEV_UP/NETDEV_CHANGE/NETDEV_GOING_DOWN events in the netdev notifier
-+chain while we are changing the master's dev->dsa_ptr (this changes what
-+netdev_uses_dsa(dev) reports).
-+
-+The dsa_master_setup() and dsa_master_teardown() functions optionally
-+require the rtnl_mutex to be held, if the tagger needs the master to be
-+promiscuous, these functions call dev_set_promiscuity(). Move the
-+rtnl_lock() from that function and make it top-level.
-+
-+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ net/dsa/dsa2.c   | 8 ++++++++
-+ net/dsa/master.c | 4 ++--
-+ 2 files changed, 10 insertions(+), 2 deletions(-)
-+
-+--- a/net/dsa/dsa2.c
-++++ b/net/dsa/dsa2.c
-+@@ -1034,6 +1034,8 @@ static int dsa_tree_setup_master(struct
-+ 	struct dsa_port *dp;
-+ 	int err;
-+ 
-++	rtnl_lock();
+++	return qca8k_fdb_add(priv, addr, port_mask, vid,
+++			     QCA8K_ATU_STATUS_STATIC);
+++}
+++
+++int qca8k_port_fdb_add(struct dsa_switch *ds, int port,
+++		       const unsigned char *addr, u16 vid)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	u16 port_mask = BIT(port);
+++
+++	return qca8k_port_fdb_insert(priv, addr, port_mask, vid);
+++}
 ++
-+ 	list_for_each_entry(dp, &dst->ports, list) {
-+ 		if (dsa_port_is_cpu(dp)) {
-+ 			err = dsa_master_setup(dp->master, dp);
-+@@ -1042,6 +1044,8 @@ static int dsa_tree_setup_master(struct
-+ 		}
-+ 	}
-+ 
-++	rtnl_unlock();
+++int qca8k_port_fdb_del(struct dsa_switch *ds, int port,
+++		       const unsigned char *addr, u16 vid)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	u16 port_mask = BIT(port);
 ++
-+ 	return 0;
-+ }
-+ 
-+@@ -1049,9 +1053,13 @@ static void dsa_tree_teardown_master(str
-+ {
-+ 	struct dsa_port *dp;
-+ 
-++	rtnl_lock();
+++	if (!vid)
+++		vid = QCA8K_PORT_VID_DEF;
 ++
-+ 	list_for_each_entry(dp, &dst->ports, list)
-+ 		if (dsa_port_is_cpu(dp))
-+ 			dsa_master_teardown(dp->master);
+++	return qca8k_fdb_del(priv, addr, port_mask, vid);
+++}
 ++
-++	rtnl_unlock();
-+ }
-+ 
-+ static int dsa_tree_setup_lags(struct dsa_switch_tree *dst)
-+--- a/net/dsa/master.c
-++++ b/net/dsa/master.c
-+@@ -267,9 +267,9 @@ static void dsa_master_set_promiscuity(s
-+ 	if (!ops->promisc_on_master)
-+ 		return;
-+ 
-+-	rtnl_lock();
-++	ASSERT_RTNL();
+++int qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
+++			dsa_fdb_dump_cb_t *cb, void *data)
+++{
+++	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+++	struct qca8k_fdb _fdb = { 0 };
+++	int cnt = QCA8K_NUM_FDB_RECORDS;
+++	bool is_static;
+++	int ret = 0;
 ++
-+ 	dev_set_promiscuity(dev, inc);
-+-	rtnl_unlock();
-+ }
-+ 
-+ static ssize_t tagging_show(struct device *d, struct device_attribute *attr,
-diff --git a/target/linux/generic/backport-5.15/765-5-net-next-net-dsa-first-set-up-shared-ports-then-non-shared-po.patch b/target/linux/generic/backport-5.15/765-5-net-next-net-dsa-first-set-up-shared-ports-then-non-shared-po.patch
+++	mutex_lock(&priv->reg_mutex);
+++	while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port)) {
+++		if (!_fdb.aging)
+++			break;
+++		is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC);
+++		ret = cb(_fdb.mac, _fdb.vid, is_static, data);
+++		if (ret)
+++			break;
+++	}
+++	mutex_unlock(&priv->reg_mutex);
+++
+++	return 0;
+++}
+++
+++int qca8k_port_mdb_add(struct dsa_switch *ds, int port,
+++		       const struct switchdev_obj_port_mdb *mdb)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	const u8 *addr = mdb->addr;
+++	u16 vid = mdb->vid;
+++
+++	return qca8k_fdb_search_and_insert(priv, BIT(port), addr, vid);
+++}
+++
+++int qca8k_port_mdb_del(struct dsa_switch *ds, int port,
+++		       const struct switchdev_obj_port_mdb *mdb)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	const u8 *addr = mdb->addr;
+++	u16 vid = mdb->vid;
+++
+++	return qca8k_fdb_search_and_del(priv, BIT(port), addr, vid);
+++}
++--- a/drivers/net/dsa/qca/qca8k.h
+++++ b/drivers/net/dsa/qca/qca8k.h
++@@ -430,11 +430,9 @@ int qca8k_read(struct qca8k_priv *priv,
++ int qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val);
++ int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val);
++ 
++-int qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len);
++-int qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len);
++-
++ /* Common ops function */
++ int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask);
+++void qca8k_fdb_flush(struct qca8k_priv *priv);
++ 
++ /* Common ethtool stats function */
++ void qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data);
++@@ -463,6 +461,23 @@ int qca8k_port_change_mtu(struct dsa_swi
++ int qca8k_port_max_mtu(struct dsa_switch *ds, int port);
++ 
++ /* Common fast age function */
+++void qca8k_port_fast_age(struct dsa_switch *ds, int port);
++ int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs);
++ 
+++/* Common FDB function */
+++int qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
+++			  u16 port_mask, u16 vid);
+++int qca8k_port_fdb_add(struct dsa_switch *ds, int port,
+++		       const unsigned char *addr, u16 vid);
+++int qca8k_port_fdb_del(struct dsa_switch *ds, int port,
+++		       const unsigned char *addr, u16 vid);
+++int qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
+++			dsa_fdb_dump_cb_t *cb, void *data);
+++
+++/* Common MDB function */
+++int qca8k_port_mdb_add(struct dsa_switch *ds, int port,
+++		       const struct switchdev_obj_port_mdb *mdb);
+++int qca8k_port_mdb_del(struct dsa_switch *ds, int port,
+++		       const struct switchdev_obj_port_mdb *mdb);
+++
++ #endif /* __QCA8K_H */
+diff --git a/target/linux/generic/backport-5.15/771-v6.0-11-net-dsa-qca8k-move-port-mirror-functions-to-common-c.patch b/target/linux/generic/backport-5.15/771-v6.0-11-net-dsa-qca8k-move-port-mirror-functions-to-common-c.patch
 new file mode 100644
-index 0000000000..e6472c61da
+index 0000000000..c1336d4a92
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/765-5-net-next-net-dsa-first-set-up-shared-ports-then-non-shared-po.patch
-@@ -0,0 +1,118 @@
-+From 1e3f407f3cacc5dcfe27166c412ed9bc263d82bf Mon Sep 17 00:00:00 2001
-+From: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Date: Thu, 6 Jan 2022 01:11:16 +0200
-+Subject: [PATCH 5/6] net: dsa: first set up shared ports, then non-shared
-+ ports
-+
-+After commit a57d8c217aad ("net: dsa: flush switchdev workqueue before
-+tearing down CPU/DSA ports"), the port setup and teardown procedure
-+became asymmetric.
-+
-+The fact of the matter is that user ports need the shared ports to be up
-+before they can be used for CPU-initiated termination. And since we
-+register net devices for the user ports, those won't be functional until
-+we also call the setup for the shared (CPU, DSA) ports. But we may do
-+that later, depending on the port numbering scheme of the hardware we
-+are dealing with.
++++ b/target/linux/generic/backport-5.15/771-v6.0-11-net-dsa-qca8k-move-port-mirror-functions-to-common-c.patch
+@@ -0,0 +1,232 @@
++From 742d37a84d3f7bb60d9b2d9ada9ad4e599f65ebf Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Wed, 27 Jul 2022 13:35:20 +0200
++Subject: [PATCH 11/14] net: dsa: qca8k: move port mirror functions to common
++ code
 +
-+It just makes sense that all shared ports are brought up before any user
-+port is. I can't pinpoint any issue due to the current behavior, but
-+let's change it nonetheless, for consistency's sake.
++The same port mirror functions are used by drivers based on qca8k family
++switch. Move them to common code to make them accessible also by other
++drivers.
 +
-+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 +---
-+ net/dsa/dsa2.c | 50 +++++++++++++++++++++++++++++++++++++-------------
-+ 1 file changed, 37 insertions(+), 13 deletions(-)
-+
-+--- a/net/dsa/dsa2.c
-++++ b/net/dsa/dsa2.c
-+@@ -999,23 +999,28 @@ static void dsa_tree_teardown_switches(s
-+ 		dsa_switch_teardown(dp->ds);
++ drivers/net/dsa/qca/qca8k-8xxx.c   | 93 ------------------------------
++ drivers/net/dsa/qca/qca8k-common.c | 91 +++++++++++++++++++++++++++++
++ drivers/net/dsa/qca/qca8k.h        |  7 +++
++ 3 files changed, 98 insertions(+), 93 deletions(-)
++
++--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
++@@ -1838,99 +1838,6 @@ exit:
 + }
 + 
-+-static int dsa_tree_setup_switches(struct dsa_switch_tree *dst)
-++/* Bring shared ports up first, then non-shared ports */
-++static int dsa_tree_setup_ports(struct dsa_switch_tree *dst)
++ static int
++-qca8k_port_mirror_add(struct dsa_switch *ds, int port,
++-		      struct dsa_mall_mirror_tc_entry *mirror,
++-		      bool ingress)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int monitor_port, ret;
++-	u32 reg, val;
++-
++-	/* Check for existent entry */
++-	if ((ingress ? priv->mirror_rx : priv->mirror_tx) & BIT(port))
++-		return -EEXIST;
++-
++-	ret = regmap_read(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0, &val);
++-	if (ret)
++-		return ret;
++-
++-	/* QCA83xx can have only one port set to mirror mode.
++-	 * Check that the correct port is requested and return error otherwise.
++-	 * When no mirror port is set, the values is set to 0xF
++-	 */
++-	monitor_port = FIELD_GET(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
++-	if (monitor_port != 0xF && monitor_port != mirror->to_local_port)
++-		return -EEXIST;
++-
++-	/* Set the monitor port */
++-	val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM,
++-			 mirror->to_local_port);
++-	ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
++-				 QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
++-	if (ret)
++-		return ret;
++-
++-	if (ingress) {
++-		reg = QCA8K_PORT_LOOKUP_CTRL(port);
++-		val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN;
++-	} else {
++-		reg = QCA8K_REG_PORT_HOL_CTRL1(port);
++-		val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN;
++-	}
++-
++-	ret = regmap_update_bits(priv->regmap, reg, val, val);
++-	if (ret)
++-		return ret;
++-
++-	/* Track mirror port for tx and rx to decide when the
++-	 * mirror port has to be disabled.
++-	 */
++-	if (ingress)
++-		priv->mirror_rx |= BIT(port);
++-	else
++-		priv->mirror_tx |= BIT(port);
++-
++-	return 0;
++-}
++-
++-static void
++-qca8k_port_mirror_del(struct dsa_switch *ds, int port,
++-		      struct dsa_mall_mirror_tc_entry *mirror)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	u32 reg, val;
++-	int ret;
++-
++-	if (mirror->ingress) {
++-		reg = QCA8K_PORT_LOOKUP_CTRL(port);
++-		val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN;
++-	} else {
++-		reg = QCA8K_REG_PORT_HOL_CTRL1(port);
++-		val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN;
++-	}
++-
++-	ret = regmap_clear_bits(priv->regmap, reg, val);
++-	if (ret)
++-		goto err;
++-
++-	if (mirror->ingress)
++-		priv->mirror_rx &= ~BIT(port);
++-	else
++-		priv->mirror_tx &= ~BIT(port);
++-
++-	/* No port set to send packet to mirror port. Disable mirror port */
++-	if (!priv->mirror_rx && !priv->mirror_tx) {
++-		val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, 0xF);
++-		ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
++-					 QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
++-		if (ret)
++-			goto err;
++-	}
++-err:
++-	dev_err(priv->dev, "Failed to del mirror port from %d", port);
++-}
++-
++-static int
++ qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
++ 			  struct netlink_ext_ack *extack)
 + {
-+ 	struct dsa_port *dp;
-+-	int err;
-++	int err = 0;
-+ 
-+ 	list_for_each_entry(dp, &dst->ports, list) {
-+-		err = dsa_switch_setup(dp->ds);
-+-		if (err)
-+-			goto teardown;
-++		if (dsa_port_is_dsa(dp) || dsa_port_is_cpu(dp)) {
-++			err = dsa_port_setup(dp);
-++			if (err)
-++				goto teardown;
-++		}
-+ 	}
-+ 
-+ 	list_for_each_entry(dp, &dst->ports, list) {
-+-		err = dsa_port_setup(dp);
-+-		if (err) {
-+-			err = dsa_port_reinit_as_unused(dp);
-+-			if (err)
-+-				goto teardown;
-++		if (dsa_port_is_user(dp) || dsa_port_is_unused(dp)) {
-++			err = dsa_port_setup(dp);
-++			if (err) {
-++				err = dsa_port_reinit_as_unused(dp);
-++				if (err)
-++					goto teardown;
-++			}
-+ 		}
-+ 	}
-+ 
-+@@ -1024,7 +1029,21 @@ static int dsa_tree_setup_switches(struc
-+ teardown:
-+ 	dsa_tree_teardown_ports(dst);
++--- a/drivers/net/dsa/qca/qca8k-common.c
+++++ b/drivers/net/dsa/qca/qca8k-common.c
++@@ -741,3 +741,94 @@ int qca8k_port_mdb_del(struct dsa_switch
 + 
-+-	dsa_tree_teardown_switches(dst);
-++	return err;
++ 	return qca8k_fdb_search_and_del(priv, BIT(port), addr, vid);
++ }
+++
+++int qca8k_port_mirror_add(struct dsa_switch *ds, int port,
+++			  struct dsa_mall_mirror_tc_entry *mirror,
+++			  bool ingress)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int monitor_port, ret;
+++	u32 reg, val;
+++
+++	/* Check for existent entry */
+++	if ((ingress ? priv->mirror_rx : priv->mirror_tx) & BIT(port))
+++		return -EEXIST;
+++
+++	ret = regmap_read(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0, &val);
+++	if (ret)
+++		return ret;
+++
+++	/* QCA83xx can have only one port set to mirror mode.
+++	 * Check that the correct port is requested and return error otherwise.
+++	 * When no mirror port is set, the values is set to 0xF
+++	 */
+++	monitor_port = FIELD_GET(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
+++	if (monitor_port != 0xF && monitor_port != mirror->to_local_port)
+++		return -EEXIST;
+++
+++	/* Set the monitor port */
+++	val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM,
+++			 mirror->to_local_port);
+++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
+++				 QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
+++	if (ret)
+++		return ret;
+++
+++	if (ingress) {
+++		reg = QCA8K_PORT_LOOKUP_CTRL(port);
+++		val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN;
+++	} else {
+++		reg = QCA8K_REG_PORT_HOL_CTRL1(port);
+++		val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN;
+++	}
+++
+++	ret = regmap_update_bits(priv->regmap, reg, val, val);
+++	if (ret)
+++		return ret;
+++
+++	/* Track mirror port for tx and rx to decide when the
+++	 * mirror port has to be disabled.
+++	 */
+++	if (ingress)
+++		priv->mirror_rx |= BIT(port);
+++	else
+++		priv->mirror_tx |= BIT(port);
+++
+++	return 0;
 ++}
 ++
-++static int dsa_tree_setup_switches(struct dsa_switch_tree *dst)
+++void qca8k_port_mirror_del(struct dsa_switch *ds, int port,
+++			   struct dsa_mall_mirror_tc_entry *mirror)
 ++{
-++	struct dsa_port *dp;
-++	int err = 0;
+++	struct qca8k_priv *priv = ds->priv;
+++	u32 reg, val;
+++	int ret;
 ++
-++	list_for_each_entry(dp, &dst->ports, list) {
-++		err = dsa_switch_setup(dp->ds);
-++		if (err) {
-++			dsa_tree_teardown_switches(dst);
-++			break;
-++		}
+++	if (mirror->ingress) {
+++		reg = QCA8K_PORT_LOOKUP_CTRL(port);
+++		val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN;
+++	} else {
+++		reg = QCA8K_REG_PORT_HOL_CTRL1(port);
+++		val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN;
 ++	}
-+ 
-+ 	return err;
-+ }
-+@@ -1111,10 +1130,14 @@ static int dsa_tree_setup(struct dsa_swi
-+ 	if (err)
-+ 		goto teardown_cpu_ports;
-+ 
-+-	err = dsa_tree_setup_master(dst);
-++	err = dsa_tree_setup_ports(dst);
-+ 	if (err)
-+ 		goto teardown_switches;
-+ 
-++	err = dsa_tree_setup_master(dst);
-++	if (err)
-++		goto teardown_ports;
 ++
-+ 	err = dsa_tree_setup_lags(dst);
-+ 	if (err)
-+ 		goto teardown_master;
-+@@ -1127,8 +1150,9 @@ static int dsa_tree_setup(struct dsa_swi
-+ 
-+ teardown_master:
-+ 	dsa_tree_teardown_master(dst);
-+-teardown_switches:
-++teardown_ports:
-+ 	dsa_tree_teardown_ports(dst);
-++teardown_switches:
-+ 	dsa_tree_teardown_switches(dst);
-+ teardown_cpu_ports:
-+ 	dsa_tree_teardown_cpu_ports(dst);
-diff --git a/target/linux/generic/backport-5.15/765-6-net-next-net-dsa-setup-master-before-ports.patch b/target/linux/generic/backport-5.15/765-6-net-next-net-dsa-setup-master-before-ports.patch
-new file mode 100644
-index 0000000000..93cad0c98a
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/765-6-net-next-net-dsa-setup-master-before-ports.patch
-@@ -0,0 +1,115 @@
-+From 11fd667dac315ea3f2469961f6d2869271a46cae Mon Sep 17 00:00:00 2001
-+From: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Date: Thu, 6 Jan 2022 01:11:17 +0200
-+Subject: [PATCH 6/6] net: dsa: setup master before ports
-+
-+It is said that as soon as a network interface is registered, all its
-+resources should have already been prepared, so that it is available for
-+sending and receiving traffic. One of the resources needed by a DSA
-+slave interface is the master.
-+
-+dsa_tree_setup
-+-> dsa_tree_setup_ports
-+   -> dsa_port_setup
-+      -> dsa_slave_create
-+         -> register_netdevice
-+-> dsa_tree_setup_master
-+   -> dsa_master_setup
-+      -> sets up master->dsa_ptr, which enables reception
-+
-+Therefore, there is a short period of time after register_netdevice()
-+during which the master isn't prepared to pass traffic to the DSA layer
-+(master->dsa_ptr is checked by eth_type_trans). Same thing during
-+unregistration, there is a time frame in which packets might be missed.
-+
-+Note that this change opens us to another race: dsa_master_find_slave()
-+will get invoked potentially earlier than the slave creation, and later
-+than the slave deletion. Since dp->slave starts off as a NULL pointer,
-+the earlier calls aren't a problem, but the later calls are. To avoid
-+use-after-free, we should zeroize dp->slave before calling
-+dsa_slave_destroy().
-+
-+In practice I cannot really test real life improvements brought by this
-+change, since in my systems, netdevice creation races with PHY autoneg
-+which takes a few seconds to complete, and that masks quite a few races.
-+Effects might be noticeable in a setup with fixed links all the way to
-+an external system.
-+
-+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ net/dsa/dsa2.c | 23 +++++++++++++----------
-+ 1 file changed, 13 insertions(+), 10 deletions(-)
-+
-+--- a/net/dsa/dsa2.c
-++++ b/net/dsa/dsa2.c
-+@@ -545,6 +545,7 @@ static void dsa_port_teardown(struct dsa
-+ 	struct devlink_port *dlp = &dp->devlink_port;
-+ 	struct dsa_switch *ds = dp->ds;
-+ 	struct dsa_mac_addr *a, *tmp;
-++	struct net_device *slave;
-+ 
-+ 	if (!dp->setup)
-+ 		return;
-+@@ -566,9 +567,11 @@ static void dsa_port_teardown(struct dsa
-+ 		dsa_port_link_unregister_of(dp);
-+ 		break;
-+ 	case DSA_PORT_TYPE_USER:
-+-		if (dp->slave) {
-+-			dsa_slave_destroy(dp->slave);
-++		slave = dp->slave;
+++	ret = regmap_clear_bits(priv->regmap, reg, val);
+++	if (ret)
+++		goto err;
 ++
-++		if (slave) {
-+ 			dp->slave = NULL;
-++			dsa_slave_destroy(slave);
-+ 		}
-+ 		break;
-+ 	}
-+@@ -1130,17 +1133,17 @@ static int dsa_tree_setup(struct dsa_swi
-+ 	if (err)
-+ 		goto teardown_cpu_ports;
-+ 
-+-	err = dsa_tree_setup_ports(dst);
-++	err = dsa_tree_setup_master(dst);
-+ 	if (err)
-+ 		goto teardown_switches;
-+ 
-+-	err = dsa_tree_setup_master(dst);
-++	err = dsa_tree_setup_ports(dst);
-+ 	if (err)
-+-		goto teardown_ports;
-++		goto teardown_master;
-+ 
-+ 	err = dsa_tree_setup_lags(dst);
-+ 	if (err)
-+-		goto teardown_master;
-++		goto teardown_ports;
-+ 
-+ 	dst->setup = true;
-+ 
-+@@ -1148,10 +1151,10 @@ static int dsa_tree_setup(struct dsa_swi
-+ 
-+ 	return 0;
-+ 
-+-teardown_master:
-+-	dsa_tree_teardown_master(dst);
-+ teardown_ports:
-+ 	dsa_tree_teardown_ports(dst);
-++teardown_master:
-++	dsa_tree_teardown_master(dst);
-+ teardown_switches:
-+ 	dsa_tree_teardown_switches(dst);
-+ teardown_cpu_ports:
-+@@ -1169,10 +1172,10 @@ static void dsa_tree_teardown(struct dsa
-+ 
-+ 	dsa_tree_teardown_lags(dst);
-+ 
-+-	dsa_tree_teardown_master(dst);
-+-
-+ 	dsa_tree_teardown_ports(dst);
-+ 
-++	dsa_tree_teardown_master(dst);
+++	if (mirror->ingress)
+++		priv->mirror_rx &= ~BIT(port);
+++	else
+++		priv->mirror_tx &= ~BIT(port);
 ++
-+ 	dsa_tree_teardown_switches(dst);
-+ 
-+ 	dsa_tree_teardown_cpu_ports(dst);
-diff --git a/target/linux/generic/backport-5.15/766-01-net-dsa-provide-switch-operations-for-tracking-the-m.patch b/target/linux/generic/backport-5.15/766-01-net-dsa-provide-switch-operations-for-tracking-the-m.patch
+++	/* No port set to send packet to mirror port. Disable mirror port */
+++	if (!priv->mirror_rx && !priv->mirror_tx) {
+++		val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, 0xF);
+++		ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
+++					 QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val);
+++		if (ret)
+++			goto err;
+++	}
+++err:
+++	dev_err(priv->dev, "Failed to del mirror port from %d", port);
+++}
++--- a/drivers/net/dsa/qca/qca8k.h
+++++ b/drivers/net/dsa/qca/qca8k.h
++@@ -480,4 +480,11 @@ int qca8k_port_mdb_add(struct dsa_switch
++ int qca8k_port_mdb_del(struct dsa_switch *ds, int port,
++ 		       const struct switchdev_obj_port_mdb *mdb);
++ 
+++/* Common port mirror function */
+++int qca8k_port_mirror_add(struct dsa_switch *ds, int port,
+++			  struct dsa_mall_mirror_tc_entry *mirror,
+++			  bool ingress);
+++void qca8k_port_mirror_del(struct dsa_switch *ds, int port,
+++			   struct dsa_mall_mirror_tc_entry *mirror);
+++
++ #endif /* __QCA8K_H */
+diff --git a/target/linux/generic/backport-5.15/771-v6.0-12-net-dsa-qca8k-move-port-VLAN-functions-to-common-cod.patch b/target/linux/generic/backport-5.15/771-v6.0-12-net-dsa-qca8k-move-port-VLAN-functions-to-common-cod.patch
 new file mode 100644
-index 0000000000..d73b745586
+index 0000000000..898010f950
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/766-01-net-dsa-provide-switch-operations-for-tracking-the-m.patch
-@@ -0,0 +1,254 @@
-+From 295ab96f478d0fa56393e85406f19a867e26ce22 Mon Sep 17 00:00:00 2001
-+From: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Date: Wed, 2 Feb 2022 01:03:20 +0100
-+Subject: [PATCH 01/16] net: dsa: provide switch operations for tracking the
-+ master state
-+
-+Certain drivers may need to send management traffic to the switch for
-+things like register access, FDB dump, etc, to accelerate what their
-+slow bus (SPI, I2C, MDIO) can already do.
-+
-+Ethernet is faster (especially in bulk transactions) but is also more
-+unreliable, since the user may decide to bring the DSA master down (or
-+not bring it up), therefore severing the link between the host and the
-+attached switch.
-+
-+Drivers needing Ethernet-based register access already should have
-+fallback logic to the slow bus if the Ethernet method fails, but that
-+fallback may be based on a timeout, and the I/O to the switch may slow
-+down to a halt if the master is down, because every Ethernet packet will
-+have to time out. The driver also doesn't have the option to turn off
-+Ethernet-based I/O momentarily, because it wouldn't know when to turn it
-+back on.
-+
-+Which is where this change comes in. By tracking NETDEV_CHANGE,
-+NETDEV_UP and NETDEV_GOING_DOWN events on the DSA master, we should know
-+the exact interval of time during which this interface is reliably
-+available for traffic. Provide this information to switches so they can
-+use it as they wish.
++++ b/target/linux/generic/backport-5.15/771-v6.0-12-net-dsa-qca8k-move-port-VLAN-functions-to-common-cod.patch
+@@ -0,0 +1,448 @@
++From c5290f636624b98e76a82bd63ffec0a8a9daa620 Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Wed, 27 Jul 2022 13:35:21 +0200
++Subject: [PATCH 12/14] net: dsa: qca8k: move port VLAN functions to common
++ code
 +
-+An helper is added dsa_port_master_is_operational() to check if a master
-+port is operational.
++The same port VLAN functions are used by drivers based on qca8k family
++switch. Move them to common code to make them accessible also by other
++drivers.
++Also drop exposing busy_wait and make it static.
 +
-+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 +---
-+ include/net/dsa.h  | 17 +++++++++++++++++
-+ net/dsa/dsa2.c     | 46 ++++++++++++++++++++++++++++++++++++++++++++++
-+ net/dsa/dsa_priv.h | 13 +++++++++++++
-+ net/dsa/slave.c    | 32 ++++++++++++++++++++++++++++++++
-+ net/dsa/switch.c   | 15 +++++++++++++++
-+ 5 files changed, 123 insertions(+)
-+
-+--- a/include/net/dsa.h
-++++ b/include/net/dsa.h
-+@@ -291,6 +291,10 @@ struct dsa_port {
-+ 	struct list_head	mdbs;
++ drivers/net/dsa/qca/qca8k-8xxx.c   | 182 -----------------------------
++ drivers/net/dsa/qca/qca8k-common.c | 179 +++++++++++++++++++++++++++-
++ drivers/net/dsa/qca/qca8k.h        |  10 +-
++ 3 files changed, 187 insertions(+), 184 deletions(-)
++
++--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
++@@ -15,7 +15,6 @@
++ #include <linux/of_net.h>
++ #include <linux/of_mdio.h>
++ #include <linux/of_platform.h>
++-#include <linux/if_bridge.h>
++ #include <linux/mdio.h>
++ #include <linux/phylink.h>
++ #include <linux/gpio/consumer.h>
++@@ -442,122 +441,6 @@ static struct regmap_config qca8k_regmap
++ };
++ 
++ static int
++-qca8k_vlan_access(struct qca8k_priv *priv, enum qca8k_vlan_cmd cmd, u16 vid)
++-{
++-	u32 reg;
++-	int ret;
++-
++-	/* Set the command and VLAN index */
++-	reg = QCA8K_VTU_FUNC1_BUSY;
++-	reg |= cmd;
++-	reg |= FIELD_PREP(QCA8K_VTU_FUNC1_VID_MASK, vid);
++-
++-	/* Write the function register triggering the table access */
++-	ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC1, reg);
++-	if (ret)
++-		return ret;
++-
++-	/* wait for completion */
++-	ret = qca8k_busy_wait(priv, QCA8K_REG_VTU_FUNC1, QCA8K_VTU_FUNC1_BUSY);
++-	if (ret)
++-		return ret;
++-
++-	/* Check for table full violation when adding an entry */
++-	if (cmd == QCA8K_VLAN_LOAD) {
++-		ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC1, &reg);
++-		if (ret < 0)
++-			return ret;
++-		if (reg & QCA8K_VTU_FUNC1_FULL)
++-			return -ENOMEM;
++-	}
++-
++-	return 0;
++-}
++-
++-static int
++-qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid, bool untagged)
++-{
++-	u32 reg;
++-	int ret;
++-
++-	/*
++-	   We do the right thing with VLAN 0 and treat it as untagged while
++-	   preserving the tag on egress.
++-	 */
++-	if (vid == 0)
++-		return 0;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid);
++-	if (ret < 0)
++-		goto out;
++-
++-	ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, &reg);
++-	if (ret < 0)
++-		goto out;
++-	reg |= QCA8K_VTU_FUNC0_VALID | QCA8K_VTU_FUNC0_IVL_EN;
++-	reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port);
++-	if (untagged)
++-		reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_UNTAG(port);
++-	else
++-		reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_TAG(port);
++-
++-	ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
++-	if (ret)
++-		goto out;
++-	ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid);
++-
++-out:
++-	mutex_unlock(&priv->reg_mutex);
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid)
++-{
++-	u32 reg, mask;
++-	int ret, i;
++-	bool del;
++-
++-	mutex_lock(&priv->reg_mutex);
++-	ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid);
++-	if (ret < 0)
++-		goto out;
++-
++-	ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, &reg);
++-	if (ret < 0)
++-		goto out;
++-	reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port);
++-	reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(port);
++-
++-	/* Check if we're the last member to be removed */
++-	del = true;
++-	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++-		mask = QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(i);
++-
++-		if ((reg & mask) != mask) {
++-			del = false;
++-			break;
++-		}
++-	}
++-
++-	if (del) {
++-		ret = qca8k_vlan_access(priv, QCA8K_VLAN_PURGE, vid);
++-	} else {
++-		ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
++-		if (ret)
++-			goto out;
++-		ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid);
++-	}
++-
++-out:
++-	mutex_unlock(&priv->reg_mutex);
++-
++-	return ret;
++-}
++-
++-static int
++ qca8k_phy_eth_busy_wait(struct qca8k_mgmt_eth_data *mgmt_eth_data,
++ 			struct sk_buff *read_skb, u32 *val)
++ {
++@@ -1836,71 +1719,6 @@ exit:
 + 
-+ 	bool setup;
-++	/* Master state bits, valid only on CPU ports */
-++	u8			master_admin_up:1;
-++	u8			master_oper_up:1;
-++
-+ };
++ 	return ret;
++ }
++-
++-static int
++-qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
++-			  struct netlink_ext_ack *extack)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret;
++-
++-	if (vlan_filtering) {
++-		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++-				QCA8K_PORT_LOOKUP_VLAN_MODE_MASK,
++-				QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE);
++-	} else {
++-		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++-				QCA8K_PORT_LOOKUP_VLAN_MODE_MASK,
++-				QCA8K_PORT_LOOKUP_VLAN_MODE_NONE);
++-	}
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_port_vlan_add(struct dsa_switch *ds, int port,
++-		    const struct switchdev_obj_port_vlan *vlan,
++-		    struct netlink_ext_ack *extack)
++-{
++-	bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
++-	bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret;
++-
++-	ret = qca8k_vlan_add(priv, port, vlan->vid, untagged);
++-	if (ret) {
++-		dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port, ret);
++-		return ret;
++-	}
++-
++-	if (pvid) {
++-		ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port),
++-				QCA8K_EGREES_VLAN_PORT_MASK(port),
++-				QCA8K_EGREES_VLAN_PORT(port, vlan->vid));
++-		if (ret)
++-			return ret;
++-
++-		ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port),
++-				  QCA8K_PORT_VLAN_CVID(vlan->vid) |
++-				  QCA8K_PORT_VLAN_SVID(vlan->vid));
++-	}
++-
++-	return ret;
++-}
++-
++-static int
++-qca8k_port_vlan_del(struct dsa_switch *ds, int port,
++-		    const struct switchdev_obj_port_vlan *vlan)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret;
++-
++-	ret = qca8k_vlan_del(priv, port, vlan->vid);
++-	if (ret)
++-		dev_err(priv->dev, "Failed to delete VLAN from port %d (%d)", port, ret);
++-
++-	return ret;
++-}
 + 
-+ /* TODO: ideally DSA ports would have a single dp->link_dp member,
-+@@ -456,6 +460,12 @@ static inline bool dsa_port_is_unused(st
-+ 	return dp->type == DSA_PORT_TYPE_UNUSED;
++ static u32 qca8k_get_phy_flags(struct dsa_switch *ds, int port)
++ {
++--- a/drivers/net/dsa/qca/qca8k-common.c
+++++ b/drivers/net/dsa/qca/qca8k-common.c
++@@ -141,7 +141,7 @@ static int qca8k_bulk_write(struct qca8k
++ 	return 0;
 + }
 + 
-++static inline bool dsa_port_master_is_operational(struct dsa_port *dp)
-++{
-++	return dsa_port_is_cpu(dp) && dp->master_admin_up &&
-++	       dp->master_oper_up;
-++}
-++
-+ static inline bool dsa_is_unused_port(struct dsa_switch *ds, int p)
++-int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask)
+++static int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask)
 + {
-+ 	return dsa_to_port(ds, p)->type == DSA_PORT_TYPE_UNUSED;
-+@@ -916,6 +926,13 @@ struct dsa_switch_ops {
-+ 	int	(*tag_8021q_vlan_add)(struct dsa_switch *ds, int port, u16 vid,
-+ 				      u16 flags);
-+ 	int	(*tag_8021q_vlan_del)(struct dsa_switch *ds, int port, u16 vid);
-++
-++	/*
-++	 * DSA master tracking operations
-++	 */
-++	void	(*master_state_change)(struct dsa_switch *ds,
-++				       const struct net_device *master,
-++				       bool operational);
-+ };
++ 	u32 val;
 + 
-+ #define DSA_DEVLINK_PARAM_DRIVER(_id, _name, _type, _cmodes)		\
-+--- a/net/dsa/dsa2.c
-++++ b/net/dsa/dsa2.c
-+@@ -1275,6 +1275,52 @@ out_unlock:
-+ 	return err;
++@@ -354,6 +354,120 @@ exit:
++ 	return ret;
 + }
 + 
-++static void dsa_tree_master_state_change(struct dsa_switch_tree *dst,
-++					 struct net_device *master)
+++static int qca8k_vlan_access(struct qca8k_priv *priv,
+++			     enum qca8k_vlan_cmd cmd, u16 vid)
 ++{
-++	struct dsa_notifier_master_state_info info;
-++	struct dsa_port *cpu_dp = master->dsa_ptr;
+++	u32 reg;
+++	int ret;
 ++
-++	info.master = master;
-++	info.operational = dsa_port_master_is_operational(cpu_dp);
+++	/* Set the command and VLAN index */
+++	reg = QCA8K_VTU_FUNC1_BUSY;
+++	reg |= cmd;
+++	reg |= FIELD_PREP(QCA8K_VTU_FUNC1_VID_MASK, vid);
 ++
-++	dsa_tree_notify(dst, DSA_NOTIFIER_MASTER_STATE_CHANGE, &info);
+++	/* Write the function register triggering the table access */
+++	ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC1, reg);
+++	if (ret)
+++		return ret;
+++
+++	/* wait for completion */
+++	ret = qca8k_busy_wait(priv, QCA8K_REG_VTU_FUNC1, QCA8K_VTU_FUNC1_BUSY);
+++	if (ret)
+++		return ret;
+++
+++	/* Check for table full violation when adding an entry */
+++	if (cmd == QCA8K_VLAN_LOAD) {
+++		ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC1, &reg);
+++		if (ret < 0)
+++			return ret;
+++		if (reg & QCA8K_VTU_FUNC1_FULL)
+++			return -ENOMEM;
+++	}
+++
+++	return 0;
 ++}
 ++
-++void dsa_tree_master_admin_state_change(struct dsa_switch_tree *dst,
-++					struct net_device *master,
-++					bool up)
+++static int qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid,
+++			  bool untagged)
 ++{
-++	struct dsa_port *cpu_dp = master->dsa_ptr;
-++	bool notify = false;
+++	u32 reg;
+++	int ret;
 ++
-++	if ((dsa_port_master_is_operational(cpu_dp)) !=
-++	    (up && cpu_dp->master_oper_up))
-++		notify = true;
+++	/* We do the right thing with VLAN 0 and treat it as untagged while
+++	 * preserving the tag on egress.
+++	 */
+++	if (vid == 0)
+++		return 0;
 ++
-++	cpu_dp->master_admin_up = up;
+++	mutex_lock(&priv->reg_mutex);
+++	ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid);
+++	if (ret < 0)
+++		goto out;
 ++
-++	if (notify)
-++		dsa_tree_master_state_change(dst, master);
+++	ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, &reg);
+++	if (ret < 0)
+++		goto out;
+++	reg |= QCA8K_VTU_FUNC0_VALID | QCA8K_VTU_FUNC0_IVL_EN;
+++	reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port);
+++	if (untagged)
+++		reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_UNTAG(port);
+++	else
+++		reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_TAG(port);
+++
+++	ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
+++	if (ret)
+++		goto out;
+++	ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid);
+++
+++out:
+++	mutex_unlock(&priv->reg_mutex);
+++
+++	return ret;
 ++}
 ++
-++void dsa_tree_master_oper_state_change(struct dsa_switch_tree *dst,
-++				       struct net_device *master,
-++				       bool up)
+++static int qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid)
 ++{
-++	struct dsa_port *cpu_dp = master->dsa_ptr;
-++	bool notify = false;
+++	u32 reg, mask;
+++	int ret, i;
+++	bool del;
 ++
-++	if ((dsa_port_master_is_operational(cpu_dp)) !=
-++	    (cpu_dp->master_admin_up && up))
-++		notify = true;
+++	mutex_lock(&priv->reg_mutex);
+++	ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid);
+++	if (ret < 0)
+++		goto out;
 ++
-++	cpu_dp->master_oper_up = up;
+++	ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, &reg);
+++	if (ret < 0)
+++		goto out;
+++	reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port);
+++	reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(port);
 ++
-++	if (notify)
-++		dsa_tree_master_state_change(dst, master);
+++	/* Check if we're the last member to be removed */
+++	del = true;
+++	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+++		mask = QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(i);
+++
+++		if ((reg & mask) != mask) {
+++			del = false;
+++			break;
+++		}
+++	}
+++
+++	if (del) {
+++		ret = qca8k_vlan_access(priv, QCA8K_VLAN_PURGE, vid);
+++	} else {
+++		ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
+++		if (ret)
+++			goto out;
+++		ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid);
+++	}
+++
+++out:
+++	mutex_unlock(&priv->reg_mutex);
+++
+++	return ret;
 ++}
 ++
-+ static struct dsa_port *dsa_port_touch(struct dsa_switch *ds, int index)
++ int qca8k_mib_init(struct qca8k_priv *priv)
 + {
-+ 	struct dsa_switch_tree *dst = ds->dst;
-+--- a/net/dsa/dsa_priv.h
-++++ b/net/dsa/dsa_priv.h
-+@@ -45,6 +45,7 @@ enum {
-+ 	DSA_NOTIFIER_MRP_DEL_RING_ROLE,
-+ 	DSA_NOTIFIER_TAG_8021Q_VLAN_ADD,
-+ 	DSA_NOTIFIER_TAG_8021Q_VLAN_DEL,
-++	DSA_NOTIFIER_MASTER_STATE_CHANGE,
-+ };
-+ 
-+ /* DSA_NOTIFIER_AGEING_TIME */
-+@@ -127,6 +128,12 @@ struct dsa_notifier_tag_8021q_vlan_info
-+ 	u16 vid;
-+ };
-+ 
-++/* DSA_NOTIFIER_MASTER_STATE_CHANGE */
-++struct dsa_notifier_master_state_info {
-++	const struct net_device *master;
-++	bool operational;
-++};
++ 	int ret;
++@@ -832,3 +946,66 @@ void qca8k_port_mirror_del(struct dsa_sw
++ err:
++ 	dev_err(priv->dev, "Failed to del mirror port from %d", port);
++ }
 ++
-+ struct dsa_switchdev_event_work {
-+ 	struct dsa_switch *ds;
-+ 	int port;
-+@@ -548,6 +555,12 @@ int dsa_tree_change_tag_proto(struct dsa
-+ 			      struct net_device *master,
-+ 			      const struct dsa_device_ops *tag_ops,
-+ 			      const struct dsa_device_ops *old_tag_ops);
-++void dsa_tree_master_admin_state_change(struct dsa_switch_tree *dst,
-++					struct net_device *master,
-++					bool up);
-++void dsa_tree_master_oper_state_change(struct dsa_switch_tree *dst,
-++				       struct net_device *master,
-++				       bool up);
-+ int dsa_bridge_num_get(const struct net_device *bridge_dev, int max);
-+ void dsa_bridge_num_put(const struct net_device *bridge_dev, int bridge_num);
-+ 
-+--- a/net/dsa/slave.c
-++++ b/net/dsa/slave.c
-+@@ -2320,6 +2320,36 @@ static int dsa_slave_netdevice_event(str
-+ 		err = dsa_port_lag_change(dp, info->lower_state_info);
-+ 		return notifier_from_errno(err);
-+ 	}
-++	case NETDEV_CHANGE:
-++	case NETDEV_UP: {
-++		/* Track state of master port.
-++		 * DSA driver may require the master port (and indirectly
-++		 * the tagger) to be available for some special operation.
-++		 */
-++		if (netdev_uses_dsa(dev)) {
-++			struct dsa_port *cpu_dp = dev->dsa_ptr;
-++			struct dsa_switch_tree *dst = cpu_dp->ds->dst;
+++int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port,
+++			      bool vlan_filtering,
+++			      struct netlink_ext_ack *extack)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int ret;
 ++
-++			/* Track when the master port is UP */
-++			dsa_tree_master_oper_state_change(dst, dev,
-++							  netif_oper_up(dev));
+++	if (vlan_filtering) {
+++		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+++				QCA8K_PORT_LOOKUP_VLAN_MODE_MASK,
+++				QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE);
+++	} else {
+++		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+++				QCA8K_PORT_LOOKUP_VLAN_MODE_MASK,
+++				QCA8K_PORT_LOOKUP_VLAN_MODE_NONE);
+++	}
 ++
-++			/* Track when the master port is ready and can accept
-++			 * packet.
-++			 * NETDEV_UP event is not enough to flag a port as ready.
-++			 * We also have to wait for linkwatch_do_dev to dev_activate
-++			 * and emit a NETDEV_CHANGE event.
-++			 * We check if a master port is ready by checking if the dev
-++			 * have a qdisc assigned and is not noop.
-++			 */
-++			dsa_tree_master_admin_state_change(dst, dev,
-++							   !qdisc_tx_is_noop(dev));
+++	return ret;
+++}
 ++
-++			return NOTIFY_OK;
-++		}
+++int qca8k_port_vlan_add(struct dsa_switch *ds, int port,
+++			const struct switchdev_obj_port_vlan *vlan,
+++			struct netlink_ext_ack *extack)
+++{
+++	bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
+++	bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
+++	struct qca8k_priv *priv = ds->priv;
+++	int ret;
 ++
-++		return NOTIFY_DONE;
+++	ret = qca8k_vlan_add(priv, port, vlan->vid, untagged);
+++	if (ret) {
+++		dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port, ret);
+++		return ret;
 ++	}
-+ 	case NETDEV_GOING_DOWN: {
-+ 		struct dsa_port *dp, *cpu_dp;
-+ 		struct dsa_switch_tree *dst;
-+@@ -2331,6 +2361,8 @@ static int dsa_slave_netdevice_event(str
-+ 		cpu_dp = dev->dsa_ptr;
-+ 		dst = cpu_dp->ds->dst;
-+ 
-++		dsa_tree_master_admin_state_change(dst, dev, false);
 ++
-+ 		list_for_each_entry(dp, &dst->ports, list) {
-+ 			if (!dsa_is_user_port(dp->ds, dp->index))
-+ 				continue;
-+--- a/net/dsa/switch.c
-++++ b/net/dsa/switch.c
-+@@ -722,6 +722,18 @@ dsa_switch_mrp_del_ring_role(struct dsa_
-+ 	return 0;
-+ }
-+ 
-++static int
-++dsa_switch_master_state_change(struct dsa_switch *ds,
-++			       struct dsa_notifier_master_state_info *info)
-++{
-++	if (!ds->ops->master_state_change)
-++		return 0;
+++	if (pvid) {
+++		ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port),
+++				QCA8K_EGREES_VLAN_PORT_MASK(port),
+++				QCA8K_EGREES_VLAN_PORT(port, vlan->vid));
+++		if (ret)
+++			return ret;
 ++
-++	ds->ops->master_state_change(ds, info->master, info->operational);
+++		ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port),
+++				  QCA8K_PORT_VLAN_CVID(vlan->vid) |
+++				  QCA8K_PORT_VLAN_SVID(vlan->vid));
+++	}
 ++
-++	return 0;
+++	return ret;
 ++}
 ++
-+ static int dsa_switch_event(struct notifier_block *nb,
-+ 			    unsigned long event, void *info)
-+ {
-+@@ -813,6 +825,9 @@ static int dsa_switch_event(struct notif
-+ 	case DSA_NOTIFIER_TAG_8021Q_VLAN_DEL:
-+ 		err = dsa_switch_tag_8021q_vlan_del(ds, info);
-+ 		break;
-++	case DSA_NOTIFIER_MASTER_STATE_CHANGE:
-++		err = dsa_switch_master_state_change(ds, info);
-++		break;
-+ 	default:
-+ 		err = -EOPNOTSUPP;
-+ 		break;
-diff --git a/target/linux/generic/backport-5.15/766-02-net-dsa-replay-master-state-events-in-dsa_tree_-setu.patch b/target/linux/generic/backport-5.15/766-02-net-dsa-replay-master-state-events-in-dsa_tree_-setu.patch
+++int qca8k_port_vlan_del(struct dsa_switch *ds, int port,
+++			const struct switchdev_obj_port_vlan *vlan)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int ret;
+++
+++	ret = qca8k_vlan_del(priv, port, vlan->vid);
+++	if (ret)
+++		dev_err(priv->dev, "Failed to delete VLAN from port %d (%d)", port, ret);
+++
+++	return ret;
+++}
++--- a/drivers/net/dsa/qca/qca8k.h
+++++ b/drivers/net/dsa/qca/qca8k.h
++@@ -431,7 +431,6 @@ int qca8k_write(struct qca8k_priv *priv,
++ int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val);
++ 
++ /* Common ops function */
++-int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask);
++ void qca8k_fdb_flush(struct qca8k_priv *priv);
++ 
++ /* Common ethtool stats function */
++@@ -487,4 +486,13 @@ int qca8k_port_mirror_add(struct dsa_swi
++ void qca8k_port_mirror_del(struct dsa_switch *ds, int port,
++ 			   struct dsa_mall_mirror_tc_entry *mirror);
++ 
+++/* Common port VLAN function */
+++int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
+++			      struct netlink_ext_ack *extack);
+++int qca8k_port_vlan_add(struct dsa_switch *ds, int port,
+++			const struct switchdev_obj_port_vlan *vlan,
+++			struct netlink_ext_ack *extack);
+++int qca8k_port_vlan_del(struct dsa_switch *ds, int port,
+++			const struct switchdev_obj_port_vlan *vlan);
+++
++ #endif /* __QCA8K_H */
+diff --git a/target/linux/generic/backport-5.15/771-v6.0-13-net-dsa-qca8k-move-port-LAG-functions-to-common-code.patch b/target/linux/generic/backport-5.15/771-v6.0-13-net-dsa-qca8k-move-port-LAG-functions-to-common-code.patch
 new file mode 100644
-index 0000000000..6478d580c0
+index 0000000000..1802b17eaa
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/766-02-net-dsa-replay-master-state-events-in-dsa_tree_-setu.patch
-@@ -0,0 +1,89 @@
-+From e83d56537859849f2223b90749e554831b1f3c27 Mon Sep 17 00:00:00 2001
-+From: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Date: Wed, 2 Feb 2022 01:03:21 +0100
-+Subject: [PATCH 02/16] net: dsa: replay master state events in
-+ dsa_tree_{setup,teardown}_master
-+
-+In order for switch driver to be able to make simple and reliable use of
-+the master tracking operations, they must also be notified of the
-+initial state of the DSA master, not just of the changes. This is
-+because they might enable certain features only during the time when
-+they know that the DSA master is up and running.
++++ b/target/linux/generic/backport-5.15/771-v6.0-13-net-dsa-qca8k-move-port-LAG-functions-to-common-code.patch
+@@ -0,0 +1,384 @@
++From e9bbf019af44b204b71ef8edf224002550aab641 Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Wed, 27 Jul 2022 13:35:22 +0200
++Subject: [PATCH 13/14] net: dsa: qca8k: move port LAG functions to common code
 +
-+Therefore, this change explicitly checks the state of the DSA master
-+under the same rtnl_mutex as we were holding during the
-+dsa_master_setup() and dsa_master_teardown() call. The idea being that
-+if the DSA master became operational in between the moment in which it
-+became a DSA master (dsa_master_setup set dev->dsa_ptr) and the moment
-+when we checked for the master being up, there is a chance that we
-+would emit a ->master_state_change() call with no actual state change.
-+We need to avoid that by serializing the concurrent netdevice event with
-+us. If the netdevice event started before, we force it to finish before
-+we begin, because we take rtnl_lock before making netdev_uses_dsa()
-+return true. So we also handle that early event and do nothing on it.
-+Similarly, if the dev_open() attempt is concurrent with us, it will
-+attempt to take the rtnl_mutex, but we're holding it. We'll see that
-+the master flag IFF_UP isn't set, then when we release the rtnl_mutex
-+we'll process the NETDEV_UP notifier.
++The same port LAG functions are used by drivers based on qca8k family
++switch. Move them to common code to make them accessible also by other
++drivers.
 +
-+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 +---
-+ net/dsa/dsa2.c | 28 ++++++++++++++++++++++++----
-+ 1 file changed, 24 insertions(+), 4 deletions(-)
-+
-+--- a/net/dsa/dsa2.c
-++++ b/net/dsa/dsa2.c
-+@@ -15,6 +15,7 @@
-+ #include <linux/of.h>
-+ #include <linux/of_net.h>
-+ #include <net/devlink.h>
-++#include <net/sch_generic.h>
-+ 
-+ #include "dsa_priv.h"
++ drivers/net/dsa/qca/qca8k-8xxx.c   | 168 -----------------------------
++ drivers/net/dsa/qca/qca8k-common.c | 165 ++++++++++++++++++++++++++++
++ drivers/net/dsa/qca/qca8k.h        |   6 ++
++ 3 files changed, 171 insertions(+), 168 deletions(-)
++
++--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
++@@ -1743,178 +1743,6 @@ qca8k_get_tag_protocol(struct dsa_switch
++ 	return DSA_TAG_PROTO_QCA;
++ }
 + 
-+@@ -1060,9 +1061,18 @@ static int dsa_tree_setup_master(struct
++-static bool
++-qca8k_lag_can_offload(struct dsa_switch *ds,
++-		      struct net_device *lag,
++-		      struct netdev_lag_upper_info *info)
++-{
++-	struct dsa_port *dp;
++-	int id, members = 0;
++-
++-	id = dsa_lag_id(ds->dst, lag);
++-	if (id < 0 || id >= ds->num_lag_ids)
++-		return false;
++-
++-	dsa_lag_foreach_port(dp, ds->dst, lag)
++-		/* Includes the port joining the LAG */
++-		members++;
++-
++-	if (members > QCA8K_NUM_PORTS_FOR_LAG)
++-		return false;
++-
++-	if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH)
++-		return false;
++-
++-	if (info->hash_type != NETDEV_LAG_HASH_L2 &&
++-	    info->hash_type != NETDEV_LAG_HASH_L23)
++-		return false;
++-
++-	return true;
++-}
++-
++-static int
++-qca8k_lag_setup_hash(struct dsa_switch *ds,
++-		     struct net_device *lag,
++-		     struct netdev_lag_upper_info *info)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	bool unique_lag = true;
++-	u32 hash = 0;
++-	int i, id;
++-
++-	id = dsa_lag_id(ds->dst, lag);
++-
++-	switch (info->hash_type) {
++-	case NETDEV_LAG_HASH_L23:
++-		hash |= QCA8K_TRUNK_HASH_SIP_EN;
++-		hash |= QCA8K_TRUNK_HASH_DIP_EN;
++-		fallthrough;
++-	case NETDEV_LAG_HASH_L2:
++-		hash |= QCA8K_TRUNK_HASH_SA_EN;
++-		hash |= QCA8K_TRUNK_HASH_DA_EN;
++-		break;
++-	default: /* We should NEVER reach this */
++-		return -EOPNOTSUPP;
++-	}
++-
++-	/* Check if we are the unique configured LAG */
++-	dsa_lags_foreach_id(i, ds->dst)
++-		if (i != id && dsa_lag_dev(ds->dst, i)) {
++-			unique_lag = false;
++-			break;
++-		}
++-
++-	/* Hash Mode is global. Make sure the same Hash Mode
++-	 * is set to all the 4 possible lag.
++-	 * If we are the unique LAG we can set whatever hash
++-	 * mode we want.
++-	 * To change hash mode it's needed to remove all LAG
++-	 * and change the mode with the latest.
++-	 */
++-	if (unique_lag) {
++-		priv->lag_hash_mode = hash;
++-	} else if (priv->lag_hash_mode != hash) {
++-		netdev_err(lag, "Error: Mismateched Hash Mode across different lag is not supported\n");
++-		return -EOPNOTSUPP;
++-	}
++-
++-	return regmap_update_bits(priv->regmap, QCA8K_TRUNK_HASH_EN_CTRL,
++-				  QCA8K_TRUNK_HASH_MASK, hash);
++-}
++-
++-static int
++-qca8k_lag_refresh_portmap(struct dsa_switch *ds, int port,
++-			  struct net_device *lag, bool delete)
++-{
++-	struct qca8k_priv *priv = ds->priv;
++-	int ret, id, i;
++-	u32 val;
++-
++-	id = dsa_lag_id(ds->dst, lag);
++-
++-	/* Read current port member */
++-	ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0, &val);
++-	if (ret)
++-		return ret;
++-
++-	/* Shift val to the correct trunk */
++-	val >>= QCA8K_REG_GOL_TRUNK_SHIFT(id);
++-	val &= QCA8K_REG_GOL_TRUNK_MEMBER_MASK;
++-	if (delete)
++-		val &= ~BIT(port);
++-	else
++-		val |= BIT(port);
++-
++-	/* Update port member. With empty portmap disable trunk */
++-	ret = regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0,
++-				 QCA8K_REG_GOL_TRUNK_MEMBER(id) |
++-				 QCA8K_REG_GOL_TRUNK_EN(id),
++-				 !val << QCA8K_REG_GOL_TRUNK_SHIFT(id) |
++-				 val << QCA8K_REG_GOL_TRUNK_SHIFT(id));
++-
++-	/* Search empty member if adding or port on deleting */
++-	for (i = 0; i < QCA8K_NUM_PORTS_FOR_LAG; i++) {
++-		ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id), &val);
++-		if (ret)
++-			return ret;
++-
++-		val >>= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i);
++-		val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_MASK;
++-
++-		if (delete) {
++-			/* If port flagged to be disabled assume this member is
++-			 * empty
++-			 */
++-			if (val != QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
++-				continue;
++-
++-			val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK;
++-			if (val != port)
++-				continue;
++-		} else {
++-			/* If port flagged to be enabled assume this member is
++-			 * already set
++-			 */
++-			if (val == QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
++-				continue;
++-		}
++-
++-		/* We have found the member to add/remove */
++-		break;
++-	}
++-
++-	/* Set port in the correct port mask or disable port if in delete mode */
++-	return regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id),
++-				  QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN(id, i) |
++-				  QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT(id, i),
++-				  !delete << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i) |
++-				  port << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i));
++-}
++-
++-static int
++-qca8k_port_lag_join(struct dsa_switch *ds, int port,
++-		    struct net_device *lag,
++-		    struct netdev_lag_upper_info *info)
++-{
++-	int ret;
++-
++-	if (!qca8k_lag_can_offload(ds, lag, info))
++-		return -EOPNOTSUPP;
++-
++-	ret = qca8k_lag_setup_hash(ds, lag, info);
++-	if (ret)
++-		return ret;
++-
++-	return qca8k_lag_refresh_portmap(ds, port, lag, false);
++-}
++-
++-static int
++-qca8k_port_lag_leave(struct dsa_switch *ds, int port,
++-		     struct net_device *lag)
++-{
++-	return qca8k_lag_refresh_portmap(ds, port, lag, true);
++-}
++-
++ static void
++ qca8k_master_change(struct dsa_switch *ds, const struct net_device *master,
++ 		    bool operational)
++--- a/drivers/net/dsa/qca/qca8k-common.c
+++++ b/drivers/net/dsa/qca/qca8k-common.c
++@@ -1009,3 +1009,169 @@ int qca8k_port_vlan_del(struct dsa_switc
 + 
-+ 	list_for_each_entry(dp, &dst->ports, list) {
-+ 		if (dsa_port_is_cpu(dp)) {
-+-			err = dsa_master_setup(dp->master, dp);
-++			struct net_device *master = dp->master;
-++			bool admin_up = (master->flags & IFF_UP) &&
-++					!qdisc_tx_is_noop(master);
++ 	return ret;
++ }
+++
+++static bool qca8k_lag_can_offload(struct dsa_switch *ds,
+++				  struct net_device *lag,
+++				  struct netdev_lag_upper_info *info)
+++{
+++	struct dsa_port *dp;
+++	int id, members = 0;
+++
+++	id = dsa_lag_id(ds->dst, lag);
+++	if (id < 0 || id >= ds->num_lag_ids)
+++		return false;
+++
+++	dsa_lag_foreach_port(dp, ds->dst, lag)
+++		/* Includes the port joining the LAG */
+++		members++;
+++
+++	if (members > QCA8K_NUM_PORTS_FOR_LAG)
+++		return false;
+++
+++	if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH)
+++		return false;
+++
+++	if (info->hash_type != NETDEV_LAG_HASH_L2 &&
+++	    info->hash_type != NETDEV_LAG_HASH_L23)
+++		return false;
+++
+++	return true;
+++}
+++
+++static int qca8k_lag_setup_hash(struct dsa_switch *ds,
+++				struct net_device *lag,
+++				struct netdev_lag_upper_info *info)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	bool unique_lag = true;
+++	u32 hash = 0;
+++	int i, id;
+++
+++	id = dsa_lag_id(ds->dst, lag);
+++
+++	switch (info->hash_type) {
+++	case NETDEV_LAG_HASH_L23:
+++		hash |= QCA8K_TRUNK_HASH_SIP_EN;
+++		hash |= QCA8K_TRUNK_HASH_DIP_EN;
+++		fallthrough;
+++	case NETDEV_LAG_HASH_L2:
+++		hash |= QCA8K_TRUNK_HASH_SA_EN;
+++		hash |= QCA8K_TRUNK_HASH_DA_EN;
+++		break;
+++	default: /* We should NEVER reach this */
+++		return -EOPNOTSUPP;
+++	}
+++
+++	/* Check if we are the unique configured LAG */
+++	dsa_lags_foreach_id(i, ds->dst)
+++		if (i != id && dsa_lag_dev(ds->dst, i)) {
+++			unique_lag = false;
+++			break;
+++		}
+++
+++	/* Hash Mode is global. Make sure the same Hash Mode
+++	 * is set to all the 4 possible lag.
+++	 * If we are the unique LAG we can set whatever hash
+++	 * mode we want.
+++	 * To change hash mode it's needed to remove all LAG
+++	 * and change the mode with the latest.
+++	 */
+++	if (unique_lag) {
+++		priv->lag_hash_mode = hash;
+++	} else if (priv->lag_hash_mode != hash) {
+++		netdev_err(lag, "Error: Mismatched Hash Mode across different lag is not supported\n");
+++		return -EOPNOTSUPP;
+++	}
+++
+++	return regmap_update_bits(priv->regmap, QCA8K_TRUNK_HASH_EN_CTRL,
+++				  QCA8K_TRUNK_HASH_MASK, hash);
+++}
 ++
-++			err = dsa_master_setup(master, dp);
-+ 			if (err)
-+ 				return err;
+++static int qca8k_lag_refresh_portmap(struct dsa_switch *ds, int port,
+++				     struct net_device *lag, bool delete)
+++{
+++	struct qca8k_priv *priv = ds->priv;
+++	int ret, id, i;
+++	u32 val;
 ++
-++			/* Replay master state event */
-++			dsa_tree_master_admin_state_change(dst, master, admin_up);
-++			dsa_tree_master_oper_state_change(dst, master,
-++							  netif_oper_up(master));
-+ 		}
-+ 	}
-+ 
-+@@ -1077,9 +1087,19 @@ static void dsa_tree_teardown_master(str
-+ 
-+ 	rtnl_lock();
-+ 
-+-	list_for_each_entry(dp, &dst->ports, list)
-+-		if (dsa_port_is_cpu(dp))
-+-			dsa_master_teardown(dp->master);
-++	list_for_each_entry(dp, &dst->ports, list) {
-++		if (dsa_port_is_cpu(dp)) {
-++			struct net_device *master = dp->master;
+++	id = dsa_lag_id(ds->dst, lag);
 ++
-++			/* Synthesizing an "admin down" state is sufficient for
-++			 * the switches to get a notification if the master is
-++			 * currently up and running.
+++	/* Read current port member */
+++	ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0, &val);
+++	if (ret)
+++		return ret;
+++
+++	/* Shift val to the correct trunk */
+++	val >>= QCA8K_REG_GOL_TRUNK_SHIFT(id);
+++	val &= QCA8K_REG_GOL_TRUNK_MEMBER_MASK;
+++	if (delete)
+++		val &= ~BIT(port);
+++	else
+++		val |= BIT(port);
+++
+++	/* Update port member. With empty portmap disable trunk */
+++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0,
+++				 QCA8K_REG_GOL_TRUNK_MEMBER(id) |
+++				 QCA8K_REG_GOL_TRUNK_EN(id),
+++				 !val << QCA8K_REG_GOL_TRUNK_SHIFT(id) |
+++				 val << QCA8K_REG_GOL_TRUNK_SHIFT(id));
+++
+++	/* Search empty member if adding or port on deleting */
+++	for (i = 0; i < QCA8K_NUM_PORTS_FOR_LAG; i++) {
+++		ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id), &val);
+++		if (ret)
+++			return ret;
+++
+++		val >>= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i);
+++		val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_MASK;
+++
+++		if (delete) {
+++			/* If port flagged to be disabled assume this member is
+++			 * empty
 ++			 */
-++			dsa_tree_master_admin_state_change(dst, master, false);
+++			if (val != QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
+++				continue;
 ++
-++			dsa_master_teardown(master);
+++			val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK;
+++			if (val != port)
+++				continue;
+++		} else {
+++			/* If port flagged to be enabled assume this member is
+++			 * already set
+++			 */
+++			if (val == QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
+++				continue;
 ++		}
+++
+++		/* We have found the member to add/remove */
+++		break;
 ++	}
-+ 
-+ 	rtnl_unlock();
-+ }
-diff --git a/target/linux/generic/backport-5.15/766-03-net-dsa-tag_qca-convert-to-FIELD-macro.patch b/target/linux/generic/backport-5.15/766-03-net-dsa-tag_qca-convert-to-FIELD-macro.patch
+++
+++	/* Set port in the correct port mask or disable port if in delete mode */
+++	return regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id),
+++				  QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN(id, i) |
+++				  QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT(id, i),
+++				  !delete << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i) |
+++				  port << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i));
+++}
+++
+++int qca8k_port_lag_join(struct dsa_switch *ds, int port, struct net_device *lag,
+++			struct netdev_lag_upper_info *info)
+++{
+++	int ret;
+++
+++	if (!qca8k_lag_can_offload(ds, lag, info))
+++		return -EOPNOTSUPP;
+++
+++	ret = qca8k_lag_setup_hash(ds, lag, info);
+++	if (ret)
+++		return ret;
+++
+++	return qca8k_lag_refresh_portmap(ds, port, lag, false);
+++}
+++
+++int qca8k_port_lag_leave(struct dsa_switch *ds, int port,
+++			 struct net_device *lag)
+++{
+++	return qca8k_lag_refresh_portmap(ds, port, lag, true);
+++}
++--- a/drivers/net/dsa/qca/qca8k.h
+++++ b/drivers/net/dsa/qca/qca8k.h
++@@ -495,4 +495,10 @@ int qca8k_port_vlan_add(struct dsa_switc
++ int qca8k_port_vlan_del(struct dsa_switch *ds, int port,
++ 			const struct switchdev_obj_port_vlan *vlan);
++ 
+++/* Common port LAG function */
+++int qca8k_port_lag_join(struct dsa_switch *ds, int port, struct net_device *lag,
+++			struct netdev_lag_upper_info *info);
+++int qca8k_port_lag_leave(struct dsa_switch *ds, int port,
+++			 struct net_device *lag);
+++
++ #endif /* __QCA8K_H */
+diff --git a/target/linux/generic/backport-5.15/771-v6.0-14-net-dsa-qca8k-move-read_switch_id-function-to-common.patch b/target/linux/generic/backport-5.15/771-v6.0-14-net-dsa-qca8k-move-read_switch_id-function-to-common.patch
 new file mode 100644
-index 0000000000..82c94b385b
+index 0000000000..d6ec8b77e0
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/766-03-net-dsa-tag_qca-convert-to-FIELD-macro.patch
-@@ -0,0 +1,86 @@
-+From 6b0458299297ca4ab6fb295800e29a4e501d50c1 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Wed, 2 Feb 2022 01:03:22 +0100
-+Subject: [PATCH 03/16] net: dsa: tag_qca: convert to FIELD macro
++++ b/target/linux/generic/backport-5.15/771-v6.0-14-net-dsa-qca8k-move-read_switch_id-function-to-common.patch
+@@ -0,0 +1,102 @@
++From 9d1bcb1f293f1391302a109c9819c3705c804700 Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Wed, 27 Jul 2022 13:35:23 +0200
++Subject: [PATCH 14/14] net: dsa: qca8k: move read_switch_id function to common
++ code
 +
-+Convert driver to FIELD macro to drop redundant define.
++The same function to read the switch id is used by drivers based on
++qca8k family switch. Move them to common code to make them accessible
++also by other drivers.
 +
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
 +Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 +---
-+ net/dsa/tag_qca.c | 34 +++++++++++++++-------------------
-+ 1 file changed, 15 insertions(+), 19 deletions(-)
-+
-+--- a/net/dsa/tag_qca.c
-++++ b/net/dsa/tag_qca.c
-+@@ -4,29 +4,24 @@
-+  */
-+ 
-+ #include <linux/etherdevice.h>
-++#include <linux/bitfield.h>
-+ 
-+ #include "dsa_priv.h"
-+ 
-+ #define QCA_HDR_LEN	2
-+ #define QCA_HDR_VERSION	0x2
-+ 
-+-#define QCA_HDR_RECV_VERSION_MASK	GENMASK(15, 14)
-+-#define QCA_HDR_RECV_VERSION_S		14
-+-#define QCA_HDR_RECV_PRIORITY_MASK	GENMASK(13, 11)
-+-#define QCA_HDR_RECV_PRIORITY_S		11
-+-#define QCA_HDR_RECV_TYPE_MASK		GENMASK(10, 6)
-+-#define QCA_HDR_RECV_TYPE_S		6
-++#define QCA_HDR_RECV_VERSION		GENMASK(15, 14)
-++#define QCA_HDR_RECV_PRIORITY		GENMASK(13, 11)
-++#define QCA_HDR_RECV_TYPE		GENMASK(10, 6)
-+ #define QCA_HDR_RECV_FRAME_IS_TAGGED	BIT(3)
-+-#define QCA_HDR_RECV_SOURCE_PORT_MASK	GENMASK(2, 0)
-++#define QCA_HDR_RECV_SOURCE_PORT	GENMASK(2, 0)
-+ 
-+-#define QCA_HDR_XMIT_VERSION_MASK	GENMASK(15, 14)
-+-#define QCA_HDR_XMIT_VERSION_S		14
-+-#define QCA_HDR_XMIT_PRIORITY_MASK	GENMASK(13, 11)
-+-#define QCA_HDR_XMIT_PRIORITY_S		11
-+-#define QCA_HDR_XMIT_CONTROL_MASK	GENMASK(10, 8)
-+-#define QCA_HDR_XMIT_CONTROL_S		8
-++#define QCA_HDR_XMIT_VERSION		GENMASK(15, 14)
-++#define QCA_HDR_XMIT_PRIORITY		GENMASK(13, 11)
-++#define QCA_HDR_XMIT_CONTROL		GENMASK(10, 8)
-+ #define QCA_HDR_XMIT_FROM_CPU		BIT(7)
-+-#define QCA_HDR_XMIT_DP_BIT_MASK	GENMASK(6, 0)
-++#define QCA_HDR_XMIT_DP_BIT		GENMASK(6, 0)
++ drivers/net/dsa/qca/qca8k-8xxx.c   | 29 -----------------------------
++ drivers/net/dsa/qca/qca8k-common.c | 29 +++++++++++++++++++++++++++++
++ drivers/net/dsa/qca/qca8k.h        |  1 +
++ 3 files changed, 30 insertions(+), 29 deletions(-)
++
++--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
++@@ -1822,35 +1822,6 @@ static const struct dsa_switch_ops qca8k
++ 	.connect_tag_protocol	= qca8k_connect_tag_protocol,
++ };
 + 
-+ static struct sk_buff *qca_tag_xmit(struct sk_buff *skb, struct net_device *dev)
++-static int qca8k_read_switch_id(struct qca8k_priv *priv)
++-{
++-	u32 val;
++-	u8 id;
++-	int ret;
++-
++-	if (!priv->info)
++-		return -ENODEV;
++-
++-	ret = qca8k_read(priv, QCA8K_REG_MASK_CTRL, &val);
++-	if (ret < 0)
++-		return -ENODEV;
++-
++-	id = QCA8K_MASK_CTRL_DEVICE_ID(val);
++-	if (id != priv->info->id) {
++-		dev_err(priv->dev,
++-			"Switch id detected %x but expected %x",
++-			id, priv->info->id);
++-		return -ENODEV;
++-	}
++-
++-	priv->switch_id = id;
++-
++-	/* Save revision to communicate to the internal PHY driver */
++-	priv->switch_revision = QCA8K_MASK_CTRL_REV_ID(val);
++-
++-	return 0;
++-}
++-
++ static int
++ qca8k_sw_probe(struct mdio_device *mdiodev)
 + {
-+@@ -40,8 +35,9 @@ static struct sk_buff *qca_tag_xmit(stru
-+ 	phdr = dsa_etype_header_pos_tx(skb);
-+ 
-+ 	/* Set the version field, and set destination port information */
-+-	hdr = QCA_HDR_VERSION << QCA_HDR_XMIT_VERSION_S |
-+-		QCA_HDR_XMIT_FROM_CPU | BIT(dp->index);
-++	hdr = FIELD_PREP(QCA_HDR_XMIT_VERSION, QCA_HDR_VERSION);
-++	hdr |= QCA_HDR_XMIT_FROM_CPU;
-++	hdr |= FIELD_PREP(QCA_HDR_XMIT_DP_BIT, BIT(dp->index));
-+ 
-+ 	*phdr = htons(hdr);
-+ 
-+@@ -62,7 +58,7 @@ static struct sk_buff *qca_tag_rcv(struc
-+ 	hdr = ntohs(*phdr);
-+ 
-+ 	/* Make sure the version is correct */
-+-	ver = (hdr & QCA_HDR_RECV_VERSION_MASK) >> QCA_HDR_RECV_VERSION_S;
-++	ver = FIELD_GET(QCA_HDR_RECV_VERSION, hdr);
-+ 	if (unlikely(ver != QCA_HDR_VERSION))
-+ 		return NULL;
-+ 
-+@@ -71,7 +67,7 @@ static struct sk_buff *qca_tag_rcv(struc
-+ 	dsa_strip_etype_header(skb, QCA_HDR_LEN);
-+ 
-+ 	/* Get source port information */
-+-	port = (hdr & QCA_HDR_RECV_SOURCE_PORT_MASK);
-++	port = FIELD_GET(QCA_HDR_RECV_SOURCE_PORT, hdr);
-+ 
-+ 	skb->dev = dsa_master_find_slave(dev, 0, port);
-+ 	if (!skb->dev)
-diff --git a/target/linux/generic/backport-5.15/766-04-net-dsa-tag_qca-move-define-to-include-linux-dsa.patch b/target/linux/generic/backport-5.15/766-04-net-dsa-tag_qca-move-define-to-include-linux-dsa.patch
-new file mode 100644
-index 0000000000..c1e74ceeeb
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/766-04-net-dsa-tag_qca-move-define-to-include-linux-dsa.patch
-@@ -0,0 +1,71 @@
-+From 3ec762fb13c7e7273800b94c80db1c2cc37590d1 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Wed, 2 Feb 2022 01:03:23 +0100
-+Subject: [PATCH 04/16] net: dsa: tag_qca: move define to include linux/dsa
-+
-+Move tag_qca define to include dir linux/dsa as the qca8k require access
-+to the tagger define to support in-band mdio read/write using ethernet
-+packet.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ include/linux/dsa/tag_qca.h | 21 +++++++++++++++++++++
-+ net/dsa/tag_qca.c           | 16 +---------------
-+ 2 files changed, 22 insertions(+), 15 deletions(-)
-+ create mode 100644 include/linux/dsa/tag_qca.h
-+
-+--- /dev/null
-++++ b/include/linux/dsa/tag_qca.h
-+@@ -0,0 +1,21 @@
-++/* SPDX-License-Identifier: GPL-2.0 */
++--- a/drivers/net/dsa/qca/qca8k-common.c
+++++ b/drivers/net/dsa/qca/qca8k-common.c
++@@ -1175,3 +1175,32 @@ int qca8k_port_lag_leave(struct dsa_swit
++ {
++ 	return qca8k_lag_refresh_portmap(ds, port, lag, true);
++ }
 ++
-++#ifndef __TAG_QCA_H
-++#define __TAG_QCA_H
+++int qca8k_read_switch_id(struct qca8k_priv *priv)
+++{
+++	u32 val;
+++	u8 id;
+++	int ret;
 ++
-++#define QCA_HDR_LEN	2
-++#define QCA_HDR_VERSION	0x2
+++	if (!priv->info)
+++		return -ENODEV;
 ++
-++#define QCA_HDR_RECV_VERSION		GENMASK(15, 14)
-++#define QCA_HDR_RECV_PRIORITY		GENMASK(13, 11)
-++#define QCA_HDR_RECV_TYPE		GENMASK(10, 6)
-++#define QCA_HDR_RECV_FRAME_IS_TAGGED	BIT(3)
-++#define QCA_HDR_RECV_SOURCE_PORT	GENMASK(2, 0)
+++	ret = qca8k_read(priv, QCA8K_REG_MASK_CTRL, &val);
+++	if (ret < 0)
+++		return -ENODEV;
 ++
-++#define QCA_HDR_XMIT_VERSION		GENMASK(15, 14)
-++#define QCA_HDR_XMIT_PRIORITY		GENMASK(13, 11)
-++#define QCA_HDR_XMIT_CONTROL		GENMASK(10, 8)
-++#define QCA_HDR_XMIT_FROM_CPU		BIT(7)
-++#define QCA_HDR_XMIT_DP_BIT		GENMASK(6, 0)
+++	id = QCA8K_MASK_CTRL_DEVICE_ID(val);
+++	if (id != priv->info->id) {
+++		dev_err(priv->dev,
+++			"Switch id detected %x but expected %x",
+++			id, priv->info->id);
+++		return -ENODEV;
+++	}
 ++
-++#endif /* __TAG_QCA_H */
-+--- a/net/dsa/tag_qca.c
-++++ b/net/dsa/tag_qca.c
-+@@ -5,24 +5,10 @@
-+ 
-+ #include <linux/etherdevice.h>
-+ #include <linux/bitfield.h>
-++#include <linux/dsa/tag_qca.h>
-+ 
-+ #include "dsa_priv.h"
-+ 
-+-#define QCA_HDR_LEN	2
-+-#define QCA_HDR_VERSION	0x2
-+-
-+-#define QCA_HDR_RECV_VERSION		GENMASK(15, 14)
-+-#define QCA_HDR_RECV_PRIORITY		GENMASK(13, 11)
-+-#define QCA_HDR_RECV_TYPE		GENMASK(10, 6)
-+-#define QCA_HDR_RECV_FRAME_IS_TAGGED	BIT(3)
-+-#define QCA_HDR_RECV_SOURCE_PORT	GENMASK(2, 0)
-+-
-+-#define QCA_HDR_XMIT_VERSION		GENMASK(15, 14)
-+-#define QCA_HDR_XMIT_PRIORITY		GENMASK(13, 11)
-+-#define QCA_HDR_XMIT_CONTROL		GENMASK(10, 8)
-+-#define QCA_HDR_XMIT_FROM_CPU		BIT(7)
-+-#define QCA_HDR_XMIT_DP_BIT		GENMASK(6, 0)
-+-
-+ static struct sk_buff *qca_tag_xmit(struct sk_buff *skb, struct net_device *dev)
-+ {
-+ 	struct dsa_port *dp = dsa_slave_to_port(dev);
-diff --git a/target/linux/generic/backport-5.15/766-05-net-dsa-tag_qca-enable-promisc_on_master-flag.patch b/target/linux/generic/backport-5.15/766-05-net-dsa-tag_qca-enable-promisc_on_master-flag.patch
+++	priv->switch_id = id;
+++
+++	/* Save revision to communicate to the internal PHY driver */
+++	priv->switch_revision = QCA8K_MASK_CTRL_REV_ID(val);
+++
+++	return 0;
+++}
++--- a/drivers/net/dsa/qca/qca8k.h
+++++ b/drivers/net/dsa/qca/qca8k.h
++@@ -424,6 +424,7 @@ extern const struct qca8k_mib_desc ar832
++ extern const struct regmap_access_table qca8k_readable_table;
++ int qca8k_mib_init(struct qca8k_priv *priv);
++ void qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable);
+++int qca8k_read_switch_id(struct qca8k_priv *priv);
++ 
++ /* Common read/write/rmw function */
++ int qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val);
+diff --git a/target/linux/generic/backport-5.15/772-v6.0-net-dsa-qca8k-fix-NULL-pointer-dereference-for-of_de.patch b/target/linux/generic/backport-5.15/772-v6.0-net-dsa-qca8k-fix-NULL-pointer-dereference-for-of_de.patch
 new file mode 100644
-index 0000000000..9394a0dabb
+index 0000000000..0cca2788f6
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/766-05-net-dsa-tag_qca-enable-promisc_on_master-flag.patch
-@@ -0,0 +1,27 @@
-+From 101c04c3463b87061e6a3d4f72c1bc57670685a6 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Wed, 2 Feb 2022 01:03:24 +0100
-+Subject: [PATCH 05/16] net: dsa: tag_qca: enable promisc_on_master flag
++++ b/target/linux/generic/backport-5.15/772-v6.0-net-dsa-qca8k-fix-NULL-pointer-dereference-for-of_de.patch
+@@ -0,0 +1,29 @@
++From 057bcf15db8e625276ddf02b2b7c668a3cb43f81 Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Sun, 4 Sep 2022 23:46:24 +0200
++Subject: [net PATCH] net: dsa: qca8k: fix NULL pointer dereference for
++ of_device_get_match_data
 +
-+Ethernet MDIO packets are non-standard and DSA master expects the first
-+6 octets to be the MAC DA. To address these kind of packet, enable
-+promisc_on_master flag for the tagger.
++of_device_get_match_data is called on priv->dev before priv->dev is
++actually set. Move of_device_get_match_data after priv->dev is correctly
++set to fix this kernel panic.
 +
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Fixes: 3bb0844e7bcd ("net: dsa: qca8k: cache match data to speed up access")
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
 +---
-+ net/dsa/tag_qca.c | 1 +
-+ 1 file changed, 1 insertion(+)
++ drivers/net/dsa/qca/qca8k-8xxx.c | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
 +
-+--- a/net/dsa/tag_qca.c
-++++ b/net/dsa/tag_qca.c
-+@@ -68,6 +68,7 @@ static const struct dsa_device_ops qca_n
-+ 	.xmit	= qca_tag_xmit,
-+ 	.rcv	= qca_tag_rcv,
-+ 	.needed_headroom = QCA_HDR_LEN,
-++	.promisc_on_master = true,
-+ };
++--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
++@@ -1835,9 +1835,9 @@ qca8k_sw_probe(struct mdio_device *mdiod
++ 	if (!priv)
++ 		return -ENOMEM;
 + 
-+ MODULE_LICENSE("GPL");
-diff --git a/target/linux/generic/backport-5.15/766-06-net-dsa-tag_qca-add-define-for-handling-mgmt-Etherne.patch b/target/linux/generic/backport-5.15/766-06-net-dsa-tag_qca-add-define-for-handling-mgmt-Etherne.patch
++-	priv->info = of_device_get_match_data(priv->dev);
++ 	priv->bus = mdiodev->bus;
++ 	priv->dev = &mdiodev->dev;
+++	priv->info = of_device_get_match_data(priv->dev);
++ 
++ 	priv->reset_gpio = devm_gpiod_get_optional(priv->dev, "reset",
++ 						   GPIOD_ASIS);
+diff --git a/target/linux/generic/backport-5.15/773-v5.18-1-net-dsa-Move-VLAN-filtering-syncing-out-of-dsa_switc.patch b/target/linux/generic/backport-5.15/773-v5.18-1-net-dsa-Move-VLAN-filtering-syncing-out-of-dsa_switc.patch
 new file mode 100644
-index 0000000000..459454e03b
+index 0000000000..44093eab95
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/766-06-net-dsa-tag_qca-add-define-for-handling-mgmt-Etherne.patch
-@@ -0,0 +1,110 @@
-+From c2ee8181fddb293d296477f60b3eb4fa3ce4e1a6 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Wed, 2 Feb 2022 01:03:25 +0100
-+Subject: [PATCH 06/16] net: dsa: tag_qca: add define for handling mgmt
-+ Ethernet packet
++++ b/target/linux/generic/backport-5.15/773-v5.18-1-net-dsa-Move-VLAN-filtering-syncing-out-of-dsa_switc.patch
+@@ -0,0 +1,77 @@
++From 381a730182f1d174e1950cd4e63e885b1c302051 Mon Sep 17 00:00:00 2001
++From: Tobias Waldekranz <tobias@waldekranz.com>
++Date: Mon, 24 Jan 2022 22:09:43 +0100
++Subject: net: dsa: Move VLAN filtering syncing out of dsa_switch_bridge_leave
 +
-+Add all the required define to prepare support for mgmt read/write in
-+Ethernet packet. Any packet of this type has to be dropped as the only
-+use of these special packet is receive ack for an mgmt write request or
-+receive data for an mgmt read request.
-+A struct is used that emulates the Ethernet header but is used for a
-+different purpose.
++Most of dsa_switch_bridge_leave was, in fact, dealing with the syncing
++of VLAN filtering for switches on which that is a global
++setting. Separate the two phases to prepare for the cross-chip related
++bugfix in the following commit.
 +
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
 +Signed-off-by: David S. Miller <davem@davemloft.net>
 +---
-+ include/linux/dsa/tag_qca.h | 44 +++++++++++++++++++++++++++++++++++++
-+ net/dsa/tag_qca.c           | 15 ++++++++++---
-+ 2 files changed, 56 insertions(+), 3 deletions(-)
++ net/dsa/switch.c | 38 +++++++++++++++++++++++++-------------
++ 1 file changed, 25 insertions(+), 13 deletions(-)
 +
-+--- a/include/linux/dsa/tag_qca.h
-++++ b/include/linux/dsa/tag_qca.h
-+@@ -12,10 +12,54 @@
-+ #define QCA_HDR_RECV_FRAME_IS_TAGGED	BIT(3)
-+ #define QCA_HDR_RECV_SOURCE_PORT	GENMASK(2, 0)
++--- a/net/dsa/switch.c
+++++ b/net/dsa/switch.c
++@@ -113,25 +113,14 @@ static int dsa_switch_bridge_join(struct
++ 	return dsa_tag_8021q_bridge_join(ds, info);
++ }
 + 
-++/* Packet type for recv */
-++#define QCA_HDR_RECV_TYPE_NORMAL	0x0
-++#define QCA_HDR_RECV_TYPE_MIB		0x1
-++#define QCA_HDR_RECV_TYPE_RW_REG_ACK	0x2
-++
-+ #define QCA_HDR_XMIT_VERSION		GENMASK(15, 14)
-+ #define QCA_HDR_XMIT_PRIORITY		GENMASK(13, 11)
-+ #define QCA_HDR_XMIT_CONTROL		GENMASK(10, 8)
-+ #define QCA_HDR_XMIT_FROM_CPU		BIT(7)
-+ #define QCA_HDR_XMIT_DP_BIT		GENMASK(6, 0)
++-static int dsa_switch_bridge_leave(struct dsa_switch *ds,
++-				   struct dsa_notifier_bridge_info *info)
+++static int dsa_switch_sync_vlan_filtering(struct dsa_switch *ds,
+++					  struct dsa_notifier_bridge_info *info)
++ {
++-	struct dsa_switch_tree *dst = ds->dst;
++ 	struct netlink_ext_ack extack = {0};
++ 	bool change_vlan_filtering = false;
++ 	bool vlan_filtering;
++ 	int err, port;
 + 
-++/* Packet type for xmit */
-++#define QCA_HDR_XMIT_TYPE_NORMAL	0x0
-++#define QCA_HDR_XMIT_TYPE_RW_REG	0x1
-++
-++/* Check code for a valid mgmt packet. Switch will ignore the packet
-++ * with this wrong.
-++ */
-++#define QCA_HDR_MGMT_CHECK_CODE_VAL	0x5
-++
-++/* Specific define for in-band MDIO read/write with Ethernet packet */
-++#define QCA_HDR_MGMT_SEQ_LEN		4 /* 4 byte for the seq */
-++#define QCA_HDR_MGMT_COMMAND_LEN	4 /* 4 byte for the command */
-++#define QCA_HDR_MGMT_DATA1_LEN		4 /* First 4 byte for the mdio data */
-++#define QCA_HDR_MGMT_HEADER_LEN		(QCA_HDR_MGMT_SEQ_LEN + \
-++					QCA_HDR_MGMT_COMMAND_LEN + \
-++					QCA_HDR_MGMT_DATA1_LEN)
-++
-++#define QCA_HDR_MGMT_DATA2_LEN		12 /* Other 12 byte for the mdio data */
-++#define QCA_HDR_MGMT_PADDING_LEN	34 /* Padding to reach the min Ethernet packet */
++-	if (dst->index == info->tree_index && ds->index == info->sw_index &&
++-	    ds->ops->port_bridge_leave)
++-		ds->ops->port_bridge_leave(ds, info->port, info->br);
++-
++-	if ((dst->index != info->tree_index || ds->index != info->sw_index) &&
++-	    ds->ops->crosschip_bridge_leave)
++-		ds->ops->crosschip_bridge_leave(ds, info->tree_index,
++-						info->sw_index, info->port,
++-						info->br);
++-
++ 	if (ds->needs_standalone_vlan_filtering && !br_vlan_enabled(info->br)) {
++ 		change_vlan_filtering = true;
++ 		vlan_filtering = true;
++@@ -172,6 +161,29 @@ static int dsa_switch_bridge_leave(struc
++ 			return err;
++ 	}
++ 
+++	return 0;
+++}
 ++
-++#define QCA_HDR_MGMT_PKT_LEN		(QCA_HDR_MGMT_HEADER_LEN + \
-++					QCA_HDR_LEN + \
-++					QCA_HDR_MGMT_DATA2_LEN + \
-++					QCA_HDR_MGMT_PADDING_LEN)
+++static int dsa_switch_bridge_leave(struct dsa_switch *ds,
+++				   struct dsa_notifier_bridge_info *info)
+++{
+++	struct dsa_switch_tree *dst = ds->dst;
+++	int err;
 ++
-++#define QCA_HDR_MGMT_SEQ_NUM		GENMASK(31, 0)  /* 63, 32 */
-++#define QCA_HDR_MGMT_CHECK_CODE		GENMASK(31, 29) /* 31, 29 */
-++#define QCA_HDR_MGMT_CMD		BIT(28)		/* 28 */
-++#define QCA_HDR_MGMT_LENGTH		GENMASK(23, 20) /* 23, 20 */
-++#define QCA_HDR_MGMT_ADDR		GENMASK(18, 0)  /* 18, 0 */
+++	if (dst->index == info->tree_index && ds->index == info->sw_index &&
+++	    ds->ops->port_bridge_leave)
+++		ds->ops->port_bridge_leave(ds, info->port, info->br);
 ++
-++/* Special struct emulating a Ethernet header */
-++struct qca_mgmt_ethhdr {
-++	u32 command;		/* command bit 31:0 */
-++	u32 seq;		/* seq 63:32 */
-++	u32 mdio_data;		/* first 4byte mdio */
-++	__be16 hdr;		/* qca hdr */
-++} __packed;
+++	if ((dst->index != info->tree_index || ds->index != info->sw_index) &&
+++	    ds->ops->crosschip_bridge_leave)
+++		ds->ops->crosschip_bridge_leave(ds, info->tree_index,
+++						info->sw_index, info->port,
+++						info->br);
 ++
-+ #endif /* __TAG_QCA_H */
-+--- a/net/dsa/tag_qca.c
-++++ b/net/dsa/tag_qca.c
-+@@ -32,10 +32,12 @@ static struct sk_buff *qca_tag_xmit(stru
-+ 
-+ static struct sk_buff *qca_tag_rcv(struct sk_buff *skb, struct net_device *dev)
-+ {
-+-	u8 ver;
-+-	u16  hdr;
-+-	int port;
-++	u8 ver, pk_type;
-+ 	__be16 *phdr;
-++	int port;
-++	u16 hdr;
+++	err = dsa_switch_sync_vlan_filtering(ds, info);
+++	if (err)
+++		return err;
 ++
-++	BUILD_BUG_ON(sizeof(struct qca_mgmt_ethhdr) != QCA_HDR_MGMT_HEADER_LEN + QCA_HDR_LEN);
-+ 
-+ 	if (unlikely(!pskb_may_pull(skb, QCA_HDR_LEN)))
-+ 		return NULL;
-+@@ -48,6 +50,13 @@ static struct sk_buff *qca_tag_rcv(struc
-+ 	if (unlikely(ver != QCA_HDR_VERSION))
-+ 		return NULL;
++ 	return dsa_tag_8021q_bridge_leave(ds, info);
++ }
 + 
-++	/* Get pk type */
-++	pk_type = FIELD_GET(QCA_HDR_RECV_TYPE, hdr);
-++
-++	/* Ethernet MDIO read/write packet */
-++	if (pk_type == QCA_HDR_RECV_TYPE_RW_REG_ACK)
-++		return NULL;
-++
-+ 	/* Remove QCA tag and recalculate checksum */
-+ 	skb_pull_rcsum(skb, QCA_HDR_LEN);
-+ 	dsa_strip_etype_header(skb, QCA_HDR_LEN);
-diff --git a/target/linux/generic/backport-5.15/766-07-net-dsa-tag_qca-add-define-for-handling-MIB-packet.patch b/target/linux/generic/backport-5.15/766-07-net-dsa-tag_qca-add-define-for-handling-MIB-packet.patch
+diff --git a/target/linux/generic/backport-5.15/773-v5.18-2-net-dsa-Avoid-cross-chip-syncing-of-VLAN-filtering.patch b/target/linux/generic/backport-5.15/773-v5.18-2-net-dsa-Avoid-cross-chip-syncing-of-VLAN-filtering.patch
 new file mode 100644
-index 0000000000..7e5dc65730
+index 0000000000..cdddbcf14e
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/766-07-net-dsa-tag_qca-add-define-for-handling-MIB-packet.patch
-@@ -0,0 +1,45 @@
-+From 18be654a4345f7d937b4bfbad74bea8093e3a93c Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Wed, 2 Feb 2022 01:03:26 +0100
-+Subject: [PATCH 07/16] net: dsa: tag_qca: add define for handling MIB packet
++++ b/target/linux/generic/backport-5.15/773-v5.18-2-net-dsa-Avoid-cross-chip-syncing-of-VLAN-filtering.patch
+@@ -0,0 +1,52 @@
++From 108dc8741c203e9d6ce4e973367f1bac20c7192b Mon Sep 17 00:00:00 2001
++From: Tobias Waldekranz <tobias@waldekranz.com>
++Date: Mon, 24 Jan 2022 22:09:44 +0100
++Subject: net: dsa: Avoid cross-chip syncing of VLAN filtering
++
++Changes to VLAN filtering are not applicable to cross-chip
++notifications.
++
++On a system like this:
 +
-+Add struct to correctly parse a mib Ethernet packet.
++.-----.   .-----.   .-----.
++| sw1 +---+ sw2 +---+ sw3 |
++'-1-2-'   '-1-2-'   '-1-2-'
 +
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Before this change, upon sw1p1 leaving a bridge, a call to
++dsa_port_vlan_filtering would also be made to sw2p1 and sw3p1.
++
++In this scenario:
++
++.---------.   .-----.   .-----.
++|   sw1   +---+ sw2 +---+ sw3 |
++'-1-2-3-4-'   '-1-2-'   '-1-2-'
++
++When sw1p4 would leave a bridge, dsa_port_vlan_filtering would be
++called for sw2 and sw3 with a non-existing port - leading to array
++out-of-bounds accesses and crashes on mv88e6xxx.
++
++Fixes: d371b7c92d19 ("net: dsa: Unset vlan_filtering when ports leave the bridge")
++Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
 +Signed-off-by: David S. Miller <davem@davemloft.net>
 +---
-+ include/linux/dsa/tag_qca.h | 10 ++++++++++
-+ net/dsa/tag_qca.c           |  4 ++++
-+ 2 files changed, 14 insertions(+)
++ net/dsa/switch.c | 8 +++++---
++ 1 file changed, 5 insertions(+), 3 deletions(-)
 +
-+--- a/include/linux/dsa/tag_qca.h
-++++ b/include/linux/dsa/tag_qca.h
-+@@ -62,4 +62,14 @@ struct qca_mgmt_ethhdr {
-+ 	__be16 hdr;		/* qca hdr */
-+ } __packed;
++--- a/net/dsa/switch.c
+++++ b/net/dsa/switch.c
++@@ -180,9 +180,11 @@ static int dsa_switch_bridge_leave(struc
++ 						info->sw_index, info->port,
++ 						info->br);
 + 
-++enum mdio_cmd {
-++	MDIO_WRITE = 0x0,
-++	MDIO_READ
-++};
-++
-++struct mib_ethhdr {
-++	u32 data[3];		/* first 3 mib counter */
-++	__be16 hdr;		/* qca hdr */
-++} __packed;
-++
-+ #endif /* __TAG_QCA_H */
-+--- a/net/dsa/tag_qca.c
-++++ b/net/dsa/tag_qca.c
-+@@ -57,6 +57,10 @@ static struct sk_buff *qca_tag_rcv(struc
-+ 	if (pk_type == QCA_HDR_RECV_TYPE_RW_REG_ACK)
-+ 		return NULL;
++-	err = dsa_switch_sync_vlan_filtering(ds, info);
++-	if (err)
++-		return err;
+++	if (ds->dst->index == info->tree_index && ds->index == info->sw_index) {
+++		err = dsa_switch_sync_vlan_filtering(ds, info);
+++		if (err)
+++			return err;
+++	}
 + 
-++	/* Ethernet MIB counter packet */
-++	if (pk_type == QCA_HDR_RECV_TYPE_MIB)
-++		return NULL;
-++
-+ 	/* Remove QCA tag and recalculate checksum */
-+ 	skb_pull_rcsum(skb, QCA_HDR_LEN);
-+ 	dsa_strip_etype_header(skb, QCA_HDR_LEN);
-diff --git a/target/linux/generic/backport-5.15/766-08-net-dsa-tag_qca-add-support-for-handling-mgmt-and-MI.patch b/target/linux/generic/backport-5.15/766-08-net-dsa-tag_qca-add-support-for-handling-mgmt-and-MI.patch
++ 	return dsa_tag_8021q_bridge_leave(ds, info);
++ }
+diff --git a/target/linux/generic/backport-5.15/774-v5.16-01-net-dsa-rtl8366rb-Support-bridge-offloading.patch b/target/linux/generic/backport-5.15/774-v5.16-01-net-dsa-rtl8366rb-Support-bridge-offloading.patch
 new file mode 100644
-index 0000000000..ad25da30e6
+index 0000000000..78570c5e6e
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/766-08-net-dsa-tag_qca-add-support-for-handling-mgmt-and-MI.patch
-@@ -0,0 +1,116 @@
-+From 31eb6b4386ad91930417e3f5c8157a4b5e31cbd5 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Wed, 2 Feb 2022 01:03:27 +0100
-+Subject: [PATCH 08/16] net: dsa: tag_qca: add support for handling mgmt and
-+ MIB Ethernet packet
++++ b/target/linux/generic/backport-5.15/774-v5.16-01-net-dsa-rtl8366rb-Support-bridge-offloading.patch
+@@ -0,0 +1,141 @@
++From c9111895fd38dadf125e07be627778a9950d8d77 Mon Sep 17 00:00:00 2001
++From: DENG Qingfang <dqfext@gmail.com>
++Date: Sun, 26 Sep 2021 00:59:24 +0200
++Subject: [PATCH 01/11] net: dsa: rtl8366rb: Support bridge offloading
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
 +
-+Add connect/disconnect helper to assign private struct to the DSA switch.
-+Add support for Ethernet mgmt and MIB if the DSA driver provide an handler
-+to correctly parse and elaborate the data.
++Use port isolation registers to configure bridge offloading.
 +
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Tested on the D-Link DIR-685, switching between ports and
++sniffing ports to make sure no packets leak.
++
++Cc: Vladimir Oltean <olteanv@gmail.com>
++Cc: Mauri Sandberg <sandberg@mailfence.com>
 +Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Reviewed-by: Alvin Å ipraga <alsi@bang-olufsen.dk>
 +Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: DENG Qingfang <dqfext@gmail.com>
++Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
 +Signed-off-by: David S. Miller <davem@davemloft.net>
 +---
-+ include/linux/dsa/tag_qca.h |  7 +++++++
-+ net/dsa/tag_qca.c           | 39 ++++++++++++++++++++++++++++++++++---
-+ 2 files changed, 43 insertions(+), 3 deletions(-)
++ drivers/net/dsa/rtl8366rb.c | 86 +++++++++++++++++++++++++++++++++++++
++ 1 file changed, 86 insertions(+)
 +
-+--- a/include/linux/dsa/tag_qca.h
-++++ b/include/linux/dsa/tag_qca.h
-+@@ -72,4 +72,11 @@ struct mib_ethhdr {
-+ 	__be16 hdr;		/* qca hdr */
-+ } __packed;
-+ 
-++struct qca_tagger_data {
-++	void (*rw_reg_ack_handler)(struct dsa_switch *ds,
-++				   struct sk_buff *skb);
-++	void (*mib_autocast_handler)(struct dsa_switch *ds,
-++				     struct sk_buff *skb);
-++};
-++
-+ #endif /* __TAG_QCA_H */
-+--- a/net/dsa/tag_qca.c
-++++ b/net/dsa/tag_qca.c
-+@@ -5,6 +5,7 @@
-+ 
-+ #include <linux/etherdevice.h>
-+ #include <linux/bitfield.h>
-++#include <net/dsa.h>
-+ #include <linux/dsa/tag_qca.h>
-+ 
-+ #include "dsa_priv.h"
-+@@ -32,6 +33,9 @@ static struct sk_buff *qca_tag_xmit(stru
-+ 
-+ static struct sk_buff *qca_tag_rcv(struct sk_buff *skb, struct net_device *dev)
-+ {
-++	struct qca_tagger_data *tagger_data;
-++	struct dsa_port *dp = dev->dsa_ptr;
-++	struct dsa_switch *ds = dp->ds;
-+ 	u8 ver, pk_type;
-+ 	__be16 *phdr;
-+ 	int port;
-+@@ -39,6 +43,8 @@ static struct sk_buff *qca_tag_rcv(struc
-+ 
-+ 	BUILD_BUG_ON(sizeof(struct qca_mgmt_ethhdr) != QCA_HDR_MGMT_HEADER_LEN + QCA_HDR_LEN);
++--- a/drivers/net/dsa/rtl8366rb.c
+++++ b/drivers/net/dsa/rtl8366rb.c
++@@ -300,6 +300,13 @@
++ #define RTL8366RB_INTERRUPT_STATUS_REG	0x0442
++ #define RTL8366RB_NUM_INTERRUPT		14 /* 0..13 */
 + 
-++	tagger_data = ds->tagger_data;
+++/* Port isolation registers */
+++#define RTL8366RB_PORT_ISO_BASE		0x0F08
+++#define RTL8366RB_PORT_ISO(pnum)	(RTL8366RB_PORT_ISO_BASE + (pnum))
+++#define RTL8366RB_PORT_ISO_EN		BIT(0)
+++#define RTL8366RB_PORT_ISO_PORTS_MASK	GENMASK(7, 1)
+++#define RTL8366RB_PORT_ISO_PORTS(pmask)	((pmask) << 1)
 ++
-+ 	if (unlikely(!pskb_may_pull(skb, QCA_HDR_LEN)))
-+ 		return NULL;
-+ 
-+@@ -53,13 +59,19 @@ static struct sk_buff *qca_tag_rcv(struc
-+ 	/* Get pk type */
-+ 	pk_type = FIELD_GET(QCA_HDR_RECV_TYPE, hdr);
++ /* bits 0..5 enable force when cleared */
++ #define RTL8366RB_MAC_FORCE_CTRL_REG	0x0F11
 + 
-+-	/* Ethernet MDIO read/write packet */
-+-	if (pk_type == QCA_HDR_RECV_TYPE_RW_REG_ACK)
-++	/* Ethernet mgmt read/write packet */
-++	if (pk_type == QCA_HDR_RECV_TYPE_RW_REG_ACK) {
-++		if (likely(tagger_data->rw_reg_ack_handler))
-++			tagger_data->rw_reg_ack_handler(ds, skb);
-+ 		return NULL;
-++	}
++@@ -835,6 +842,21 @@ static int rtl8366rb_setup(struct dsa_sw
++ 	if (ret)
++ 		return ret;
 + 
-+ 	/* Ethernet MIB counter packet */
-+-	if (pk_type == QCA_HDR_RECV_TYPE_MIB)
-++	if (pk_type == QCA_HDR_RECV_TYPE_MIB) {
-++		if (likely(tagger_data->mib_autocast_handler))
-++			tagger_data->mib_autocast_handler(ds, skb);
-+ 		return NULL;
+++	/* Isolate all user ports so they can only send packets to itself and the CPU port */
+++	for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) {
+++		ret = regmap_write(smi->map, RTL8366RB_PORT_ISO(i),
+++				   RTL8366RB_PORT_ISO_PORTS(BIT(RTL8366RB_PORT_NUM_CPU)) |
+++				   RTL8366RB_PORT_ISO_EN);
+++		if (ret)
+++			return ret;
 ++	}
-+ 
-+ 	/* Remove QCA tag and recalculate checksum */
-+ 	skb_pull_rcsum(skb, QCA_HDR_LEN);
-+@@ -75,9 +87,30 @@ static struct sk_buff *qca_tag_rcv(struc
-+ 	return skb;
+++	/* CPU port can send packets to all ports */
+++	ret = regmap_write(smi->map, RTL8366RB_PORT_ISO(RTL8366RB_PORT_NUM_CPU),
+++			   RTL8366RB_PORT_ISO_PORTS(dsa_user_ports(ds)) |
+++			   RTL8366RB_PORT_ISO_EN);
+++	if (ret)
+++		return ret;
+++
++ 	/* Set up the "green ethernet" feature */
++ 	ret = rtl8366rb_jam_table(rtl8366rb_green_jam,
++ 				  ARRAY_SIZE(rtl8366rb_green_jam), smi, false);
++@@ -1127,6 +1149,68 @@ rtl8366rb_port_disable(struct dsa_switch
++ 	rb8366rb_set_port_led(smi, port, false);
 + }
 + 
-++static int qca_tag_connect(struct dsa_switch *ds)
+++static int
+++rtl8366rb_port_bridge_join(struct dsa_switch *ds, int port,
+++			   struct net_device *bridge)
 ++{
-++	struct qca_tagger_data *tagger_data;
+++	struct realtek_smi *smi = ds->priv;
+++	unsigned int port_bitmap = 0;
+++	int ret, i;
 ++
-++	tagger_data = kzalloc(sizeof(*tagger_data), GFP_KERNEL);
-++	if (!tagger_data)
-++		return -ENOMEM;
+++	/* Loop over all other ports than the current one */
+++	for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) {
+++		/* Current port handled last */
+++		if (i == port)
+++			continue;
+++		/* Not on this bridge */
+++		if (dsa_to_port(ds, i)->bridge_dev != bridge)
+++			continue;
+++		/* Join this port to each other port on the bridge */
+++		ret = regmap_update_bits(smi->map, RTL8366RB_PORT_ISO(i),
+++					 RTL8366RB_PORT_ISO_PORTS(BIT(port)),
+++					 RTL8366RB_PORT_ISO_PORTS(BIT(port)));
+++		if (ret)
+++			dev_err(smi->dev, "failed to join port %d\n", port);
 ++
-++	ds->tagger_data = tagger_data;
+++		port_bitmap |= BIT(i);
+++	}
 ++
-++	return 0;
+++	/* Set the bits for the ports we can access */
+++	return regmap_update_bits(smi->map, RTL8366RB_PORT_ISO(port),
+++				  RTL8366RB_PORT_ISO_PORTS(port_bitmap),
+++				  RTL8366RB_PORT_ISO_PORTS(port_bitmap));
 ++}
 ++
-++static void qca_tag_disconnect(struct dsa_switch *ds)
+++static void
+++rtl8366rb_port_bridge_leave(struct dsa_switch *ds, int port,
+++			    struct net_device *bridge)
 ++{
-++	kfree(ds->tagger_data);
-++	ds->tagger_data = NULL;
+++	struct realtek_smi *smi = ds->priv;
+++	unsigned int port_bitmap = 0;
+++	int ret, i;
+++
+++	/* Loop over all other ports than this one */
+++	for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) {
+++		/* Current port handled last */
+++		if (i == port)
+++			continue;
+++		/* Not on this bridge */
+++		if (dsa_to_port(ds, i)->bridge_dev != bridge)
+++			continue;
+++		/* Remove this port from any other port on the bridge */
+++		ret = regmap_update_bits(smi->map, RTL8366RB_PORT_ISO(i),
+++					 RTL8366RB_PORT_ISO_PORTS(BIT(port)), 0);
+++		if (ret)
+++			dev_err(smi->dev, "failed to leave port %d\n", port);
+++
+++		port_bitmap |= BIT(i);
+++	}
+++
+++	/* Clear the bits for the ports we can not access, leave ourselves */
+++	regmap_update_bits(smi->map, RTL8366RB_PORT_ISO(port),
+++			   RTL8366RB_PORT_ISO_PORTS(port_bitmap), 0);
 ++}
 ++
-+ static const struct dsa_device_ops qca_netdev_ops = {
-+ 	.name	= "qca",
-+ 	.proto	= DSA_TAG_PROTO_QCA,
-++	.connect = qca_tag_connect,
-++	.disconnect = qca_tag_disconnect,
-+ 	.xmit	= qca_tag_xmit,
-+ 	.rcv	= qca_tag_rcv,
-+ 	.needed_headroom = QCA_HDR_LEN,
-diff --git a/target/linux/generic/backport-5.15/766-09-net-dsa-qca8k-add-tracking-state-of-master-port.patch b/target/linux/generic/backport-5.15/766-09-net-dsa-qca8k-add-tracking-state-of-master-port.patch
++ static int rtl8366rb_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
++ {
++ 	struct realtek_smi *smi = ds->priv;
++@@ -1510,6 +1594,8 @@ static const struct dsa_switch_ops rtl83
++ 	.get_strings = rtl8366_get_strings,
++ 	.get_ethtool_stats = rtl8366_get_ethtool_stats,
++ 	.get_sset_count = rtl8366_get_sset_count,
+++	.port_bridge_join = rtl8366rb_port_bridge_join,
+++	.port_bridge_leave = rtl8366rb_port_bridge_leave,
++ 	.port_vlan_filtering = rtl8366_vlan_filtering,
++ 	.port_vlan_add = rtl8366_vlan_add,
++ 	.port_vlan_del = rtl8366_vlan_del,
+diff --git a/target/linux/generic/backport-5.15/774-v5.16-02-net-dsa-rtl8366-Drop-custom-VLAN-set-up.patch b/target/linux/generic/backport-5.15/774-v5.16-02-net-dsa-rtl8366-Drop-custom-VLAN-set-up.patch
 new file mode 100644
-index 0000000000..ff8fdca512
+index 0000000000..e61349a32c
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/766-09-net-dsa-qca8k-add-tracking-state-of-master-port.patch
-@@ -0,0 +1,67 @@
-+From cddbec19466a1dfb4d45ddd507d9f09f991d54ae Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Wed, 2 Feb 2022 01:03:28 +0100
-+Subject: [PATCH 09/16] net: dsa: qca8k: add tracking state of master port
++++ b/target/linux/generic/backport-5.15/774-v5.16-02-net-dsa-rtl8366-Drop-custom-VLAN-set-up.patch
+@@ -0,0 +1,118 @@
++From 96cf10a8e7297065459473c081a6fb6432a22312 Mon Sep 17 00:00:00 2001
++From: Linus Walleij <linus.walleij@linaro.org>
++Date: Sun, 26 Sep 2021 00:59:25 +0200
++Subject: [PATCH 02/11] net: dsa: rtl8366: Drop custom VLAN set-up
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
 +
-+MDIO/MIB Ethernet require the master port and the tagger availabale to
-+correctly work. Use the new api master_state_change to track when master
-+is operational or not and set a bool in qca8k_priv.
-+We cache the first cached master available and we check if other cpu
-+port are operational when the cached one goes down.
-+This cached master will later be used by mdio read/write and mib request to
-+correctly use the working function.
++This hacky default VLAN setup was done in order to direct
++packets to the right ports and provide port isolation, both
++which we now support properly using custom tags and proper
++bridge port isolation.
 +
-+qca8k implementation for MDIO/MIB Ethernet is bad. CPU port0 is the only
-+one that answers with the ack packet or sends MIB Ethernet packets. For
-+this reason the master_state_change ignore CPU port6 and only checks
-+CPU port0 if it's operational and enables this mode.
++We can drop the custom VLAN code and leave all VLAN handling
++alone, as users expect things to be. We can also drop
++ds->configure_vlan_while_not_filtering = false; and let
++the core deal with any VLANs it wants.
 +
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Cc: Mauri Sandberg <sandberg@mailfence.com>
++Cc: DENG Qingfang <dqfext@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Reviewed-by: Alvin Å ipraga <alsi@bang-olufsen.dk>
 +Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
 +Signed-off-by: David S. Miller <davem@davemloft.net>
 +---
-+ drivers/net/dsa/qca8k.c | 15 +++++++++++++++
-+ drivers/net/dsa/qca8k.h |  1 +
-+ 2 files changed, 16 insertions(+)
++ drivers/net/dsa/realtek-smi-core.h |  1 -
++ drivers/net/dsa/rtl8366.c          | 48 ------------------------------
++ drivers/net/dsa/rtl8366rb.c        |  4 +--
++ 3 files changed, 1 insertion(+), 52 deletions(-)
 +
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -2382,6 +2382,20 @@ qca8k_port_lag_leave(struct dsa_switch *
-+ 	return qca8k_lag_refresh_portmap(ds, port, lag, true);
++--- a/drivers/net/dsa/realtek-smi-core.h
+++++ b/drivers/net/dsa/realtek-smi-core.h
++@@ -129,7 +129,6 @@ int rtl8366_set_pvid(struct realtek_smi
++ int rtl8366_enable_vlan4k(struct realtek_smi *smi, bool enable);
++ int rtl8366_enable_vlan(struct realtek_smi *smi, bool enable);
++ int rtl8366_reset_vlan(struct realtek_smi *smi);
++-int rtl8366_init_vlan(struct realtek_smi *smi);
++ int rtl8366_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
++ 			   struct netlink_ext_ack *extack);
++ int rtl8366_vlan_add(struct dsa_switch *ds, int port,
++--- a/drivers/net/dsa/rtl8366.c
+++++ b/drivers/net/dsa/rtl8366.c
++@@ -292,54 +292,6 @@ int rtl8366_reset_vlan(struct realtek_sm
 + }
++ EXPORT_SYMBOL_GPL(rtl8366_reset_vlan);
 + 
-++static void
-++qca8k_master_change(struct dsa_switch *ds, const struct net_device *master,
-++		    bool operational)
-++{
-++	struct dsa_port *dp = master->dsa_ptr;
-++	struct qca8k_priv *priv = ds->priv;
-++
-++	/* Ethernet MIB/MDIO is only supported for CPU port 0 */
-++	if (dp->index != 0)
-++		return;
-++
-++	priv->mgmt_master = operational ? (struct net_device *)master : NULL;
-++}
-++
-+ static const struct dsa_switch_ops qca8k_switch_ops = {
-+ 	.get_tag_protocol	= qca8k_get_tag_protocol,
-+ 	.setup			= qca8k_setup,
-+@@ -2417,6 +2431,7 @@ static const struct dsa_switch_ops qca8k
-+ 	.get_phy_flags		= qca8k_get_phy_flags,
-+ 	.port_lag_join		= qca8k_port_lag_join,
-+ 	.port_lag_leave		= qca8k_port_lag_leave,
-++	.master_state_change	= qca8k_master_change,
-+ };
++-int rtl8366_init_vlan(struct realtek_smi *smi)
++-{
++-	int port;
++-	int ret;
++-
++-	ret = rtl8366_reset_vlan(smi);
++-	if (ret)
++-		return ret;
++-
++-	/* Loop over the available ports, for each port, associate
++-	 * it with the VLAN (port+1)
++-	 */
++-	for (port = 0; port < smi->num_ports; port++) {
++-		u32 mask;
++-
++-		if (port == smi->cpu_port)
++-			/* For the CPU port, make all ports members of this
++-			 * VLAN.
++-			 */
++-			mask = GENMASK((int)smi->num_ports - 1, 0);
++-		else
++-			/* For all other ports, enable itself plus the
++-			 * CPU port.
++-			 */
++-			mask = BIT(port) | BIT(smi->cpu_port);
++-
++-		/* For each port, set the port as member of VLAN (port+1)
++-		 * and untagged, except for the CPU port: the CPU port (5) is
++-		 * member of VLAN 6 and so are ALL the other ports as well.
++-		 * Use filter 0 (no filter).
++-		 */
++-		dev_info(smi->dev, "VLAN%d port mask for port %d, %08x\n",
++-			 (port + 1), port, mask);
++-		ret = rtl8366_set_vlan(smi, (port + 1), mask, mask, 0);
++-		if (ret)
++-			return ret;
++-
++-		dev_info(smi->dev, "VLAN%d port %d, PVID set to %d\n",
++-			 (port + 1), port, (port + 1));
++-		ret = rtl8366_set_pvid(smi, port, (port + 1));
++-		if (ret)
++-			return ret;
++-	}
++-
++-	return rtl8366_enable_vlan(smi, true);
++-}
++-EXPORT_SYMBOL_GPL(rtl8366_init_vlan);
++-
++ int rtl8366_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
++ 			   struct netlink_ext_ack *extack)
++ {
++--- a/drivers/net/dsa/rtl8366rb.c
+++++ b/drivers/net/dsa/rtl8366rb.c
++@@ -985,7 +985,7 @@ static int rtl8366rb_setup(struct dsa_sw
++ 			return ret;
++ 	}
 + 
-+ static int qca8k_read_switch_id(struct qca8k_priv *priv)
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -353,6 +353,7 @@ struct qca8k_priv {
-+ 	struct dsa_switch_ops ops;
-+ 	struct gpio_desc *reset_gpio;
-+ 	unsigned int port_mtu[QCA8K_NUM_PORTS];
-++	struct net_device *mgmt_master; /* Track if mdio/mib Ethernet is available */
-+ };
++-	ret = rtl8366_init_vlan(smi);
+++	ret = rtl8366_reset_vlan(smi);
++ 	if (ret)
++ 		return ret;
 + 
-+ struct qca8k_mib_desc {
-diff --git a/target/linux/generic/backport-5.15/766-10-net-dsa-qca8k-add-support-for-mgmt-read-write-in-Eth.patch b/target/linux/generic/backport-5.15/766-10-net-dsa-qca8k-add-support-for-mgmt-read-write-in-Eth.patch
++@@ -999,8 +999,6 @@ static int rtl8366rb_setup(struct dsa_sw
++ 		return -ENODEV;
++ 	}
++ 
++-	ds->configure_vlan_while_not_filtering = false;
++-
++ 	return 0;
++ }
++ 
+diff --git a/target/linux/generic/backport-5.15/774-v5.16-03-net-dsa-rtl8366rb-Rewrite-weird-VLAN-filering-enable.patch b/target/linux/generic/backport-5.15/774-v5.16-03-net-dsa-rtl8366rb-Rewrite-weird-VLAN-filering-enable.patch
 new file mode 100644
-index 0000000000..43656ad79d
+index 0000000000..2b19752399
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/766-10-net-dsa-qca8k-add-support-for-mgmt-read-write-in-Eth.patch
-@@ -0,0 +1,363 @@
-+From 5950c7c0a68c915b336c70f79388626e2d576ab7 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Wed, 2 Feb 2022 01:03:29 +0100
-+Subject: [PATCH 10/16] net: dsa: qca8k: add support for mgmt read/write in
-+ Ethernet packet
-+
-+Add qca8k side support for mgmt read/write in Ethernet packet.
-+qca8k supports some specially crafted Ethernet packet that can be used
-+for mgmt read/write instead of the legacy method uart/internal mdio.
-+This add support for the qca8k side to craft the packet and enqueue it.
-+Each port and the qca8k_priv have a special struct to put data in it.
-+The completion API is used to wait for the packet to be received back
-+with the requested data.
++++ b/target/linux/generic/backport-5.15/774-v5.16-03-net-dsa-rtl8366rb-Rewrite-weird-VLAN-filering-enable.patch
+@@ -0,0 +1,270 @@
++From 7028f54b620f8df344b18e46e4a78e266091ab45 Mon Sep 17 00:00:00 2001
++From: Linus Walleij <linus.walleij@linaro.org>
++Date: Sun, 26 Sep 2021 00:59:26 +0200
++Subject: [PATCH 03/11] net: dsa: rtl8366rb: Rewrite weird VLAN filering
++ enablement
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
 +
-+The various steps are:
-+1. Craft the special packet with the qca hdr set to mgmt read/write
-+   mode.
-+2. Set the lock in the dedicated mgmt struct.
-+3. Increment the seq number and set it in the mgmt pkt
-+4. Reinit the completion.
-+5. Enqueue the packet.
-+6. Wait the packet to be received.
-+7. Use the data set by the tagger to complete the mdio operation.
++While we were defining one VLAN per port for isolating the ports
++the port_vlan_filtering() callback was implemented to enable a
++VLAN on the port + 1. This function makes no sense, not only is
++it incomplete as it only enables the VLAN, it doesn't do what
++the callback is supposed to do, which is to selectively enable
++and disable filtering on a certain port.
 +
-+If the completion timeouts or the ack value is not true, the legacy
-+mdio way is used.
++Implement the correct callback: we have two registers dealing
++with filtering on the RTL9366RB, so we implement an ASIC-specific
++callback and implement filering using the register bit that makes
++the switch drop frames if the port is not in the VLAN member set.
 +
-+It has to be considered that in the initial setup mdio is still used and
-+mdio is still used until DSA is ready to accept and tag packet.
++The DSA documentation Documentation/networking/switchdev.rst states:
 +
-+tag_proto_connect() is used to fill the required handler for the tagger
-+to correctly parse and elaborate the special Ethernet mdio packet.
++  When the bridge has VLAN filtering enabled and a PVID is not
++  configured on the ingress port, untagged and 802.1p tagged
++  packets must be dropped. When the bridge has VLAN filtering
++  enabled and a PVID exists on the ingress port, untagged and
++  priority-tagged packets must be accepted and forwarded according
++  to the bridge's port membership of the PVID VLAN. When the
++  bridge has VLAN filtering disabled, the presence/lack of a
++  PVID should not influence the packet forwarding decision.
 +
-+Locking is added to qca8k_master_change() to make sure no mgmt Ethernet
-+are in progress.
++To comply with this, we add two arrays of bool in the RTL8366RB
++state that keeps track of if filtering and PVID is enabled or
++not for each port. We then add code such that whenever filtering
++or PVID changes, we update the filter according to the
++specification.
 +
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Cc: Vladimir Oltean <olteanv@gmail.com>
++Cc: Mauri Sandberg <sandberg@mailfence.com>
++Cc: Alvin Å ipraga <alsi@bang-olufsen.dk>
++Cc: Florian Fainelli <f.fainelli@gmail.com>
++Cc: DENG Qingfang <dqfext@gmail.com>
++Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
 +Signed-off-by: David S. Miller <davem@davemloft.net>
 +---
-+ drivers/net/dsa/qca8k.c | 225 ++++++++++++++++++++++++++++++++++++++++
-+ drivers/net/dsa/qca8k.h |  13 +++
-+ 2 files changed, 238 insertions(+)
++ drivers/net/dsa/realtek-smi-core.h |   2 -
++ drivers/net/dsa/rtl8366.c          |  35 ----------
++ drivers/net/dsa/rtl8366rb.c        | 102 +++++++++++++++++++++++++++--
++ 3 files changed, 95 insertions(+), 44 deletions(-)
 +
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -20,6 +20,7 @@
-+ #include <linux/phylink.h>
-+ #include <linux/gpio/consumer.h>
-+ #include <linux/etherdevice.h>
-++#include <linux/dsa/tag_qca.h>
++--- a/drivers/net/dsa/realtek-smi-core.h
+++++ b/drivers/net/dsa/realtek-smi-core.h
++@@ -129,8 +129,6 @@ int rtl8366_set_pvid(struct realtek_smi
++ int rtl8366_enable_vlan4k(struct realtek_smi *smi, bool enable);
++ int rtl8366_enable_vlan(struct realtek_smi *smi, bool enable);
++ int rtl8366_reset_vlan(struct realtek_smi *smi);
++-int rtl8366_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
++-			   struct netlink_ext_ack *extack);
++ int rtl8366_vlan_add(struct dsa_switch *ds, int port,
++ 		     const struct switchdev_obj_port_vlan *vlan,
++ 		     struct netlink_ext_ack *extack);
++--- a/drivers/net/dsa/rtl8366.c
+++++ b/drivers/net/dsa/rtl8366.c
++@@ -292,41 +292,6 @@ int rtl8366_reset_vlan(struct realtek_sm
++ }
++ EXPORT_SYMBOL_GPL(rtl8366_reset_vlan);
 + 
-+ #include "qca8k.h"
++-int rtl8366_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
++-			   struct netlink_ext_ack *extack)
++-{
++-	struct realtek_smi *smi = ds->priv;
++-	struct rtl8366_vlan_4k vlan4k;
++-	int ret;
++-
++-	/* Use VLAN nr port + 1 since VLAN0 is not valid */
++-	if (!smi->ops->is_vlan_valid(smi, port + 1))
++-		return -EINVAL;
++-
++-	dev_info(smi->dev, "%s filtering on port %d\n",
++-		 vlan_filtering ? "enable" : "disable",
++-		 port);
++-
++-	/* TODO:
++-	 * The hardware support filter ID (FID) 0..7, I have no clue how to
++-	 * support this in the driver when the callback only says on/off.
++-	 */
++-	ret = smi->ops->get_vlan_4k(smi, port + 1, &vlan4k);
++-	if (ret)
++-		return ret;
++-
++-	/* Just set the filter to FID 1 for now then */
++-	ret = rtl8366_set_vlan(smi, port + 1,
++-			       vlan4k.member,
++-			       vlan4k.untag,
++-			       1);
++-	if (ret)
++-		return ret;
++-
++-	return 0;
++-}
++-EXPORT_SYMBOL_GPL(rtl8366_vlan_filtering);
++-
++ int rtl8366_vlan_add(struct dsa_switch *ds, int port,
++ 		     const struct switchdev_obj_port_vlan *vlan,
++ 		     struct netlink_ext_ack *extack)
++--- a/drivers/net/dsa/rtl8366rb.c
+++++ b/drivers/net/dsa/rtl8366rb.c
++@@ -143,6 +143,21 @@
++ #define RTL8366RB_PHY_NO_OFFSET			9
++ #define RTL8366RB_PHY_NO_MASK			(0x1f << 9)
 + 
-+@@ -170,6 +171,194 @@ qca8k_rmw(struct qca8k_priv *priv, u32 r
-+ 	return regmap_update_bits(priv->regmap, reg, mask, write_val);
+++/* VLAN Ingress Control Register 1, one bit per port.
+++ * bit 0 .. 5 will make the switch drop ingress frames without
+++ * VID such as untagged or priority-tagged frames for respective
+++ * port.
+++ * bit 6 .. 11 will make the switch drop ingress frames carrying
+++ * a C-tag with VID != 0 for respective port.
+++ */
+++#define RTL8366RB_VLAN_INGRESS_CTRL1_REG	0x037E
+++#define RTL8366RB_VLAN_INGRESS_CTRL1_DROP(port)	(BIT((port)) | BIT((port) + 6))
+++
+++/* VLAN Ingress Control Register 2, one bit per port.
+++ * bit0 .. bit5 will make the switch drop all ingress frames with
+++ * a VLAN classification that does not include the port is in its
+++ * member set.
+++ */
++ #define RTL8366RB_VLAN_INGRESS_CTRL2_REG	0x037f
++ 
++ /* LED control registers */
++@@ -321,9 +336,13 @@
++ /**
++  * struct rtl8366rb - RTL8366RB-specific data
++  * @max_mtu: per-port max MTU setting
+++ * @pvid_enabled: if PVID is set for respective port
+++ * @vlan_filtering: if VLAN filtering is enabled for respective port
++  */
++ struct rtl8366rb {
++ 	unsigned int max_mtu[RTL8366RB_NUM_PORTS];
+++	bool pvid_enabled[RTL8366RB_NUM_PORTS];
+++	bool vlan_filtering[RTL8366RB_NUM_PORTS];
++ };
++ 
++ static struct rtl8366_mib_counter rtl8366rb_mib_counters[] = {
++@@ -933,11 +952,13 @@ static int rtl8366rb_setup(struct dsa_sw
++ 	if (ret)
++ 		return ret;
++ 
++-	/* Discard VLAN tagged packets if the port is not a member of
++-	 * the VLAN with which the packets is associated.
++-	 */
+++	/* Accept all packets by default, we enable filtering on-demand */
+++	ret = regmap_write(smi->map, RTL8366RB_VLAN_INGRESS_CTRL1_REG,
+++			   0);
+++	if (ret)
+++		return ret;
++ 	ret = regmap_write(smi->map, RTL8366RB_VLAN_INGRESS_CTRL2_REG,
++-			   RTL8366RB_PORT_ALL);
+++			   0);
++ 	if (ret)
++ 		return ret;
++ 
++@@ -1209,6 +1230,53 @@ rtl8366rb_port_bridge_leave(struct dsa_s
++ 			   RTL8366RB_PORT_ISO_PORTS(port_bitmap), 0);
 + }
 + 
-++static void qca8k_rw_reg_ack_handler(struct dsa_switch *ds, struct sk_buff *skb)
-++{
-++	struct qca8k_mgmt_eth_data *mgmt_eth_data;
-++	struct qca8k_priv *priv = ds->priv;
-++	struct qca_mgmt_ethhdr *mgmt_ethhdr;
-++	u8 len, cmd;
-++
-++	mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb_mac_header(skb);
-++	mgmt_eth_data = &priv->mgmt_eth_data;
-++
-++	cmd = FIELD_GET(QCA_HDR_MGMT_CMD, mgmt_ethhdr->command);
-++	len = FIELD_GET(QCA_HDR_MGMT_LENGTH, mgmt_ethhdr->command);
-++
-++	/* Make sure the seq match the requested packet */
-++	if (mgmt_ethhdr->seq == mgmt_eth_data->seq)
-++		mgmt_eth_data->ack = true;
-++
-++	if (cmd == MDIO_READ) {
-++		mgmt_eth_data->data[0] = mgmt_ethhdr->mdio_data;
-++
-++		/* Get the rest of the 12 byte of data */
-++		if (len > QCA_HDR_MGMT_DATA1_LEN)
-++			memcpy(mgmt_eth_data->data + 1, skb->data,
-++			       QCA_HDR_MGMT_DATA2_LEN);
-++	}
-++
-++	complete(&mgmt_eth_data->rw_done);
-++}
-++
-++static struct sk_buff *qca8k_alloc_mdio_header(enum mdio_cmd cmd, u32 reg, u32 *val,
-++					       int priority)
-++{
-++	struct qca_mgmt_ethhdr *mgmt_ethhdr;
-++	struct sk_buff *skb;
-++	u16 hdr;
-++
-++	skb = dev_alloc_skb(QCA_HDR_MGMT_PKT_LEN);
-++	if (!skb)
-++		return NULL;
-++
-++	skb_reset_mac_header(skb);
-++	skb_set_network_header(skb, skb->len);
-++
-++	mgmt_ethhdr = skb_push(skb, QCA_HDR_MGMT_HEADER_LEN + QCA_HDR_LEN);
-++
-++	hdr = FIELD_PREP(QCA_HDR_XMIT_VERSION, QCA_HDR_VERSION);
-++	hdr |= FIELD_PREP(QCA_HDR_XMIT_PRIORITY, priority);
-++	hdr |= QCA_HDR_XMIT_FROM_CPU;
-++	hdr |= FIELD_PREP(QCA_HDR_XMIT_DP_BIT, BIT(0));
-++	hdr |= FIELD_PREP(QCA_HDR_XMIT_CONTROL, QCA_HDR_XMIT_TYPE_RW_REG);
-++
-++	mgmt_ethhdr->command = FIELD_PREP(QCA_HDR_MGMT_ADDR, reg);
-++	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_LENGTH, 4);
-++	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CMD, cmd);
-++	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CHECK_CODE,
-++					   QCA_HDR_MGMT_CHECK_CODE_VAL);
-++
-++	if (cmd == MDIO_WRITE)
-++		mgmt_ethhdr->mdio_data = *val;
-++
-++	mgmt_ethhdr->hdr = htons(hdr);
-++
-++	skb_put_zero(skb, QCA_HDR_MGMT_DATA2_LEN + QCA_HDR_MGMT_PADDING_LEN);
-++
-++	return skb;
-++}
-++
-++static void qca8k_mdio_header_fill_seq_num(struct sk_buff *skb, u32 seq_num)
+++/**
+++ * rtl8366rb_drop_untagged() - make the switch drop untagged and C-tagged frames
+++ * @smi: SMI state container
+++ * @port: the port to drop untagged and C-tagged frames on
+++ * @drop: whether to drop or pass untagged and C-tagged frames
+++ */
+++static int rtl8366rb_drop_untagged(struct realtek_smi *smi, int port, bool drop)
 ++{
-++	struct qca_mgmt_ethhdr *mgmt_ethhdr;
-++
-++	mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb->data;
-++	mgmt_ethhdr->seq = FIELD_PREP(QCA_HDR_MGMT_SEQ_NUM, seq_num);
+++	return regmap_update_bits(smi->map, RTL8366RB_VLAN_INGRESS_CTRL1_REG,
+++				  RTL8366RB_VLAN_INGRESS_CTRL1_DROP(port),
+++				  drop ? RTL8366RB_VLAN_INGRESS_CTRL1_DROP(port) : 0);
 ++}
 ++
-++static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val)
+++static int rtl8366rb_vlan_filtering(struct dsa_switch *ds, int port,
+++				    bool vlan_filtering,
+++				    struct netlink_ext_ack *extack)
 ++{
-++	struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
-++	struct sk_buff *skb;
-++	bool ack;
+++	struct realtek_smi *smi = ds->priv;
+++	struct rtl8366rb *rb;
 ++	int ret;
 ++
-++	skb = qca8k_alloc_mdio_header(MDIO_READ, reg, NULL,
-++				      QCA8K_ETHERNET_MDIO_PRIORITY);
-++	if (!skb)
-++		return -ENOMEM;
-++
-++	mutex_lock(&mgmt_eth_data->mutex);
-++
-++	/* Check mgmt_master if is operational */
-++	if (!priv->mgmt_master) {
-++		kfree_skb(skb);
-++		mutex_unlock(&mgmt_eth_data->mutex);
-++		return -EINVAL;
-++	}
-++
-++	skb->dev = priv->mgmt_master;
-++
-++	reinit_completion(&mgmt_eth_data->rw_done);
-++
-++	/* Increment seq_num and set it in the mdio pkt */
-++	mgmt_eth_data->seq++;
-++	qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
-++	mgmt_eth_data->ack = false;
-++
-++	dev_queue_xmit(skb);
-++
-++	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
-++					  msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
+++	rb = smi->chip_data;
 ++
-++	*val = mgmt_eth_data->data[0];
-++	ack = mgmt_eth_data->ack;
+++	dev_dbg(smi->dev, "port %d: %s VLAN filtering\n", port,
+++		vlan_filtering ? "enable" : "disable");
 ++
-++	mutex_unlock(&mgmt_eth_data->mutex);
+++	/* If the port is not in the member set, the frame will be dropped */
+++	ret = regmap_update_bits(smi->map, RTL8366RB_VLAN_INGRESS_CTRL2_REG,
+++				 BIT(port), vlan_filtering ? BIT(port) : 0);
+++	if (ret)
+++		return ret;
 ++
-++	if (ret <= 0)
-++		return -ETIMEDOUT;
+++	/* Keep track if filtering is enabled on each port */
+++	rb->vlan_filtering[port] = vlan_filtering;
 ++
-++	if (!ack)
-++		return -EINVAL;
+++	/* If VLAN filtering is enabled and PVID is also enabled, we must
+++	 * not drop any untagged or C-tagged frames. If we turn off VLAN
+++	 * filtering on a port, we need ti accept any frames.
+++	 */
+++	if (vlan_filtering)
+++		ret = rtl8366rb_drop_untagged(smi, port, !rb->pvid_enabled[port]);
+++	else
+++		ret = rtl8366rb_drop_untagged(smi, port, false);
 ++
-++	return 0;
+++	return ret;
 ++}
 ++
-++static int qca8k_write_eth(struct qca8k_priv *priv, u32 reg, u32 val)
-++{
-++	struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
-++	struct sk_buff *skb;
-++	bool ack;
++ static int rtl8366rb_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
++ {
++ 	struct realtek_smi *smi = ds->priv;
++@@ -1420,14 +1488,34 @@ static int rtl8366rb_get_mc_index(struct
++ 
++ static int rtl8366rb_set_mc_index(struct realtek_smi *smi, int port, int index)
++ {
+++	struct rtl8366rb *rb;
+++	bool pvid_enabled;
 ++	int ret;
 ++
-++	skb = qca8k_alloc_mdio_header(MDIO_WRITE, reg, &val,
-++				      QCA8K_ETHERNET_MDIO_PRIORITY);
-++	if (!skb)
-++		return -ENOMEM;
-++
-++	mutex_lock(&mgmt_eth_data->mutex);
-++
-++	/* Check mgmt_master if is operational */
-++	if (!priv->mgmt_master) {
-++		kfree_skb(skb);
-++		mutex_unlock(&mgmt_eth_data->mutex);
-++		return -EINVAL;
-++	}
-++
-++	skb->dev = priv->mgmt_master;
-++
-++	reinit_completion(&mgmt_eth_data->rw_done);
-++
-++	/* Increment seq_num and set it in the mdio pkt */
-++	mgmt_eth_data->seq++;
-++	qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
-++	mgmt_eth_data->ack = false;
-++
-++	dev_queue_xmit(skb);
-++
-++	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
-++					  msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
-++
-++	ack = mgmt_eth_data->ack;
-++
-++	mutex_unlock(&mgmt_eth_data->mutex);
-++
-++	if (ret <= 0)
-++		return -ETIMEDOUT;
-++
-++	if (!ack)
-++		return -EINVAL;
-++
-++	return 0;
-++}
-++
-++static int
-++qca8k_regmap_update_bits_eth(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
-++{
-++	u32 val = 0;
-++	int ret;
+++	rb = smi->chip_data;
+++	pvid_enabled = !!index;
 ++
-++	ret = qca8k_read_eth(priv, reg, &val);
++ 	if (port >= smi->num_ports || index >= RTL8366RB_NUM_VLANS)
++ 		return -EINVAL;
++ 
++-	return regmap_update_bits(smi->map, RTL8366RB_PORT_VLAN_CTRL_REG(port),
+++	ret = regmap_update_bits(smi->map, RTL8366RB_PORT_VLAN_CTRL_REG(port),
++ 				RTL8366RB_PORT_VLAN_CTRL_MASK <<
++ 					RTL8366RB_PORT_VLAN_CTRL_SHIFT(port),
++ 				(index & RTL8366RB_PORT_VLAN_CTRL_MASK) <<
++ 					RTL8366RB_PORT_VLAN_CTRL_SHIFT(port));
 ++	if (ret)
 ++		return ret;
 ++
-++	val &= ~mask;
-++	val |= write_val;
+++	rb->pvid_enabled[port] = pvid_enabled;
 ++
-++	return qca8k_write_eth(priv, reg, val);
-++}
+++	/* If VLAN filtering is enabled and PVID is also enabled, we must
+++	 * not drop any untagged or C-tagged frames. Make sure to update the
+++	 * filtering setting.
+++	 */
+++	if (rb->vlan_filtering[port])
+++		ret = rtl8366rb_drop_untagged(smi, port, !pvid_enabled);
 ++
-+ static int
-+ qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
-+ {
-+@@ -178,6 +367,9 @@ qca8k_regmap_read(void *ctx, uint32_t re
-+ 	u16 r1, r2, page;
-+ 	int ret;
+++	return ret;
++ }
++ 
++ static bool rtl8366rb_is_vlan_valid(struct realtek_smi *smi, unsigned int vlan)
++@@ -1437,7 +1525,7 @@ static bool rtl8366rb_is_vlan_valid(stru
++ 	if (smi->vlan4k_enabled)
++ 		max = RTL8366RB_NUM_VIDS - 1;
++ 
++-	if (vlan == 0 || vlan > max)
+++	if (vlan > max)
++ 		return false;
++ 
++ 	return true;
++@@ -1594,7 +1682,7 @@ static const struct dsa_switch_ops rtl83
++ 	.get_sset_count = rtl8366_get_sset_count,
++ 	.port_bridge_join = rtl8366rb_port_bridge_join,
++ 	.port_bridge_leave = rtl8366rb_port_bridge_leave,
++-	.port_vlan_filtering = rtl8366_vlan_filtering,
+++	.port_vlan_filtering = rtl8366rb_vlan_filtering,
++ 	.port_vlan_add = rtl8366_vlan_add,
++ 	.port_vlan_del = rtl8366_vlan_del,
++ 	.port_enable = rtl8366rb_port_enable,
+diff --git a/target/linux/generic/backport-5.15/774-v5.16-06-net-dsa-rtl8366-Drop-and-depromote-pointless-prints.patch b/target/linux/generic/backport-5.15/774-v5.16-06-net-dsa-rtl8366-Drop-and-depromote-pointless-prints.patch
+new file mode 100644
+index 0000000000..b56032c366
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/774-v5.16-06-net-dsa-rtl8366-Drop-and-depromote-pointless-prints.patch
+@@ -0,0 +1,51 @@
++From ddb59a5dc42714999c335dab4bf256125ba3120c Mon Sep 17 00:00:00 2001
++From: Linus Walleij <linus.walleij@linaro.org>
++Date: Sun, 26 Sep 2021 00:59:29 +0200
++Subject: [PATCH 06/11] net: dsa: rtl8366: Drop and depromote pointless prints
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++We don't need a message for every VLAN association, dbg
++is fine. The message about adding the DSA or CPU
++port to a VLAN is directly misleading, this is perfectly
++fine.
++
++Cc: Vladimir Oltean <olteanv@gmail.com>
++Cc: Mauri Sandberg <sandberg@mailfence.com>
++Cc: DENG Qingfang <dqfext@gmail.com>
++Reviewed-by: Alvin Å ipraga <alsi@bang-olufsen.dk>
++Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/rtl8366.c | 11 ++++-------
++ 1 file changed, 4 insertions(+), 7 deletions(-)
++
++--- a/drivers/net/dsa/rtl8366.c
+++++ b/drivers/net/dsa/rtl8366.c
++@@ -318,12 +318,9 @@ int rtl8366_vlan_add(struct dsa_switch *
++ 		return ret;
++ 	}
++ 
++-	dev_info(smi->dev, "add VLAN %d on port %d, %s, %s\n",
++-		 vlan->vid, port, untagged ? "untagged" : "tagged",
++-		 pvid ? " PVID" : "no PVID");
++-
++-	if (dsa_is_dsa_port(ds, port) || dsa_is_cpu_port(ds, port))
++-		dev_err(smi->dev, "port is DSA or CPU port\n");
+++	dev_dbg(smi->dev, "add VLAN %d on port %d, %s, %s\n",
+++		vlan->vid, port, untagged ? "untagged" : "tagged",
+++		pvid ? "PVID" : "no PVID");
++ 
++ 	member |= BIT(port);
++ 
++@@ -356,7 +353,7 @@ int rtl8366_vlan_del(struct dsa_switch *
++ 	struct realtek_smi *smi = ds->priv;
++ 	int ret, i;
++ 
++-	dev_info(smi->dev, "del VLAN %04x on port %d\n", vlan->vid, port);
+++	dev_dbg(smi->dev, "del VLAN %d on port %d\n", vlan->vid, port);
++ 
++ 	for (i = 0; i < smi->num_vlan_mc; i++) {
++ 		struct rtl8366_vlan_mc vlanmc;
+diff --git a/target/linux/generic/backport-5.15/774-v5.16-07-net-dsa-rtl8366rb-Use-core-filtering-tracking.patch b/target/linux/generic/backport-5.15/774-v5.16-07-net-dsa-rtl8366rb-Use-core-filtering-tracking.patch
+new file mode 100644
+index 0000000000..8cd1df97f2
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/774-v5.16-07-net-dsa-rtl8366rb-Use-core-filtering-tracking.patch
+@@ -0,0 +1,61 @@
++From 5c9b66f3c8a3f72fa2a58e89a57c6d7afd550bf0 Mon Sep 17 00:00:00 2001
++From: Linus Walleij <linus.walleij@linaro.org>
++Date: Wed, 29 Sep 2021 13:23:22 +0200
++Subject: [PATCH 07/11] net: dsa: rtl8366rb: Use core filtering tracking
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++We added a state variable to track whether a certain port
++was VLAN filtering or not, but we can just inquire the DSA
++core about this.
++
++Cc: Vladimir Oltean <olteanv@gmail.com>
++Cc: Mauri Sandberg <sandberg@mailfence.com>
++Cc: DENG Qingfang <dqfext@gmail.com>
++Cc: Alvin Å ipraga <alsi@bang-olufsen.dk>
++Cc: Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
++Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/rtl8366rb.c | 9 ++-------
++ 1 file changed, 2 insertions(+), 7 deletions(-)
++
++--- a/drivers/net/dsa/rtl8366rb.c
+++++ b/drivers/net/dsa/rtl8366rb.c
++@@ -337,12 +337,10 @@
++  * struct rtl8366rb - RTL8366RB-specific data
++  * @max_mtu: per-port max MTU setting
++  * @pvid_enabled: if PVID is set for respective port
++- * @vlan_filtering: if VLAN filtering is enabled for respective port
++  */
++ struct rtl8366rb {
++ 	unsigned int max_mtu[RTL8366RB_NUM_PORTS];
++ 	bool pvid_enabled[RTL8366RB_NUM_PORTS];
++-	bool vlan_filtering[RTL8366RB_NUM_PORTS];
++ };
++ 
++ static struct rtl8366_mib_counter rtl8366rb_mib_counters[] = {
++@@ -1262,12 +1260,9 @@ static int rtl8366rb_vlan_filtering(stru
++ 	if (ret)
++ 		return ret;
++ 
++-	/* Keep track if filtering is enabled on each port */
++-	rb->vlan_filtering[port] = vlan_filtering;
++-
++ 	/* If VLAN filtering is enabled and PVID is also enabled, we must
++ 	 * not drop any untagged or C-tagged frames. If we turn off VLAN
++-	 * filtering on a port, we need ti accept any frames.
+++	 * filtering on a port, we need to accept any frames.
++ 	 */
++ 	if (vlan_filtering)
++ 		ret = rtl8366rb_drop_untagged(smi, port, !rb->pvid_enabled[port]);
++@@ -1512,7 +1507,7 @@ static int rtl8366rb_set_mc_index(struct
++ 	 * not drop any untagged or C-tagged frames. Make sure to update the
++ 	 * filtering setting.
++ 	 */
++-	if (rb->vlan_filtering[port])
+++	if (dsa_port_is_vlan_filtering(dsa_to_port(smi->ds, port)))
++ 		ret = rtl8366rb_drop_untagged(smi, port, !pvid_enabled);
 + 
-++	if (!qca8k_read_eth(priv, reg, val))
-++		return 0;
-++
-+ 	qca8k_split_addr(reg, &r1, &r2, &page);
++ 	return ret;
+diff --git a/target/linux/generic/backport-5.15/774-v5.16-08-net-dsa-rtl8366rb-Support-disabling-learning.patch b/target/linux/generic/backport-5.15/774-v5.16-08-net-dsa-rtl8366rb-Support-disabling-learning.patch
+new file mode 100644
+index 0000000000..8306eb5aef
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/774-v5.16-08-net-dsa-rtl8366rb-Support-disabling-learning.patch
+@@ -0,0 +1,115 @@
++From 831a3d26bea0d14f8563eecf96def660a74a3000 Mon Sep 17 00:00:00 2001
++From: Linus Walleij <linus.walleij@linaro.org>
++Date: Tue, 5 Oct 2021 21:47:02 +0200
++Subject: [PATCH 08/11] net: dsa: rtl8366rb: Support disabling learning
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++The RTL8366RB hardware supports disabling learning per-port
++so let's make use of this feature. Rename some unfortunately
++named registers in the process.
++
++Suggested-by: Vladimir Oltean <olteanv@gmail.com>
++Cc: Alvin Å ipraga <alsi@bang-olufsen.dk>
++Cc: Mauri Sandberg <sandberg@mailfence.com>
++Cc: Florian Fainelli <f.fainelli@gmail.com>
++Cc: DENG Qingfang <dqfext@gmail.com>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/rtl8366rb.c | 50 ++++++++++++++++++++++++++++++++-----
++ 1 file changed, 44 insertions(+), 6 deletions(-)
++
++--- a/drivers/net/dsa/rtl8366rb.c
+++++ b/drivers/net/dsa/rtl8366rb.c
++@@ -14,6 +14,7 @@
 + 
-+ 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
-+@@ -201,6 +393,9 @@ qca8k_regmap_write(void *ctx, uint32_t r
-+ 	u16 r1, r2, page;
-+ 	int ret;
++ #include <linux/bitops.h>
++ #include <linux/etherdevice.h>
+++#include <linux/if_bridge.h>
++ #include <linux/interrupt.h>
++ #include <linux/irqdomain.h>
++ #include <linux/irqchip/chained_irq.h>
++@@ -42,9 +43,12 @@
++ /* Port Enable Control register */
++ #define RTL8366RB_PECR				0x0001
 + 
-++	if (!qca8k_write_eth(priv, reg, val))
-++		return 0;
++-/* Switch Security Control registers */
++-#define RTL8366RB_SSCR0				0x0002
++-#define RTL8366RB_SSCR1				0x0003
+++/* Switch per-port learning disablement register */
+++#define RTL8366RB_PORT_LEARNDIS_CTRL		0x0002
 ++
-+ 	qca8k_split_addr(reg, &r1, &r2, &page);
+++/* Security control, actually aging register */
+++#define RTL8366RB_SECURITY_CTRL			0x0003
+++
++ #define RTL8366RB_SSCR2				0x0004
++ #define RTL8366RB_SSCR2_DROP_UNKNOWN_DA		BIT(0)
 + 
-+ 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
-+@@ -225,6 +420,9 @@ qca8k_regmap_update_bits(void *ctx, uint
-+ 	u32 val;
-+ 	int ret;
++@@ -927,13 +931,14 @@ static int rtl8366rb_setup(struct dsa_sw
++ 		/* layer 2 size, see rtl8366rb_change_mtu() */
++ 		rb->max_mtu[i] = 1532;
 + 
-++	if (!qca8k_regmap_update_bits_eth(priv, reg, mask, write_val))
-++		return 0;
-++
-+ 	qca8k_split_addr(reg, &r1, &r2, &page);
++-	/* Enable learning for all ports */
++-	ret = regmap_write(smi->map, RTL8366RB_SSCR0, 0);
+++	/* Disable learning for all ports */
+++	ret = regmap_write(smi->map, RTL8366RB_PORT_LEARNDIS_CTRL,
+++			   RTL8366RB_PORT_ALL);
++ 	if (ret)
++ 		return ret;
 + 
-+ 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
-+@@ -2393,7 +2591,30 @@ qca8k_master_change(struct dsa_switch *d
-+ 	if (dp->index != 0)
-+ 		return;
++ 	/* Enable auto ageing for all ports */
++-	ret = regmap_write(smi->map, RTL8366RB_SSCR1, 0);
+++	ret = regmap_write(smi->map, RTL8366RB_SECURITY_CTRL, 0);
++ 	if (ret)
++ 		return ret;
 + 
-++	mutex_lock(&priv->mgmt_eth_data.mutex);
-++
-+ 	priv->mgmt_master = operational ? (struct net_device *)master : NULL;
++@@ -1272,6 +1277,37 @@ static int rtl8366rb_vlan_filtering(stru
++ 	return ret;
++ }
++ 
+++static int
+++rtl8366rb_port_pre_bridge_flags(struct dsa_switch *ds, int port,
+++				struct switchdev_brport_flags flags,
+++				struct netlink_ext_ack *extack)
+++{
+++	/* We support enabling/disabling learning */
+++	if (flags.mask & ~(BR_LEARNING))
+++		return -EINVAL;
 ++
-++	mutex_unlock(&priv->mgmt_eth_data.mutex);
+++	return 0;
 ++}
 ++
-++static int qca8k_connect_tag_protocol(struct dsa_switch *ds,
-++				      enum dsa_tag_protocol proto)
+++static int
+++rtl8366rb_port_bridge_flags(struct dsa_switch *ds, int port,
+++			    struct switchdev_brport_flags flags,
+++			    struct netlink_ext_ack *extack)
 ++{
-++	struct qca_tagger_data *tagger_data;
-++
-++	switch (proto) {
-++	case DSA_TAG_PROTO_QCA:
-++		tagger_data = ds->tagger_data;
-++
-++		tagger_data->rw_reg_ack_handler = qca8k_rw_reg_ack_handler;
+++	struct realtek_smi *smi = ds->priv;
+++	int ret;
 ++
-++		break;
-++	default:
-++		return -EOPNOTSUPP;
+++	if (flags.mask & BR_LEARNING) {
+++		ret = regmap_update_bits(smi->map, RTL8366RB_PORT_LEARNDIS_CTRL,
+++					 BIT(port),
+++					 (flags.val & BR_LEARNING) ? 0 : BIT(port));
+++		if (ret)
+++			return ret;
 ++	}
 ++
 ++	return 0;
-+ }
-+ 
-+ static const struct dsa_switch_ops qca8k_switch_ops = {
-+@@ -2432,6 +2653,7 @@ static const struct dsa_switch_ops qca8k
-+ 	.port_lag_join		= qca8k_port_lag_join,
-+ 	.port_lag_leave		= qca8k_port_lag_leave,
-+ 	.master_state_change	= qca8k_master_change,
-++	.connect_tag_protocol	= qca8k_connect_tag_protocol,
-+ };
-+ 
-+ static int qca8k_read_switch_id(struct qca8k_priv *priv)
-+@@ -2511,6 +2733,9 @@ qca8k_sw_probe(struct mdio_device *mdiod
-+ 	if (!priv->ds)
-+ 		return -ENOMEM;
-+ 
-++	mutex_init(&priv->mgmt_eth_data.mutex);
-++	init_completion(&priv->mgmt_eth_data.rw_done);
-++
-+ 	priv->ds->dev = &mdiodev->dev;
-+ 	priv->ds->num_ports = QCA8K_NUM_PORTS;
-+ 	priv->ds->priv = priv;
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -11,6 +11,10 @@
-+ #include <linux/delay.h>
-+ #include <linux/regmap.h>
-+ #include <linux/gpio.h>
-++#include <linux/dsa/tag_qca.h>
+++}
 ++
-++#define QCA8K_ETHERNET_MDIO_PRIORITY			7
-++#define QCA8K_ETHERNET_TIMEOUT				100
-+ 
-+ #define QCA8K_NUM_PORTS					7
-+ #define QCA8K_NUM_CPU_PORTS				2
-+@@ -328,6 +332,14 @@ enum {
-+ 	QCA8K_CPU_PORT6,
++ static int rtl8366rb_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
++ {
++ 	struct realtek_smi *smi = ds->priv;
++@@ -1682,6 +1718,8 @@ static const struct dsa_switch_ops rtl83
++ 	.port_vlan_del = rtl8366_vlan_del,
++ 	.port_enable = rtl8366rb_port_enable,
++ 	.port_disable = rtl8366rb_port_disable,
+++	.port_pre_bridge_flags = rtl8366rb_port_pre_bridge_flags,
+++	.port_bridge_flags = rtl8366rb_port_bridge_flags,
++ 	.port_change_mtu = rtl8366rb_change_mtu,
++ 	.port_max_mtu = rtl8366rb_max_mtu,
 + };
+diff --git a/target/linux/generic/backport-5.15/774-v5.16-09-net-dsa-rtl8366rb-Support-fast-aging.patch b/target/linux/generic/backport-5.15/774-v5.16-09-net-dsa-rtl8366rb-Support-fast-aging.patch
+new file mode 100644
+index 0000000000..a74108e23d
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/774-v5.16-09-net-dsa-rtl8366rb-Support-fast-aging.patch
+@@ -0,0 +1,57 @@
++From 8eb13420eb9ab4a4e2ebd612bf5dc9dba0039236 Mon Sep 17 00:00:00 2001
++From: Linus Walleij <linus.walleij@linaro.org>
++Date: Tue, 5 Oct 2021 21:47:03 +0200
++Subject: [PATCH 09/11] net: dsa: rtl8366rb: Support fast aging
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++This implements fast aging per-port using the special "security"
++register, which will flush any learned L2 LUT entries on a port.
++
++The vendor API just enabled setting and clearing this bit, so
++we set it to age out any entries on the port and then we clear
++it again.
++
++Suggested-by: Vladimir Oltean <olteanv@gmail.com>
++Cc: Mauri Sandberg <sandberg@mailfence.com>
++Cc: DENG Qingfang <dqfext@gmail.com>
++Cc: Florian Fainelli <f.fainelli@gmail.com>
++Reviewed-by: Alvin Å ipraga <alsi@bang-olufsen.dk>
++Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/rtl8366rb.c | 14 ++++++++++++++
++ 1 file changed, 14 insertions(+)
++
++--- a/drivers/net/dsa/rtl8366rb.c
+++++ b/drivers/net/dsa/rtl8366rb.c
++@@ -1308,6 +1308,19 @@ rtl8366rb_port_bridge_flags(struct dsa_s
++ 	return 0;
++ }
 + 
-++struct qca8k_mgmt_eth_data {
-++	struct completion rw_done;
-++	struct mutex mutex; /* Enforce one mdio read/write at time */
-++	bool ack;
-++	u32 seq;
-++	u32 data[4];
-++};
+++static void
+++rtl8366rb_port_fast_age(struct dsa_switch *ds, int port)
+++{
+++	struct realtek_smi *smi = ds->priv;
 ++
-+ struct qca8k_ports_config {
-+ 	bool sgmii_rx_clk_falling_edge;
-+ 	bool sgmii_tx_clk_falling_edge;
-+@@ -354,6 +366,7 @@ struct qca8k_priv {
-+ 	struct gpio_desc *reset_gpio;
-+ 	unsigned int port_mtu[QCA8K_NUM_PORTS];
-+ 	struct net_device *mgmt_master; /* Track if mdio/mib Ethernet is available */
-++	struct qca8k_mgmt_eth_data mgmt_eth_data;
+++	/* This will age out any learned L2 entries */
+++	regmap_update_bits(smi->map, RTL8366RB_SECURITY_CTRL,
+++			   BIT(port), BIT(port));
+++	/* Restore the normal state of things */
+++	regmap_update_bits(smi->map, RTL8366RB_SECURITY_CTRL,
+++			   BIT(port), 0);
+++}
+++
++ static int rtl8366rb_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
++ {
++ 	struct realtek_smi *smi = ds->priv;
++@@ -1720,6 +1733,7 @@ static const struct dsa_switch_ops rtl83
++ 	.port_disable = rtl8366rb_port_disable,
++ 	.port_pre_bridge_flags = rtl8366rb_port_pre_bridge_flags,
++ 	.port_bridge_flags = rtl8366rb_port_bridge_flags,
+++	.port_fast_age = rtl8366rb_port_fast_age,
++ 	.port_change_mtu = rtl8366rb_change_mtu,
++ 	.port_max_mtu = rtl8366rb_max_mtu,
 + };
-+ 
-+ struct qca8k_mib_desc {
-diff --git a/target/linux/generic/backport-5.15/766-11-net-dsa-qca8k-add-support-for-mib-autocast-in-Ethern.patch b/target/linux/generic/backport-5.15/766-11-net-dsa-qca8k-add-support-for-mib-autocast-in-Ethern.patch
+diff --git a/target/linux/generic/backport-5.15/774-v5.16-10-net-dsa-rtl8366rb-Support-setting-STP-state.patch b/target/linux/generic/backport-5.15/774-v5.16-10-net-dsa-rtl8366rb-Support-setting-STP-state.patch
 new file mode 100644
-index 0000000000..c4bc2b3646
+index 0000000000..e787ce9481
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/766-11-net-dsa-qca8k-add-support-for-mib-autocast-in-Ethern.patch
-@@ -0,0 +1,226 @@
-+From 5c957c7ca78cce5e4b96866722b0115bd758d945 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Wed, 2 Feb 2022 01:03:30 +0100
-+Subject: [PATCH 11/16] net: dsa: qca8k: add support for mib autocast in
-+ Ethernet packet
++++ b/target/linux/generic/backport-5.15/774-v5.16-10-net-dsa-rtl8366rb-Support-setting-STP-state.patch
+@@ -0,0 +1,107 @@
++From 90c855471a89d3e05ecf5b6464bd04abf2c83b70 Mon Sep 17 00:00:00 2001
++From: Linus Walleij <linus.walleij@linaro.org>
++Date: Tue, 5 Oct 2021 21:47:04 +0200
++Subject: [PATCH 10/11] net: dsa: rtl8366rb: Support setting STP state
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
 +
-+The switch can autocast MIB counter using Ethernet packet.
-+Add support for this and provide a handler for the tagger.
-+The switch will send packet with MIB counter for each port, the switch
-+will use completion API to wait for the correct packet to be received
-+and will complete the task only when each packet is received.
-+Although the handler will drop all the other packet, we still have to
-+consume each MIB packet to complete the request. This is done to prevent
-+mixed data with concurrent ethtool request.
++This adds support for setting the STP state to the RTL8366RB
++DSA switch. This rids the following message from the kernel on
++e.g. OpenWrt:
 +
-+connect_tag_protocol() is used to add the handler to the tag_qca tagger,
-+master_state_change() use the MIB lock to make sure no MIB Ethernet is
-+in progress.
++DSA: failed to set STP state 3 (-95)
 +
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
++Since the RTL8366RB has one STP state register per FID with
++two bit per port in each, we simply loop over all the FIDs
++and set the state on all of them.
++
++Cc: Vladimir Oltean <olteanv@gmail.com>
++Cc: Alvin Å ipraga <alsi@bang-olufsen.dk>
++Cc: Mauri Sandberg <sandberg@mailfence.com>
++Cc: DENG Qingfang <dqfext@gmail.com>
++Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
++Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
 +Signed-off-by: David S. Miller <davem@davemloft.net>
 +---
-+ drivers/net/dsa/qca8k.c | 106 +++++++++++++++++++++++++++++++++++++++-
-+ drivers/net/dsa/qca8k.h |  17 ++++++-
-+ 2 files changed, 121 insertions(+), 2 deletions(-)
++ drivers/net/dsa/rtl8366rb.c | 48 +++++++++++++++++++++++++++++++++++++
++ 1 file changed, 48 insertions(+)
 +
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -830,7 +830,10 @@ qca8k_mib_init(struct qca8k_priv *priv)
-+ 	int ret;
++--- a/drivers/net/dsa/rtl8366rb.c
+++++ b/drivers/net/dsa/rtl8366rb.c
++@@ -110,6 +110,18 @@
 + 
-+ 	mutex_lock(&priv->reg_mutex);
-+-	ret = regmap_set_bits(priv->regmap, QCA8K_REG_MIB, QCA8K_MIB_FLUSH | QCA8K_MIB_BUSY);
-++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB,
-++				 QCA8K_MIB_FUNC | QCA8K_MIB_BUSY,
-++				 FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_FLUSH) |
-++				 QCA8K_MIB_BUSY);
-+ 	if (ret)
-+ 		goto exit;
++ #define RTL8366RB_POWER_SAVING_REG	0x0021
 + 
-+@@ -1901,6 +1904,97 @@ qca8k_get_strings(struct dsa_switch *ds,
-+ 			ETH_GSTRING_LEN);
+++/* Spanning tree status (STP) control, two bits per port per FID */
+++#define RTL8366RB_STP_STATE_BASE	0x0050 /* 0x0050..0x0057 */
+++#define RTL8366RB_STP_STATE_DISABLED	0x0
+++#define RTL8366RB_STP_STATE_BLOCKING	0x1
+++#define RTL8366RB_STP_STATE_LEARNING	0x2
+++#define RTL8366RB_STP_STATE_FORWARDING	0x3
+++#define RTL8366RB_STP_MASK		GENMASK(1, 0)
+++#define RTL8366RB_STP_STATE(port, state) \
+++	((state) << ((port) * 2))
+++#define RTL8366RB_STP_STATE_MASK(port) \
+++	RTL8366RB_STP_STATE((port), RTL8366RB_STP_MASK)
+++
++ /* CPU port control reg */
++ #define RTL8368RB_CPU_CTRL_REG		0x0061
++ #define RTL8368RB_CPU_PORTS_MSK		0x00FF
++@@ -234,6 +246,7 @@
++ #define RTL8366RB_NUM_LEDGROUPS		4
++ #define RTL8366RB_NUM_VIDS		4096
++ #define RTL8366RB_PRIORITYMAX		7
+++#define RTL8366RB_NUM_FIDS		8
++ #define RTL8366RB_FIDMAX		7
++ 
++ #define RTL8366RB_PORT_1		BIT(0) /* In userspace port 0 */
++@@ -1309,6 +1322,40 @@ rtl8366rb_port_bridge_flags(struct dsa_s
 + }
 + 
-++static void qca8k_mib_autocast_handler(struct dsa_switch *ds, struct sk_buff *skb)
++ static void
+++rtl8366rb_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
 ++{
-++	const struct qca8k_match_data *match_data;
-++	struct qca8k_mib_eth_data *mib_eth_data;
-++	struct qca8k_priv *priv = ds->priv;
-++	const struct qca8k_mib_desc *mib;
-++	struct mib_ethhdr *mib_ethhdr;
-++	int i, mib_len, offset = 0;
-++	u64 *data;
-++	u8 port;
-++
-++	mib_ethhdr = (struct mib_ethhdr *)skb_mac_header(skb);
-++	mib_eth_data = &priv->mib_eth_data;
-++
-++	/* The switch autocast every port. Ignore other packet and
-++	 * parse only the requested one.
-++	 */
-++	port = FIELD_GET(QCA_HDR_RECV_SOURCE_PORT, ntohs(mib_ethhdr->hdr));
-++	if (port != mib_eth_data->req_port)
-++		goto exit;
-++
-++	match_data = device_get_match_data(priv->dev);
-++	data = mib_eth_data->data;
-++
-++	for (i = 0; i < match_data->mib_count; i++) {
-++		mib = &ar8327_mib[i];
-++
-++		/* First 3 mib are present in the skb head */
-++		if (i < 3) {
-++			data[i] = mib_ethhdr->data[i];
-++			continue;
-++		}
-++
-++		mib_len = sizeof(uint32_t);
-++
-++		/* Some mib are 64 bit wide */
-++		if (mib->size == 2)
-++			mib_len = sizeof(uint64_t);
+++	struct realtek_smi *smi = ds->priv;
+++	u32 val;
+++	int i;
 ++
-++		/* Copy the mib value from packet to the */
-++		memcpy(data + i, skb->data + offset, mib_len);
+++	switch (state) {
+++	case BR_STATE_DISABLED:
+++		val = RTL8366RB_STP_STATE_DISABLED;
+++		break;
+++	case BR_STATE_BLOCKING:
+++	case BR_STATE_LISTENING:
+++		val = RTL8366RB_STP_STATE_BLOCKING;
+++		break;
+++	case BR_STATE_LEARNING:
+++		val = RTL8366RB_STP_STATE_LEARNING;
+++		break;
+++	case BR_STATE_FORWARDING:
+++		val = RTL8366RB_STP_STATE_FORWARDING;
+++		break;
+++	default:
+++		dev_err(smi->dev, "unknown bridge state requested\n");
+++		return;
+++	};
 ++
-++		/* Set the offset for the next mib */
-++		offset += mib_len;
+++	/* Set the same status for the port on all the FIDs */
+++	for (i = 0; i < RTL8366RB_NUM_FIDS; i++) {
+++		regmap_update_bits(smi->map, RTL8366RB_STP_STATE_BASE + i,
+++				   RTL8366RB_STP_STATE_MASK(port),
+++				   RTL8366RB_STP_STATE(port, val));
 ++	}
-++
-++exit:
-++	/* Complete on receiving all the mib packet */
-++	if (refcount_dec_and_test(&mib_eth_data->port_parsed))
-++		complete(&mib_eth_data->rw_done);
-++}
-++
-++static int
-++qca8k_get_ethtool_stats_eth(struct dsa_switch *ds, int port, u64 *data)
-++{
-++	struct dsa_port *dp = dsa_to_port(ds, port);
-++	struct qca8k_mib_eth_data *mib_eth_data;
-++	struct qca8k_priv *priv = ds->priv;
-++	int ret;
-++
-++	mib_eth_data = &priv->mib_eth_data;
-++
-++	mutex_lock(&mib_eth_data->mutex);
-++
-++	reinit_completion(&mib_eth_data->rw_done);
-++
-++	mib_eth_data->req_port = dp->index;
-++	mib_eth_data->data = data;
-++	refcount_set(&mib_eth_data->port_parsed, QCA8K_NUM_PORTS);
-++
-++	mutex_lock(&priv->reg_mutex);
-++
-++	/* Send mib autocast request */
-++	ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB,
-++				 QCA8K_MIB_FUNC | QCA8K_MIB_BUSY,
-++				 FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_CAST) |
-++				 QCA8K_MIB_BUSY);
-++
-++	mutex_unlock(&priv->reg_mutex);
-++
-++	if (ret)
-++		goto exit;
-++
-++	ret = wait_for_completion_timeout(&mib_eth_data->rw_done, QCA8K_ETHERNET_TIMEOUT);
-++
-++exit:
-++	mutex_unlock(&mib_eth_data->mutex);
-++
-++	return ret;
 ++}
 ++
-+ static void
-+ qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
-+ 			uint64_t *data)
-+@@ -1912,6 +2006,10 @@ qca8k_get_ethtool_stats(struct dsa_switc
-+ 	u32 hi = 0;
-+ 	int ret;
-+ 
-++	if (priv->mgmt_master &&
-++	    qca8k_get_ethtool_stats_eth(ds, port, data) > 0)
-++		return;
-++
-+ 	match_data = of_device_get_match_data(priv->dev);
-+ 
-+ 	for (i = 0; i < match_data->mib_count; i++) {
-+@@ -2592,9 +2690,11 @@ qca8k_master_change(struct dsa_switch *d
-+ 		return;
-+ 
-+ 	mutex_lock(&priv->mgmt_eth_data.mutex);
-++	mutex_lock(&priv->mib_eth_data.mutex);
-+ 
-+ 	priv->mgmt_master = operational ? (struct net_device *)master : NULL;
-+ 
-++	mutex_unlock(&priv->mib_eth_data.mutex);
-+ 	mutex_unlock(&priv->mgmt_eth_data.mutex);
-+ }
+++static void
++ rtl8366rb_port_fast_age(struct dsa_switch *ds, int port)
++ {
++ 	struct realtek_smi *smi = ds->priv;
++@@ -1733,6 +1780,7 @@ static const struct dsa_switch_ops rtl83
++ 	.port_disable = rtl8366rb_port_disable,
++ 	.port_pre_bridge_flags = rtl8366rb_port_pre_bridge_flags,
++ 	.port_bridge_flags = rtl8366rb_port_bridge_flags,
+++	.port_stp_state_set = rtl8366rb_port_stp_state_set,
++ 	.port_fast_age = rtl8366rb_port_fast_age,
++ 	.port_change_mtu = rtl8366rb_change_mtu,
++ 	.port_max_mtu = rtl8366rb_max_mtu,
+diff --git a/target/linux/generic/backport-5.15/775-v6.0-01-net-ethernet-stmicro-stmmac-move-queue-reset-to-dedi.patch b/target/linux/generic/backport-5.15/775-v6.0-01-net-ethernet-stmicro-stmmac-move-queue-reset-to-dedi.patch
+new file mode 100644
+index 0000000000..78a5494bf2
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/775-v6.0-01-net-ethernet-stmicro-stmmac-move-queue-reset-to-dedi.patch
+@@ -0,0 +1,142 @@
++From f9ec5723c3dbfcede9c7b0dcdf85e401ce16316c Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Sat, 23 Jul 2022 16:29:29 +0200
++Subject: [PATCH 1/5] net: ethernet: stmicro: stmmac: move queue reset to
++ dedicated functions
++
++Move queue reset to dedicated functions. This aside from a simple
++cleanup is also required to allocate a dma conf without resetting the tx
++queue while the device is temporarily detached as now the reset is not
++part of the dma init function and can be done later in the code flow.
++
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ .../net/ethernet/stmicro/stmmac/stmmac_main.c | 59 ++++++++++---------
++ 1 file changed, 31 insertions(+), 28 deletions(-)
++
++--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
++@@ -130,6 +130,9 @@ static irqreturn_t stmmac_mac_interrupt(
++ static irqreturn_t stmmac_safety_interrupt(int irq, void *dev_id);
++ static irqreturn_t stmmac_msi_intr_tx(int irq, void *data);
++ static irqreturn_t stmmac_msi_intr_rx(int irq, void *data);
+++static void stmmac_reset_rx_queue(struct stmmac_priv *priv, u32 queue);
+++static void stmmac_reset_tx_queue(struct stmmac_priv *priv, u32 queue);
+++static void stmmac_reset_queues_param(struct stmmac_priv *priv);
++ static void stmmac_tx_timer_arm(struct stmmac_priv *priv, u32 queue);
++ static void stmmac_flush_tx_descriptors(struct stmmac_priv *priv, int queue);
++ 
++@@ -1697,9 +1700,6 @@ static int __init_dma_rx_desc_rings(stru
++ 			return -ENOMEM;
++ 	}
 + 
-+@@ -2608,6 +2708,7 @@ static int qca8k_connect_tag_protocol(st
-+ 		tagger_data = ds->tagger_data;
++-	rx_q->cur_rx = 0;
++-	rx_q->dirty_rx = 0;
++-
++ 	/* Setup the chained descriptor addresses */
++ 	if (priv->mode == STMMAC_CHAIN_MODE) {
++ 		if (priv->extend_desc)
++@@ -1805,12 +1805,6 @@ static int __init_dma_tx_desc_rings(stru
++ 		tx_q->tx_skbuff[i] = NULL;
++ 	}
 + 
-+ 		tagger_data->rw_reg_ack_handler = qca8k_rw_reg_ack_handler;
-++		tagger_data->mib_autocast_handler = qca8k_mib_autocast_handler;
++-	tx_q->dirty_tx = 0;
++-	tx_q->cur_tx = 0;
++-	tx_q->mss = 0;
++-
++-	netdev_tx_reset_queue(netdev_get_tx_queue(priv->dev, queue));
++-
++ 	return 0;
++ }
 + 
-+ 		break;
-+ 	default:
-+@@ -2736,6 +2837,9 @@ qca8k_sw_probe(struct mdio_device *mdiod
-+ 	mutex_init(&priv->mgmt_eth_data.mutex);
-+ 	init_completion(&priv->mgmt_eth_data.rw_done);
++@@ -2679,10 +2673,7 @@ static void stmmac_tx_err(struct stmmac_
++ 	stmmac_stop_tx_dma(priv, chan);
++ 	dma_free_tx_skbufs(priv, chan);
++ 	stmmac_clear_tx_descriptors(priv, chan);
++-	tx_q->dirty_tx = 0;
++-	tx_q->cur_tx = 0;
++-	tx_q->mss = 0;
++-	netdev_tx_reset_queue(netdev_get_tx_queue(priv->dev, chan));
+++	stmmac_reset_tx_queue(priv, chan);
++ 	stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
++ 			    tx_q->dma_tx_phy, chan);
++ 	stmmac_start_tx_dma(priv, chan);
++@@ -3766,6 +3757,8 @@ static int stmmac_open(struct net_device
++ 		}
++ 	}
 + 
-++	mutex_init(&priv->mib_eth_data.mutex);
-++	init_completion(&priv->mib_eth_data.rw_done);
+++	stmmac_reset_queues_param(priv);
 ++
-+ 	priv->ds->dev = &mdiodev->dev;
-+ 	priv->ds->num_ports = QCA8K_NUM_PORTS;
-+ 	priv->ds->priv = priv;
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -67,7 +67,7 @@
-+ #define QCA8K_REG_MODULE_EN				0x030
-+ #define   QCA8K_MODULE_EN_MIB				BIT(0)
-+ #define QCA8K_REG_MIB					0x034
-+-#define   QCA8K_MIB_FLUSH				BIT(24)
-++#define   QCA8K_MIB_FUNC				GENMASK(26, 24)
-+ #define   QCA8K_MIB_CPU_KEEP				BIT(20)
-+ #define   QCA8K_MIB_BUSY				BIT(17)
-+ #define QCA8K_MDIO_MASTER_CTRL				0x3c
-+@@ -317,6 +317,12 @@ enum qca8k_vlan_cmd {
-+ 	QCA8K_VLAN_READ = 6,
-+ };
++ 	ret = stmmac_hw_setup(dev, true);
++ 	if (ret < 0) {
++ 		netdev_err(priv->dev, "%s: Hw setup failed\n", __func__);
++@@ -6409,6 +6402,7 @@ void stmmac_enable_rx_queue(struct stmma
++ 		return;
++ 	}
 + 
-++enum qca8k_mid_cmd {
-++	QCA8K_MIB_FLUSH = 1,
-++	QCA8K_MIB_FLUSH_PORT = 2,
-++	QCA8K_MIB_CAST = 3,
-++};
-++
-+ struct ar8xxx_port_status {
-+ 	int enabled;
-+ };
-+@@ -340,6 +346,14 @@ struct qca8k_mgmt_eth_data {
-+ 	u32 data[4];
-+ };
+++	stmmac_reset_rx_queue(priv, queue);
++ 	stmmac_clear_rx_descriptors(priv, queue);
 + 
-++struct qca8k_mib_eth_data {
-++	struct completion rw_done;
-++	struct mutex mutex; /* Process one command at time */
-++	refcount_t port_parsed; /* Counter to track parsed port */
-++	u8 req_port;
-++	u64 *data; /* pointer to ethtool data */
-++};
-++
-+ struct qca8k_ports_config {
-+ 	bool sgmii_rx_clk_falling_edge;
-+ 	bool sgmii_tx_clk_falling_edge;
-+@@ -367,6 +381,7 @@ struct qca8k_priv {
-+ 	unsigned int port_mtu[QCA8K_NUM_PORTS];
-+ 	struct net_device *mgmt_master; /* Track if mdio/mib Ethernet is available */
-+ 	struct qca8k_mgmt_eth_data mgmt_eth_data;
-++	struct qca8k_mib_eth_data mib_eth_data;
-+ };
++ 	stmmac_init_rx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
++@@ -6470,6 +6464,7 @@ void stmmac_enable_tx_queue(struct stmma
++ 		return;
++ 	}
 + 
-+ struct qca8k_mib_desc {
-diff --git a/target/linux/generic/backport-5.15/766-12-net-dsa-qca8k-add-support-for-phy-read-write-with-mg.patch b/target/linux/generic/backport-5.15/766-12-net-dsa-qca8k-add-support-for-phy-read-write-with-mg.patch
-new file mode 100644
-index 0000000000..f5899eb590
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/766-12-net-dsa-qca8k-add-support-for-phy-read-write-with-mg.patch
-@@ -0,0 +1,287 @@
-+From 2cd5485663847d468dc207b3ff85fb1fab44d97f Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Wed, 2 Feb 2022 01:03:31 +0100
-+Subject: [PATCH 12/16] net: dsa: qca8k: add support for phy read/write with
-+ mgmt Ethernet
-+
-+Use mgmt Ethernet also for phy read/write if availabale. Use a different
-+seq number to make sure we receive the correct packet.
-+On any error, we fallback to the legacy mdio read/write.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 216 ++++++++++++++++++++++++++++++++++++++++
-+ drivers/net/dsa/qca8k.h |   1 +
-+ 2 files changed, 217 insertions(+)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -867,6 +867,199 @@ qca8k_port_set_status(struct qca8k_priv
-+ 		regmap_clear_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
+++	stmmac_reset_tx_queue(priv, queue);
++ 	stmmac_clear_tx_descriptors(priv, queue);
++ 
++ 	stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
++@@ -7390,6 +7385,25 @@ int stmmac_suspend(struct device *dev)
 + }
++ EXPORT_SYMBOL_GPL(stmmac_suspend);
 + 
-++static int
-++qca8k_phy_eth_busy_wait(struct qca8k_mgmt_eth_data *mgmt_eth_data,
-++			struct sk_buff *read_skb, u32 *val)
+++static void stmmac_reset_rx_queue(struct stmmac_priv *priv, u32 queue)
 ++{
-++	struct sk_buff *skb = skb_copy(read_skb, GFP_KERNEL);
-++	bool ack;
-++	int ret;
-++
-++	reinit_completion(&mgmt_eth_data->rw_done);
-++
-++	/* Increment seq_num and set it in the copy pkt */
-++	mgmt_eth_data->seq++;
-++	qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
-++	mgmt_eth_data->ack = false;
-++
-++	dev_queue_xmit(skb);
-++
-++	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
-++					  QCA8K_ETHERNET_TIMEOUT);
+++	struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
 ++
-++	ack = mgmt_eth_data->ack;
-++
-++	if (ret <= 0)
-++		return -ETIMEDOUT;
-++
-++	if (!ack)
-++		return -EINVAL;
-++
-++	*val = mgmt_eth_data->data[0];
-++
-++	return 0;
+++	rx_q->cur_rx = 0;
+++	rx_q->dirty_rx = 0;
 ++}
 ++
-++static int
-++qca8k_phy_eth_command(struct qca8k_priv *priv, bool read, int phy,
-++		      int regnum, u16 data)
+++static void stmmac_reset_tx_queue(struct stmmac_priv *priv, u32 queue)
 ++{
-++	struct sk_buff *write_skb, *clear_skb, *read_skb;
-++	struct qca8k_mgmt_eth_data *mgmt_eth_data;
-++	u32 write_val, clear_val = 0, val;
-++	struct net_device *mgmt_master;
-++	int ret, ret1;
-++	bool ack;
-++
-++	if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
-++		return -EINVAL;
-++
-++	mgmt_eth_data = &priv->mgmt_eth_data;
-++
-++	write_val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
-++		    QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
-++		    QCA8K_MDIO_MASTER_REG_ADDR(regnum);
-++
-++	if (read) {
-++		write_val |= QCA8K_MDIO_MASTER_READ;
-++	} else {
-++		write_val |= QCA8K_MDIO_MASTER_WRITE;
-++		write_val |= QCA8K_MDIO_MASTER_DATA(data);
-++	}
-++
-++	/* Prealloc all the needed skb before the lock */
-++	write_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL,
-++					    &write_val, QCA8K_ETHERNET_PHY_PRIORITY);
-++	if (!write_skb)
-++		return -ENOMEM;
-++
-++	clear_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL,
-++					    &clear_val, QCA8K_ETHERNET_PHY_PRIORITY);
-++	if (!write_skb) {
-++		ret = -ENOMEM;
-++		goto err_clear_skb;
-++	}
-++
-++	read_skb = qca8k_alloc_mdio_header(MDIO_READ, QCA8K_MDIO_MASTER_CTRL,
-++					   &clear_val, QCA8K_ETHERNET_PHY_PRIORITY);
-++	if (!write_skb) {
-++		ret = -ENOMEM;
-++		goto err_read_skb;
-++	}
-++
-++	/* Actually start the request:
-++	 * 1. Send mdio master packet
-++	 * 2. Busy Wait for mdio master command
-++	 * 3. Get the data if we are reading
-++	 * 4. Reset the mdio master (even with error)
-++	 */
-++	mutex_lock(&mgmt_eth_data->mutex);
-++
-++	/* Check if mgmt_master is operational */
-++	mgmt_master = priv->mgmt_master;
-++	if (!mgmt_master) {
-++		mutex_unlock(&mgmt_eth_data->mutex);
-++		ret = -EINVAL;
-++		goto err_mgmt_master;
-++	}
-++
-++	read_skb->dev = mgmt_master;
-++	clear_skb->dev = mgmt_master;
-++	write_skb->dev = mgmt_master;
-++
-++	reinit_completion(&mgmt_eth_data->rw_done);
-++
-++	/* Increment seq_num and set it in the write pkt */
-++	mgmt_eth_data->seq++;
-++	qca8k_mdio_header_fill_seq_num(write_skb, mgmt_eth_data->seq);
-++	mgmt_eth_data->ack = false;
-++
-++	dev_queue_xmit(write_skb);
-++
-++	ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
-++					  QCA8K_ETHERNET_TIMEOUT);
-++
-++	ack = mgmt_eth_data->ack;
-++
-++	if (ret <= 0) {
-++		ret = -ETIMEDOUT;
-++		kfree_skb(read_skb);
-++		goto exit;
-++	}
-++
-++	if (!ack) {
-++		ret = -EINVAL;
-++		kfree_skb(read_skb);
-++		goto exit;
-++	}
-++
-++	ret = read_poll_timeout(qca8k_phy_eth_busy_wait, ret1,
-++				!(val & QCA8K_MDIO_MASTER_BUSY), 0,
-++				QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
-++				mgmt_eth_data, read_skb, &val);
-++
-++	if (ret < 0 && ret1 < 0) {
-++		ret = ret1;
-++		goto exit;
-++	}
-++
-++	if (read) {
-++		reinit_completion(&mgmt_eth_data->rw_done);
-++
-++		/* Increment seq_num and set it in the read pkt */
-++		mgmt_eth_data->seq++;
-++		qca8k_mdio_header_fill_seq_num(read_skb, mgmt_eth_data->seq);
-++		mgmt_eth_data->ack = false;
-++
-++		dev_queue_xmit(read_skb);
-++
-++		ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
-++						  QCA8K_ETHERNET_TIMEOUT);
-++
-++		ack = mgmt_eth_data->ack;
-++
-++		if (ret <= 0) {
-++			ret = -ETIMEDOUT;
-++			goto exit;
-++		}
-++
-++		if (!ack) {
-++			ret = -EINVAL;
-++			goto exit;
-++		}
-++
-++		ret = mgmt_eth_data->data[0] & QCA8K_MDIO_MASTER_DATA_MASK;
-++	} else {
-++		kfree_skb(read_skb);
-++	}
-++exit:
-++	reinit_completion(&mgmt_eth_data->rw_done);
+++	struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
 ++
-++	/* Increment seq_num and set it in the clear pkt */
-++	mgmt_eth_data->seq++;
-++	qca8k_mdio_header_fill_seq_num(clear_skb, mgmt_eth_data->seq);
-++	mgmt_eth_data->ack = false;
+++	tx_q->cur_tx = 0;
+++	tx_q->dirty_tx = 0;
+++	tx_q->mss = 0;
 ++
-++	dev_queue_xmit(clear_skb);
-++
-++	wait_for_completion_timeout(&mgmt_eth_data->rw_done,
-++				    QCA8K_ETHERNET_TIMEOUT);
+++	netdev_tx_reset_queue(netdev_get_tx_queue(priv->dev, queue));
+++}
 ++
-++	mutex_unlock(&mgmt_eth_data->mutex);
++ /**
++  * stmmac_reset_queues_param - reset queue parameters
++  * @priv: device pointer
++@@ -7400,22 +7414,11 @@ static void stmmac_reset_queues_param(st
++ 	u32 tx_cnt = priv->plat->tx_queues_to_use;
++ 	u32 queue;
++ 
++-	for (queue = 0; queue < rx_cnt; queue++) {
++-		struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++	for (queue = 0; queue < rx_cnt; queue++)
+++		stmmac_reset_rx_queue(priv, queue);
++ 
++-		rx_q->cur_rx = 0;
++-		rx_q->dirty_rx = 0;
++-	}
++-
++-	for (queue = 0; queue < tx_cnt; queue++) {
++-		struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
++-
++-		tx_q->cur_tx = 0;
++-		tx_q->dirty_tx = 0;
++-		tx_q->mss = 0;
++-
++-		netdev_tx_reset_queue(netdev_get_tx_queue(priv->dev, queue));
++-	}
+++	for (queue = 0; queue < tx_cnt; queue++)
+++		stmmac_reset_tx_queue(priv, queue);
++ }
++ 
++ /**
+diff --git a/target/linux/generic/backport-5.15/775-v6.0-02-net-ethernet-stmicro-stmmac-first-disable-all-queues.patch b/target/linux/generic/backport-5.15/775-v6.0-02-net-ethernet-stmicro-stmmac-first-disable-all-queues.patch
+new file mode 100644
+index 0000000000..0bb28c79e7
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/775-v6.0-02-net-ethernet-stmicro-stmmac-first-disable-all-queues.patch
+@@ -0,0 +1,37 @@
++From 7028471edb646bfc532fec0973e50e784cdcb7c6 Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Sat, 23 Jul 2022 16:29:30 +0200
++Subject: [PATCH 2/5] net: ethernet: stmicro: stmmac: first disable all queues
++ and disconnect in release
++
++Disable all queues and disconnect before tx_disable in stmmac_release to
++prevent a corner case where packet may be still queued at the same time
++tx_disable is called resulting in kernel panic if some packet still has
++to be processed.
++
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 4 ++--
++ 1 file changed, 2 insertions(+), 2 deletions(-)
++
++--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
++@@ -3818,8 +3818,6 @@ static int stmmac_release(struct net_dev
++ 	struct stmmac_priv *priv = netdev_priv(dev);
++ 	u32 chan;
++ 
++-	netif_tx_disable(dev);
++-
++ 	if (device_may_wakeup(priv->device))
++ 		phylink_speed_down(priv->phylink, false);
++ 	/* Stop and disconnect the PHY */
++@@ -3831,6 +3829,8 @@ static int stmmac_release(struct net_dev
++ 	for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
++ 		hrtimer_cancel(&priv->tx_queue[chan].txtimer);
++ 
+++	netif_tx_disable(dev);
 ++
-++	return ret;
++ 	/* Free the IRQ lines */
++ 	stmmac_free_irq(dev, REQ_IRQ_ERR_ALL, 0);
++ 
+diff --git a/target/linux/generic/backport-5.15/775-v6.0-03-net-ethernet-stmicro-stmmac-move-dma-conf-to-dedicat.patch b/target/linux/generic/backport-5.15/775-v6.0-03-net-ethernet-stmicro-stmmac-move-dma-conf-to-dedicat.patch
+new file mode 100644
+index 0000000000..5cfb14aaab
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/775-v6.0-03-net-ethernet-stmicro-stmmac-move-dma-conf-to-dedicat.patch
+@@ -0,0 +1,1289 @@
++From 8531c80800c10e8ef7952022326c2f983e1314bf Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Sat, 23 Jul 2022 16:29:31 +0200
++Subject: [PATCH 3/5] net: ethernet: stmicro: stmmac: move dma conf to
++ dedicated struct
++
++Move dma buf conf to dedicated struct. This in preparation for code
++rework that will permit to allocate separate dma_conf without affecting
++the priv struct.
++
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ .../net/ethernet/stmicro/stmmac/chain_mode.c  |   6 +-
++ .../net/ethernet/stmicro/stmmac/ring_mode.c   |   4 +-
++ drivers/net/ethernet/stmicro/stmmac/stmmac.h  |  21 +-
++ .../ethernet/stmicro/stmmac/stmmac_ethtool.c  |   4 +-
++ .../net/ethernet/stmicro/stmmac/stmmac_main.c | 286 +++++++++---------
++ .../stmicro/stmmac/stmmac_selftests.c         |   8 +-
++ .../net/ethernet/stmicro/stmmac/stmmac_tc.c   |   6 +-
++ 7 files changed, 172 insertions(+), 163 deletions(-)
++
++--- a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c
+++++ b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c
++@@ -46,7 +46,7 @@ static int jumbo_frm(void *p, struct sk_
++ 
++ 	while (len != 0) {
++ 		tx_q->tx_skbuff[entry] = NULL;
++-		entry = STMMAC_GET_ENTRY(entry, priv->dma_tx_size);
+++		entry = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_tx_size);
++ 		desc = tx_q->dma_tx + entry;
++ 
++ 		if (len > bmax) {
++@@ -137,7 +137,7 @@ static void refill_desc3(void *priv_ptr,
++ 		 */
++ 		p->des3 = cpu_to_le32((unsigned int)(rx_q->dma_rx_phy +
++ 				      (((rx_q->dirty_rx) + 1) %
++-				       priv->dma_rx_size) *
+++				       priv->dma_conf.dma_rx_size) *
++ 				      sizeof(struct dma_desc)));
++ }
++ 
++@@ -155,7 +155,7 @@ static void clean_desc3(void *priv_ptr,
++ 		 */
++ 		p->des3 = cpu_to_le32((unsigned int)((tx_q->dma_tx_phy +
++ 				      ((tx_q->dirty_tx + 1) %
++-				       priv->dma_tx_size))
+++				       priv->dma_conf.dma_tx_size))
++ 				      * sizeof(struct dma_desc)));
++ }
++ 
++--- a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c
+++++ b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c
++@@ -51,7 +51,7 @@ static int jumbo_frm(void *p, struct sk_
++ 		stmmac_prepare_tx_desc(priv, desc, 1, bmax, csum,
++ 				STMMAC_RING_MODE, 0, false, skb->len);
++ 		tx_q->tx_skbuff[entry] = NULL;
++-		entry = STMMAC_GET_ENTRY(entry, priv->dma_tx_size);
+++		entry = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_tx_size);
++ 
++ 		if (priv->extend_desc)
++ 			desc = (struct dma_desc *)(tx_q->dma_etx + entry);
++@@ -107,7 +107,7 @@ static void refill_desc3(void *priv_ptr,
++ 	struct stmmac_priv *priv = rx_q->priv_data;
++ 
++ 	/* Fill DES3 in case of RING mode */
++-	if (priv->dma_buf_sz == BUF_SIZE_16KiB)
+++	if (priv->dma_conf.dma_buf_sz == BUF_SIZE_16KiB)
++ 		p->des3 = cpu_to_le32(le32_to_cpu(p->des2) + BUF_SIZE_8KiB);
++ }
++ 
++--- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h
+++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
++@@ -185,6 +185,18 @@ struct stmmac_rfs_entry {
++ 	int tc;
++ };
++ 
+++struct stmmac_dma_conf {
+++	unsigned int dma_buf_sz;
 ++
-++	/* Error handling before lock */
-++err_mgmt_master:
-++	kfree_skb(read_skb);
-++err_read_skb:
-++	kfree_skb(clear_skb);
-++err_clear_skb:
-++	kfree_skb(write_skb);
+++	/* RX Queue */
+++	struct stmmac_rx_queue rx_queue[MTL_MAX_RX_QUEUES];
+++	unsigned int dma_rx_size;
 ++
-++	return ret;
-++}
+++	/* TX Queue */
+++	struct stmmac_tx_queue tx_queue[MTL_MAX_TX_QUEUES];
+++	unsigned int dma_tx_size;
+++};
 ++
-+ static u32
-+ qca8k_port_to_phy(int port)
++ struct stmmac_priv {
++ 	/* Frequently used values are kept adjacent for cache effect */
++ 	u32 tx_coal_frames[MTL_MAX_TX_QUEUES];
++@@ -199,7 +211,6 @@ struct stmmac_priv {
++ 	int sph_cap;
++ 	u32 sarc_type;
++ 
++-	unsigned int dma_buf_sz;
++ 	unsigned int rx_copybreak;
++ 	u32 rx_riwt[MTL_MAX_TX_QUEUES];
++ 	int hwts_rx_en;
++@@ -211,13 +222,7 @@ struct stmmac_priv {
++ 	int (*hwif_quirks)(struct stmmac_priv *priv);
++ 	struct mutex lock;
++ 
++-	/* RX Queue */
++-	struct stmmac_rx_queue rx_queue[MTL_MAX_RX_QUEUES];
++-	unsigned int dma_rx_size;
++-
++-	/* TX Queue */
++-	struct stmmac_tx_queue tx_queue[MTL_MAX_TX_QUEUES];
++-	unsigned int dma_tx_size;
+++	struct stmmac_dma_conf dma_conf;
++ 
++ 	/* Generic channel for NAPI */
++ 	struct stmmac_channel channel[STMMAC_CH_MAX];
++--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c
+++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c
++@@ -484,8 +484,8 @@ static void stmmac_get_ringparam(struct
++ 
++ 	ring->rx_max_pending = DMA_MAX_RX_SIZE;
++ 	ring->tx_max_pending = DMA_MAX_TX_SIZE;
++-	ring->rx_pending = priv->dma_rx_size;
++-	ring->tx_pending = priv->dma_tx_size;
+++	ring->rx_pending = priv->dma_conf.dma_rx_size;
+++	ring->tx_pending = priv->dma_conf.dma_tx_size;
++ }
++ 
++ static int stmmac_set_ringparam(struct net_device *netdev,
++--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
++@@ -74,8 +74,8 @@ static int phyaddr = -1;
++ module_param(phyaddr, int, 0444);
++ MODULE_PARM_DESC(phyaddr, "Physical device address");
++ 
++-#define STMMAC_TX_THRESH(x)	((x)->dma_tx_size / 4)
++-#define STMMAC_RX_THRESH(x)	((x)->dma_rx_size / 4)
+++#define STMMAC_TX_THRESH(x)	((x)->dma_conf.dma_tx_size / 4)
+++#define STMMAC_RX_THRESH(x)	((x)->dma_conf.dma_rx_size / 4)
++ 
++ /* Limit to make sure XDP TX and slow path can coexist */
++ #define STMMAC_XSK_TX_BUDGET_MAX	256
++@@ -232,7 +232,7 @@ static void stmmac_disable_all_queues(st
++ 
++ 	/* synchronize_rcu() needed for pending XDP buffers to drain */
++ 	for (queue = 0; queue < rx_queues_cnt; queue++) {
++-		rx_q = &priv->rx_queue[queue];
+++		rx_q = &priv->dma_conf.rx_queue[queue];
++ 		if (rx_q->xsk_pool) {
++ 			synchronize_rcu();
++ 			break;
++@@ -358,13 +358,13 @@ static void print_pkt(unsigned char *buf
++ 
++ static inline u32 stmmac_tx_avail(struct stmmac_priv *priv, u32 queue)
 + {
-+@@ -989,6 +1182,12 @@ qca8k_internal_mdio_write(struct mii_bus
++-	struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
++ 	u32 avail;
++ 
++ 	if (tx_q->dirty_tx > tx_q->cur_tx)
++ 		avail = tx_q->dirty_tx - tx_q->cur_tx - 1;
++ 	else
++-		avail = priv->dma_tx_size - tx_q->cur_tx + tx_q->dirty_tx - 1;
+++		avail = priv->dma_conf.dma_tx_size - tx_q->cur_tx + tx_q->dirty_tx - 1;
++ 
++ 	return avail;
++ }
++@@ -376,13 +376,13 @@ static inline u32 stmmac_tx_avail(struct
++  */
++ static inline u32 stmmac_rx_dirty(struct stmmac_priv *priv, u32 queue)
 + {
-+ 	struct qca8k_priv *priv = slave_bus->priv;
-+ 	struct mii_bus *bus = priv->bus;
-++	int ret;
-++
-++	/* Use mdio Ethernet when available, fallback to legacy one on error */
-++	ret = qca8k_phy_eth_command(priv, false, phy, regnum, data);
-++	if (!ret)
-++		return 0;
++-	struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 	u32 dirty;
 + 
-+ 	return qca8k_mdio_write(bus, phy, regnum, data);
++ 	if (rx_q->dirty_rx <= rx_q->cur_rx)
++ 		dirty = rx_q->cur_rx - rx_q->dirty_rx;
++ 	else
++-		dirty = priv->dma_rx_size - rx_q->dirty_rx + rx_q->cur_rx;
+++		dirty = priv->dma_conf.dma_rx_size - rx_q->dirty_rx + rx_q->cur_rx;
++ 
++ 	return dirty;
 + }
-+@@ -998,6 +1197,12 @@ qca8k_internal_mdio_read(struct mii_bus
++@@ -410,7 +410,7 @@ static int stmmac_enable_eee_mode(struct
++ 
++ 	/* check if all TX queues have the work finished */
++ 	for (queue = 0; queue < tx_cnt; queue++) {
++-		struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
+++		struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
++ 
++ 		if (tx_q->dirty_tx != tx_q->cur_tx)
++ 			return -EBUSY; /* still unfinished work */
++@@ -1294,7 +1294,7 @@ static void stmmac_display_rx_rings(stru
++ 
++ 	/* Display RX rings */
++ 	for (queue = 0; queue < rx_cnt; queue++) {
++-		struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++		struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 
++ 		pr_info("\tRX Queue %u rings\n", queue);
++ 
++@@ -1307,7 +1307,7 @@ static void stmmac_display_rx_rings(stru
++ 		}
++ 
++ 		/* Display RX ring */
++-		stmmac_display_ring(priv, head_rx, priv->dma_rx_size, true,
+++		stmmac_display_ring(priv, head_rx, priv->dma_conf.dma_rx_size, true,
++ 				    rx_q->dma_rx_phy, desc_size);
++ 	}
++ }
++@@ -1321,7 +1321,7 @@ static void stmmac_display_tx_rings(stru
++ 
++ 	/* Display TX rings */
++ 	for (queue = 0; queue < tx_cnt; queue++) {
++-		struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
+++		struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
++ 
++ 		pr_info("\tTX Queue %d rings\n", queue);
++ 
++@@ -1336,7 +1336,7 @@ static void stmmac_display_tx_rings(stru
++ 			desc_size = sizeof(struct dma_desc);
++ 		}
++ 
++-		stmmac_display_ring(priv, head_tx, priv->dma_tx_size, false,
+++		stmmac_display_ring(priv, head_tx, priv->dma_conf.dma_tx_size, false,
++ 				    tx_q->dma_tx_phy, desc_size);
++ 	}
++ }
++@@ -1377,21 +1377,21 @@ static int stmmac_set_bfsize(int mtu, in
++  */
++ static void stmmac_clear_rx_descriptors(struct stmmac_priv *priv, u32 queue)
 + {
-+ 	struct qca8k_priv *priv = slave_bus->priv;
-+ 	struct mii_bus *bus = priv->bus;
-++	int ret;
-++
-++	/* Use mdio Ethernet when available, fallback to legacy one on error */
-++	ret = qca8k_phy_eth_command(priv, true, phy, regnum, 0);
-++	if (ret >= 0)
-++		return ret;
++-	struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 	int i;
++ 
++ 	/* Clear the RX descriptors */
++-	for (i = 0; i < priv->dma_rx_size; i++)
+++	for (i = 0; i < priv->dma_conf.dma_rx_size; i++)
++ 		if (priv->extend_desc)
++ 			stmmac_init_rx_desc(priv, &rx_q->dma_erx[i].basic,
++ 					priv->use_riwt, priv->mode,
++-					(i == priv->dma_rx_size - 1),
++-					priv->dma_buf_sz);
+++					(i == priv->dma_conf.dma_rx_size - 1),
+++					priv->dma_conf.dma_buf_sz);
++ 		else
++ 			stmmac_init_rx_desc(priv, &rx_q->dma_rx[i],
++ 					priv->use_riwt, priv->mode,
++-					(i == priv->dma_rx_size - 1),
++-					priv->dma_buf_sz);
+++					(i == priv->dma_conf.dma_rx_size - 1),
+++					priv->dma_conf.dma_buf_sz);
++ }
++ 
++ /**
++@@ -1403,12 +1403,12 @@ static void stmmac_clear_rx_descriptors(
++  */
++ static void stmmac_clear_tx_descriptors(struct stmmac_priv *priv, u32 queue)
++ {
++-	struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
++ 	int i;
++ 
++ 	/* Clear the TX descriptors */
++-	for (i = 0; i < priv->dma_tx_size; i++) {
++-		int last = (i == (priv->dma_tx_size - 1));
+++	for (i = 0; i < priv->dma_conf.dma_tx_size; i++) {
+++		int last = (i == (priv->dma_conf.dma_tx_size - 1));
++ 		struct dma_desc *p;
++ 
++ 		if (priv->extend_desc)
++@@ -1456,7 +1456,7 @@ static void stmmac_clear_descriptors(str
++ static int stmmac_init_rx_buffers(struct stmmac_priv *priv, struct dma_desc *p,
++ 				  int i, gfp_t flags, u32 queue)
++ {
++-	struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 	struct stmmac_rx_buffer *buf = &rx_q->buf_pool[i];
++ 
++ 	if (!buf->page) {
++@@ -1481,7 +1481,7 @@ static int stmmac_init_rx_buffers(struct
++ 	buf->addr = page_pool_get_dma_addr(buf->page) + buf->page_offset;
++ 
++ 	stmmac_set_desc_addr(priv, p, buf->addr);
++-	if (priv->dma_buf_sz == BUF_SIZE_16KiB)
+++	if (priv->dma_conf.dma_buf_sz == BUF_SIZE_16KiB)
++ 		stmmac_init_desc3(priv, p);
++ 
++ 	return 0;
++@@ -1495,7 +1495,7 @@ static int stmmac_init_rx_buffers(struct
++  */
++ static void stmmac_free_rx_buffer(struct stmmac_priv *priv, u32 queue, int i)
++ {
++-	struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 	struct stmmac_rx_buffer *buf = &rx_q->buf_pool[i];
++ 
++ 	if (buf->page)
++@@ -1515,7 +1515,7 @@ static void stmmac_free_rx_buffer(struct
++  */
++ static void stmmac_free_tx_buffer(struct stmmac_priv *priv, u32 queue, int i)
++ {
++-	struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
++ 
++ 	if (tx_q->tx_skbuff_dma[i].buf &&
++ 	    tx_q->tx_skbuff_dma[i].buf_type != STMMAC_TXBUF_T_XDP_TX) {
++@@ -1560,17 +1560,17 @@ static void dma_free_rx_skbufs(struct st
++ {
++ 	int i;
++ 
++-	for (i = 0; i < priv->dma_rx_size; i++)
+++	for (i = 0; i < priv->dma_conf.dma_rx_size; i++)
++ 		stmmac_free_rx_buffer(priv, queue, i);
++ }
++ 
++ static int stmmac_alloc_rx_buffers(struct stmmac_priv *priv, u32 queue,
++ 				   gfp_t flags)
++ {
++-	struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 	int i;
++ 
++-	for (i = 0; i < priv->dma_rx_size; i++) {
+++	for (i = 0; i < priv->dma_conf.dma_rx_size; i++) {
++ 		struct dma_desc *p;
++ 		int ret;
++ 
++@@ -1597,10 +1597,10 @@ static int stmmac_alloc_rx_buffers(struc
++  */
++ static void dma_free_rx_xskbufs(struct stmmac_priv *priv, u32 queue)
++ {
++-	struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 	int i;
++ 
++-	for (i = 0; i < priv->dma_rx_size; i++) {
+++	for (i = 0; i < priv->dma_conf.dma_rx_size; i++) {
++ 		struct stmmac_rx_buffer *buf = &rx_q->buf_pool[i];
++ 
++ 		if (!buf->xdp)
++@@ -1613,10 +1613,10 @@ static void dma_free_rx_xskbufs(struct s
++ 
++ static int stmmac_alloc_rx_buffers_zc(struct stmmac_priv *priv, u32 queue)
++ {
++-	struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 	int i;
++ 
++-	for (i = 0; i < priv->dma_rx_size; i++) {
+++	for (i = 0; i < priv->dma_conf.dma_rx_size; i++) {
++ 		struct stmmac_rx_buffer *buf;
++ 		dma_addr_t dma_addr;
++ 		struct dma_desc *p;
++@@ -1659,7 +1659,7 @@ static struct xsk_buff_pool *stmmac_get_
++  */
++ static int __init_dma_rx_desc_rings(struct stmmac_priv *priv, u32 queue, gfp_t flags)
++ {
++-	struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 	int ret;
++ 
++ 	netif_dbg(priv, probe, priv->dev,
++@@ -1705,11 +1705,11 @@ static int __init_dma_rx_desc_rings(stru
++ 		if (priv->extend_desc)
++ 			stmmac_mode_init(priv, rx_q->dma_erx,
++ 					 rx_q->dma_rx_phy,
++-					 priv->dma_rx_size, 1);
+++					 priv->dma_conf.dma_rx_size, 1);
++ 		else
++ 			stmmac_mode_init(priv, rx_q->dma_rx,
++ 					 rx_q->dma_rx_phy,
++-					 priv->dma_rx_size, 0);
+++					 priv->dma_conf.dma_rx_size, 0);
++ 	}
++ 
++ 	return 0;
++@@ -1736,7 +1736,7 @@ static int init_dma_rx_desc_rings(struct
++ 
++ err_init_rx_buffers:
++ 	while (queue >= 0) {
++-		struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++		struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 
++ 		if (rx_q->xsk_pool)
++ 			dma_free_rx_xskbufs(priv, queue);
++@@ -1765,7 +1765,7 @@ err_init_rx_buffers:
++  */
++ static int __init_dma_tx_desc_rings(struct stmmac_priv *priv, u32 queue)
++ {
++-	struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
++ 	int i;
++ 
++ 	netif_dbg(priv, probe, priv->dev,
++@@ -1777,16 +1777,16 @@ static int __init_dma_tx_desc_rings(stru
++ 		if (priv->extend_desc)
++ 			stmmac_mode_init(priv, tx_q->dma_etx,
++ 					 tx_q->dma_tx_phy,
++-					 priv->dma_tx_size, 1);
+++					 priv->dma_conf.dma_tx_size, 1);
++ 		else if (!(tx_q->tbs & STMMAC_TBS_AVAIL))
++ 			stmmac_mode_init(priv, tx_q->dma_tx,
++ 					 tx_q->dma_tx_phy,
++-					 priv->dma_tx_size, 0);
+++					 priv->dma_conf.dma_tx_size, 0);
++ 	}
++ 
++ 	tx_q->xsk_pool = stmmac_get_xsk_pool(priv, queue);
++ 
++-	for (i = 0; i < priv->dma_tx_size; i++) {
+++	for (i = 0; i < priv->dma_conf.dma_tx_size; i++) {
++ 		struct dma_desc *p;
++ 
++ 		if (priv->extend_desc)
++@@ -1856,12 +1856,12 @@ static int init_dma_desc_rings(struct ne
++  */
++ static void dma_free_tx_skbufs(struct stmmac_priv *priv, u32 queue)
++ {
++-	struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
++ 	int i;
++ 
++ 	tx_q->xsk_frames_done = 0;
++ 
++-	for (i = 0; i < priv->dma_tx_size; i++)
+++	for (i = 0; i < priv->dma_conf.dma_tx_size; i++)
++ 		stmmac_free_tx_buffer(priv, queue, i);
++ 
++ 	if (tx_q->xsk_pool && tx_q->xsk_frames_done) {
++@@ -1891,7 +1891,7 @@ static void stmmac_free_tx_skbufs(struct
++  */
++ static void __free_dma_rx_desc_resources(struct stmmac_priv *priv, u32 queue)
++ {
++-	struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 
++ 	/* Release the DMA RX socket buffers */
++ 	if (rx_q->xsk_pool)
++@@ -1904,11 +1904,11 @@ static void __free_dma_rx_desc_resources
++ 
++ 	/* Free DMA regions of consistent memory previously allocated */
++ 	if (!priv->extend_desc)
++-		dma_free_coherent(priv->device, priv->dma_rx_size *
+++		dma_free_coherent(priv->device, priv->dma_conf.dma_rx_size *
++ 				  sizeof(struct dma_desc),
++ 				  rx_q->dma_rx, rx_q->dma_rx_phy);
++ 	else
++-		dma_free_coherent(priv->device, priv->dma_rx_size *
+++		dma_free_coherent(priv->device, priv->dma_conf.dma_rx_size *
++ 				  sizeof(struct dma_extended_desc),
++ 				  rx_q->dma_erx, rx_q->dma_rx_phy);
++ 
++@@ -1937,7 +1937,7 @@ static void free_dma_rx_desc_resources(s
++  */
++ static void __free_dma_tx_desc_resources(struct stmmac_priv *priv, u32 queue)
++ {
++-	struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
++ 	size_t size;
++ 	void *addr;
++ 
++@@ -1955,7 +1955,7 @@ static void __free_dma_tx_desc_resources
++ 		addr = tx_q->dma_tx;
++ 	}
++ 
++-	size *= priv->dma_tx_size;
+++	size *= priv->dma_conf.dma_tx_size;
++ 
++ 	dma_free_coherent(priv->device, size, addr, tx_q->dma_tx_phy);
++ 
++@@ -1984,7 +1984,7 @@ static void free_dma_tx_desc_resources(s
++  */
++ static int __alloc_dma_rx_desc_resources(struct stmmac_priv *priv, u32 queue)
++ {
++-	struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 	struct stmmac_channel *ch = &priv->channel[queue];
++ 	bool xdp_prog = stmmac_xdp_is_enabled(priv);
++ 	struct page_pool_params pp_params = { 0 };
++@@ -1996,8 +1996,8 @@ static int __alloc_dma_rx_desc_resources
++ 	rx_q->priv_data = priv;
++ 
++ 	pp_params.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV;
++-	pp_params.pool_size = priv->dma_rx_size;
++-	num_pages = DIV_ROUND_UP(priv->dma_buf_sz, PAGE_SIZE);
+++	pp_params.pool_size = priv->dma_conf.dma_rx_size;
+++	num_pages = DIV_ROUND_UP(priv->dma_conf.dma_buf_sz, PAGE_SIZE);
++ 	pp_params.order = ilog2(num_pages);
++ 	pp_params.nid = dev_to_node(priv->device);
++ 	pp_params.dev = priv->device;
++@@ -2012,7 +2012,7 @@ static int __alloc_dma_rx_desc_resources
++ 		return ret;
++ 	}
++ 
++-	rx_q->buf_pool = kcalloc(priv->dma_rx_size,
+++	rx_q->buf_pool = kcalloc(priv->dma_conf.dma_rx_size,
++ 				 sizeof(*rx_q->buf_pool),
++ 				 GFP_KERNEL);
++ 	if (!rx_q->buf_pool)
++@@ -2020,7 +2020,7 @@ static int __alloc_dma_rx_desc_resources
++ 
++ 	if (priv->extend_desc) {
++ 		rx_q->dma_erx = dma_alloc_coherent(priv->device,
++-						   priv->dma_rx_size *
+++						   priv->dma_conf.dma_rx_size *
++ 						   sizeof(struct dma_extended_desc),
++ 						   &rx_q->dma_rx_phy,
++ 						   GFP_KERNEL);
++@@ -2029,7 +2029,7 @@ static int __alloc_dma_rx_desc_resources
++ 
++ 	} else {
++ 		rx_q->dma_rx = dma_alloc_coherent(priv->device,
++-						  priv->dma_rx_size *
+++						  priv->dma_conf.dma_rx_size *
++ 						  sizeof(struct dma_desc),
++ 						  &rx_q->dma_rx_phy,
++ 						  GFP_KERNEL);
++@@ -2086,20 +2086,20 @@ err_dma:
++  */
++ static int __alloc_dma_tx_desc_resources(struct stmmac_priv *priv, u32 queue)
++ {
++-	struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
++ 	size_t size;
++ 	void *addr;
++ 
++ 	tx_q->queue_index = queue;
++ 	tx_q->priv_data = priv;
++ 
++-	tx_q->tx_skbuff_dma = kcalloc(priv->dma_tx_size,
+++	tx_q->tx_skbuff_dma = kcalloc(priv->dma_conf.dma_tx_size,
++ 				      sizeof(*tx_q->tx_skbuff_dma),
++ 				      GFP_KERNEL);
++ 	if (!tx_q->tx_skbuff_dma)
++ 		return -ENOMEM;
++ 
++-	tx_q->tx_skbuff = kcalloc(priv->dma_tx_size,
+++	tx_q->tx_skbuff = kcalloc(priv->dma_conf.dma_tx_size,
++ 				  sizeof(struct sk_buff *),
++ 				  GFP_KERNEL);
++ 	if (!tx_q->tx_skbuff)
++@@ -2112,7 +2112,7 @@ static int __alloc_dma_tx_desc_resources
++ 	else
++ 		size = sizeof(struct dma_desc);
++ 
++-	size *= priv->dma_tx_size;
+++	size *= priv->dma_conf.dma_tx_size;
++ 
++ 	addr = dma_alloc_coherent(priv->device, size,
++ 				  &tx_q->dma_tx_phy, GFP_KERNEL);
++@@ -2356,7 +2356,7 @@ static void stmmac_dma_operation_mode(st
++ 
++ 	/* configure all channels */
++ 	for (chan = 0; chan < rx_channels_count; chan++) {
++-		struct stmmac_rx_queue *rx_q = &priv->rx_queue[chan];
+++		struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[chan];
++ 		u32 buf_size;
++ 
++ 		qmode = priv->plat->rx_queues_cfg[chan].mode_to_use;
++@@ -2371,7 +2371,7 @@ static void stmmac_dma_operation_mode(st
++ 					      chan);
++ 		} else {
++ 			stmmac_set_dma_bfsize(priv, priv->ioaddr,
++-					      priv->dma_buf_sz,
+++					      priv->dma_conf.dma_buf_sz,
++ 					      chan);
++ 		}
++ 	}
++@@ -2387,7 +2387,7 @@ static void stmmac_dma_operation_mode(st
++ static bool stmmac_xdp_xmit_zc(struct stmmac_priv *priv, u32 queue, u32 budget)
++ {
++ 	struct netdev_queue *nq = netdev_get_tx_queue(priv->dev, queue);
++-	struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
++ 	struct xsk_buff_pool *pool = tx_q->xsk_pool;
++ 	unsigned int entry = tx_q->cur_tx;
++ 	struct dma_desc *tx_desc = NULL;
++@@ -2462,7 +2462,7 @@ static bool stmmac_xdp_xmit_zc(struct st
++ 
++ 		stmmac_enable_dma_transmission(priv, priv->ioaddr);
++ 
++-		tx_q->cur_tx = STMMAC_GET_ENTRY(tx_q->cur_tx, priv->dma_tx_size);
+++		tx_q->cur_tx = STMMAC_GET_ENTRY(tx_q->cur_tx, priv->dma_conf.dma_tx_size);
++ 		entry = tx_q->cur_tx;
++ 	}
++ 
++@@ -2488,7 +2488,7 @@ static bool stmmac_xdp_xmit_zc(struct st
++  */
++ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue)
++ {
++-	struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
++ 	unsigned int bytes_compl = 0, pkts_compl = 0;
++ 	unsigned int entry, xmits = 0, count = 0;
++ 
++@@ -2501,7 +2501,7 @@ static int stmmac_tx_clean(struct stmmac
++ 	entry = tx_q->dirty_tx;
++ 
++ 	/* Try to clean all TX complete frame in 1 shot */
++-	while ((entry != tx_q->cur_tx) && count < priv->dma_tx_size) {
+++	while ((entry != tx_q->cur_tx) && count < priv->dma_conf.dma_tx_size) {
++ 		struct xdp_frame *xdpf;
++ 		struct sk_buff *skb;
++ 		struct dma_desc *p;
++@@ -2601,7 +2601,7 @@ static int stmmac_tx_clean(struct stmmac
++ 
++ 		stmmac_release_tx_desc(priv, p, priv->mode);
++ 
++-		entry = STMMAC_GET_ENTRY(entry, priv->dma_tx_size);
+++		entry = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_tx_size);
++ 	}
++ 	tx_q->dirty_tx = entry;
++ 
++@@ -2666,7 +2666,7 @@ static int stmmac_tx_clean(struct stmmac
++  */
++ static void stmmac_tx_err(struct stmmac_priv *priv, u32 chan)
++ {
++-	struct stmmac_tx_queue *tx_q = &priv->tx_queue[chan];
+++	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan];
++ 
++ 	netif_tx_stop_queue(netdev_get_tx_queue(priv->dev, chan));
++ 
++@@ -2733,8 +2733,8 @@ static int stmmac_napi_check(struct stmm
++ {
++ 	int status = stmmac_dma_interrupt_status(priv, priv->ioaddr,
++ 						 &priv->xstats, chan, dir);
++-	struct stmmac_rx_queue *rx_q = &priv->rx_queue[chan];
++-	struct stmmac_tx_queue *tx_q = &priv->tx_queue[chan];
+++	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[chan];
+++	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan];
++ 	struct stmmac_channel *ch = &priv->channel[chan];
++ 	struct napi_struct *rx_napi;
++ 	struct napi_struct *tx_napi;
++@@ -2910,7 +2910,7 @@ static int stmmac_init_dma_engine(struct
++ 
++ 	/* DMA RX Channel Configuration */
++ 	for (chan = 0; chan < rx_channels_count; chan++) {
++-		rx_q = &priv->rx_queue[chan];
+++		rx_q = &priv->dma_conf.rx_queue[chan];
++ 
++ 		stmmac_init_rx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
++ 				    rx_q->dma_rx_phy, chan);
++@@ -2924,7 +2924,7 @@ static int stmmac_init_dma_engine(struct
++ 
++ 	/* DMA TX Channel Configuration */
++ 	for (chan = 0; chan < tx_channels_count; chan++) {
++-		tx_q = &priv->tx_queue[chan];
+++		tx_q = &priv->dma_conf.tx_queue[chan];
++ 
++ 		stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
++ 				    tx_q->dma_tx_phy, chan);
++@@ -2939,7 +2939,7 @@ static int stmmac_init_dma_engine(struct
++ 
++ static void stmmac_tx_timer_arm(struct stmmac_priv *priv, u32 queue)
++ {
++-	struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
++ 
++ 	hrtimer_start(&tx_q->txtimer,
++ 		      STMMAC_COAL_TIMER(priv->tx_coal_timer[queue]),
++@@ -2989,7 +2989,7 @@ static void stmmac_init_coalesce(struct
++ 	u32 chan;
++ 
++ 	for (chan = 0; chan < tx_channel_count; chan++) {
++-		struct stmmac_tx_queue *tx_q = &priv->tx_queue[chan];
+++		struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan];
++ 
++ 		priv->tx_coal_frames[chan] = STMMAC_TX_FRAMES;
++ 		priv->tx_coal_timer[chan] = STMMAC_COAL_TX_TIMER;
++@@ -3011,12 +3011,12 @@ static void stmmac_set_rings_length(stru
++ 	/* set TX ring length */
++ 	for (chan = 0; chan < tx_channels_count; chan++)
++ 		stmmac_set_tx_ring_len(priv, priv->ioaddr,
++-				       (priv->dma_tx_size - 1), chan);
+++				       (priv->dma_conf.dma_tx_size - 1), chan);
++ 
++ 	/* set RX ring length */
++ 	for (chan = 0; chan < rx_channels_count; chan++)
++ 		stmmac_set_rx_ring_len(priv, priv->ioaddr,
++-				       (priv->dma_rx_size - 1), chan);
+++				       (priv->dma_conf.dma_rx_size - 1), chan);
++ }
++ 
++ /**
++@@ -3351,7 +3351,7 @@ static int stmmac_hw_setup(struct net_de
++ 	/* Enable TSO */
++ 	if (priv->tso) {
++ 		for (chan = 0; chan < tx_cnt; chan++) {
++-			struct stmmac_tx_queue *tx_q = &priv->tx_queue[chan];
+++			struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan];
++ 
++ 			/* TSO and TBS cannot co-exist */
++ 			if (tx_q->tbs & STMMAC_TBS_AVAIL)
++@@ -3373,7 +3373,7 @@ static int stmmac_hw_setup(struct net_de
++ 
++ 	/* TBS */
++ 	for (chan = 0; chan < tx_cnt; chan++) {
++-		struct stmmac_tx_queue *tx_q = &priv->tx_queue[chan];
+++		struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan];
++ 		int enable = tx_q->tbs & STMMAC_TBS_AVAIL;
++ 
++ 		stmmac_enable_tbs(priv, priv->ioaddr, enable, chan);
++@@ -3417,7 +3417,7 @@ static void stmmac_free_irq(struct net_d
++ 		for (j = irq_idx - 1; j >= 0; j--) {
++ 			if (priv->tx_irq[j] > 0) {
++ 				irq_set_affinity_hint(priv->tx_irq[j], NULL);
++-				free_irq(priv->tx_irq[j], &priv->tx_queue[j]);
+++				free_irq(priv->tx_irq[j], &priv->dma_conf.tx_queue[j]);
++ 			}
++ 		}
++ 		irq_idx = priv->plat->rx_queues_to_use;
++@@ -3426,7 +3426,7 @@ static void stmmac_free_irq(struct net_d
++ 		for (j = irq_idx - 1; j >= 0; j--) {
++ 			if (priv->rx_irq[j] > 0) {
++ 				irq_set_affinity_hint(priv->rx_irq[j], NULL);
++-				free_irq(priv->rx_irq[j], &priv->rx_queue[j]);
+++				free_irq(priv->rx_irq[j], &priv->dma_conf.rx_queue[j]);
++ 			}
++ 		}
++ 
++@@ -3559,7 +3559,7 @@ static int stmmac_request_irq_multi_msi(
++ 		sprintf(int_name, "%s:%s-%d", dev->name, "rx", i);
++ 		ret = request_irq(priv->rx_irq[i],
++ 				  stmmac_msi_intr_rx,
++-				  0, int_name, &priv->rx_queue[i]);
+++				  0, int_name, &priv->dma_conf.rx_queue[i]);
++ 		if (unlikely(ret < 0)) {
++ 			netdev_err(priv->dev,
++ 				   "%s: alloc rx-%d  MSI %d (error: %d)\n",
++@@ -3582,7 +3582,7 @@ static int stmmac_request_irq_multi_msi(
++ 		sprintf(int_name, "%s:%s-%d", dev->name, "tx", i);
++ 		ret = request_irq(priv->tx_irq[i],
++ 				  stmmac_msi_intr_tx,
++-				  0, int_name, &priv->tx_queue[i]);
+++				  0, int_name, &priv->dma_conf.tx_queue[i]);
++ 		if (unlikely(ret < 0)) {
++ 			netdev_err(priv->dev,
++ 				   "%s: alloc tx-%d  MSI %d (error: %d)\n",
++@@ -3713,21 +3713,21 @@ static int stmmac_open(struct net_device
++ 		bfsize = 0;
++ 
++ 	if (bfsize < BUF_SIZE_16KiB)
++-		bfsize = stmmac_set_bfsize(dev->mtu, priv->dma_buf_sz);
+++		bfsize = stmmac_set_bfsize(dev->mtu, priv->dma_conf.dma_buf_sz);
++ 
++-	priv->dma_buf_sz = bfsize;
+++	priv->dma_conf.dma_buf_sz = bfsize;
++ 	buf_sz = bfsize;
++ 
++ 	priv->rx_copybreak = STMMAC_RX_COPYBREAK;
++ 
++-	if (!priv->dma_tx_size)
++-		priv->dma_tx_size = DMA_DEFAULT_TX_SIZE;
++-	if (!priv->dma_rx_size)
++-		priv->dma_rx_size = DMA_DEFAULT_RX_SIZE;
+++	if (!priv->dma_conf.dma_tx_size)
+++		priv->dma_conf.dma_tx_size = DMA_DEFAULT_TX_SIZE;
+++	if (!priv->dma_conf.dma_rx_size)
+++		priv->dma_conf.dma_rx_size = DMA_DEFAULT_RX_SIZE;
++ 
++ 	/* Earlier check for TBS */
++ 	for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++) {
++-		struct stmmac_tx_queue *tx_q = &priv->tx_queue[chan];
+++		struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan];
++ 		int tbs_en = priv->plat->tx_queues_cfg[chan].tbs_en;
++ 
++ 		/* Setup per-TXQ tbs flag before TX descriptor alloc */
++@@ -3785,7 +3785,7 @@ irq_error:
++ 	phylink_stop(priv->phylink);
++ 
++ 	for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
++-		hrtimer_cancel(&priv->tx_queue[chan].txtimer);
+++		hrtimer_cancel(&priv->dma_conf.tx_queue[chan].txtimer);
++ 
++ 	stmmac_hw_teardown(dev);
++ init_error:
++@@ -3827,7 +3827,7 @@ static int stmmac_release(struct net_dev
++ 	stmmac_disable_all_queues(priv);
++ 
++ 	for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
++-		hrtimer_cancel(&priv->tx_queue[chan].txtimer);
+++		hrtimer_cancel(&priv->dma_conf.tx_queue[chan].txtimer);
++ 
++ 	netif_tx_disable(dev);
++ 
++@@ -3891,7 +3891,7 @@ static bool stmmac_vlan_insert(struct st
++ 		return false;
++ 
++ 	stmmac_set_tx_owner(priv, p);
++-	tx_q->cur_tx = STMMAC_GET_ENTRY(tx_q->cur_tx, priv->dma_tx_size);
+++	tx_q->cur_tx = STMMAC_GET_ENTRY(tx_q->cur_tx, priv->dma_conf.dma_tx_size);
++ 	return true;
++ }
++ 
++@@ -3909,7 +3909,7 @@ static bool stmmac_vlan_insert(struct st
++ static void stmmac_tso_allocator(struct stmmac_priv *priv, dma_addr_t des,
++ 				 int total_len, bool last_segment, u32 queue)
++ {
++-	struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
++ 	struct dma_desc *desc;
++ 	u32 buff_size;
++ 	int tmp_len;
++@@ -3920,7 +3920,7 @@ static void stmmac_tso_allocator(struct
++ 		dma_addr_t curr_addr;
++ 
++ 		tx_q->cur_tx = STMMAC_GET_ENTRY(tx_q->cur_tx,
++-						priv->dma_tx_size);
+++						priv->dma_conf.dma_tx_size);
++ 		WARN_ON(tx_q->tx_skbuff[tx_q->cur_tx]);
++ 
++ 		if (tx_q->tbs & STMMAC_TBS_AVAIL)
++@@ -3948,7 +3948,7 @@ static void stmmac_tso_allocator(struct
++ 
++ static void stmmac_flush_tx_descriptors(struct stmmac_priv *priv, int queue)
++ {
++-	struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
++ 	int desc_size;
++ 
++ 	if (likely(priv->extend_desc))
++@@ -4010,7 +4010,7 @@ static netdev_tx_t stmmac_tso_xmit(struc
++ 	dma_addr_t des;
++ 	int i;
++ 
++-	tx_q = &priv->tx_queue[queue];
+++	tx_q = &priv->dma_conf.tx_queue[queue];
++ 	first_tx = tx_q->cur_tx;
++ 
++ 	/* Compute header lengths */
++@@ -4050,7 +4050,7 @@ static netdev_tx_t stmmac_tso_xmit(struc
++ 		stmmac_set_mss(priv, mss_desc, mss);
++ 		tx_q->mss = mss;
++ 		tx_q->cur_tx = STMMAC_GET_ENTRY(tx_q->cur_tx,
++-						priv->dma_tx_size);
+++						priv->dma_conf.dma_tx_size);
++ 		WARN_ON(tx_q->tx_skbuff[tx_q->cur_tx]);
++ 	}
++ 
++@@ -4162,7 +4162,7 @@ static netdev_tx_t stmmac_tso_xmit(struc
++ 	 * ndo_start_xmit will fill this descriptor the next time it's
++ 	 * called and stmmac_tx_clean may clean up to this descriptor.
++ 	 */
++-	tx_q->cur_tx = STMMAC_GET_ENTRY(tx_q->cur_tx, priv->dma_tx_size);
+++	tx_q->cur_tx = STMMAC_GET_ENTRY(tx_q->cur_tx, priv->dma_conf.dma_tx_size);
++ 
++ 	if (unlikely(stmmac_tx_avail(priv, queue) <= (MAX_SKB_FRAGS + 1))) {
++ 		netif_dbg(priv, hw, priv->dev, "%s: stop transmitted packets\n",
++@@ -4250,7 +4250,7 @@ static netdev_tx_t stmmac_xmit(struct sk
++ 	int entry, first_tx;
++ 	dma_addr_t des;
++ 
++-	tx_q = &priv->tx_queue[queue];
+++	tx_q = &priv->dma_conf.tx_queue[queue];
++ 	first_tx = tx_q->cur_tx;
++ 
++ 	if (priv->tx_path_in_lpi_mode && priv->eee_sw_timer_en)
++@@ -4313,7 +4313,7 @@ static netdev_tx_t stmmac_xmit(struct sk
++ 		int len = skb_frag_size(frag);
++ 		bool last_segment = (i == (nfrags - 1));
++ 
++-		entry = STMMAC_GET_ENTRY(entry, priv->dma_tx_size);
+++		entry = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_tx_size);
++ 		WARN_ON(tx_q->tx_skbuff[entry]);
++ 
++ 		if (likely(priv->extend_desc))
++@@ -4384,7 +4384,7 @@ static netdev_tx_t stmmac_xmit(struct sk
++ 	 * ndo_start_xmit will fill this descriptor the next time it's
++ 	 * called and stmmac_tx_clean may clean up to this descriptor.
++ 	 */
++-	entry = STMMAC_GET_ENTRY(entry, priv->dma_tx_size);
+++	entry = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_tx_size);
++ 	tx_q->cur_tx = entry;
++ 
++ 	if (netif_msg_pktdata(priv)) {
++@@ -4499,7 +4499,7 @@ static void stmmac_rx_vlan(struct net_de
++  */
++ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue)
++ {
++-	struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 	int dirty = stmmac_rx_dirty(priv, queue);
++ 	unsigned int entry = rx_q->dirty_rx;
++ 
++@@ -4549,7 +4549,7 @@ static inline void stmmac_rx_refill(stru
++ 		dma_wmb();
++ 		stmmac_set_rx_owner(priv, p, use_rx_wd);
++ 
++-		entry = STMMAC_GET_ENTRY(entry, priv->dma_rx_size);
+++		entry = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_rx_size);
++ 	}
++ 	rx_q->dirty_rx = entry;
++ 	rx_q->rx_tail_addr = rx_q->dma_rx_phy +
++@@ -4577,12 +4577,12 @@ static unsigned int stmmac_rx_buf1_len(s
++ 
++ 	/* First descriptor, not last descriptor and not split header */
++ 	if (status & rx_not_ls)
++-		return priv->dma_buf_sz;
+++		return priv->dma_conf.dma_buf_sz;
++ 
++ 	plen = stmmac_get_rx_frame_len(priv, p, coe);
++ 
++ 	/* First descriptor and last descriptor and not split header */
++-	return min_t(unsigned int, priv->dma_buf_sz, plen);
+++	return min_t(unsigned int, priv->dma_conf.dma_buf_sz, plen);
++ }
++ 
++ static unsigned int stmmac_rx_buf2_len(struct stmmac_priv *priv,
++@@ -4598,7 +4598,7 @@ static unsigned int stmmac_rx_buf2_len(s
++ 
++ 	/* Not last descriptor */
++ 	if (status & rx_not_ls)
++-		return priv->dma_buf_sz;
+++		return priv->dma_conf.dma_buf_sz;
++ 
++ 	plen = stmmac_get_rx_frame_len(priv, p, coe);
++ 
++@@ -4609,7 +4609,7 @@ static unsigned int stmmac_rx_buf2_len(s
++ static int stmmac_xdp_xmit_xdpf(struct stmmac_priv *priv, int queue,
++ 				struct xdp_frame *xdpf, bool dma_map)
++ {
++-	struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
++ 	unsigned int entry = tx_q->cur_tx;
++ 	struct dma_desc *tx_desc;
++ 	dma_addr_t dma_addr;
++@@ -4672,7 +4672,7 @@ static int stmmac_xdp_xmit_xdpf(struct s
++ 
++ 	stmmac_enable_dma_transmission(priv, priv->ioaddr);
++ 
++-	entry = STMMAC_GET_ENTRY(entry, priv->dma_tx_size);
+++	entry = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_tx_size);
++ 	tx_q->cur_tx = entry;
++ 
++ 	return STMMAC_XDP_TX;
++@@ -4846,7 +4846,7 @@ static void stmmac_dispatch_skb_zc(struc
 + 
-+ 	return qca8k_mdio_read(bus, phy, regnum);
-+ }
-+@@ -1006,6 +1211,7 @@ static int
-+ qca8k_phy_write(struct dsa_switch *ds, int port, int regnum, u16 data)
++ static bool stmmac_rx_refill_zc(struct stmmac_priv *priv, u32 queue, u32 budget)
 + {
-+ 	struct qca8k_priv *priv = ds->priv;
-++	int ret;
++-	struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 	unsigned int entry = rx_q->dirty_rx;
++ 	struct dma_desc *rx_desc = NULL;
++ 	bool ret = true;
++@@ -4889,7 +4889,7 @@ static bool stmmac_rx_refill_zc(struct s
++ 		dma_wmb();
++ 		stmmac_set_rx_owner(priv, rx_desc, use_rx_wd);
++ 
++-		entry = STMMAC_GET_ENTRY(entry, priv->dma_rx_size);
+++		entry = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_rx_size);
++ 	}
 + 
-+ 	/* Check if the legacy mapping should be used and the
-+ 	 * port is not correctly mapped to the right PHY in the
-+@@ -1014,6 +1220,11 @@ qca8k_phy_write(struct dsa_switch *ds, i
-+ 	if (priv->legacy_phy_port_mapping)
-+ 		port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
++ 	if (rx_desc) {
++@@ -4904,7 +4904,7 @@ static bool stmmac_rx_refill_zc(struct s
 + 
-++	/* Use mdio Ethernet when available, fallback to legacy one on error */
-++	ret = qca8k_phy_eth_command(priv, false, port, regnum, 0);
-++	if (!ret)
-++		return ret;
-++
-+ 	return qca8k_mdio_write(priv->bus, port, regnum, data);
-+ }
++ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue)
++ {
++-	struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 	unsigned int count = 0, error = 0, len = 0;
++ 	int dirty = stmmac_rx_dirty(priv, queue);
++ 	unsigned int next_entry = rx_q->cur_rx;
++@@ -4926,7 +4926,7 @@ static int stmmac_rx_zc(struct stmmac_pr
++ 			desc_size = sizeof(struct dma_desc);
++ 		}
 + 
-+@@ -1030,6 +1241,11 @@ qca8k_phy_read(struct dsa_switch *ds, in
-+ 	if (priv->legacy_phy_port_mapping)
-+ 		port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
++-		stmmac_display_ring(priv, rx_head, priv->dma_rx_size, true,
+++		stmmac_display_ring(priv, rx_head, priv->dma_conf.dma_rx_size, true,
++ 				    rx_q->dma_rx_phy, desc_size);
++ 	}
++ 	while (count < limit) {
++@@ -4973,7 +4973,7 @@ read_again:
 + 
-++	/* Use mdio Ethernet when available, fallback to legacy one on error */
-++	ret = qca8k_phy_eth_command(priv, true, port, regnum, 0);
-++	if (ret >= 0)
-++		return ret;
-++
-+ 	ret = qca8k_mdio_read(priv->bus, port, regnum);
++ 		/* Prefetch the next RX descriptor */
++ 		rx_q->cur_rx = STMMAC_GET_ENTRY(rx_q->cur_rx,
++-						priv->dma_rx_size);
+++						priv->dma_conf.dma_rx_size);
++ 		next_entry = rx_q->cur_rx;
 + 
-+ 	if (ret < 0)
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -14,6 +14,7 @@
-+ #include <linux/dsa/tag_qca.h>
++ 		if (priv->extend_desc)
++@@ -5094,7 +5094,7 @@ read_again:
++  */
++ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
++ {
++-	struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 	struct stmmac_channel *ch = &priv->channel[queue];
++ 	unsigned int count = 0, error = 0, len = 0;
++ 	int status = 0, coe = priv->hw->rx_csum;
++@@ -5107,7 +5107,7 @@ static int stmmac_rx(struct stmmac_priv
++ 	int buf_sz;
++ 
++ 	dma_dir = page_pool_get_dma_dir(rx_q->page_pool);
++-	buf_sz = DIV_ROUND_UP(priv->dma_buf_sz, PAGE_SIZE) * PAGE_SIZE;
+++	buf_sz = DIV_ROUND_UP(priv->dma_conf.dma_buf_sz, PAGE_SIZE) * PAGE_SIZE;
++ 
++ 	if (netif_msg_rx_status(priv)) {
++ 		void *rx_head;
++@@ -5121,7 +5121,7 @@ static int stmmac_rx(struct stmmac_priv
++ 			desc_size = sizeof(struct dma_desc);
++ 		}
 + 
-+ #define QCA8K_ETHERNET_MDIO_PRIORITY			7
-++#define QCA8K_ETHERNET_PHY_PRIORITY			6
-+ #define QCA8K_ETHERNET_TIMEOUT				100
++-		stmmac_display_ring(priv, rx_head, priv->dma_rx_size, true,
+++		stmmac_display_ring(priv, rx_head, priv->dma_conf.dma_rx_size, true,
++ 				    rx_q->dma_rx_phy, desc_size);
++ 	}
++ 	while (count < limit) {
++@@ -5165,7 +5165,7 @@ read_again:
++ 			break;
 + 
-+ #define QCA8K_NUM_PORTS					7
-diff --git a/target/linux/generic/backport-5.15/766-13-net-dsa-qca8k-move-page-cache-to-driver-priv.patch b/target/linux/generic/backport-5.15/766-13-net-dsa-qca8k-move-page-cache-to-driver-priv.patch
-new file mode 100644
-index 0000000000..dc8160966c
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/766-13-net-dsa-qca8k-move-page-cache-to-driver-priv.patch
-@@ -0,0 +1,208 @@
-+From 4264350acb75430d5021a1d7de56a33faf69a097 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Wed, 2 Feb 2022 01:03:32 +0100
-+Subject: [PATCH 13/16] net: dsa: qca8k: move page cache to driver priv
-+
-+There can be multiple qca8k switch on the same system. Move the static
-+qca8k_current_page to qca8k_priv and make it specific for each switch.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 42 ++++++++++++++++++++---------------------
-+ drivers/net/dsa/qca8k.h |  9 +++++++++
-+ 2 files changed, 29 insertions(+), 22 deletions(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -75,12 +75,6 @@ static const struct qca8k_mib_desc ar832
-+ 	MIB_DESC(1, 0xac, "TXUnicast"),
-+ };
++ 		rx_q->cur_rx = STMMAC_GET_ENTRY(rx_q->cur_rx,
++-						priv->dma_rx_size);
+++						priv->dma_conf.dma_rx_size);
++ 		next_entry = rx_q->cur_rx;
++ 
++ 		if (priv->extend_desc)
++@@ -5299,7 +5299,7 @@ read_again:
++ 						buf1_len, dma_dir);
++ 			skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
++ 					buf->page, buf->page_offset, buf1_len,
++-					priv->dma_buf_sz);
+++					priv->dma_conf.dma_buf_sz);
++ 
++ 			/* Data payload appended into SKB */
++ 			page_pool_release_page(rx_q->page_pool, buf->page);
++@@ -5311,7 +5311,7 @@ read_again:
++ 						buf2_len, dma_dir);
++ 			skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
++ 					buf->sec_page, 0, buf2_len,
++-					priv->dma_buf_sz);
+++					priv->dma_conf.dma_buf_sz);
++ 
++ 			/* Data payload appended into SKB */
++ 			page_pool_release_page(rx_q->page_pool, buf->sec_page);
++@@ -5753,11 +5753,13 @@ static irqreturn_t stmmac_safety_interru
++ static irqreturn_t stmmac_msi_intr_tx(int irq, void *data)
++ {
++ 	struct stmmac_tx_queue *tx_q = (struct stmmac_tx_queue *)data;
+++	struct stmmac_dma_conf *dma_conf;
++ 	int chan = tx_q->queue_index;
++ 	struct stmmac_priv *priv;
++ 	int status;
++ 
++-	priv = container_of(tx_q, struct stmmac_priv, tx_queue[chan]);
+++	dma_conf = container_of(tx_q, struct stmmac_dma_conf, tx_queue[chan]);
+++	priv = container_of(dma_conf, struct stmmac_priv, dma_conf);
++ 
++ 	if (unlikely(!data)) {
++ 		netdev_err(priv->dev, "%s: invalid dev pointer\n", __func__);
++@@ -5797,10 +5799,12 @@ static irqreturn_t stmmac_msi_intr_tx(in
++ static irqreturn_t stmmac_msi_intr_rx(int irq, void *data)
++ {
++ 	struct stmmac_rx_queue *rx_q = (struct stmmac_rx_queue *)data;
+++	struct stmmac_dma_conf *dma_conf;
++ 	int chan = rx_q->queue_index;
++ 	struct stmmac_priv *priv;
++ 
++-	priv = container_of(rx_q, struct stmmac_priv, rx_queue[chan]);
+++	dma_conf = container_of(rx_q, struct stmmac_dma_conf, rx_queue[chan]);
+++	priv = container_of(dma_conf, struct stmmac_priv, dma_conf);
++ 
++ 	if (unlikely(!data)) {
++ 		netdev_err(priv->dev, "%s: invalid dev pointer\n", __func__);
++@@ -5831,10 +5835,10 @@ static void stmmac_poll_controller(struc
++ 
++ 	if (priv->plat->multi_msi_en) {
++ 		for (i = 0; i < priv->plat->rx_queues_to_use; i++)
++-			stmmac_msi_intr_rx(0, &priv->rx_queue[i]);
+++			stmmac_msi_intr_rx(0, &priv->dma_conf.rx_queue[i]);
++ 
++ 		for (i = 0; i < priv->plat->tx_queues_to_use; i++)
++-			stmmac_msi_intr_tx(0, &priv->tx_queue[i]);
+++			stmmac_msi_intr_tx(0, &priv->dma_conf.tx_queue[i]);
++ 	} else {
++ 		disable_irq(dev->irq);
++ 		stmmac_interrupt(dev->irq, dev);
++@@ -6015,34 +6019,34 @@ static int stmmac_rings_status_show(stru
++ 		return 0;
 + 
-+-/* The 32bit switch registers are accessed indirectly. To achieve this we need
-+- * to set the page of the register. Track the last page that was set to reduce
-+- * mdio writes
-+- */
-+-static u16 qca8k_current_page = 0xffff;
-+-
-+ static void
-+ qca8k_split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
++ 	for (queue = 0; queue < rx_count; queue++) {
++-		struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++		struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 
++ 		seq_printf(seq, "RX Queue %d:\n", queue);
++ 
++ 		if (priv->extend_desc) {
++ 			seq_printf(seq, "Extended descriptor ring:\n");
++ 			sysfs_display_ring((void *)rx_q->dma_erx,
++-					   priv->dma_rx_size, 1, seq, rx_q->dma_rx_phy);
+++					   priv->dma_conf.dma_rx_size, 1, seq, rx_q->dma_rx_phy);
++ 		} else {
++ 			seq_printf(seq, "Descriptor ring:\n");
++ 			sysfs_display_ring((void *)rx_q->dma_rx,
++-					   priv->dma_rx_size, 0, seq, rx_q->dma_rx_phy);
+++					   priv->dma_conf.dma_rx_size, 0, seq, rx_q->dma_rx_phy);
++ 		}
++ 	}
++ 
++ 	for (queue = 0; queue < tx_count; queue++) {
++-		struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
+++		struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
++ 
++ 		seq_printf(seq, "TX Queue %d:\n", queue);
++ 
++ 		if (priv->extend_desc) {
++ 			seq_printf(seq, "Extended descriptor ring:\n");
++ 			sysfs_display_ring((void *)tx_q->dma_etx,
++-					   priv->dma_tx_size, 1, seq, tx_q->dma_tx_phy);
+++					   priv->dma_conf.dma_tx_size, 1, seq, tx_q->dma_tx_phy);
++ 		} else if (!(tx_q->tbs & STMMAC_TBS_AVAIL)) {
++ 			seq_printf(seq, "Descriptor ring:\n");
++ 			sysfs_display_ring((void *)tx_q->dma_tx,
++-					   priv->dma_tx_size, 0, seq, tx_q->dma_tx_phy);
+++					   priv->dma_conf.dma_tx_size, 0, seq, tx_q->dma_tx_phy);
++ 		}
++ 	}
++ 
++@@ -6383,7 +6387,7 @@ void stmmac_disable_rx_queue(struct stmm
++ 
++ void stmmac_enable_rx_queue(struct stmmac_priv *priv, u32 queue)
 + {
-+@@ -134,11 +128,13 @@ qca8k_mii_write32(struct mii_bus *bus, i
-+ }
++-	struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 	struct stmmac_channel *ch = &priv->channel[queue];
++ 	unsigned long flags;
++ 	u32 buf_size;
++@@ -6420,7 +6424,7 @@ void stmmac_enable_rx_queue(struct stmma
++ 				      rx_q->queue_index);
++ 	} else {
++ 		stmmac_set_dma_bfsize(priv, priv->ioaddr,
++-				      priv->dma_buf_sz,
+++				      priv->dma_conf.dma_buf_sz,
++ 				      rx_q->queue_index);
++ 	}
 + 
-+ static int
-+-qca8k_set_page(struct mii_bus *bus, u16 page)
-++qca8k_set_page(struct qca8k_priv *priv, u16 page)
++@@ -6446,7 +6450,7 @@ void stmmac_disable_tx_queue(struct stmm
++ 
++ void stmmac_enable_tx_queue(struct stmmac_priv *priv, u32 queue)
 + {
-++	u16 *cached_page = &priv->mdio_cache.page;
-++	struct mii_bus *bus = priv->bus;
++-	struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
++ 	struct stmmac_channel *ch = &priv->channel[queue];
++ 	unsigned long flags;
 + 	int ret;
++@@ -6496,7 +6500,7 @@ void stmmac_xdp_release(struct net_devic
++ 	stmmac_disable_all_queues(priv);
++ 
++ 	for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
++-		hrtimer_cancel(&priv->tx_queue[chan].txtimer);
+++		hrtimer_cancel(&priv->dma_conf.tx_queue[chan].txtimer);
++ 
++ 	/* Free the IRQ lines */
++ 	stmmac_free_irq(dev, REQ_IRQ_ERR_ALL, 0);
++@@ -6555,7 +6559,7 @@ int stmmac_xdp_open(struct net_device *d
++ 
++ 	/* DMA RX Channel Configuration */
++ 	for (chan = 0; chan < rx_cnt; chan++) {
++-		rx_q = &priv->rx_queue[chan];
+++		rx_q = &priv->dma_conf.rx_queue[chan];
++ 
++ 		stmmac_init_rx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
++ 				    rx_q->dma_rx_phy, chan);
++@@ -6573,7 +6577,7 @@ int stmmac_xdp_open(struct net_device *d
++ 					      rx_q->queue_index);
++ 		} else {
++ 			stmmac_set_dma_bfsize(priv, priv->ioaddr,
++-					      priv->dma_buf_sz,
+++					      priv->dma_conf.dma_buf_sz,
++ 					      rx_q->queue_index);
++ 		}
 + 
-+-	if (page == qca8k_current_page)
-++	if (page == *cached_page)
-+ 		return 0;
++@@ -6582,7 +6586,7 @@ int stmmac_xdp_open(struct net_device *d
 + 
-+ 	ret = bus->write(bus, 0x18, 0, page);
-+@@ -148,7 +144,7 @@ qca8k_set_page(struct mii_bus *bus, u16
-+ 		return ret;
-+ 	}
++ 	/* DMA TX Channel Configuration */
++ 	for (chan = 0; chan < tx_cnt; chan++) {
++-		tx_q = &priv->tx_queue[chan];
+++		tx_q = &priv->dma_conf.tx_queue[chan];
 + 
-+-	qca8k_current_page = page;
-++	*cached_page = page;
-+ 	usleep_range(1000, 2000);
-+ 	return 0;
-+ }
-+@@ -374,7 +370,7 @@ qca8k_regmap_read(void *ctx, uint32_t re
++ 		stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
++ 				    tx_q->dma_tx_phy, chan);
++@@ -6615,7 +6619,7 @@ int stmmac_xdp_open(struct net_device *d
 + 
-+ 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ irq_error:
++ 	for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
++-		hrtimer_cancel(&priv->tx_queue[chan].txtimer);
+++		hrtimer_cancel(&priv->dma_conf.tx_queue[chan].txtimer);
 + 
-+-	ret = qca8k_set_page(bus, page);
-++	ret = qca8k_set_page(priv, page);
-+ 	if (ret < 0)
-+ 		goto exit;
++ 	stmmac_hw_teardown(dev);
++ init_error:
++@@ -6642,8 +6646,8 @@ int stmmac_xsk_wakeup(struct net_device
++ 	    queue >= priv->plat->tx_queues_to_use)
++ 		return -EINVAL;
 + 
-+@@ -400,7 +396,7 @@ qca8k_regmap_write(void *ctx, uint32_t r
++-	rx_q = &priv->rx_queue[queue];
++-	tx_q = &priv->tx_queue[queue];
+++	rx_q = &priv->dma_conf.rx_queue[queue];
+++	tx_q = &priv->dma_conf.tx_queue[queue];
++ 	ch = &priv->channel[queue];
 + 
-+ 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ 	if (!rx_q->xsk_pool && !tx_q->xsk_pool)
++@@ -6899,8 +6903,8 @@ int stmmac_reinit_ringparam(struct net_d
++ 	if (netif_running(dev))
++ 		stmmac_release(dev);
 + 
-+-	ret = qca8k_set_page(bus, page);
-++	ret = qca8k_set_page(priv, page);
-+ 	if (ret < 0)
-+ 		goto exit;
++-	priv->dma_rx_size = rx_size;
++-	priv->dma_tx_size = tx_size;
+++	priv->dma_conf.dma_rx_size = rx_size;
+++	priv->dma_conf.dma_tx_size = tx_size;
 + 
-+@@ -427,7 +423,7 @@ qca8k_regmap_update_bits(void *ctx, uint
++ 	if (netif_running(dev))
++ 		ret = stmmac_open(dev);
++@@ -7336,7 +7340,7 @@ int stmmac_suspend(struct device *dev)
++ 	stmmac_disable_all_queues(priv);
 + 
-+ 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ 	for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
++-		hrtimer_cancel(&priv->tx_queue[chan].txtimer);
+++		hrtimer_cancel(&priv->dma_conf.tx_queue[chan].txtimer);
 + 
-+-	ret = qca8k_set_page(bus, page);
-++	ret = qca8k_set_page(priv, page);
-+ 	if (ret < 0)
-+ 		goto exit;
++ 	if (priv->eee_enabled) {
++ 		priv->tx_path_in_lpi_mode = false;
++@@ -7387,7 +7391,7 @@ EXPORT_SYMBOL_GPL(stmmac_suspend);
 + 
-+@@ -1098,8 +1094,9 @@ qca8k_mdio_busy_wait(struct mii_bus *bus
-+ }
++ static void stmmac_reset_rx_queue(struct stmmac_priv *priv, u32 queue)
++ {
++-	struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
 + 
-+ static int
-+-qca8k_mdio_write(struct mii_bus *bus, int phy, int regnum, u16 data)
-++qca8k_mdio_write(struct qca8k_priv *priv, int phy, int regnum, u16 data)
++ 	rx_q->cur_rx = 0;
++ 	rx_q->dirty_rx = 0;
++@@ -7395,7 +7399,7 @@ static void stmmac_reset_rx_queue(struct
++ 
++ static void stmmac_reset_tx_queue(struct stmmac_priv *priv, u32 queue)
 + {
-++	struct mii_bus *bus = priv->bus;
-+ 	u16 r1, r2, page;
-+ 	u32 val;
-+ 	int ret;
-+@@ -1116,7 +1113,7 @@ qca8k_mdio_write(struct mii_bus *bus, in
++-	struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
++ 
++ 	tx_q->cur_tx = 0;
++ 	tx_q->dirty_tx = 0;
++--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c
+++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c
++@@ -795,8 +795,8 @@ static int stmmac_test_flowctrl(struct s
++ 		struct stmmac_channel *ch = &priv->channel[i];
++ 		u32 tail;
++ 
++-		tail = priv->rx_queue[i].dma_rx_phy +
++-			(priv->dma_rx_size * sizeof(struct dma_desc));
+++		tail = priv->dma_conf.rx_queue[i].dma_rx_phy +
+++			(priv->dma_conf.dma_rx_size * sizeof(struct dma_desc));
++ 
++ 		stmmac_set_rx_tail_ptr(priv, priv->ioaddr, tail, i);
++ 		stmmac_start_rx(priv, priv->ioaddr, i);
++@@ -1680,7 +1680,7 @@ cleanup:
++ static int __stmmac_test_jumbo(struct stmmac_priv *priv, u16 queue)
++ {
++ 	struct stmmac_packet_attrs attr = { };
++-	int size = priv->dma_buf_sz;
+++	int size = priv->dma_conf.dma_buf_sz;
++ 
++ 	attr.dst = priv->dev->dev_addr;
++ 	attr.max_size = size - ETH_FCS_LEN;
++@@ -1763,7 +1763,7 @@ static int stmmac_test_tbs(struct stmmac
++ 
++ 	/* Find first TBS enabled Queue, if any */
++ 	for (i = 0; i < priv->plat->tx_queues_to_use; i++)
++-		if (priv->tx_queue[i].tbs & STMMAC_TBS_AVAIL)
+++		if (priv->dma_conf.tx_queue[i].tbs & STMMAC_TBS_AVAIL)
++ 			break;
 + 
-+ 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ 	if (i >= priv->plat->tx_queues_to_use)
++--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
+++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
++@@ -970,13 +970,13 @@ static int tc_setup_etf(struct stmmac_pr
++ 		return -EOPNOTSUPP;
++ 	if (qopt->queue >= priv->plat->tx_queues_to_use)
++ 		return -EINVAL;
++-	if (!(priv->tx_queue[qopt->queue].tbs & STMMAC_TBS_AVAIL))
+++	if (!(priv->dma_conf.tx_queue[qopt->queue].tbs & STMMAC_TBS_AVAIL))
++ 		return -EINVAL;
 + 
-+-	ret = qca8k_set_page(bus, page);
-++	ret = qca8k_set_page(priv, page);
-+ 	if (ret)
-+ 		goto exit;
++ 	if (qopt->enable)
++-		priv->tx_queue[qopt->queue].tbs |= STMMAC_TBS_EN;
+++		priv->dma_conf.tx_queue[qopt->queue].tbs |= STMMAC_TBS_EN;
++ 	else
++-		priv->tx_queue[qopt->queue].tbs &= ~STMMAC_TBS_EN;
+++		priv->dma_conf.tx_queue[qopt->queue].tbs &= ~STMMAC_TBS_EN;
 + 
-+@@ -1135,8 +1132,9 @@ exit:
++ 	netdev_info(priv->dev, "%s ETF for Queue %d\n",
++ 		    qopt->enable ? "enabled" : "disabled", qopt->queue);
+diff --git a/target/linux/generic/backport-5.15/775-v6.0-04-net-ethernet-stmicro-stmmac-generate-stmmac-dma-conf.patch b/target/linux/generic/backport-5.15/775-v6.0-04-net-ethernet-stmicro-stmmac-generate-stmmac-dma-conf.patch
+new file mode 100644
+index 0000000000..4c41d3c743
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/775-v6.0-04-net-ethernet-stmicro-stmmac-generate-stmmac-dma-conf.patch
+@@ -0,0 +1,1161 @@
++From ba39b344e9240a4a5fd4ab8178200b85cd1809da Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Sat, 23 Jul 2022 16:29:32 +0200
++Subject: [PATCH 4/5] net: ethernet: stmicro: stmmac: generate stmmac dma conf
++ before open
++
++Rework the driver to generate the stmmac dma_conf before stmmac_open.
++This permits a function to first check if it's possible to allocate a
++new dma_config and then pass it directly to __stmmac_open and "open" the
++interface with the new configuration.
++
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ .../net/ethernet/stmicro/stmmac/stmmac_main.c | 462 +++++++++++-------
++ 1 file changed, 289 insertions(+), 173 deletions(-)
++
++--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
++@@ -1285,7 +1285,8 @@ static int stmmac_phy_setup(struct stmma
++ 	return 0;
 + }
 + 
-+ static int
-+-qca8k_mdio_read(struct mii_bus *bus, int phy, int regnum)
-++qca8k_mdio_read(struct qca8k_priv *priv, int phy, int regnum)
++-static void stmmac_display_rx_rings(struct stmmac_priv *priv)
+++static void stmmac_display_rx_rings(struct stmmac_priv *priv,
+++				    struct stmmac_dma_conf *dma_conf)
 + {
-++	struct mii_bus *bus = priv->bus;
-+ 	u16 r1, r2, page;
-+ 	u32 val;
-+ 	int ret;
-+@@ -1152,7 +1150,7 @@ qca8k_mdio_read(struct mii_bus *bus, int
++ 	u32 rx_cnt = priv->plat->rx_queues_to_use;
++ 	unsigned int desc_size;
++@@ -1294,7 +1295,7 @@ static void stmmac_display_rx_rings(stru
 + 
-+ 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ 	/* Display RX rings */
++ 	for (queue = 0; queue < rx_cnt; queue++) {
++-		struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+++		struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
 + 
-+-	ret = qca8k_set_page(bus, page);
-++	ret = qca8k_set_page(priv, page);
-+ 	if (ret)
-+ 		goto exit;
++ 		pr_info("\tRX Queue %u rings\n", queue);
 + 
-+@@ -1181,7 +1179,6 @@ static int
-+ qca8k_internal_mdio_write(struct mii_bus *slave_bus, int phy, int regnum, u16 data)
++@@ -1307,12 +1308,13 @@ static void stmmac_display_rx_rings(stru
++ 		}
++ 
++ 		/* Display RX ring */
++-		stmmac_display_ring(priv, head_rx, priv->dma_conf.dma_rx_size, true,
+++		stmmac_display_ring(priv, head_rx, dma_conf->dma_rx_size, true,
++ 				    rx_q->dma_rx_phy, desc_size);
++ 	}
++ }
++ 
++-static void stmmac_display_tx_rings(struct stmmac_priv *priv)
+++static void stmmac_display_tx_rings(struct stmmac_priv *priv,
+++				    struct stmmac_dma_conf *dma_conf)
 + {
-+ 	struct qca8k_priv *priv = slave_bus->priv;
-+-	struct mii_bus *bus = priv->bus;
-+ 	int ret;
++ 	u32 tx_cnt = priv->plat->tx_queues_to_use;
++ 	unsigned int desc_size;
++@@ -1321,7 +1323,7 @@ static void stmmac_display_tx_rings(stru
 + 
-+ 	/* Use mdio Ethernet when available, fallback to legacy one on error */
-+@@ -1189,14 +1186,13 @@ qca8k_internal_mdio_write(struct mii_bus
-+ 	if (!ret)
-+ 		return 0;
++ 	/* Display TX rings */
++ 	for (queue = 0; queue < tx_cnt; queue++) {
++-		struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+++		struct stmmac_tx_queue *tx_q = &dma_conf->tx_queue[queue];
 + 
-+-	return qca8k_mdio_write(bus, phy, regnum, data);
-++	return qca8k_mdio_write(priv, phy, regnum, data);
++ 		pr_info("\tTX Queue %d rings\n", queue);
++ 
++@@ -1336,18 +1338,19 @@ static void stmmac_display_tx_rings(stru
++ 			desc_size = sizeof(struct dma_desc);
++ 		}
++ 
++-		stmmac_display_ring(priv, head_tx, priv->dma_conf.dma_tx_size, false,
+++		stmmac_display_ring(priv, head_tx, dma_conf->dma_tx_size, false,
++ 				    tx_q->dma_tx_phy, desc_size);
++ 	}
 + }
 + 
-+ static int
-+ qca8k_internal_mdio_read(struct mii_bus *slave_bus, int phy, int regnum)
++-static void stmmac_display_rings(struct stmmac_priv *priv)
+++static void stmmac_display_rings(struct stmmac_priv *priv,
+++				 struct stmmac_dma_conf *dma_conf)
 + {
-+ 	struct qca8k_priv *priv = slave_bus->priv;
-+-	struct mii_bus *bus = priv->bus;
-+ 	int ret;
++ 	/* Display RX ring */
++-	stmmac_display_rx_rings(priv);
+++	stmmac_display_rx_rings(priv, dma_conf);
 + 
-+ 	/* Use mdio Ethernet when available, fallback to legacy one on error */
-+@@ -1204,7 +1200,7 @@ qca8k_internal_mdio_read(struct mii_bus
-+ 	if (ret >= 0)
-+ 		return ret;
++ 	/* Display TX ring */
++-	stmmac_display_tx_rings(priv);
+++	stmmac_display_tx_rings(priv, dma_conf);
++ }
 + 
-+-	return qca8k_mdio_read(bus, phy, regnum);
-++	return qca8k_mdio_read(priv, phy, regnum);
++ static int stmmac_set_bfsize(int mtu, int bufsize)
++@@ -1371,44 +1374,50 @@ static int stmmac_set_bfsize(int mtu, in
++ /**
++  * stmmac_clear_rx_descriptors - clear RX descriptors
++  * @priv: driver private structure
+++ * @dma_conf: structure to take the dma data
++  * @queue: RX queue index
++  * Description: this function is called to clear the RX descriptors
++  * in case of both basic and extended descriptors are used.
++  */
++-static void stmmac_clear_rx_descriptors(struct stmmac_priv *priv, u32 queue)
+++static void stmmac_clear_rx_descriptors(struct stmmac_priv *priv,
+++					struct stmmac_dma_conf *dma_conf,
+++					u32 queue)
++ {
++-	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
++ 	int i;
++ 
++ 	/* Clear the RX descriptors */
++-	for (i = 0; i < priv->dma_conf.dma_rx_size; i++)
+++	for (i = 0; i < dma_conf->dma_rx_size; i++)
++ 		if (priv->extend_desc)
++ 			stmmac_init_rx_desc(priv, &rx_q->dma_erx[i].basic,
++ 					priv->use_riwt, priv->mode,
++-					(i == priv->dma_conf.dma_rx_size - 1),
++-					priv->dma_conf.dma_buf_sz);
+++					(i == dma_conf->dma_rx_size - 1),
+++					dma_conf->dma_buf_sz);
++ 		else
++ 			stmmac_init_rx_desc(priv, &rx_q->dma_rx[i],
++ 					priv->use_riwt, priv->mode,
++-					(i == priv->dma_conf.dma_rx_size - 1),
++-					priv->dma_conf.dma_buf_sz);
+++					(i == dma_conf->dma_rx_size - 1),
+++					dma_conf->dma_buf_sz);
 + }
 + 
-+ static int
-+@@ -1225,7 +1221,7 @@ qca8k_phy_write(struct dsa_switch *ds, i
-+ 	if (!ret)
-+ 		return ret;
++ /**
++  * stmmac_clear_tx_descriptors - clear tx descriptors
++  * @priv: driver private structure
+++ * @dma_conf: structure to take the dma data
++  * @queue: TX queue index.
++  * Description: this function is called to clear the TX descriptors
++  * in case of both basic and extended descriptors are used.
++  */
++-static void stmmac_clear_tx_descriptors(struct stmmac_priv *priv, u32 queue)
+++static void stmmac_clear_tx_descriptors(struct stmmac_priv *priv,
+++					struct stmmac_dma_conf *dma_conf,
+++					u32 queue)
++ {
++-	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &dma_conf->tx_queue[queue];
++ 	int i;
 + 
-+-	return qca8k_mdio_write(priv->bus, port, regnum, data);
-++	return qca8k_mdio_write(priv, port, regnum, data);
++ 	/* Clear the TX descriptors */
++-	for (i = 0; i < priv->dma_conf.dma_tx_size; i++) {
++-		int last = (i == (priv->dma_conf.dma_tx_size - 1));
+++	for (i = 0; i < dma_conf->dma_tx_size; i++) {
+++		int last = (i == (dma_conf->dma_tx_size - 1));
++ 		struct dma_desc *p;
++ 
++ 		if (priv->extend_desc)
++@@ -1425,10 +1434,12 @@ static void stmmac_clear_tx_descriptors(
++ /**
++  * stmmac_clear_descriptors - clear descriptors
++  * @priv: driver private structure
+++ * @dma_conf: structure to take the dma data
++  * Description: this function is called to clear the TX and RX descriptors
++  * in case of both basic and extended descriptors are used.
++  */
++-static void stmmac_clear_descriptors(struct stmmac_priv *priv)
+++static void stmmac_clear_descriptors(struct stmmac_priv *priv,
+++				     struct stmmac_dma_conf *dma_conf)
++ {
++ 	u32 rx_queue_cnt = priv->plat->rx_queues_to_use;
++ 	u32 tx_queue_cnt = priv->plat->tx_queues_to_use;
++@@ -1436,16 +1447,17 @@ static void stmmac_clear_descriptors(str
++ 
++ 	/* Clear the RX descriptors */
++ 	for (queue = 0; queue < rx_queue_cnt; queue++)
++-		stmmac_clear_rx_descriptors(priv, queue);
+++		stmmac_clear_rx_descriptors(priv, dma_conf, queue);
++ 
++ 	/* Clear the TX descriptors */
++ 	for (queue = 0; queue < tx_queue_cnt; queue++)
++-		stmmac_clear_tx_descriptors(priv, queue);
+++		stmmac_clear_tx_descriptors(priv, dma_conf, queue);
 + }
 + 
-+ static int
-+@@ -1246,7 +1242,7 @@ qca8k_phy_read(struct dsa_switch *ds, in
-+ 	if (ret >= 0)
-+ 		return ret;
++ /**
++  * stmmac_init_rx_buffers - init the RX descriptor buffer.
++  * @priv: driver private structure
+++ * @dma_conf: structure to take the dma data
++  * @p: descriptor pointer
++  * @i: descriptor index
++  * @flags: gfp flag
++@@ -1453,10 +1465,12 @@ static void stmmac_clear_descriptors(str
++  * Description: this function is called to allocate a receive buffer, perform
++  * the DMA mapping and init the descriptor.
++  */
++-static int stmmac_init_rx_buffers(struct stmmac_priv *priv, struct dma_desc *p,
+++static int stmmac_init_rx_buffers(struct stmmac_priv *priv,
+++				  struct stmmac_dma_conf *dma_conf,
+++				  struct dma_desc *p,
++ 				  int i, gfp_t flags, u32 queue)
++ {
++-	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
++ 	struct stmmac_rx_buffer *buf = &rx_q->buf_pool[i];
 + 
-+-	ret = qca8k_mdio_read(priv->bus, port, regnum);
-++	ret = qca8k_mdio_read(priv, port, regnum);
++ 	if (!buf->page) {
++@@ -1481,7 +1495,7 @@ static int stmmac_init_rx_buffers(struct
++ 	buf->addr = page_pool_get_dma_addr(buf->page) + buf->page_offset;
 + 
-+ 	if (ret < 0)
-+ 		return 0xffff;
-+@@ -3041,6 +3037,8 @@ qca8k_sw_probe(struct mdio_device *mdiod
-+ 		return PTR_ERR(priv->regmap);
-+ 	}
++ 	stmmac_set_desc_addr(priv, p, buf->addr);
++-	if (priv->dma_conf.dma_buf_sz == BUF_SIZE_16KiB)
+++	if (dma_conf->dma_buf_sz == BUF_SIZE_16KiB)
++ 		stmmac_init_desc3(priv, p);
 + 
-++	priv->mdio_cache.page = 0xffff;
-++
-+ 	/* Check the detected switch id */
-+ 	ret = qca8k_read_switch_id(priv);
-+ 	if (ret)
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -363,6 +363,14 @@ struct qca8k_ports_config {
-+ 	u8 rgmii_tx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
-+ };
++ 	return 0;
++@@ -1490,12 +1504,13 @@ static int stmmac_init_rx_buffers(struct
++ /**
++  * stmmac_free_rx_buffer - free RX dma buffers
++  * @priv: private structure
++- * @queue: RX queue index
+++ * @rx_q: RX queue
++  * @i: buffer index.
++  */
++-static void stmmac_free_rx_buffer(struct stmmac_priv *priv, u32 queue, int i)
+++static void stmmac_free_rx_buffer(struct stmmac_priv *priv,
+++				  struct stmmac_rx_queue *rx_q,
+++				  int i)
++ {
++-	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
++ 	struct stmmac_rx_buffer *buf = &rx_q->buf_pool[i];
 + 
-++struct qca8k_mdio_cache {
-++/* The 32bit switch registers are accessed indirectly. To achieve this we need
-++ * to set the page of the register. Track the last page that was set to reduce
-++ * mdio writes
-++ */
-++	u16 page;
-++};
-++
-+ struct qca8k_priv {
-+ 	u8 switch_id;
-+ 	u8 switch_revision;
-+@@ -383,6 +391,7 @@ struct qca8k_priv {
-+ 	struct net_device *mgmt_master; /* Track if mdio/mib Ethernet is available */
-+ 	struct qca8k_mgmt_eth_data mgmt_eth_data;
-+ 	struct qca8k_mib_eth_data mib_eth_data;
-++	struct qca8k_mdio_cache mdio_cache;
-+ };
++ 	if (buf->page)
++@@ -1510,12 +1525,15 @@ static void stmmac_free_rx_buffer(struct
++ /**
++  * stmmac_free_tx_buffer - free RX dma buffers
++  * @priv: private structure
+++ * @dma_conf: structure to take the dma data
++  * @queue: RX queue index
++  * @i: buffer index.
++  */
++-static void stmmac_free_tx_buffer(struct stmmac_priv *priv, u32 queue, int i)
+++static void stmmac_free_tx_buffer(struct stmmac_priv *priv,
+++				  struct stmmac_dma_conf *dma_conf,
+++				  u32 queue, int i)
++ {
++-	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &dma_conf->tx_queue[queue];
 + 
-+ struct qca8k_mib_desc {
-diff --git a/target/linux/generic/backport-5.15/766-14-net-dsa-qca8k-cache-lo-and-hi-for-mdio-write.patch b/target/linux/generic/backport-5.15/766-14-net-dsa-qca8k-cache-lo-and-hi-for-mdio-write.patch
-new file mode 100644
-index 0000000000..2d483730c9
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/766-14-net-dsa-qca8k-cache-lo-and-hi-for-mdio-write.patch
-@@ -0,0 +1,164 @@
-+From 2481d206fae7884cd07014fd1318e63af35e99eb Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Wed, 2 Feb 2022 01:03:33 +0100
-+Subject: [PATCH 14/16] net: dsa: qca8k: cache lo and hi for mdio write
-+
-+From Documentation, we can cache lo and hi the same way we do with the
-+page. This massively reduce the mdio write as 3/4 of the time as we only
-+require to write the lo or hi part for a mdio write.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 61 +++++++++++++++++++++++++++++++++--------
-+ drivers/net/dsa/qca8k.h |  5 ++++
-+ 2 files changed, 54 insertions(+), 12 deletions(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -89,6 +89,44 @@ qca8k_split_addr(u32 regaddr, u16 *r1, u
++ 	if (tx_q->tx_skbuff_dma[i].buf &&
++ 	    tx_q->tx_skbuff_dma[i].buf_type != STMMAC_TXBUF_T_XDP_TX) {
++@@ -1554,23 +1572,28 @@ static void stmmac_free_tx_buffer(struct
++ /**
++  * dma_free_rx_skbufs - free RX dma buffers
++  * @priv: private structure
+++ * @dma_conf: structure to take the dma data
++  * @queue: RX queue index
++  */
++-static void dma_free_rx_skbufs(struct stmmac_priv *priv, u32 queue)
+++static void dma_free_rx_skbufs(struct stmmac_priv *priv,
+++			       struct stmmac_dma_conf *dma_conf,
+++			       u32 queue)
++ {
+++	struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
++ 	int i;
++ 
++-	for (i = 0; i < priv->dma_conf.dma_rx_size; i++)
++-		stmmac_free_rx_buffer(priv, queue, i);
+++	for (i = 0; i < dma_conf->dma_rx_size; i++)
+++		stmmac_free_rx_buffer(priv, rx_q, i);
 + }
 + 
-+ static int
-++qca8k_set_lo(struct qca8k_priv *priv, int phy_id, u32 regnum, u16 lo)
-++{
-++	u16 *cached_lo = &priv->mdio_cache.lo;
-++	struct mii_bus *bus = priv->bus;
-++	int ret;
-++
-++	if (lo == *cached_lo)
-++		return 0;
-++
-++	ret = bus->write(bus, phy_id, regnum, lo);
-++	if (ret < 0)
-++		dev_err_ratelimited(&bus->dev,
-++				    "failed to write qca8k 32bit lo register\n");
-++
-++	*cached_lo = lo;
-++	return 0;
-++}
-++
-++static int
-++qca8k_set_hi(struct qca8k_priv *priv, int phy_id, u32 regnum, u16 hi)
-++{
-++	u16 *cached_hi = &priv->mdio_cache.hi;
-++	struct mii_bus *bus = priv->bus;
-++	int ret;
-++
-++	if (hi == *cached_hi)
-++		return 0;
-++
-++	ret = bus->write(bus, phy_id, regnum, hi);
-++	if (ret < 0)
-++		dev_err_ratelimited(&bus->dev,
-++				    "failed to write qca8k 32bit hi register\n");
-++
-++	*cached_hi = hi;
-++	return 0;
-++}
-++
-++static int
-+ qca8k_mii_read32(struct mii_bus *bus, int phy_id, u32 regnum, u32 *val)
++-static int stmmac_alloc_rx_buffers(struct stmmac_priv *priv, u32 queue,
++-				   gfp_t flags)
+++static int stmmac_alloc_rx_buffers(struct stmmac_priv *priv,
+++				   struct stmmac_dma_conf *dma_conf,
+++				   u32 queue, gfp_t flags)
 + {
-+ 	int ret;
-+@@ -111,7 +149,7 @@ qca8k_mii_read32(struct mii_bus *bus, in
-+ }
++-	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
++ 	int i;
 + 
-+ static void
-+-qca8k_mii_write32(struct mii_bus *bus, int phy_id, u32 regnum, u32 val)
-++qca8k_mii_write32(struct qca8k_priv *priv, int phy_id, u32 regnum, u32 val)
++-	for (i = 0; i < priv->dma_conf.dma_rx_size; i++) {
+++	for (i = 0; i < dma_conf->dma_rx_size; i++) {
++ 		struct dma_desc *p;
++ 		int ret;
++ 
++@@ -1579,7 +1602,7 @@ static int stmmac_alloc_rx_buffers(struc
++ 		else
++ 			p = rx_q->dma_rx + i;
++ 
++-		ret = stmmac_init_rx_buffers(priv, p, i, flags,
+++		ret = stmmac_init_rx_buffers(priv, dma_conf, p, i, flags,
++ 					     queue);
++ 		if (ret)
++ 			return ret;
++@@ -1593,14 +1616,17 @@ static int stmmac_alloc_rx_buffers(struc
++ /**
++  * dma_free_rx_xskbufs - free RX dma buffers from XSK pool
++  * @priv: private structure
+++ * @dma_conf: structure to take the dma data
++  * @queue: RX queue index
++  */
++-static void dma_free_rx_xskbufs(struct stmmac_priv *priv, u32 queue)
+++static void dma_free_rx_xskbufs(struct stmmac_priv *priv,
+++				struct stmmac_dma_conf *dma_conf,
+++				u32 queue)
 + {
-+ 	u16 lo, hi;
-+ 	int ret;
-+@@ -119,12 +157,9 @@ qca8k_mii_write32(struct mii_bus *bus, i
-+ 	lo = val & 0xffff;
-+ 	hi = (u16)(val >> 16);
++-	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
++ 	int i;
 + 
-+-	ret = bus->write(bus, phy_id, regnum, lo);
-++	ret = qca8k_set_lo(priv, phy_id, regnum, lo);
-+ 	if (ret >= 0)
-+-		ret = bus->write(bus, phy_id, regnum + 1, hi);
-+-	if (ret < 0)
-+-		dev_err_ratelimited(&bus->dev,
-+-				    "failed to write qca8k 32bit register\n");
-++		ret = qca8k_set_hi(priv, phy_id, regnum + 1, hi);
++-	for (i = 0; i < priv->dma_conf.dma_rx_size; i++) {
+++	for (i = 0; i < dma_conf->dma_rx_size; i++) {
++ 		struct stmmac_rx_buffer *buf = &rx_q->buf_pool[i];
++ 
++ 		if (!buf->xdp)
++@@ -1611,12 +1637,14 @@ static void dma_free_rx_xskbufs(struct s
++ 	}
 + }
 + 
-+ static int
-+@@ -400,7 +435,7 @@ qca8k_regmap_write(void *ctx, uint32_t r
-+ 	if (ret < 0)
-+ 		goto exit;
++-static int stmmac_alloc_rx_buffers_zc(struct stmmac_priv *priv, u32 queue)
+++static int stmmac_alloc_rx_buffers_zc(struct stmmac_priv *priv,
+++				      struct stmmac_dma_conf *dma_conf,
+++				      u32 queue)
++ {
++-	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
++ 	int i;
 + 
-+-	qca8k_mii_write32(bus, 0x10 | r2, r1, val);
-++	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
++-	for (i = 0; i < priv->dma_conf.dma_rx_size; i++) {
+++	for (i = 0; i < dma_conf->dma_rx_size; i++) {
++ 		struct stmmac_rx_buffer *buf;
++ 		dma_addr_t dma_addr;
++ 		struct dma_desc *p;
++@@ -1651,22 +1679,25 @@ static struct xsk_buff_pool *stmmac_get_
++ /**
++  * __init_dma_rx_desc_rings - init the RX descriptor ring (per queue)
++  * @priv: driver private structure
+++ * @dma_conf: structure to take the dma data
++  * @queue: RX queue index
++  * @flags: gfp flag.
++  * Description: this function initializes the DMA RX descriptors
++  * and allocates the socket buffers. It supports the chained and ring
++  * modes.
++  */
++-static int __init_dma_rx_desc_rings(struct stmmac_priv *priv, u32 queue, gfp_t flags)
+++static int __init_dma_rx_desc_rings(struct stmmac_priv *priv,
+++				    struct stmmac_dma_conf *dma_conf,
+++				    u32 queue, gfp_t flags)
++ {
++-	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
++ 	int ret;
 + 
-+ exit:
-+ 	mutex_unlock(&bus->mdio_lock);
-+@@ -433,7 +468,7 @@ qca8k_regmap_update_bits(void *ctx, uint
++ 	netif_dbg(priv, probe, priv->dev,
++ 		  "(%s) dma_rx_phy=0x%08x\n", __func__,
++ 		  (u32)rx_q->dma_rx_phy);
 + 
-+ 	val &= ~mask;
-+ 	val |= write_val;
-+-	qca8k_mii_write32(bus, 0x10 | r2, r1, val);
-++	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
++-	stmmac_clear_rx_descriptors(priv, queue);
+++	stmmac_clear_rx_descriptors(priv, dma_conf, queue);
 + 
-+ exit:
-+ 	mutex_unlock(&bus->mdio_lock);
-+@@ -1117,14 +1152,14 @@ qca8k_mdio_write(struct qca8k_priv *priv
-+ 	if (ret)
-+ 		goto exit;
++ 	xdp_rxq_info_unreg_mem_model(&rx_q->xdp_rxq);
 + 
-+-	qca8k_mii_write32(bus, 0x10 | r2, r1, val);
-++	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
++@@ -1693,9 +1724,9 @@ static int __init_dma_rx_desc_rings(stru
++ 		/* RX XDP ZC buffer pool may not be populated, e.g.
++ 		 * xdpsock TX-only.
++ 		 */
++-		stmmac_alloc_rx_buffers_zc(priv, queue);
+++		stmmac_alloc_rx_buffers_zc(priv, dma_conf, queue);
++ 	} else {
++-		ret = stmmac_alloc_rx_buffers(priv, queue, flags);
+++		ret = stmmac_alloc_rx_buffers(priv, dma_conf, queue, flags);
++ 		if (ret < 0)
++ 			return -ENOMEM;
++ 	}
++@@ -1705,17 +1736,19 @@ static int __init_dma_rx_desc_rings(stru
++ 		if (priv->extend_desc)
++ 			stmmac_mode_init(priv, rx_q->dma_erx,
++ 					 rx_q->dma_rx_phy,
++-					 priv->dma_conf.dma_rx_size, 1);
+++					 dma_conf->dma_rx_size, 1);
++ 		else
++ 			stmmac_mode_init(priv, rx_q->dma_rx,
++ 					 rx_q->dma_rx_phy,
++-					 priv->dma_conf.dma_rx_size, 0);
+++					 dma_conf->dma_rx_size, 0);
++ 	}
 + 
-+ 	ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
-+ 				   QCA8K_MDIO_MASTER_BUSY);
++ 	return 0;
++ }
 + 
-+ exit:
-+ 	/* even if the busy_wait timeouts try to clear the MASTER_EN */
-+-	qca8k_mii_write32(bus, 0x10 | r2, r1, 0);
-++	qca8k_mii_write32(priv, 0x10 | r2, r1, 0);
++-static int init_dma_rx_desc_rings(struct net_device *dev, gfp_t flags)
+++static int init_dma_rx_desc_rings(struct net_device *dev,
+++				  struct stmmac_dma_conf *dma_conf,
+++				  gfp_t flags)
++ {
++ 	struct stmmac_priv *priv = netdev_priv(dev);
++ 	u32 rx_count = priv->plat->rx_queues_to_use;
++@@ -1727,7 +1760,7 @@ static int init_dma_rx_desc_rings(struct
++ 		  "SKB addresses:\nskb\t\tskb data\tdma data\n");
++ 
++ 	for (queue = 0; queue < rx_count; queue++) {
++-		ret = __init_dma_rx_desc_rings(priv, queue, flags);
+++		ret = __init_dma_rx_desc_rings(priv, dma_conf, queue, flags);
++ 		if (ret)
++ 			goto err_init_rx_buffers;
++ 	}
++@@ -1736,12 +1769,12 @@ static int init_dma_rx_desc_rings(struct
 + 
-+ 	mutex_unlock(&bus->mdio_lock);
++ err_init_rx_buffers:
++ 	while (queue >= 0) {
++-		struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+++		struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
 + 
-+@@ -1154,7 +1189,7 @@ qca8k_mdio_read(struct qca8k_priv *priv,
-+ 	if (ret)
-+ 		goto exit;
++ 		if (rx_q->xsk_pool)
++-			dma_free_rx_xskbufs(priv, queue);
+++			dma_free_rx_xskbufs(priv, dma_conf, queue);
++ 		else
++-			dma_free_rx_skbufs(priv, queue);
+++			dma_free_rx_skbufs(priv, dma_conf, queue);
 + 
-+-	qca8k_mii_write32(bus, 0x10 | r2, r1, val);
-++	qca8k_mii_write32(priv, 0x10 | r2, r1, val);
++ 		rx_q->buf_alloc_num = 0;
++ 		rx_q->xsk_pool = NULL;
++@@ -1758,14 +1791,17 @@ err_init_rx_buffers:
++ /**
++  * __init_dma_tx_desc_rings - init the TX descriptor ring (per queue)
++  * @priv: driver private structure
++- * @queue : TX queue index
+++ * @dma_conf: structure to take the dma data
+++ * @queue: TX queue index
++  * Description: this function initializes the DMA TX descriptors
++  * and allocates the socket buffers. It supports the chained and ring
++  * modes.
++  */
++-static int __init_dma_tx_desc_rings(struct stmmac_priv *priv, u32 queue)
+++static int __init_dma_tx_desc_rings(struct stmmac_priv *priv,
+++				    struct stmmac_dma_conf *dma_conf,
+++				    u32 queue)
++ {
++-	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &dma_conf->tx_queue[queue];
++ 	int i;
 + 
-+ 	ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
-+ 				   QCA8K_MDIO_MASTER_BUSY);
-+@@ -1165,7 +1200,7 @@ qca8k_mdio_read(struct qca8k_priv *priv,
++ 	netif_dbg(priv, probe, priv->dev,
++@@ -1777,16 +1813,16 @@ static int __init_dma_tx_desc_rings(stru
++ 		if (priv->extend_desc)
++ 			stmmac_mode_init(priv, tx_q->dma_etx,
++ 					 tx_q->dma_tx_phy,
++-					 priv->dma_conf.dma_tx_size, 1);
+++					 dma_conf->dma_tx_size, 1);
++ 		else if (!(tx_q->tbs & STMMAC_TBS_AVAIL))
++ 			stmmac_mode_init(priv, tx_q->dma_tx,
++ 					 tx_q->dma_tx_phy,
++-					 priv->dma_conf.dma_tx_size, 0);
+++					 dma_conf->dma_tx_size, 0);
++ 	}
 + 
-+ exit:
-+ 	/* even if the busy_wait timeouts try to clear the MASTER_EN */
-+-	qca8k_mii_write32(bus, 0x10 | r2, r1, 0);
-++	qca8k_mii_write32(priv, 0x10 | r2, r1, 0);
++ 	tx_q->xsk_pool = stmmac_get_xsk_pool(priv, queue);
 + 
-+ 	mutex_unlock(&bus->mdio_lock);
++-	for (i = 0; i < priv->dma_conf.dma_tx_size; i++) {
+++	for (i = 0; i < dma_conf->dma_tx_size; i++) {
++ 		struct dma_desc *p;
 + 
-+@@ -3038,6 +3073,8 @@ qca8k_sw_probe(struct mdio_device *mdiod
-+ 	}
++ 		if (priv->extend_desc)
++@@ -1808,7 +1844,8 @@ static int __init_dma_tx_desc_rings(stru
++ 	return 0;
++ }
 + 
-+ 	priv->mdio_cache.page = 0xffff;
-++	priv->mdio_cache.lo = 0xffff;
-++	priv->mdio_cache.hi = 0xffff;
++-static int init_dma_tx_desc_rings(struct net_device *dev)
+++static int init_dma_tx_desc_rings(struct net_device *dev,
+++				  struct stmmac_dma_conf *dma_conf)
++ {
++ 	struct stmmac_priv *priv = netdev_priv(dev);
++ 	u32 tx_queue_cnt;
++@@ -1817,7 +1854,7 @@ static int init_dma_tx_desc_rings(struct
++ 	tx_queue_cnt = priv->plat->tx_queues_to_use;
 + 
-+ 	/* Check the detected switch id */
-+ 	ret = qca8k_read_switch_id(priv);
-+--- a/drivers/net/dsa/qca8k.h
-++++ b/drivers/net/dsa/qca8k.h
-+@@ -369,6 +369,11 @@ struct qca8k_mdio_cache {
-+  * mdio writes
++ 	for (queue = 0; queue < tx_queue_cnt; queue++)
++-		__init_dma_tx_desc_rings(priv, queue);
+++		__init_dma_tx_desc_rings(priv, dma_conf, queue);
++ 
++ 	return 0;
++ }
++@@ -1825,26 +1862,29 @@ static int init_dma_tx_desc_rings(struct
++ /**
++  * init_dma_desc_rings - init the RX/TX descriptor rings
++  * @dev: net device structure
+++ * @dma_conf: structure to take the dma data
++  * @flags: gfp flag.
++  * Description: this function initializes the DMA RX/TX descriptors
++  * and allocates the socket buffers. It supports the chained and ring
++  * modes.
 +  */
-+ 	u16 page;
-++/* lo and hi can also be cached and from Documentation we can skip one
-++ * extra mdio write if lo or hi is didn't change.
-++ */
-++	u16 lo;
-++	u16 hi;
-+ };
++-static int init_dma_desc_rings(struct net_device *dev, gfp_t flags)
+++static int init_dma_desc_rings(struct net_device *dev,
+++			       struct stmmac_dma_conf *dma_conf,
+++			       gfp_t flags)
++ {
++ 	struct stmmac_priv *priv = netdev_priv(dev);
++ 	int ret;
 + 
-+ struct qca8k_priv {
-diff --git a/target/linux/generic/backport-5.15/766-15-net-dsa-qca8k-add-support-for-larger-read-write-size.patch b/target/linux/generic/backport-5.15/766-15-net-dsa-qca8k-add-support-for-larger-read-write-size.patch
-new file mode 100644
-index 0000000000..5acd13dbad
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/766-15-net-dsa-qca8k-add-support-for-larger-read-write-size.patch
-@@ -0,0 +1,206 @@
-+From 90386223f44e2a751d7e9e9ac8f78ea33358a891 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Wed, 2 Feb 2022 01:03:34 +0100
-+Subject: [PATCH 15/16] net: dsa: qca8k: add support for larger read/write size
-+ with mgmt Ethernet
-+
-+mgmt Ethernet packet can read/write up to 16byte at times. The len reg
-+is limited to 15 (0xf). The switch actually sends and accepts data in 4
-+different steps of len values.
-+Len steps:
-+- 0: nothing
-+- 1-4: first 4 byte
-+- 5-6: first 12 byte
-+- 7-15: all 16 byte
-+
-+In the alloc skb function we check if the len is 16 and we fix it to a
-+len of 15. It the read/write function interest to extract the real asked
-+data. The tagger handler will always copy the fully 16byte with a READ
-+command. This is useful for some big regs like the fdb reg that are
-+more than 4byte of data. This permits to introduce a bulk function that
-+will send and request the entire entry in one go.
-+Write function is changed and it does now require to pass the pointer to
-+val to also handle array val.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 61 +++++++++++++++++++++++++++--------------
-+ 1 file changed, 41 insertions(+), 20 deletions(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -222,7 +222,9 @@ static void qca8k_rw_reg_ack_handler(str
-+ 	if (cmd == MDIO_READ) {
-+ 		mgmt_eth_data->data[0] = mgmt_ethhdr->mdio_data;
++-	ret = init_dma_rx_desc_rings(dev, flags);
+++	ret = init_dma_rx_desc_rings(dev, dma_conf, flags);
++ 	if (ret)
++ 		return ret;
++ 
++-	ret = init_dma_tx_desc_rings(dev);
+++	ret = init_dma_tx_desc_rings(dev, dma_conf);
++ 
++-	stmmac_clear_descriptors(priv);
+++	stmmac_clear_descriptors(priv, dma_conf);
++ 
++ 	if (netif_msg_hw(priv))
++-		stmmac_display_rings(priv);
+++		stmmac_display_rings(priv, dma_conf);
++ 
++ 	return ret;
++ }
++@@ -1852,17 +1892,20 @@ static int init_dma_desc_rings(struct ne
++ /**
++  * dma_free_tx_skbufs - free TX dma buffers
++  * @priv: private structure
+++ * @dma_conf: structure to take the dma data
++  * @queue: TX queue index
++  */
++-static void dma_free_tx_skbufs(struct stmmac_priv *priv, u32 queue)
+++static void dma_free_tx_skbufs(struct stmmac_priv *priv,
+++			       struct stmmac_dma_conf *dma_conf,
+++			       u32 queue)
++ {
++-	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &dma_conf->tx_queue[queue];
++ 	int i;
 + 
-+-		/* Get the rest of the 12 byte of data */
-++		/* Get the rest of the 12 byte of data.
-++		 * The read/write function will extract the requested data.
-++		 */
-+ 		if (len > QCA_HDR_MGMT_DATA1_LEN)
-+ 			memcpy(mgmt_eth_data->data + 1, skb->data,
-+ 			       QCA_HDR_MGMT_DATA2_LEN);
-+@@ -232,16 +234,30 @@ static void qca8k_rw_reg_ack_handler(str
++ 	tx_q->xsk_frames_done = 0;
++ 
++-	for (i = 0; i < priv->dma_conf.dma_tx_size; i++)
++-		stmmac_free_tx_buffer(priv, queue, i);
+++	for (i = 0; i < dma_conf->dma_tx_size; i++)
+++		stmmac_free_tx_buffer(priv, dma_conf, queue, i);
++ 
++ 	if (tx_q->xsk_pool && tx_q->xsk_frames_done) {
++ 		xsk_tx_completed(tx_q->xsk_pool, tx_q->xsk_frames_done);
++@@ -1881,34 +1924,37 @@ static void stmmac_free_tx_skbufs(struct
++ 	u32 queue;
++ 
++ 	for (queue = 0; queue < tx_queue_cnt; queue++)
++-		dma_free_tx_skbufs(priv, queue);
+++		dma_free_tx_skbufs(priv, &priv->dma_conf, queue);
 + }
 + 
-+ static struct sk_buff *qca8k_alloc_mdio_header(enum mdio_cmd cmd, u32 reg, u32 *val,
-+-					       int priority)
-++					       int priority, unsigned int len)
++ /**
++  * __free_dma_rx_desc_resources - free RX dma desc resources (per queue)
++  * @priv: private structure
+++ * @dma_conf: structure to take the dma data
++  * @queue: RX queue index
++  */
++-static void __free_dma_rx_desc_resources(struct stmmac_priv *priv, u32 queue)
+++static void __free_dma_rx_desc_resources(struct stmmac_priv *priv,
+++					 struct stmmac_dma_conf *dma_conf,
+++					 u32 queue)
 + {
-+ 	struct qca_mgmt_ethhdr *mgmt_ethhdr;
-++	unsigned int real_len;
-+ 	struct sk_buff *skb;
-++	u32 *data2;
-+ 	u16 hdr;
++-	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
 + 
-+ 	skb = dev_alloc_skb(QCA_HDR_MGMT_PKT_LEN);
-+ 	if (!skb)
-+ 		return NULL;
++ 	/* Release the DMA RX socket buffers */
++ 	if (rx_q->xsk_pool)
++-		dma_free_rx_xskbufs(priv, queue);
+++		dma_free_rx_xskbufs(priv, dma_conf, queue);
++ 	else
++-		dma_free_rx_skbufs(priv, queue);
+++		dma_free_rx_skbufs(priv, dma_conf, queue);
++ 
++ 	rx_q->buf_alloc_num = 0;
++ 	rx_q->xsk_pool = NULL;
++ 
++ 	/* Free DMA regions of consistent memory previously allocated */
++ 	if (!priv->extend_desc)
++-		dma_free_coherent(priv->device, priv->dma_conf.dma_rx_size *
+++		dma_free_coherent(priv->device, dma_conf->dma_rx_size *
++ 				  sizeof(struct dma_desc),
++ 				  rx_q->dma_rx, rx_q->dma_rx_phy);
++ 	else
++-		dma_free_coherent(priv->device, priv->dma_conf.dma_rx_size *
+++		dma_free_coherent(priv->device, dma_conf->dma_rx_size *
++ 				  sizeof(struct dma_extended_desc),
++ 				  rx_q->dma_erx, rx_q->dma_rx_phy);
 + 
-++	/* Max value for len reg is 15 (0xf) but the switch actually return 16 byte
-++	 * Actually for some reason the steps are:
-++	 * 0: nothing
-++	 * 1-4: first 4 byte
-++	 * 5-6: first 12 byte
-++	 * 7-15: all 16 byte
-++	 */
-++	if (len == 16)
-++		real_len = 15;
-++	else
-++		real_len = len;
-++
-+ 	skb_reset_mac_header(skb);
-+ 	skb_set_network_header(skb, skb->len);
++@@ -1920,29 +1966,33 @@ static void __free_dma_rx_desc_resources
++ 		page_pool_destroy(rx_q->page_pool);
++ }
 + 
-+@@ -254,7 +270,7 @@ static struct sk_buff *qca8k_alloc_mdio_
-+ 	hdr |= FIELD_PREP(QCA_HDR_XMIT_CONTROL, QCA_HDR_XMIT_TYPE_RW_REG);
++-static void free_dma_rx_desc_resources(struct stmmac_priv *priv)
+++static void free_dma_rx_desc_resources(struct stmmac_priv *priv,
+++				       struct stmmac_dma_conf *dma_conf)
++ {
++ 	u32 rx_count = priv->plat->rx_queues_to_use;
++ 	u32 queue;
 + 
-+ 	mgmt_ethhdr->command = FIELD_PREP(QCA_HDR_MGMT_ADDR, reg);
-+-	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_LENGTH, 4);
-++	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_LENGTH, real_len);
-+ 	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CMD, cmd);
-+ 	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CHECK_CODE,
-+ 					   QCA_HDR_MGMT_CHECK_CODE_VAL);
-+@@ -264,7 +280,9 @@ static struct sk_buff *qca8k_alloc_mdio_
++ 	/* Free RX queue resources */
++ 	for (queue = 0; queue < rx_count; queue++)
++-		__free_dma_rx_desc_resources(priv, queue);
+++		__free_dma_rx_desc_resources(priv, dma_conf, queue);
++ }
 + 
-+ 	mgmt_ethhdr->hdr = htons(hdr);
++ /**
++  * __free_dma_tx_desc_resources - free TX dma desc resources (per queue)
++  * @priv: private structure
+++ * @dma_conf: structure to take the dma data
++  * @queue: TX queue index
++  */
++-static void __free_dma_tx_desc_resources(struct stmmac_priv *priv, u32 queue)
+++static void __free_dma_tx_desc_resources(struct stmmac_priv *priv,
+++					 struct stmmac_dma_conf *dma_conf,
+++					 u32 queue)
++ {
++-	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &dma_conf->tx_queue[queue];
++ 	size_t size;
++ 	void *addr;
++ 
++ 	/* Release the DMA TX socket buffers */
++-	dma_free_tx_skbufs(priv, queue);
+++	dma_free_tx_skbufs(priv, dma_conf, queue);
++ 
++ 	if (priv->extend_desc) {
++ 		size = sizeof(struct dma_extended_desc);
++@@ -1955,7 +2005,7 @@ static void __free_dma_tx_desc_resources
++ 		addr = tx_q->dma_tx;
++ 	}
 + 
-+-	skb_put_zero(skb, QCA_HDR_MGMT_DATA2_LEN + QCA_HDR_MGMT_PADDING_LEN);
-++	data2 = skb_put_zero(skb, QCA_HDR_MGMT_DATA2_LEN + QCA_HDR_MGMT_PADDING_LEN);
-++	if (cmd == MDIO_WRITE && len > QCA_HDR_MGMT_DATA1_LEN)
-++		memcpy(data2, val + 1, len - QCA_HDR_MGMT_DATA1_LEN);
++-	size *= priv->dma_conf.dma_tx_size;
+++	size *= dma_conf->dma_tx_size;
 + 
-+ 	return skb;
-+ }
-+@@ -277,7 +295,7 @@ static void qca8k_mdio_header_fill_seq_n
-+ 	mgmt_ethhdr->seq = FIELD_PREP(QCA_HDR_MGMT_SEQ_NUM, seq_num);
++ 	dma_free_coherent(priv->device, size, addr, tx_q->dma_tx_phy);
++ 
++@@ -1963,28 +2013,32 @@ static void __free_dma_tx_desc_resources
++ 	kfree(tx_q->tx_skbuff);
 + }
 + 
-+-static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val)
-++static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++-static void free_dma_tx_desc_resources(struct stmmac_priv *priv)
+++static void free_dma_tx_desc_resources(struct stmmac_priv *priv,
+++				       struct stmmac_dma_conf *dma_conf)
 + {
-+ 	struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
-+ 	struct sk_buff *skb;
-+@@ -285,7 +303,7 @@ static int qca8k_read_eth(struct qca8k_p
-+ 	int ret;
++ 	u32 tx_count = priv->plat->tx_queues_to_use;
++ 	u32 queue;
 + 
-+ 	skb = qca8k_alloc_mdio_header(MDIO_READ, reg, NULL,
-+-				      QCA8K_ETHERNET_MDIO_PRIORITY);
-++				      QCA8K_ETHERNET_MDIO_PRIORITY, len);
-+ 	if (!skb)
-+ 		return -ENOMEM;
++ 	/* Free TX queue resources */
++ 	for (queue = 0; queue < tx_count; queue++)
++-		__free_dma_tx_desc_resources(priv, queue);
+++		__free_dma_tx_desc_resources(priv, dma_conf, queue);
++ }
 + 
-+@@ -313,6 +331,9 @@ static int qca8k_read_eth(struct qca8k_p
-+ 					  msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
++ /**
++  * __alloc_dma_rx_desc_resources - alloc RX resources (per queue).
++  * @priv: private structure
+++ * @dma_conf: structure to take the dma data
++  * @queue: RX queue index
++  * Description: according to which descriptor can be used (extend or basic)
++  * this function allocates the resources for TX and RX paths. In case of
++  * reception, for example, it pre-allocated the RX socket buffer in order to
++  * allow zero-copy mechanism.
++  */
++-static int __alloc_dma_rx_desc_resources(struct stmmac_priv *priv, u32 queue)
+++static int __alloc_dma_rx_desc_resources(struct stmmac_priv *priv,
+++					 struct stmmac_dma_conf *dma_conf,
+++					 u32 queue)
++ {
++-	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+++	struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
++ 	struct stmmac_channel *ch = &priv->channel[queue];
++ 	bool xdp_prog = stmmac_xdp_is_enabled(priv);
++ 	struct page_pool_params pp_params = { 0 };
++@@ -1996,8 +2050,8 @@ static int __alloc_dma_rx_desc_resources
++ 	rx_q->priv_data = priv;
++ 
++ 	pp_params.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV;
++-	pp_params.pool_size = priv->dma_conf.dma_rx_size;
++-	num_pages = DIV_ROUND_UP(priv->dma_conf.dma_buf_sz, PAGE_SIZE);
+++	pp_params.pool_size = dma_conf->dma_rx_size;
+++	num_pages = DIV_ROUND_UP(dma_conf->dma_buf_sz, PAGE_SIZE);
++ 	pp_params.order = ilog2(num_pages);
++ 	pp_params.nid = dev_to_node(priv->device);
++ 	pp_params.dev = priv->device;
++@@ -2012,7 +2066,7 @@ static int __alloc_dma_rx_desc_resources
++ 		return ret;
++ 	}
 + 
-+ 	*val = mgmt_eth_data->data[0];
-++	if (len > QCA_HDR_MGMT_DATA1_LEN)
-++		memcpy(val + 1, mgmt_eth_data->data + 1, len - QCA_HDR_MGMT_DATA1_LEN);
-++
-+ 	ack = mgmt_eth_data->ack;
++-	rx_q->buf_pool = kcalloc(priv->dma_conf.dma_rx_size,
+++	rx_q->buf_pool = kcalloc(dma_conf->dma_rx_size,
++ 				 sizeof(*rx_q->buf_pool),
++ 				 GFP_KERNEL);
++ 	if (!rx_q->buf_pool)
++@@ -2020,7 +2074,7 @@ static int __alloc_dma_rx_desc_resources
++ 
++ 	if (priv->extend_desc) {
++ 		rx_q->dma_erx = dma_alloc_coherent(priv->device,
++-						   priv->dma_conf.dma_rx_size *
+++						   dma_conf->dma_rx_size *
++ 						   sizeof(struct dma_extended_desc),
++ 						   &rx_q->dma_rx_phy,
++ 						   GFP_KERNEL);
++@@ -2029,7 +2083,7 @@ static int __alloc_dma_rx_desc_resources
 + 
-+ 	mutex_unlock(&mgmt_eth_data->mutex);
-+@@ -326,15 +347,15 @@ static int qca8k_read_eth(struct qca8k_p
++ 	} else {
++ 		rx_q->dma_rx = dma_alloc_coherent(priv->device,
++-						  priv->dma_conf.dma_rx_size *
+++						  dma_conf->dma_rx_size *
++ 						  sizeof(struct dma_desc),
++ 						  &rx_q->dma_rx_phy,
++ 						  GFP_KERNEL);
++@@ -2054,7 +2108,8 @@ static int __alloc_dma_rx_desc_resources
 + 	return 0;
 + }
 + 
-+-static int qca8k_write_eth(struct qca8k_priv *priv, u32 reg, u32 val)
-++static int qca8k_write_eth(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++-static int alloc_dma_rx_desc_resources(struct stmmac_priv *priv)
+++static int alloc_dma_rx_desc_resources(struct stmmac_priv *priv,
+++				       struct stmmac_dma_conf *dma_conf)
 + {
-+ 	struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
-+ 	struct sk_buff *skb;
-+ 	bool ack;
-+ 	int ret;
++ 	u32 rx_count = priv->plat->rx_queues_to_use;
++ 	u32 queue;
++@@ -2062,7 +2117,7 @@ static int alloc_dma_rx_desc_resources(s
++ 
++ 	/* RX queues buffers and DMA */
++ 	for (queue = 0; queue < rx_count; queue++) {
++-		ret = __alloc_dma_rx_desc_resources(priv, queue);
+++		ret = __alloc_dma_rx_desc_resources(priv, dma_conf, queue);
++ 		if (ret)
++ 			goto err_dma;
++ 	}
++@@ -2070,7 +2125,7 @@ static int alloc_dma_rx_desc_resources(s
++ 	return 0;
 + 
-+-	skb = qca8k_alloc_mdio_header(MDIO_WRITE, reg, &val,
-+-				      QCA8K_ETHERNET_MDIO_PRIORITY);
-++	skb = qca8k_alloc_mdio_header(MDIO_WRITE, reg, val,
-++				      QCA8K_ETHERNET_MDIO_PRIORITY, len);
-+ 	if (!skb)
-+ 		return -ENOMEM;
++ err_dma:
++-	free_dma_rx_desc_resources(priv);
+++	free_dma_rx_desc_resources(priv, dma_conf);
 + 
-+@@ -380,14 +401,14 @@ qca8k_regmap_update_bits_eth(struct qca8
-+ 	u32 val = 0;
-+ 	int ret;
++ 	return ret;
++ }
++@@ -2078,28 +2133,31 @@ err_dma:
++ /**
++  * __alloc_dma_tx_desc_resources - alloc TX resources (per queue).
++  * @priv: private structure
+++ * @dma_conf: structure to take the dma data
++  * @queue: TX queue index
++  * Description: according to which descriptor can be used (extend or basic)
++  * this function allocates the resources for TX and RX paths. In case of
++  * reception, for example, it pre-allocated the RX socket buffer in order to
++  * allow zero-copy mechanism.
++  */
++-static int __alloc_dma_tx_desc_resources(struct stmmac_priv *priv, u32 queue)
+++static int __alloc_dma_tx_desc_resources(struct stmmac_priv *priv,
+++					 struct stmmac_dma_conf *dma_conf,
+++					 u32 queue)
++ {
++-	struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+++	struct stmmac_tx_queue *tx_q = &dma_conf->tx_queue[queue];
++ 	size_t size;
++ 	void *addr;
++ 
++ 	tx_q->queue_index = queue;
++ 	tx_q->priv_data = priv;
++ 
++-	tx_q->tx_skbuff_dma = kcalloc(priv->dma_conf.dma_tx_size,
+++	tx_q->tx_skbuff_dma = kcalloc(dma_conf->dma_tx_size,
++ 				      sizeof(*tx_q->tx_skbuff_dma),
++ 				      GFP_KERNEL);
++ 	if (!tx_q->tx_skbuff_dma)
++ 		return -ENOMEM;
 + 
-+-	ret = qca8k_read_eth(priv, reg, &val);
-++	ret = qca8k_read_eth(priv, reg, &val, sizeof(val));
-+ 	if (ret)
-+ 		return ret;
++-	tx_q->tx_skbuff = kcalloc(priv->dma_conf.dma_tx_size,
+++	tx_q->tx_skbuff = kcalloc(dma_conf->dma_tx_size,
++ 				  sizeof(struct sk_buff *),
++ 				  GFP_KERNEL);
++ 	if (!tx_q->tx_skbuff)
++@@ -2112,7 +2170,7 @@ static int __alloc_dma_tx_desc_resources
++ 	else
++ 		size = sizeof(struct dma_desc);
 + 
-+ 	val &= ~mask;
-+ 	val |= write_val;
++-	size *= priv->dma_conf.dma_tx_size;
+++	size *= dma_conf->dma_tx_size;
 + 
-+-	return qca8k_write_eth(priv, reg, val);
-++	return qca8k_write_eth(priv, reg, &val, sizeof(val));
++ 	addr = dma_alloc_coherent(priv->device, size,
++ 				  &tx_q->dma_tx_phy, GFP_KERNEL);
++@@ -2129,7 +2187,8 @@ static int __alloc_dma_tx_desc_resources
++ 	return 0;
 + }
 + 
-+ static int
-+@@ -398,7 +419,7 @@ qca8k_regmap_read(void *ctx, uint32_t re
-+ 	u16 r1, r2, page;
-+ 	int ret;
++-static int alloc_dma_tx_desc_resources(struct stmmac_priv *priv)
+++static int alloc_dma_tx_desc_resources(struct stmmac_priv *priv,
+++				       struct stmmac_dma_conf *dma_conf)
++ {
++ 	u32 tx_count = priv->plat->tx_queues_to_use;
++ 	u32 queue;
++@@ -2137,7 +2196,7 @@ static int alloc_dma_tx_desc_resources(s
++ 
++ 	/* TX queues buffers and DMA */
++ 	for (queue = 0; queue < tx_count; queue++) {
++-		ret = __alloc_dma_tx_desc_resources(priv, queue);
+++		ret = __alloc_dma_tx_desc_resources(priv, dma_conf, queue);
++ 		if (ret)
++ 			goto err_dma;
++ 	}
++@@ -2145,27 +2204,29 @@ static int alloc_dma_tx_desc_resources(s
++ 	return 0;
 + 
-+-	if (!qca8k_read_eth(priv, reg, val))
-++	if (!qca8k_read_eth(priv, reg, val, sizeof(val)))
-+ 		return 0;
++ err_dma:
++-	free_dma_tx_desc_resources(priv);
+++	free_dma_tx_desc_resources(priv, dma_conf);
++ 	return ret;
++ }
 + 
-+ 	qca8k_split_addr(reg, &r1, &r2, &page);
-+@@ -424,7 +445,7 @@ qca8k_regmap_write(void *ctx, uint32_t r
-+ 	u16 r1, r2, page;
-+ 	int ret;
++ /**
++  * alloc_dma_desc_resources - alloc TX/RX resources.
++  * @priv: private structure
+++ * @dma_conf: structure to take the dma data
++  * Description: according to which descriptor can be used (extend or basic)
++  * this function allocates the resources for TX and RX paths. In case of
++  * reception, for example, it pre-allocated the RX socket buffer in order to
++  * allow zero-copy mechanism.
++  */
++-static int alloc_dma_desc_resources(struct stmmac_priv *priv)
+++static int alloc_dma_desc_resources(struct stmmac_priv *priv,
+++				    struct stmmac_dma_conf *dma_conf)
++ {
++ 	/* RX Allocation */
++-	int ret = alloc_dma_rx_desc_resources(priv);
+++	int ret = alloc_dma_rx_desc_resources(priv, dma_conf);
 + 
-+-	if (!qca8k_write_eth(priv, reg, val))
-++	if (!qca8k_write_eth(priv, reg, &val, sizeof(val)))
-+ 		return 0;
++ 	if (ret)
++ 		return ret;
 + 
-+ 	qca8k_split_addr(reg, &r1, &r2, &page);
-+@@ -959,21 +980,21 @@ qca8k_phy_eth_command(struct qca8k_priv
-+ 	}
++-	ret = alloc_dma_tx_desc_resources(priv);
+++	ret = alloc_dma_tx_desc_resources(priv, dma_conf);
 + 
-+ 	/* Prealloc all the needed skb before the lock */
-+-	write_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL,
-+-					    &write_val, QCA8K_ETHERNET_PHY_PRIORITY);
-++	write_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL, &write_val,
-++					    QCA8K_ETHERNET_PHY_PRIORITY, sizeof(write_val));
-+ 	if (!write_skb)
-+ 		return -ENOMEM;
++ 	return ret;
++ }
++@@ -2173,16 +2234,18 @@ static int alloc_dma_desc_resources(stru
++ /**
++  * free_dma_desc_resources - free dma desc resources
++  * @priv: private structure
+++ * @dma_conf: structure to take the dma data
++  */
++-static void free_dma_desc_resources(struct stmmac_priv *priv)
+++static void free_dma_desc_resources(struct stmmac_priv *priv,
+++				    struct stmmac_dma_conf *dma_conf)
++ {
++ 	/* Release the DMA TX socket buffers */
++-	free_dma_tx_desc_resources(priv);
+++	free_dma_tx_desc_resources(priv, dma_conf);
 + 
-+-	clear_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL,
-+-					    &clear_val, QCA8K_ETHERNET_PHY_PRIORITY);
-++	clear_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL, &clear_val,
-++					    QCA8K_ETHERNET_PHY_PRIORITY, sizeof(clear_val));
-+ 	if (!write_skb) {
-+ 		ret = -ENOMEM;
-+ 		goto err_clear_skb;
-+ 	}
++ 	/* Release the DMA RX socket buffers later
++ 	 * to ensure all pending XDP_TX buffers are returned.
++ 	 */
++-	free_dma_rx_desc_resources(priv);
+++	free_dma_rx_desc_resources(priv, dma_conf);
++ }
 + 
-+-	read_skb = qca8k_alloc_mdio_header(MDIO_READ, QCA8K_MDIO_MASTER_CTRL,
-+-					   &clear_val, QCA8K_ETHERNET_PHY_PRIORITY);
-+-	if (!write_skb) {
-++	read_skb = qca8k_alloc_mdio_header(MDIO_READ, QCA8K_MDIO_MASTER_CTRL, &clear_val,
-++					   QCA8K_ETHERNET_PHY_PRIORITY, sizeof(clear_val));
-++	if (!read_skb) {
-+ 		ret = -ENOMEM;
-+ 		goto err_read_skb;
-+ 	}
-diff --git a/target/linux/generic/backport-5.15/766-16-net-dsa-qca8k-introduce-qca8k_bulk_read-write-functi.patch b/target/linux/generic/backport-5.15/766-16-net-dsa-qca8k-introduce-qca8k_bulk_read-write-functi.patch
-new file mode 100644
-index 0000000000..f26c6b91ac
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/766-16-net-dsa-qca8k-introduce-qca8k_bulk_read-write-functi.patch
-@@ -0,0 +1,104 @@
-+From 4f3701fc599820568ba4395070d34e4248800fc0 Mon Sep 17 00:00:00 2001
-+From: Ansuel Smith <ansuelsmth@gmail.com>
-+Date: Wed, 2 Feb 2022 01:03:35 +0100
-+Subject: [PATCH 16/16] net: dsa: qca8k: introduce qca8k_bulk_read/write
-+ function
-+
-+Introduce qca8k_bulk_read/write() function to use mgmt Ethernet way to
-+read/write packet in bulk. Make use of this new function in the fdb
-+function and while at it reduce the reg for fdb_read from 4 to 3 as the
-+max bit for the ARL(fdb) table is 83 bits.
-+
-+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/qca8k.c | 55 ++++++++++++++++++++++++++++++++---------
-+ 1 file changed, 43 insertions(+), 12 deletions(-)
-+
-+--- a/drivers/net/dsa/qca8k.c
-++++ b/drivers/net/dsa/qca8k.c
-+@@ -412,6 +412,43 @@ qca8k_regmap_update_bits_eth(struct qca8
++ /**
++@@ -2671,8 +2734,8 @@ static void stmmac_tx_err(struct stmmac_
++ 	netif_tx_stop_queue(netdev_get_tx_queue(priv->dev, chan));
++ 
++ 	stmmac_stop_tx_dma(priv, chan);
++-	dma_free_tx_skbufs(priv, chan);
++-	stmmac_clear_tx_descriptors(priv, chan);
+++	dma_free_tx_skbufs(priv, &priv->dma_conf, chan);
+++	stmmac_clear_tx_descriptors(priv, &priv->dma_conf, chan);
++ 	stmmac_reset_tx_queue(priv, chan);
++ 	stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
++ 			    tx_q->dma_tx_phy, chan);
++@@ -3669,19 +3732,93 @@ static int stmmac_request_irq(struct net
 + }
 + 
-+ static int
-++qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++ /**
++- *  stmmac_open - open entry point of the driver
+++ *  stmmac_setup_dma_desc - Generate a dma_conf and allocate DMA queue
+++ *  @priv: driver private structure
+++ *  @mtu: MTU to setup the dma queue and buf with
+++ *  Description: Allocate and generate a dma_conf based on the provided MTU.
+++ *  Allocate the Tx/Rx DMA queue and init them.
+++ *  Return value:
+++ *  the dma_conf allocated struct on success and an appropriate ERR_PTR on failure.
+++ */
+++static struct stmmac_dma_conf *
+++stmmac_setup_dma_desc(struct stmmac_priv *priv, unsigned int mtu)
 ++{
-++	int i, count = len / sizeof(u32), ret;
+++	struct stmmac_dma_conf *dma_conf;
+++	int chan, bfsize, ret;
+++
+++	dma_conf = kzalloc(sizeof(*dma_conf), GFP_KERNEL);
+++	if (!dma_conf) {
+++		netdev_err(priv->dev, "%s: DMA conf allocation failed\n",
+++			   __func__);
+++		return ERR_PTR(-ENOMEM);
+++	}
 ++
-++	if (priv->mgmt_master && !qca8k_read_eth(priv, reg, val, len))
-++		return 0;
+++	bfsize = stmmac_set_16kib_bfsize(priv, mtu);
+++	if (bfsize < 0)
+++		bfsize = 0;
 ++
-++	for (i = 0; i < count; i++) {
-++		ret = regmap_read(priv->regmap, reg + (i * 4), val + i);
-++		if (ret < 0)
-++			return ret;
-++	}
+++	if (bfsize < BUF_SIZE_16KiB)
+++		bfsize = stmmac_set_bfsize(mtu, 0);
 ++
-++	return 0;
-++}
+++	dma_conf->dma_buf_sz = bfsize;
+++	/* Chose the tx/rx size from the already defined one in the
+++	 * priv struct. (if defined)
+++	 */
+++	dma_conf->dma_tx_size = priv->dma_conf.dma_tx_size;
+++	dma_conf->dma_rx_size = priv->dma_conf.dma_rx_size;
 ++
-++static int
-++qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
-++{
-++	int i, count = len / sizeof(u32), ret;
-++	u32 tmp;
+++	if (!dma_conf->dma_tx_size)
+++		dma_conf->dma_tx_size = DMA_DEFAULT_TX_SIZE;
+++	if (!dma_conf->dma_rx_size)
+++		dma_conf->dma_rx_size = DMA_DEFAULT_RX_SIZE;
 ++
-++	if (priv->mgmt_master && !qca8k_write_eth(priv, reg, val, len))
-++		return 0;
+++	/* Earlier check for TBS */
+++	for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++) {
+++		struct stmmac_tx_queue *tx_q = &dma_conf->tx_queue[chan];
+++		int tbs_en = priv->plat->tx_queues_cfg[chan].tbs_en;
 ++
-++	for (i = 0; i < count; i++) {
-++		tmp = val[i];
+++		/* Setup per-TXQ tbs flag before TX descriptor alloc */
+++		tx_q->tbs |= tbs_en ? STMMAC_TBS_AVAIL : 0;
+++	}
 ++
-++		ret = regmap_write(priv->regmap, reg + (i * 4), tmp);
-++		if (ret < 0)
-++			return ret;
+++	ret = alloc_dma_desc_resources(priv, dma_conf);
+++	if (ret < 0) {
+++		netdev_err(priv->dev, "%s: DMA descriptors allocation failed\n",
+++			   __func__);
+++		goto alloc_error;
 ++	}
 ++
-++	return 0;
+++	ret = init_dma_desc_rings(priv->dev, dma_conf, GFP_KERNEL);
+++	if (ret < 0) {
+++		netdev_err(priv->dev, "%s: DMA descriptors initialization failed\n",
+++			   __func__);
+++		goto init_error;
+++	}
+++
+++	return dma_conf;
+++
+++init_error:
+++	free_dma_desc_resources(priv, dma_conf);
+++alloc_error:
+++	kfree(dma_conf);
+++	return ERR_PTR(ret);
 ++}
 ++
-++static int
-+ qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
-+ {
-+ 	struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
-+@@ -546,17 +583,13 @@ qca8k_busy_wait(struct qca8k_priv *priv,
-+ static int
-+ qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb)
+++/**
+++ *  __stmmac_open - open entry point of the driver
++  *  @dev : pointer to the device structure.
+++ *  @dma_conf :  structure to take the dma data
++  *  Description:
++  *  This function is the open entry point of the driver.
++  *  Return value:
++  *  0 on success and an appropriate (-)ve integer as defined in errno.h
++  *  file on failure.
++  */
++-static int stmmac_open(struct net_device *dev)
+++static int __stmmac_open(struct net_device *dev,
+++			 struct stmmac_dma_conf *dma_conf)
 + {
-+-	u32 reg[4], val;
-+-	int i, ret;
-++	u32 reg[3];
-++	int ret;
++ 	struct stmmac_priv *priv = netdev_priv(dev);
++ 	int mode = priv->plat->phy_interface;
++-	int bfsize = 0;
++ 	u32 chan;
++ 	int ret;
 + 
-+ 	/* load the ARL table into an array */
-+-	for (i = 0; i < 4; i++) {
-+-		ret = qca8k_read(priv, QCA8K_REG_ATU_DATA0 + (i * 4), &val);
-+-		if (ret < 0)
-+-			return ret;
++@@ -3708,45 +3845,10 @@ static int stmmac_open(struct net_device
++ 	memset(&priv->xstats, 0, sizeof(struct stmmac_extra_stats));
++ 	priv->xstats.threshold = tc;
++ 
++-	bfsize = stmmac_set_16kib_bfsize(priv, dev->mtu);
++-	if (bfsize < 0)
++-		bfsize = 0;
 +-
-+-		reg[i] = val;
++-	if (bfsize < BUF_SIZE_16KiB)
++-		bfsize = stmmac_set_bfsize(dev->mtu, priv->dma_conf.dma_buf_sz);
++-
++-	priv->dma_conf.dma_buf_sz = bfsize;
++-	buf_sz = bfsize;
++-
++ 	priv->rx_copybreak = STMMAC_RX_COPYBREAK;
++ 
++-	if (!priv->dma_conf.dma_tx_size)
++-		priv->dma_conf.dma_tx_size = DMA_DEFAULT_TX_SIZE;
++-	if (!priv->dma_conf.dma_rx_size)
++-		priv->dma_conf.dma_rx_size = DMA_DEFAULT_RX_SIZE;
++-
++-	/* Earlier check for TBS */
++-	for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++) {
++-		struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan];
++-		int tbs_en = priv->plat->tx_queues_cfg[chan].tbs_en;
++-
++-		/* Setup per-TXQ tbs flag before TX descriptor alloc */
++-		tx_q->tbs |= tbs_en ? STMMAC_TBS_AVAIL : 0;
 +-	}
-++	ret = qca8k_bulk_read(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
-++	if (ret)
-++		return ret;
++-
++-	ret = alloc_dma_desc_resources(priv);
++-	if (ret < 0) {
++-		netdev_err(priv->dev, "%s: DMA descriptors allocation failed\n",
++-			   __func__);
++-		goto dma_desc_error;
++-	}
++-
++-	ret = init_dma_desc_rings(dev, GFP_KERNEL);
++-	if (ret < 0) {
++-		netdev_err(priv->dev, "%s: DMA descriptors initialization failed\n",
++-			   __func__);
++-		goto init_error;
++-	}
+++	buf_sz = dma_conf->dma_buf_sz;
+++	memcpy(&priv->dma_conf, dma_conf, sizeof(*dma_conf));
++ 
++ 	if (priv->plat->serdes_powerup) {
++ 		ret = priv->plat->serdes_powerup(dev, priv->plat->bsp_priv);
++@@ -3789,14 +3891,28 @@ irq_error:
++ 
++ 	stmmac_hw_teardown(dev);
++ init_error:
++-	free_dma_desc_resources(priv);
++-dma_desc_error:
+++	free_dma_desc_resources(priv, &priv->dma_conf);
++ 	phylink_disconnect_phy(priv->phylink);
++ init_phy_error:
++ 	pm_runtime_put(priv->device);
++ 	return ret;
++ }
 + 
-+ 	/* vid - 83:72 */
-+ 	fdb->vid = FIELD_GET(QCA8K_ATU_VID_MASK, reg[2]);
-+@@ -580,7 +613,6 @@ qca8k_fdb_write(struct qca8k_priv *priv,
-+ 		u8 aging)
+++static int stmmac_open(struct net_device *dev)
+++{
+++	struct stmmac_priv *priv = netdev_priv(dev);
+++	struct stmmac_dma_conf *dma_conf;
+++	int ret;
+++
+++	dma_conf = stmmac_setup_dma_desc(priv, dev->mtu);
+++	if (IS_ERR(dma_conf))
+++		return PTR_ERR(dma_conf);
+++
+++	ret = __stmmac_open(dev, dma_conf);
+++	kfree(dma_conf);
+++	return ret;
+++}
+++
++ static void stmmac_fpe_stop_wq(struct stmmac_priv *priv)
 + {
-+ 	u32 reg[3] = { 0 };
-+-	int i;
++ 	set_bit(__FPE_REMOVING, &priv->fpe_task_state);
++@@ -3843,7 +3959,7 @@ static int stmmac_release(struct net_dev
++ 	stmmac_stop_all_dma(priv);
++ 
++ 	/* Release and free the Rx/Tx resources */
++-	free_dma_desc_resources(priv);
+++	free_dma_desc_resources(priv, &priv->dma_conf);
++ 
++ 	/* Disable the MAC Rx/Tx */
++ 	stmmac_mac_set(priv, priv->ioaddr, false);
++@@ -6382,7 +6498,7 @@ void stmmac_disable_rx_queue(struct stmm
++ 	spin_unlock_irqrestore(&ch->lock, flags);
++ 
++ 	stmmac_stop_rx_dma(priv, queue);
++-	__free_dma_rx_desc_resources(priv, queue);
+++	__free_dma_rx_desc_resources(priv, &priv->dma_conf, queue);
++ }
 + 
-+ 	/* vid - 83:72 */
-+ 	reg[2] = FIELD_PREP(QCA8K_ATU_VID_MASK, vid);
-+@@ -597,8 +629,7 @@ qca8k_fdb_write(struct qca8k_priv *priv,
-+ 	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR5_MASK, mac[5]);
++ void stmmac_enable_rx_queue(struct stmmac_priv *priv, u32 queue)
++@@ -6393,21 +6509,21 @@ void stmmac_enable_rx_queue(struct stmma
++ 	u32 buf_size;
++ 	int ret;
 + 
-+ 	/* load the array into the ARL table */
-+-	for (i = 0; i < 3; i++)
-+-		qca8k_write(priv, QCA8K_REG_ATU_DATA0 + (i * 4), reg[i]);
-++	qca8k_bulk_write(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
++-	ret = __alloc_dma_rx_desc_resources(priv, queue);
+++	ret = __alloc_dma_rx_desc_resources(priv, &priv->dma_conf, queue);
++ 	if (ret) {
++ 		netdev_err(priv->dev, "Failed to alloc RX desc.\n");
++ 		return;
++ 	}
++ 
++-	ret = __init_dma_rx_desc_rings(priv, queue, GFP_KERNEL);
+++	ret = __init_dma_rx_desc_rings(priv, &priv->dma_conf, queue, GFP_KERNEL);
++ 	if (ret) {
++-		__free_dma_rx_desc_resources(priv, queue);
+++		__free_dma_rx_desc_resources(priv, &priv->dma_conf, queue);
++ 		netdev_err(priv->dev, "Failed to init RX desc.\n");
++ 		return;
++ 	}
++ 
++ 	stmmac_reset_rx_queue(priv, queue);
++-	stmmac_clear_rx_descriptors(priv, queue);
+++	stmmac_clear_rx_descriptors(priv, &priv->dma_conf, queue);
++ 
++ 	stmmac_init_rx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
++ 			    rx_q->dma_rx_phy, rx_q->queue_index);
++@@ -6445,7 +6561,7 @@ void stmmac_disable_tx_queue(struct stmm
++ 	spin_unlock_irqrestore(&ch->lock, flags);
++ 
++ 	stmmac_stop_tx_dma(priv, queue);
++-	__free_dma_tx_desc_resources(priv, queue);
+++	__free_dma_tx_desc_resources(priv, &priv->dma_conf, queue);
 + }
 + 
-+ static int
-diff --git a/target/linux/generic/backport-5.15/773-v5.18-1-net-dsa-Move-VLAN-filtering-syncing-out-of-dsa_switc.patch b/target/linux/generic/backport-5.15/773-v5.18-1-net-dsa-Move-VLAN-filtering-syncing-out-of-dsa_switc.patch
++ void stmmac_enable_tx_queue(struct stmmac_priv *priv, u32 queue)
++@@ -6455,21 +6571,21 @@ void stmmac_enable_tx_queue(struct stmma
++ 	unsigned long flags;
++ 	int ret;
++ 
++-	ret = __alloc_dma_tx_desc_resources(priv, queue);
+++	ret = __alloc_dma_tx_desc_resources(priv, &priv->dma_conf, queue);
++ 	if (ret) {
++ 		netdev_err(priv->dev, "Failed to alloc TX desc.\n");
++ 		return;
++ 	}
++ 
++-	ret = __init_dma_tx_desc_rings(priv, queue);
+++	ret = __init_dma_tx_desc_rings(priv,  &priv->dma_conf, queue);
++ 	if (ret) {
++-		__free_dma_tx_desc_resources(priv, queue);
+++		__free_dma_tx_desc_resources(priv, &priv->dma_conf, queue);
++ 		netdev_err(priv->dev, "Failed to init TX desc.\n");
++ 		return;
++ 	}
++ 
++ 	stmmac_reset_tx_queue(priv, queue);
++-	stmmac_clear_tx_descriptors(priv, queue);
+++	stmmac_clear_tx_descriptors(priv, &priv->dma_conf, queue);
++ 
++ 	stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
++ 			    tx_q->dma_tx_phy, tx_q->queue_index);
++@@ -6509,7 +6625,7 @@ void stmmac_xdp_release(struct net_devic
++ 	stmmac_stop_all_dma(priv);
++ 
++ 	/* Release and free the Rx/Tx resources */
++-	free_dma_desc_resources(priv);
+++	free_dma_desc_resources(priv, &priv->dma_conf);
++ 
++ 	/* Disable the MAC Rx/Tx */
++ 	stmmac_mac_set(priv, priv->ioaddr, false);
++@@ -6534,14 +6650,14 @@ int stmmac_xdp_open(struct net_device *d
++ 	u32 chan;
++ 	int ret;
++ 
++-	ret = alloc_dma_desc_resources(priv);
+++	ret = alloc_dma_desc_resources(priv, &priv->dma_conf);
++ 	if (ret < 0) {
++ 		netdev_err(dev, "%s: DMA descriptors allocation failed\n",
++ 			   __func__);
++ 		goto dma_desc_error;
++ 	}
++ 
++-	ret = init_dma_desc_rings(dev, GFP_KERNEL);
+++	ret = init_dma_desc_rings(dev, &priv->dma_conf, GFP_KERNEL);
++ 	if (ret < 0) {
++ 		netdev_err(dev, "%s: DMA descriptors initialization failed\n",
++ 			   __func__);
++@@ -6623,7 +6739,7 @@ irq_error:
++ 
++ 	stmmac_hw_teardown(dev);
++ init_error:
++-	free_dma_desc_resources(priv);
+++	free_dma_desc_resources(priv, &priv->dma_conf);
++ dma_desc_error:
++ 	return ret;
++ }
++@@ -7482,7 +7598,7 @@ int stmmac_resume(struct device *dev)
++ 	stmmac_reset_queues_param(priv);
++ 
++ 	stmmac_free_tx_skbufs(priv);
++-	stmmac_clear_descriptors(priv);
+++	stmmac_clear_descriptors(priv, &priv->dma_conf);
++ 
++ 	stmmac_hw_setup(ndev, false);
++ 	stmmac_init_coalesce(priv);
+diff --git a/target/linux/generic/backport-5.15/775-v6.0-05-net-ethernet-stmicro-stmmac-permit-MTU-change-with-i.patch b/target/linux/generic/backport-5.15/775-v6.0-05-net-ethernet-stmicro-stmmac-permit-MTU-change-with-i.patch
 new file mode 100644
-index 0000000000..44093eab95
+index 0000000000..996c28119e
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/773-v5.18-1-net-dsa-Move-VLAN-filtering-syncing-out-of-dsa_switc.patch
-@@ -0,0 +1,77 @@
-+From 381a730182f1d174e1950cd4e63e885b1c302051 Mon Sep 17 00:00:00 2001
-+From: Tobias Waldekranz <tobias@waldekranz.com>
-+Date: Mon, 24 Jan 2022 22:09:43 +0100
-+Subject: net: dsa: Move VLAN filtering syncing out of dsa_switch_bridge_leave
++++ b/target/linux/generic/backport-5.15/775-v6.0-05-net-ethernet-stmicro-stmmac-permit-MTU-change-with-i.patch
+@@ -0,0 +1,73 @@
++From 3470079687448abac42deb62774253be1d6bdef3 Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Sat, 23 Jul 2022 16:29:33 +0200
++Subject: [PATCH 5/5] net: ethernet: stmicro: stmmac: permit MTU change with
++ interface up
 +
-+Most of dsa_switch_bridge_leave was, in fact, dealing with the syncing
-+of VLAN filtering for switches on which that is a global
-+setting. Separate the two phases to prepare for the cross-chip related
-+bugfix in the following commit.
++Remove the limitation where the interface needs to be down to change
++MTU by releasing and opening the stmmac driver to set the new MTU.
++Also call the set_filter function to correctly init the port.
++This permits to remove the EBUSY error while the ethernet port is
++running permitting a correct MTU change if for example a DSA request
++a MTU change for a switch CPU port.
 +
-+Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
-+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 +---
-+ net/dsa/switch.c | 38 +++++++++++++++++++++++++-------------
-+ 1 file changed, 25 insertions(+), 13 deletions(-)
++ .../net/ethernet/stmicro/stmmac/stmmac_main.c | 30 +++++++++++++++----
++ 1 file changed, 24 insertions(+), 6 deletions(-)
 +
-+--- a/net/dsa/switch.c
-++++ b/net/dsa/switch.c
-+@@ -113,25 +113,14 @@ static int dsa_switch_bridge_join(struct
-+ 	return dsa_tag_8021q_bridge_join(ds, info);
-+ }
-+ 
-+-static int dsa_switch_bridge_leave(struct dsa_switch *ds,
-+-				   struct dsa_notifier_bridge_info *info)
-++static int dsa_switch_sync_vlan_filtering(struct dsa_switch *ds,
-++					  struct dsa_notifier_bridge_info *info)
++--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
++@@ -5611,18 +5611,15 @@ static int stmmac_change_mtu(struct net_
 + {
-+-	struct dsa_switch_tree *dst = ds->dst;
-+ 	struct netlink_ext_ack extack = {0};
-+ 	bool change_vlan_filtering = false;
-+ 	bool vlan_filtering;
-+ 	int err, port;
++ 	struct stmmac_priv *priv = netdev_priv(dev);
++ 	int txfifosz = priv->plat->tx_fifo_size;
+++	struct stmmac_dma_conf *dma_conf;
++ 	const int mtu = new_mtu;
+++	int ret;
 + 
-+-	if (dst->index == info->tree_index && ds->index == info->sw_index &&
-+-	    ds->ops->port_bridge_leave)
-+-		ds->ops->port_bridge_leave(ds, info->port, info->br);
-+-
-+-	if ((dst->index != info->tree_index || ds->index != info->sw_index) &&
-+-	    ds->ops->crosschip_bridge_leave)
-+-		ds->ops->crosschip_bridge_leave(ds, info->tree_index,
-+-						info->sw_index, info->port,
-+-						info->br);
++ 	if (txfifosz == 0)
++ 		txfifosz = priv->dma_cap.tx_fifo_size;
++ 
++ 	txfifosz /= priv->plat->tx_queues_to_use;
++ 
++-	if (netif_running(dev)) {
++-		netdev_err(priv->dev, "must be stopped to change its MTU\n");
++-		return -EBUSY;
++-	}
 +-
-+ 	if (ds->needs_standalone_vlan_filtering && !br_vlan_enabled(info->br)) {
-+ 		change_vlan_filtering = true;
-+ 		vlan_filtering = true;
-+@@ -172,6 +161,29 @@ static int dsa_switch_bridge_leave(struc
-+ 			return err;
-+ 	}
++ 	if (stmmac_xdp_is_enabled(priv) && new_mtu > ETH_DATA_LEN) {
++ 		netdev_dbg(priv->dev, "Jumbo frames not supported for XDP\n");
++ 		return -EINVAL;
++@@ -5634,8 +5631,29 @@ static int stmmac_change_mtu(struct net_
++ 	if ((txfifosz < new_mtu) || (new_mtu > BUF_SIZE_16KiB))
++ 		return -EINVAL;
 + 
-++	return 0;
-++}
-++
-++static int dsa_switch_bridge_leave(struct dsa_switch *ds,
-++				   struct dsa_notifier_bridge_info *info)
-++{
-++	struct dsa_switch_tree *dst = ds->dst;
-++	int err;
-++
-++	if (dst->index == info->tree_index && ds->index == info->sw_index &&
-++	    ds->ops->port_bridge_leave)
-++		ds->ops->port_bridge_leave(ds, info->port, info->br);
++-	dev->mtu = mtu;
+++	if (netif_running(dev)) {
+++		netdev_dbg(priv->dev, "restarting interface to change its MTU\n");
+++		/* Try to allocate the new DMA conf with the new mtu */
+++		dma_conf = stmmac_setup_dma_desc(priv, mtu);
+++		if (IS_ERR(dma_conf)) {
+++			netdev_err(priv->dev, "failed allocating new dma conf for new MTU %d\n",
+++				   mtu);
+++			return PTR_ERR(dma_conf);
+++		}
 ++
-++	if ((dst->index != info->tree_index || ds->index != info->sw_index) &&
-++	    ds->ops->crosschip_bridge_leave)
-++		ds->ops->crosschip_bridge_leave(ds, info->tree_index,
-++						info->sw_index, info->port,
-++						info->br);
+++		stmmac_release(dev);
 ++
-++	err = dsa_switch_sync_vlan_filtering(ds, info);
-++	if (err)
-++		return err;
+++		ret = __stmmac_open(dev, dma_conf);
+++		kfree(dma_conf);
+++		if (ret) {
+++			netdev_err(priv->dev, "failed reopening the interface after MTU change\n");
+++			return ret;
+++		}
 ++
-+ 	return dsa_tag_8021q_bridge_leave(ds, info);
-+ }
+++		stmmac_set_rx_mode(dev);
+++	}
 + 
-diff --git a/target/linux/generic/backport-5.15/773-v5.18-2-net-dsa-Avoid-cross-chip-syncing-of-VLAN-filtering.patch b/target/linux/generic/backport-5.15/773-v5.18-2-net-dsa-Avoid-cross-chip-syncing-of-VLAN-filtering.patch
+++	dev->mtu = mtu;
++ 	netdev_update_features(dev);
++ 
++ 	return 0;
+diff --git a/target/linux/generic/backport-5.15/776-v6.1-01-net-dsa-qca8k-fix-inband-mgmt-for-big-endian-systems.patch b/target/linux/generic/backport-5.15/776-v6.1-01-net-dsa-qca8k-fix-inband-mgmt-for-big-endian-systems.patch
 new file mode 100644
-index 0000000000..cdddbcf14e
+index 0000000000..4f0d95f456
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/773-v5.18-2-net-dsa-Avoid-cross-chip-syncing-of-VLAN-filtering.patch
-@@ -0,0 +1,52 @@
-+From 108dc8741c203e9d6ce4e973367f1bac20c7192b Mon Sep 17 00:00:00 2001
-+From: Tobias Waldekranz <tobias@waldekranz.com>
-+Date: Mon, 24 Jan 2022 22:09:44 +0100
-+Subject: net: dsa: Avoid cross-chip syncing of VLAN filtering
-+
-+Changes to VLAN filtering are not applicable to cross-chip
-+notifications.
-+
-+On a system like this:
-+
-+.-----.   .-----.   .-----.
-+| sw1 +---+ sw2 +---+ sw3 |
-+'-1-2-'   '-1-2-'   '-1-2-'
-+
-+Before this change, upon sw1p1 leaving a bridge, a call to
-+dsa_port_vlan_filtering would also be made to sw2p1 and sw3p1.
-+
-+In this scenario:
++++ b/target/linux/generic/backport-5.15/776-v6.1-01-net-dsa-qca8k-fix-inband-mgmt-for-big-endian-systems.patch
+@@ -0,0 +1,156 @@
++From a2550d3ce53c68f54042bc5e468c4d07491ffe0e Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Wed, 12 Oct 2022 19:18:36 +0200
++Subject: [PATCH 1/2] net: dsa: qca8k: fix inband mgmt for big-endian systems
 +
-+.---------.   .-----.   .-----.
-+|   sw1   +---+ sw2 +---+ sw3 |
-+'-1-2-3-4-'   '-1-2-'   '-1-2-'
++The header and the data of the skb for the inband mgmt requires
++to be in little-endian. This is problematic for big-endian system
++as the mgmt header is written in the cpu byte order.
 +
-+When sw1p4 would leave a bridge, dsa_port_vlan_filtering would be
-+called for sw2 and sw3 with a non-existing port - leading to array
-+out-of-bounds accesses and crashes on mv88e6xxx.
++Fix this by converting each value for the mgmt header and data to
++little-endian, and convert to cpu byte order the mgmt header and
++data sent by the switch.
 +
-+Fixes: d371b7c92d19 ("net: dsa: Unset vlan_filtering when ports leave the bridge")
-+Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
-+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Fixes: 5950c7c0a68c ("net: dsa: qca8k: add support for mgmt read/write in Ethernet packet")
++Tested-by: Pawel Dembicki <paweldembicki@gmail.com>
++Tested-by: Lech Perczak <lech.perczak@gmail.com>
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
++Reviewed-by: Lech Perczak <lech.perczak@gmail.com>
 +Signed-off-by: David S. Miller <davem@davemloft.net>
 +---
-+ net/dsa/switch.c | 8 +++++---
-+ 1 file changed, 5 insertions(+), 3 deletions(-)
-+
-+--- a/net/dsa/switch.c
-++++ b/net/dsa/switch.c
-+@@ -180,9 +180,11 @@ static int dsa_switch_bridge_leave(struc
-+ 						info->sw_index, info->port,
-+ 						info->br);
++ drivers/net/dsa/qca/qca8k-8xxx.c | 63 ++++++++++++++++++++++++--------
++ include/linux/dsa/tag_qca.h      |  6 +--
++ 2 files changed, 50 insertions(+), 19 deletions(-)
++
++--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
++@@ -137,27 +137,42 @@ static void qca8k_rw_reg_ack_handler(str
++ 	struct qca8k_mgmt_eth_data *mgmt_eth_data;
++ 	struct qca8k_priv *priv = ds->priv;
++ 	struct qca_mgmt_ethhdr *mgmt_ethhdr;
+++	u32 command;
++ 	u8 len, cmd;
+++	int i;
 + 
-+-	err = dsa_switch_sync_vlan_filtering(ds, info);
-+-	if (err)
-+-		return err;
-++	if (ds->dst->index == info->tree_index && ds->index == info->sw_index) {
-++		err = dsa_switch_sync_vlan_filtering(ds, info);
-++		if (err)
-++			return err;
++ 	mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb_mac_header(skb);
++ 	mgmt_eth_data = &priv->mgmt_eth_data;
++ 
++-	cmd = FIELD_GET(QCA_HDR_MGMT_CMD, mgmt_ethhdr->command);
++-	len = FIELD_GET(QCA_HDR_MGMT_LENGTH, mgmt_ethhdr->command);
+++	command = get_unaligned_le32(&mgmt_ethhdr->command);
+++	cmd = FIELD_GET(QCA_HDR_MGMT_CMD, command);
+++	len = FIELD_GET(QCA_HDR_MGMT_LENGTH, command);
++ 
++ 	/* Make sure the seq match the requested packet */
++-	if (mgmt_ethhdr->seq == mgmt_eth_data->seq)
+++	if (get_unaligned_le32(&mgmt_ethhdr->seq) == mgmt_eth_data->seq)
++ 		mgmt_eth_data->ack = true;
++ 
++ 	if (cmd == MDIO_READ) {
++-		mgmt_eth_data->data[0] = mgmt_ethhdr->mdio_data;
+++		u32 *val = mgmt_eth_data->data;
+++
+++		*val = get_unaligned_le32(&mgmt_ethhdr->mdio_data);
++ 
++ 		/* Get the rest of the 12 byte of data.
++ 		 * The read/write function will extract the requested data.
++ 		 */
++-		if (len > QCA_HDR_MGMT_DATA1_LEN)
++-			memcpy(mgmt_eth_data->data + 1, skb->data,
++-			       QCA_HDR_MGMT_DATA2_LEN);
+++		if (len > QCA_HDR_MGMT_DATA1_LEN) {
+++			__le32 *data2 = (__le32 *)skb->data;
+++			int data_len = min_t(int, QCA_HDR_MGMT_DATA2_LEN,
+++					     len - QCA_HDR_MGMT_DATA1_LEN);
+++
+++			val++;
+++
+++			for (i = sizeof(u32); i <= data_len; i += sizeof(u32)) {
+++				*val = get_unaligned_le32(data2);
+++				val++;
+++				data2++;
+++			}
+++		}
++ 	}
++ 
++ 	complete(&mgmt_eth_data->rw_done);
++@@ -169,8 +184,10 @@ static struct sk_buff *qca8k_alloc_mdio_
++ 	struct qca_mgmt_ethhdr *mgmt_ethhdr;
++ 	unsigned int real_len;
++ 	struct sk_buff *skb;
++-	u32 *data2;
+++	__le32 *data2;
+++	u32 command;
++ 	u16 hdr;
+++	int i;
++ 
++ 	skb = dev_alloc_skb(QCA_HDR_MGMT_PKT_LEN);
++ 	if (!skb)
++@@ -199,20 +216,32 @@ static struct sk_buff *qca8k_alloc_mdio_
++ 	hdr |= FIELD_PREP(QCA_HDR_XMIT_DP_BIT, BIT(0));
++ 	hdr |= FIELD_PREP(QCA_HDR_XMIT_CONTROL, QCA_HDR_XMIT_TYPE_RW_REG);
++ 
++-	mgmt_ethhdr->command = FIELD_PREP(QCA_HDR_MGMT_ADDR, reg);
++-	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_LENGTH, real_len);
++-	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CMD, cmd);
++-	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CHECK_CODE,
+++	command = FIELD_PREP(QCA_HDR_MGMT_ADDR, reg);
+++	command |= FIELD_PREP(QCA_HDR_MGMT_LENGTH, real_len);
+++	command |= FIELD_PREP(QCA_HDR_MGMT_CMD, cmd);
+++	command |= FIELD_PREP(QCA_HDR_MGMT_CHECK_CODE,
++ 					   QCA_HDR_MGMT_CHECK_CODE_VAL);
++ 
+++	put_unaligned_le32(command, &mgmt_ethhdr->command);
+++
++ 	if (cmd == MDIO_WRITE)
++-		mgmt_ethhdr->mdio_data = *val;
+++		put_unaligned_le32(*val, &mgmt_ethhdr->mdio_data);
++ 
++ 	mgmt_ethhdr->hdr = htons(hdr);
++ 
++ 	data2 = skb_put_zero(skb, QCA_HDR_MGMT_DATA2_LEN + QCA_HDR_MGMT_PADDING_LEN);
++-	if (cmd == MDIO_WRITE && len > QCA_HDR_MGMT_DATA1_LEN)
++-		memcpy(data2, val + 1, len - QCA_HDR_MGMT_DATA1_LEN);
+++	if (cmd == MDIO_WRITE && len > QCA_HDR_MGMT_DATA1_LEN) {
+++		int data_len = min_t(int, QCA_HDR_MGMT_DATA2_LEN,
+++				     len - QCA_HDR_MGMT_DATA1_LEN);
+++
+++		val++;
+++
+++		for (i = sizeof(u32); i <= data_len; i += sizeof(u32)) {
+++			put_unaligned_le32(*val, data2);
+++			data2++;
+++			val++;
+++		}
 ++	}
 + 
-+ 	return dsa_tag_8021q_bridge_leave(ds, info);
++ 	return skb;
++ }
++@@ -220,9 +249,11 @@ static struct sk_buff *qca8k_alloc_mdio_
++ static void qca8k_mdio_header_fill_seq_num(struct sk_buff *skb, u32 seq_num)
++ {
++ 	struct qca_mgmt_ethhdr *mgmt_ethhdr;
+++	u32 seq;
++ 
+++	seq = FIELD_PREP(QCA_HDR_MGMT_SEQ_NUM, seq_num);
++ 	mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb->data;
++-	mgmt_ethhdr->seq = FIELD_PREP(QCA_HDR_MGMT_SEQ_NUM, seq_num);
+++	put_unaligned_le32(seq, &mgmt_ethhdr->seq);
 + }
-diff --git a/target/linux/generic/backport-5.15/774-v5.16-01-net-dsa-rtl8366rb-Support-bridge-offloading.patch b/target/linux/generic/backport-5.15/774-v5.16-01-net-dsa-rtl8366rb-Support-bridge-offloading.patch
++ 
++ static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++--- a/include/linux/dsa/tag_qca.h
+++++ b/include/linux/dsa/tag_qca.h
++@@ -56,9 +56,9 @@
++ 
++ /* Special struct emulating a Ethernet header */
++ struct qca_mgmt_ethhdr {
++-	u32 command;		/* command bit 31:0 */
++-	u32 seq;		/* seq 63:32 */
++-	u32 mdio_data;		/* first 4byte mdio */
+++	__le32 command;		/* command bit 31:0 */
+++	__le32 seq;		/* seq 63:32 */
+++	__le32 mdio_data;		/* first 4byte mdio */
++ 	__be16 hdr;		/* qca hdr */
++ } __packed;
++ 
+diff --git a/target/linux/generic/backport-5.15/776-v6.1-02-net-dsa-qca8k-fix-ethtool-autocast-mib-for-big-endia.patch b/target/linux/generic/backport-5.15/776-v6.1-02-net-dsa-qca8k-fix-ethtool-autocast-mib-for-big-endia.patch
 new file mode 100644
-index 0000000000..78570c5e6e
+index 0000000000..d13014bf93
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/774-v5.16-01-net-dsa-rtl8366rb-Support-bridge-offloading.patch
-@@ -0,0 +1,141 @@
-+From c9111895fd38dadf125e07be627778a9950d8d77 Mon Sep 17 00:00:00 2001
-+From: DENG Qingfang <dqfext@gmail.com>
-+Date: Sun, 26 Sep 2021 00:59:24 +0200
-+Subject: [PATCH 01/11] net: dsa: rtl8366rb: Support bridge offloading
-+MIME-Version: 1.0
-+Content-Type: text/plain; charset=UTF-8
-+Content-Transfer-Encoding: 8bit
++++ b/target/linux/generic/backport-5.15/776-v6.1-02-net-dsa-qca8k-fix-ethtool-autocast-mib-for-big-endia.patch
+@@ -0,0 +1,81 @@
++From 0d4636f7d72df3179b20a2d32b647881917a5e2a Mon Sep 17 00:00:00 2001
++From: Christian Marangi <ansuelsmth@gmail.com>
++Date: Wed, 12 Oct 2022 19:18:37 +0200
++Subject: [PATCH 2/2] net: dsa: qca8k: fix ethtool autocast mib for big-endian
++ systems
 +
-+Use port isolation registers to configure bridge offloading.
++The switch sends autocast mib in little-endian. This is problematic for
++big-endian system as the values needs to be converted.
 +
-+Tested on the D-Link DIR-685, switching between ports and
-+sniffing ports to make sure no packets leak.
++Fix this by converting each mib value to cpu byte order.
 +
-+Cc: Vladimir Oltean <olteanv@gmail.com>
-+Cc: Mauri Sandberg <sandberg@mailfence.com>
-+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Reviewed-by: Alvin Å ipraga <alsi@bang-olufsen.dk>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: DENG Qingfang <dqfext@gmail.com>
-+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
++Fixes: 5c957c7ca78c ("net: dsa: qca8k: add support for mib autocast in Ethernet packet")
++Tested-by: Pawel Dembicki <paweldembicki@gmail.com>
++Tested-by: Lech Perczak <lech.perczak@gmail.com>
++Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
 +Signed-off-by: David S. Miller <davem@davemloft.net>
 +---
-+ drivers/net/dsa/rtl8366rb.c | 86 +++++++++++++++++++++++++++++++++++++
-+ 1 file changed, 86 insertions(+)
++ drivers/net/dsa/qca/qca8k-8xxx.c | 20 ++++++++------------
++ include/linux/dsa/tag_qca.h      |  2 +-
++ 2 files changed, 9 insertions(+), 13 deletions(-)
 +
-+--- a/drivers/net/dsa/rtl8366rb.c
-++++ b/drivers/net/dsa/rtl8366rb.c
-+@@ -300,6 +300,13 @@
-+ #define RTL8366RB_INTERRUPT_STATUS_REG	0x0442
-+ #define RTL8366RB_NUM_INTERRUPT		14 /* 0..13 */
++--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
++@@ -1668,9 +1668,9 @@ static void qca8k_mib_autocast_handler(s
++ 	struct qca8k_priv *priv = ds->priv;
++ 	const struct qca8k_mib_desc *mib;
++ 	struct mib_ethhdr *mib_ethhdr;
++-	int i, mib_len, offset = 0;
++-	u64 *data;
+++	__le32 *data2;
++ 	u8 port;
+++	int i;
 + 
-++/* Port isolation registers */
-++#define RTL8366RB_PORT_ISO_BASE		0x0F08
-++#define RTL8366RB_PORT_ISO(pnum)	(RTL8366RB_PORT_ISO_BASE + (pnum))
-++#define RTL8366RB_PORT_ISO_EN		BIT(0)
-++#define RTL8366RB_PORT_ISO_PORTS_MASK	GENMASK(7, 1)
-++#define RTL8366RB_PORT_ISO_PORTS(pmask)	((pmask) << 1)
-++
-+ /* bits 0..5 enable force when cleared */
-+ #define RTL8366RB_MAC_FORCE_CTRL_REG	0x0F11
++ 	mib_ethhdr = (struct mib_ethhdr *)skb_mac_header(skb);
++ 	mib_eth_data = &priv->mib_eth_data;
++@@ -1682,28 +1682,24 @@ static void qca8k_mib_autocast_handler(s
++ 	if (port != mib_eth_data->req_port)
++ 		goto exit;
 + 
-+@@ -835,6 +842,21 @@ static int rtl8366rb_setup(struct dsa_sw
-+ 	if (ret)
-+ 		return ret;
++-	data = mib_eth_data->data;
+++	data2 = (__le32 *)skb->data;
 + 
-++	/* Isolate all user ports so they can only send packets to itself and the CPU port */
-++	for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) {
-++		ret = regmap_write(smi->map, RTL8366RB_PORT_ISO(i),
-++				   RTL8366RB_PORT_ISO_PORTS(BIT(RTL8366RB_PORT_NUM_CPU)) |
-++				   RTL8366RB_PORT_ISO_EN);
-++		if (ret)
-++			return ret;
-++	}
-++	/* CPU port can send packets to all ports */
-++	ret = regmap_write(smi->map, RTL8366RB_PORT_ISO(RTL8366RB_PORT_NUM_CPU),
-++			   RTL8366RB_PORT_ISO_PORTS(dsa_user_ports(ds)) |
-++			   RTL8366RB_PORT_ISO_EN);
-++	if (ret)
-++		return ret;
-++
-+ 	/* Set up the "green ethernet" feature */
-+ 	ret = rtl8366rb_jam_table(rtl8366rb_green_jam,
-+ 				  ARRAY_SIZE(rtl8366rb_green_jam), smi, false);
-+@@ -1127,6 +1149,68 @@ rtl8366rb_port_disable(struct dsa_switch
-+ 	rb8366rb_set_port_led(smi, port, false);
-+ }
++ 	for (i = 0; i < priv->info->mib_count; i++) {
++ 		mib = &ar8327_mib[i];
 + 
-++static int
-++rtl8366rb_port_bridge_join(struct dsa_switch *ds, int port,
-++			   struct net_device *bridge)
-++{
-++	struct realtek_smi *smi = ds->priv;
-++	unsigned int port_bitmap = 0;
-++	int ret, i;
++ 		/* First 3 mib are present in the skb head */
++ 		if (i < 3) {
++-			data[i] = mib_ethhdr->data[i];
+++			mib_eth_data->data[i] = get_unaligned_le32(mib_ethhdr->data + i);
++ 			continue;
++ 		}
++ 
++-		mib_len = sizeof(uint32_t);
++-
++ 		/* Some mib are 64 bit wide */
++ 		if (mib->size == 2)
++-			mib_len = sizeof(uint64_t);
++-
++-		/* Copy the mib value from packet to the */
++-		memcpy(data + i, skb->data + offset, mib_len);
+++			mib_eth_data->data[i] = get_unaligned_le64((__le64 *)data2);
+++		else
+++			mib_eth_data->data[i] = get_unaligned_le32(data2);
++ 
++-		/* Set the offset for the next mib */
++-		offset += mib_len;
+++		data2 += mib->size;
++ 	}
++ 
++ exit:
++--- a/include/linux/dsa/tag_qca.h
+++++ b/include/linux/dsa/tag_qca.h
++@@ -68,7 +68,7 @@ enum mdio_cmd {
++ };
++ 
++ struct mib_ethhdr {
++-	u32 data[3];		/* first 3 mib counter */
+++	__le32 data[3];		/* first 3 mib counter */
++ 	__be16 hdr;		/* qca hdr */
++ } __packed;
++ 
+diff --git a/target/linux/generic/backport-5.15/780-v5.16-bus-mhi-pci_generic-Introduce-Sierra-EM919X-support.patch b/target/linux/generic/backport-5.15/780-v5.16-bus-mhi-pci_generic-Introduce-Sierra-EM919X-support.patch
+new file mode 100644
+index 0000000000..5fe10e0caa
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/780-v5.16-bus-mhi-pci_generic-Introduce-Sierra-EM919X-support.patch
+@@ -0,0 +1,80 @@
++From 789d3eeb2367f92193a0882f7cdab03f0f9d6930 Mon Sep 17 00:00:00 2001
++From: Thomas Perrot <thomas.perrot@bootlin.com>
++Date: Thu, 16 Dec 2021 13:42:27 +0530
++Subject: [PATCH] bus: mhi: pci_generic: Introduce Sierra EM919X support
++
++Add support for EM919X modems, this modem series is based on SDX55
++qcom chip.
++
++It is mandatory to use the same ring for control+data and diag events.
++
++Link: https://lore.kernel.org/r/20211123081541.648426-1-thomas.perrot@bootlin.com
++Tested-by: Aleksander Morgado <aleksander@aleksander.es>
++Reviewed-by: Manivannan Sadhasivam <mani@kernel.org>
++Signed-off-by: Thomas Perrot <thomas.perrot@bootlin.com>
++Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
++Link: https://lore.kernel.org/r/20211216081227.237749-11-manivannan.sadhasivam@linaro.org
++Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
++---
++ drivers/bus/mhi/pci_generic.c | 43 +++++++++++++++++++++++++++++++++++
++ 1 file changed, 43 insertions(+)
++
++--- a/drivers/bus/mhi/pci_generic.c
+++++ b/drivers/bus/mhi/pci_generic.c
++@@ -406,6 +406,46 @@ static const struct mhi_pci_dev_info mhi
++ 	.mru_default = 32768,
++ };
++ 
+++static const struct mhi_channel_config mhi_sierra_em919x_channels[] = {
+++	MHI_CHANNEL_CONFIG_UL_SBL(2, "SAHARA", 32, 0),
+++	MHI_CHANNEL_CONFIG_DL_SBL(3, "SAHARA", 256, 0),
+++	MHI_CHANNEL_CONFIG_UL(4, "DIAG", 32, 0),
+++	MHI_CHANNEL_CONFIG_DL(5, "DIAG", 32, 0),
+++	MHI_CHANNEL_CONFIG_UL(12, "MBIM", 128, 0),
+++	MHI_CHANNEL_CONFIG_DL(13, "MBIM", 128, 0),
+++	MHI_CHANNEL_CONFIG_UL(14, "QMI", 32, 0),
+++	MHI_CHANNEL_CONFIG_DL(15, "QMI", 32, 0),
+++	MHI_CHANNEL_CONFIG_UL(32, "DUN", 32, 0),
+++	MHI_CHANNEL_CONFIG_DL(33, "DUN", 32, 0),
+++	MHI_CHANNEL_CONFIG_HW_UL(100, "IP_HW0", 512, 1),
+++	MHI_CHANNEL_CONFIG_HW_DL(101, "IP_HW0", 512, 2),
+++};
 ++
-++	/* Loop over all other ports than the current one */
-++	for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) {
-++		/* Current port handled last */
-++		if (i == port)
-++			continue;
-++		/* Not on this bridge */
-++		if (dsa_to_port(ds, i)->bridge_dev != bridge)
-++			continue;
-++		/* Join this port to each other port on the bridge */
-++		ret = regmap_update_bits(smi->map, RTL8366RB_PORT_ISO(i),
-++					 RTL8366RB_PORT_ISO_PORTS(BIT(port)),
-++					 RTL8366RB_PORT_ISO_PORTS(BIT(port)));
-++		if (ret)
-++			dev_err(smi->dev, "failed to join port %d\n", port);
+++static struct mhi_event_config modem_sierra_em919x_mhi_events[] = {
+++	/* first ring is control+data and DIAG ring */
+++	MHI_EVENT_CONFIG_CTRL(0, 2048),
+++	/* Hardware channels request dedicated hardware event rings */
+++	MHI_EVENT_CONFIG_HW_DATA(1, 2048, 100),
+++	MHI_EVENT_CONFIG_HW_DATA(2, 2048, 101)
+++};
 ++
-++		port_bitmap |= BIT(i);
-++	}
+++static const struct mhi_controller_config modem_sierra_em919x_config = {
+++	.max_channels = 128,
+++	.timeout_ms = 24000,
+++	.num_channels = ARRAY_SIZE(mhi_sierra_em919x_channels),
+++	.ch_cfg = mhi_sierra_em919x_channels,
+++	.num_events = ARRAY_SIZE(modem_sierra_em919x_mhi_events),
+++	.event_cfg = modem_sierra_em919x_mhi_events,
+++};
 ++
-++	/* Set the bits for the ports we can access */
-++	return regmap_update_bits(smi->map, RTL8366RB_PORT_ISO(port),
-++				  RTL8366RB_PORT_ISO_PORTS(port_bitmap),
-++				  RTL8366RB_PORT_ISO_PORTS(port_bitmap));
-++}
+++static const struct mhi_pci_dev_info mhi_sierra_em919x_info = {
+++	.name = "sierra-em919x",
+++	.config = &modem_sierra_em919x_config,
+++	.bar_num = MHI_PCI_DEFAULT_BAR_NUM,
+++	.dma_data_width = 32,
+++	.sideband_wake = false,
+++};
 ++
++ static const struct mhi_channel_config mhi_telit_fn980_hw_v1_channels[] = {
++ 	MHI_CHANNEL_CONFIG_UL(14, "QMI", 32, 0),
++ 	MHI_CHANNEL_CONFIG_DL(15, "QMI", 32, 0),
++@@ -480,6 +520,9 @@ static const struct mhi_pci_dev_info mhi
++ };
++ 
++ static const struct pci_device_id mhi_pci_id_table[] = {
+++	/* EM919x (sdx55), use the same vid:pid as qcom-sdx55m */
+++	{ PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0306, 0x18d7, 0x0200),
+++		.driver_data = (kernel_ulong_t) &mhi_sierra_em919x_info },
++ 	/* Telit FN980 hardware revision v1 */
++ 	{ PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0306, 0x1C5D, 0x2000),
++ 		.driver_data = (kernel_ulong_t) &mhi_telit_fn980_hw_v1_info },
+diff --git a/target/linux/generic/backport-5.15/781-v6.1-bus-mhi-host-always-print-detected-modem-name.patch b/target/linux/generic/backport-5.15/781-v6.1-bus-mhi-host-always-print-detected-modem-name.patch
+new file mode 100644
+index 0000000000..e974c21cb7
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/781-v6.1-bus-mhi-host-always-print-detected-modem-name.patch
+@@ -0,0 +1,37 @@
++From f369e9ad52ec9361827e21a631b7198c9fca438e Mon Sep 17 00:00:00 2001
++From: Koen Vandeputte <koen.vandeputte@citymesh.com>
++Date: Wed, 31 Aug 2022 12:03:49 +0200
++Subject: [PATCH] bus: mhi: host: always print detected modem name
++
++This harmless print provides a very easy way of knowing
++if the modem is detected properly during probing.
++
++Promote it to an informational print so no hassle is required
++enabling kernel debugging info to obtain it.
++
++The rationale here is that:
++On a lot of low-storage embedded devices, extensive kernel
++debugging info is not always present as this would
++increase it's size to much causing partition size issues.
++
++Signed-off-by: Koen Vandeputte <koen.vandeputte@citymesh.com>
++Reviewed-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
++Reviewed-by: Loic Poulain <loic.poulain@linaro.org>
++Link: https://lore.kernel.org/r/20220831100349.1488762-1-koen.vandeputte@citymesh.com
++[mani: added missing review tags]
++Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
++---
++ drivers/bus/mhi/pci_generic.c | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
++
++--- a/drivers/bus/mhi/pci_generic.c
+++++ b/drivers/bus/mhi/pci_generic.c
++@@ -806,7 +806,7 @@ static int mhi_pci_probe(struct pci_dev
++ 	struct mhi_controller *mhi_cntrl;
++ 	int err;
++ 
++-	dev_dbg(&pdev->dev, "MHI PCI device found: %s\n", info->name);
+++	dev_info(&pdev->dev, "MHI PCI device found: %s\n", info->name);
++ 
++ 	/* mhi_pdev.mhi_cntrl must be zero-initialized */
++ 	mhi_pdev = devm_kzalloc(&pdev->dev, sizeof(*mhi_pdev), GFP_KERNEL);
+diff --git a/target/linux/generic/backport-5.15/782-v6.1-net-dsa-mt7530-add-support-for-in-band-link-status.patch b/target/linux/generic/backport-5.15/782-v6.1-net-dsa-mt7530-add-support-for-in-band-link-status.patch
+new file mode 100644
+index 0000000000..7bb6530266
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/782-v6.1-net-dsa-mt7530-add-support-for-in-band-link-status.patch
+@@ -0,0 +1,130 @@
++From e19de30d20809af3221ef8a2648b8a8a52e02d90 Mon Sep 17 00:00:00 2001
++From: Daniel Golle <daniel@makrotopia.org>
++Date: Wed, 21 Sep 2022 01:23:14 +0100
++Subject: [PATCH 1/1] net: dsa: mt7530: add support for in-band link status
++
++Read link status from SGMII PCS for in-band managed 2500Base-X and
++1000Base-X connection on a MAC port of the MT7531. This is needed to
++get the SFP cage working which is connected to SGMII interface of
++port 5 of the MT7531 switch IC on the Bananapi BPi-R3 board.
++While at it also handle an_complete for both the autoneg and the
++non-autoneg codepath.
++
++Signed-off-by: Daniel Golle <daniel@makrotopia.org>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/dsa/mt7530.c | 50 +++++++++++++++++++++++++++++-----------
++ drivers/net/dsa/mt7530.h |  1 +
++ 2 files changed, 38 insertions(+), 13 deletions(-)
++
++--- a/drivers/net/dsa/mt7530.c
+++++ b/drivers/net/dsa/mt7530.c
++@@ -2699,9 +2699,6 @@ mt7531_mac_config(struct dsa_switch *ds,
++ 	case PHY_INTERFACE_MODE_NA:
++ 	case PHY_INTERFACE_MODE_1000BASEX:
++ 	case PHY_INTERFACE_MODE_2500BASEX:
++-		if (phylink_autoneg_inband(mode))
++-			return -EINVAL;
++-
++ 		return mt7531_sgmii_setup_mode_force(priv, port, interface);
++ 	default:
++ 		return -EINVAL;
++@@ -2777,13 +2774,6 @@ unsupported:
++ 		return;
++ 	}
++ 
++-	if (phylink_autoneg_inband(mode) &&
++-	    state->interface != PHY_INTERFACE_MODE_SGMII) {
++-		dev_err(ds->dev, "%s: in-band negotiation unsupported\n",
++-			__func__);
++-		return;
++-	}
++-
++ 	mcr_cur = mt7530_read(priv, MT7530_PMCR_P(port));
++ 	mcr_new = mcr_cur;
++ 	mcr_new &= ~PMCR_LINK_SETTINGS_MASK;
++@@ -2920,6 +2910,9 @@ static void mt753x_phylink_get_caps(stru
++ 	config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
++ 				   MAC_10 | MAC_100 | MAC_1000FD;
++ 
+++	if ((priv->id == ID_MT7531) && mt753x_is_mac_port(port))
+++		config->mac_capabilities |= MAC_2500FD;
+++
++ 	/* This driver does not make use of the speed, duplex, pause or the
++ 	 * advertisement in its mac_config, so it is safe to mark this driver
++ 	 * as non-legacy.
++@@ -2985,6 +2978,7 @@ mt7531_sgmii_pcs_get_state_an(struct mt7
++ 
++ 	status = mt7530_read(priv, MT7531_PCS_CONTROL_1(port));
++ 	state->link = !!(status & MT7531_SGMII_LINK_STATUS);
+++	state->an_complete = !!(status & MT7531_SGMII_AN_COMPLETE);
++ 	if (state->interface == PHY_INTERFACE_MODE_SGMII &&
++ 	    (status & MT7531_SGMII_AN_ENABLE)) {
++ 		val = mt7530_read(priv, MT7531_PCS_SPEED_ABILITY(port));
++@@ -3015,16 +3009,44 @@ mt7531_sgmii_pcs_get_state_an(struct mt7
++ 	return 0;
++ }
++ 
 ++static void
-++rtl8366rb_port_bridge_leave(struct dsa_switch *ds, int port,
-++			    struct net_device *bridge)
+++mt7531_sgmii_pcs_get_state_inband(struct mt7530_priv *priv, int port,
+++				  struct phylink_link_state *state)
 ++{
-++	struct realtek_smi *smi = ds->priv;
-++	unsigned int port_bitmap = 0;
-++	int ret, i;
+++	unsigned int val;
 ++
-++	/* Loop over all other ports than this one */
-++	for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) {
-++		/* Current port handled last */
-++		if (i == port)
-++			continue;
-++		/* Not on this bridge */
-++		if (dsa_to_port(ds, i)->bridge_dev != bridge)
-++			continue;
-++		/* Remove this port from any other port on the bridge */
-++		ret = regmap_update_bits(smi->map, RTL8366RB_PORT_ISO(i),
-++					 RTL8366RB_PORT_ISO_PORTS(BIT(port)), 0);
-++		if (ret)
-++			dev_err(smi->dev, "failed to leave port %d\n", port);
+++	val = mt7530_read(priv, MT7531_PCS_CONTROL_1(port));
+++	state->link = !!(val & MT7531_SGMII_LINK_STATUS);
+++	if (!state->link)
+++		return;
 ++
-++		port_bitmap |= BIT(i);
-++	}
+++	state->an_complete = state->link;
 ++
-++	/* Clear the bits for the ports we can not access, leave ourselves */
-++	regmap_update_bits(smi->map, RTL8366RB_PORT_ISO(port),
-++			   RTL8366RB_PORT_ISO_PORTS(port_bitmap), 0);
+++	if (state->interface == PHY_INTERFACE_MODE_2500BASEX)
+++		state->speed = SPEED_2500;
+++	else
+++		state->speed = SPEED_1000;
+++
+++	state->duplex = DUPLEX_FULL;
+++	state->pause = MLO_PAUSE_NONE;
 ++}
 ++
-+ static int rtl8366rb_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
++ static void mt7531_pcs_get_state(struct phylink_pcs *pcs,
++ 				 struct phylink_link_state *state)
 + {
-+ 	struct realtek_smi *smi = ds->priv;
-+@@ -1510,6 +1594,8 @@ static const struct dsa_switch_ops rtl83
-+ 	.get_strings = rtl8366_get_strings,
-+ 	.get_ethtool_stats = rtl8366_get_ethtool_stats,
-+ 	.get_sset_count = rtl8366_get_sset_count,
-++	.port_bridge_join = rtl8366rb_port_bridge_join,
-++	.port_bridge_leave = rtl8366rb_port_bridge_leave,
-+ 	.port_vlan_filtering = rtl8366_vlan_filtering,
-+ 	.port_vlan_add = rtl8366_vlan_add,
-+ 	.port_vlan_del = rtl8366_vlan_del,
-diff --git a/target/linux/generic/backport-5.15/774-v5.16-02-net-dsa-rtl8366-Drop-custom-VLAN-set-up.patch b/target/linux/generic/backport-5.15/774-v5.16-02-net-dsa-rtl8366-Drop-custom-VLAN-set-up.patch
-new file mode 100644
-index 0000000000..e61349a32c
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/774-v5.16-02-net-dsa-rtl8366-Drop-custom-VLAN-set-up.patch
-@@ -0,0 +1,118 @@
-+From 96cf10a8e7297065459473c081a6fb6432a22312 Mon Sep 17 00:00:00 2001
-+From: Linus Walleij <linus.walleij@linaro.org>
-+Date: Sun, 26 Sep 2021 00:59:25 +0200
-+Subject: [PATCH 02/11] net: dsa: rtl8366: Drop custom VLAN set-up
-+MIME-Version: 1.0
-+Content-Type: text/plain; charset=UTF-8
-+Content-Transfer-Encoding: 8bit
-+
-+This hacky default VLAN setup was done in order to direct
-+packets to the right ports and provide port isolation, both
-+which we now support properly using custom tags and proper
-+bridge port isolation.
-+
-+We can drop the custom VLAN code and leave all VLAN handling
-+alone, as users expect things to be. We can also drop
-+ds->configure_vlan_while_not_filtering = false; and let
-+the core deal with any VLANs it wants.
-+
-+Cc: Mauri Sandberg <sandberg@mailfence.com>
-+Cc: DENG Qingfang <dqfext@gmail.com>
-+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Reviewed-by: Alvin Å ipraga <alsi@bang-olufsen.dk>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/realtek-smi-core.h |  1 -
-+ drivers/net/dsa/rtl8366.c          | 48 ------------------------------
-+ drivers/net/dsa/rtl8366rb.c        |  4 +--
-+ 3 files changed, 1 insertion(+), 52 deletions(-)
-+
-+--- a/drivers/net/dsa/realtek-smi-core.h
-++++ b/drivers/net/dsa/realtek-smi-core.h
-+@@ -129,7 +129,6 @@ int rtl8366_set_pvid(struct realtek_smi
-+ int rtl8366_enable_vlan4k(struct realtek_smi *smi, bool enable);
-+ int rtl8366_enable_vlan(struct realtek_smi *smi, bool enable);
-+ int rtl8366_reset_vlan(struct realtek_smi *smi);
-+-int rtl8366_init_vlan(struct realtek_smi *smi);
-+ int rtl8366_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
-+ 			   struct netlink_ext_ack *extack);
-+ int rtl8366_vlan_add(struct dsa_switch *ds, int port,
-+--- a/drivers/net/dsa/rtl8366.c
-++++ b/drivers/net/dsa/rtl8366.c
-+@@ -292,54 +292,6 @@ int rtl8366_reset_vlan(struct realtek_sm
++ 	struct mt7530_priv *priv = pcs_to_mt753x_pcs(pcs)->priv;
++ 	int port = pcs_to_mt753x_pcs(pcs)->port;
++ 
++-	if (state->interface == PHY_INTERFACE_MODE_SGMII)
+++	if (state->interface == PHY_INTERFACE_MODE_SGMII) {
++ 		mt7531_sgmii_pcs_get_state_an(priv, port, state);
++-	else
++-		state->link = false;
+++		return;
+++	} else if ((state->interface == PHY_INTERFACE_MODE_1000BASEX) ||
+++		   (state->interface == PHY_INTERFACE_MODE_2500BASEX)) {
+++		mt7531_sgmii_pcs_get_state_inband(priv, port, state);
+++		return;
+++	}
+++
+++	state->link = false;
 + }
-+ EXPORT_SYMBOL_GPL(rtl8366_reset_vlan);
 + 
-+-int rtl8366_init_vlan(struct realtek_smi *smi)
-+-{
-+-	int port;
-+-	int ret;
-+-
-+-	ret = rtl8366_reset_vlan(smi);
-+-	if (ret)
-+-		return ret;
-+-
-+-	/* Loop over the available ports, for each port, associate
-+-	 * it with the VLAN (port+1)
-+-	 */
-+-	for (port = 0; port < smi->num_ports; port++) {
-+-		u32 mask;
-+-
-+-		if (port == smi->cpu_port)
-+-			/* For the CPU port, make all ports members of this
-+-			 * VLAN.
-+-			 */
-+-			mask = GENMASK((int)smi->num_ports - 1, 0);
-+-		else
-+-			/* For all other ports, enable itself plus the
-+-			 * CPU port.
-+-			 */
-+-			mask = BIT(port) | BIT(smi->cpu_port);
-+-
-+-		/* For each port, set the port as member of VLAN (port+1)
-+-		 * and untagged, except for the CPU port: the CPU port (5) is
-+-		 * member of VLAN 6 and so are ALL the other ports as well.
-+-		 * Use filter 0 (no filter).
-+-		 */
-+-		dev_info(smi->dev, "VLAN%d port mask for port %d, %08x\n",
-+-			 (port + 1), port, mask);
-+-		ret = rtl8366_set_vlan(smi, (port + 1), mask, mask, 0);
-+-		if (ret)
-+-			return ret;
-+-
-+-		dev_info(smi->dev, "VLAN%d port %d, PVID set to %d\n",
-+-			 (port + 1), port, (port + 1));
-+-		ret = rtl8366_set_pvid(smi, port, (port + 1));
-+-		if (ret)
-+-			return ret;
-+-	}
-+-
-+-	return rtl8366_enable_vlan(smi, true);
-+-}
-+-EXPORT_SYMBOL_GPL(rtl8366_init_vlan);
-+-
-+ int rtl8366_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
-+ 			   struct netlink_ext_ack *extack)
-+ {
-+--- a/drivers/net/dsa/rtl8366rb.c
-++++ b/drivers/net/dsa/rtl8366rb.c
-+@@ -985,7 +985,7 @@ static int rtl8366rb_setup(struct dsa_sw
-+ 			return ret;
++ static int mt753x_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
++@@ -3065,6 +3087,8 @@ mt753x_setup(struct dsa_switch *ds)
++ 		priv->pcs[i].pcs.ops = priv->info->pcs_ops;
++ 		priv->pcs[i].priv = priv;
++ 		priv->pcs[i].port = i;
+++		if (mt753x_is_mac_port(i))
+++			priv->pcs[i].pcs.poll = 1;
 + 	}
 + 
-+-	ret = rtl8366_init_vlan(smi);
-++	ret = rtl8366_reset_vlan(smi);
-+ 	if (ret)
++ 	ret = priv->info->sw_setup(ds);
++--- a/drivers/net/dsa/mt7530.h
+++++ b/drivers/net/dsa/mt7530.h
++@@ -373,6 +373,7 @@ enum mt7530_vlan_port_acc_frm {
++ #define  MT7531_SGMII_LINK_STATUS	BIT(18)
++ #define  MT7531_SGMII_AN_ENABLE		BIT(12)
++ #define  MT7531_SGMII_AN_RESTART	BIT(9)
+++#define  MT7531_SGMII_AN_COMPLETE	BIT(21)
++ 
++ /* Register for SGMII PCS_SPPED_ABILITY */
++ #define MT7531_PCS_SPEED_ABILITY(p)	MT7531_SGMII_REG(p, 0x08)
+diff --git a/target/linux/generic/backport-5.15/783-v6.1-net-sfp-re-implement-soft-state-polling-setup.patch b/target/linux/generic/backport-5.15/783-v6.1-net-sfp-re-implement-soft-state-polling-setup.patch
+new file mode 100644
+index 0000000000..6f69b7ddfe
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/783-v6.1-net-sfp-re-implement-soft-state-polling-setup.patch
+@@ -0,0 +1,98 @@
++From 8475c4b70b040f9d8cbc308100f2c4d865f810b3 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Tue, 13 Sep 2022 20:06:27 +0100
++Subject: [PATCH 1/1] net: sfp: re-implement soft state polling setup
++
++Re-implement the decision making for soft state polling. Instead of
++generating the soft state mask in sfp_soft_start_poll() by looking at
++which GPIOs are available, record their availability in
++sfp_sm_mod_probe() in sfp->state_hw_mask.
++
++This will then allow us to clear bits in sfp->state_hw_mask in module
++specific quirks when the hardware signals should not be used, thereby
++allowing us to switch to using the software state polling.
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
++---
++ drivers/net/phy/sfp.c | 38 ++++++++++++++++++++++++++------------
++ 1 file changed, 26 insertions(+), 12 deletions(-)
++
++--- a/drivers/net/phy/sfp.c
+++++ b/drivers/net/phy/sfp.c
++@@ -234,6 +234,7 @@ struct sfp {
++ 	bool need_poll;
++ 
++ 	struct mutex st_mutex;			/* Protects state */
+++	unsigned int state_hw_mask;
++ 	unsigned int state_soft_mask;
++ 	unsigned int state;
++ 	struct delayed_work poll;
++@@ -499,17 +500,18 @@ static void sfp_soft_set_state(struct sf
++ static void sfp_soft_start_poll(struct sfp *sfp)
++ {
++ 	const struct sfp_eeprom_id *id = &sfp->id;
+++	unsigned int mask = 0;
++ 
++ 	sfp->state_soft_mask = 0;
++-	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_DISABLE &&
++-	    !sfp->gpio[GPIO_TX_DISABLE])
++-		sfp->state_soft_mask |= SFP_F_TX_DISABLE;
++-	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_FAULT &&
++-	    !sfp->gpio[GPIO_TX_FAULT])
++-		sfp->state_soft_mask |= SFP_F_TX_FAULT;
++-	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_RX_LOS &&
++-	    !sfp->gpio[GPIO_LOS])
++-		sfp->state_soft_mask |= SFP_F_LOS;
+++	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_DISABLE)
+++		mask |= SFP_F_TX_DISABLE;
+++	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_FAULT)
+++		mask |= SFP_F_TX_FAULT;
+++	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_RX_LOS)
+++		mask |= SFP_F_LOS;
+++
+++	// Poll the soft state for hardware pins we want to ignore
+++	sfp->state_soft_mask = ~sfp->state_hw_mask & mask;
++ 
++ 	if (sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT) &&
++ 	    !sfp->need_poll)
++@@ -523,10 +525,11 @@ static void sfp_soft_stop_poll(struct sf
++ 
++ static unsigned int sfp_get_state(struct sfp *sfp)
++ {
++-	unsigned int state = sfp->get_state(sfp);
+++	unsigned int soft = sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT);
+++	unsigned int state;
++ 
++-	if (state & SFP_F_PRESENT &&
++-	    sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT))
+++	state = sfp->get_state(sfp) & sfp->state_hw_mask;
+++	if (state & SFP_F_PRESENT && soft)
++ 		state |= sfp_soft_get_state(sfp);
++ 
++ 	return state;
++@@ -1940,6 +1943,15 @@ static int sfp_sm_mod_probe(struct sfp *
++ 	if (ret < 0)
 + 		return ret;
 + 
-+@@ -999,8 +999,6 @@ static int rtl8366rb_setup(struct dsa_sw
-+ 		return -ENODEV;
-+ 	}
+++	/* Initialise state bits to use from hardware */
+++	sfp->state_hw_mask = SFP_F_PRESENT;
+++	if (sfp->gpio[GPIO_TX_DISABLE])
+++		sfp->state_hw_mask |= SFP_F_TX_DISABLE;
+++	if (sfp->gpio[GPIO_TX_FAULT])
+++		sfp->state_hw_mask |= SFP_F_TX_FAULT;
+++	if (sfp->gpio[GPIO_LOS])
+++		sfp->state_hw_mask |= SFP_F_LOS;
+++
++ 	if (!memcmp(id.base.vendor_name, "ALCATELLUCENT   ", 16) &&
++ 	    !memcmp(id.base.vendor_pn, "3FE46541AA      ", 16))
++ 		sfp->module_t_start_up = T_START_UP_BAD_GPON;
++@@ -2565,6 +2577,8 @@ static int sfp_probe(struct platform_dev
++ 				return PTR_ERR(sfp->gpio[i]);
++ 		}
 + 
-+-	ds->configure_vlan_while_not_filtering = false;
-+-
-+ 	return 0;
-+ }
+++	sfp->state_hw_mask = SFP_F_PRESENT;
+++
++ 	sfp->get_state = sfp_gpio_get_state;
++ 	sfp->set_state = sfp_gpio_set_state;
 + 
-diff --git a/target/linux/generic/backport-5.15/774-v5.16-03-net-dsa-rtl8366rb-Rewrite-weird-VLAN-filering-enable.patch b/target/linux/generic/backport-5.15/774-v5.16-03-net-dsa-rtl8366rb-Rewrite-weird-VLAN-filering-enable.patch
+diff --git a/target/linux/generic/backport-5.15/784-v6.1-net-sfp-move-quirk-handling-into-sfp.c.patch b/target/linux/generic/backport-5.15/784-v6.1-net-sfp-move-quirk-handling-into-sfp.c.patch
 new file mode 100644
-index 0000000000..2b19752399
+index 0000000000..e5f8031636
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/774-v5.16-03-net-dsa-rtl8366rb-Rewrite-weird-VLAN-filering-enable.patch
-@@ -0,0 +1,270 @@
-+From 7028f54b620f8df344b18e46e4a78e266091ab45 Mon Sep 17 00:00:00 2001
-+From: Linus Walleij <linus.walleij@linaro.org>
-+Date: Sun, 26 Sep 2021 00:59:26 +0200
-+Subject: [PATCH 03/11] net: dsa: rtl8366rb: Rewrite weird VLAN filering
-+ enablement
-+MIME-Version: 1.0
-+Content-Type: text/plain; charset=UTF-8
-+Content-Transfer-Encoding: 8bit
-+
-+While we were defining one VLAN per port for isolating the ports
-+the port_vlan_filtering() callback was implemented to enable a
-+VLAN on the port + 1. This function makes no sense, not only is
-+it incomplete as it only enables the VLAN, it doesn't do what
-+the callback is supposed to do, which is to selectively enable
-+and disable filtering on a certain port.
-+
-+Implement the correct callback: we have two registers dealing
-+with filtering on the RTL9366RB, so we implement an ASIC-specific
-+callback and implement filering using the register bit that makes
-+the switch drop frames if the port is not in the VLAN member set.
-+
-+The DSA documentation Documentation/networking/switchdev.rst states:
-+
-+  When the bridge has VLAN filtering enabled and a PVID is not
-+  configured on the ingress port, untagged and 802.1p tagged
-+  packets must be dropped. When the bridge has VLAN filtering
-+  enabled and a PVID exists on the ingress port, untagged and
-+  priority-tagged packets must be accepted and forwarded according
-+  to the bridge's port membership of the PVID VLAN. When the
-+  bridge has VLAN filtering disabled, the presence/lack of a
-+  PVID should not influence the packet forwarding decision.
-+
-+To comply with this, we add two arrays of bool in the RTL8366RB
-+state that keeps track of if filtering and PVID is enabled or
-+not for each port. We then add code such that whenever filtering
-+or PVID changes, we update the filter according to the
-+specification.
-+
-+Cc: Vladimir Oltean <olteanv@gmail.com>
-+Cc: Mauri Sandberg <sandberg@mailfence.com>
-+Cc: Alvin Å ipraga <alsi@bang-olufsen.dk>
-+Cc: Florian Fainelli <f.fainelli@gmail.com>
-+Cc: DENG Qingfang <dqfext@gmail.com>
-+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++++ b/target/linux/generic/backport-5.15/784-v6.1-net-sfp-move-quirk-handling-into-sfp.c.patch
+@@ -0,0 +1,291 @@
++From 23571c7b96437483d28a990c906cc81f5f66374e Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Tue, 13 Sep 2022 20:06:32 +0100
++Subject: [PATCH 1/1] net: sfp: move quirk handling into sfp.c
++
++We need to handle more quirks than just those which affect the link
++modes of the module. Move the quirk lookup into sfp.c, and pass the
++quirk to sfp-bus.c
++
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 +---
-+ drivers/net/dsa/realtek-smi-core.h |   2 -
-+ drivers/net/dsa/rtl8366.c          |  35 ----------
-+ drivers/net/dsa/rtl8366rb.c        | 102 +++++++++++++++++++++++++++--
-+ 3 files changed, 95 insertions(+), 44 deletions(-)
++ drivers/net/phy/sfp-bus.c | 98 ++-------------------------------------
++ drivers/net/phy/sfp.c     | 94 ++++++++++++++++++++++++++++++++++++-
++ drivers/net/phy/sfp.h     |  9 +++-
++ 3 files changed, 104 insertions(+), 97 deletions(-)
 +
-+--- a/drivers/net/dsa/realtek-smi-core.h
-++++ b/drivers/net/dsa/realtek-smi-core.h
-+@@ -129,8 +129,6 @@ int rtl8366_set_pvid(struct realtek_smi
-+ int rtl8366_enable_vlan4k(struct realtek_smi *smi, bool enable);
-+ int rtl8366_enable_vlan(struct realtek_smi *smi, bool enable);
-+ int rtl8366_reset_vlan(struct realtek_smi *smi);
-+-int rtl8366_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
-+-			   struct netlink_ext_ack *extack);
-+ int rtl8366_vlan_add(struct dsa_switch *ds, int port,
-+ 		     const struct switchdev_obj_port_vlan *vlan,
-+ 		     struct netlink_ext_ack *extack);
-+--- a/drivers/net/dsa/rtl8366.c
-++++ b/drivers/net/dsa/rtl8366.c
-+@@ -292,41 +292,6 @@ int rtl8366_reset_vlan(struct realtek_sm
-+ }
-+ EXPORT_SYMBOL_GPL(rtl8366_reset_vlan);
++--- a/drivers/net/phy/sfp-bus.c
+++++ b/drivers/net/phy/sfp-bus.c
++@@ -10,12 +10,6 @@
 + 
-+-int rtl8366_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
-+-			   struct netlink_ext_ack *extack)
++ #include "sfp.h"
++ 
++-struct sfp_quirk {
++-	const char *vendor;
++-	const char *part;
++-	void (*modes)(const struct sfp_eeprom_id *id, unsigned long *modes);
++-};
++-
++ /**
++  * struct sfp_bus - internal representation of a sfp bus
++  */
++@@ -38,93 +32,6 @@ struct sfp_bus {
++ 	bool started;
++ };
++ 
++-static void sfp_quirk_2500basex(const struct sfp_eeprom_id *id,
++-				unsigned long *modes)
 +-{
-+-	struct realtek_smi *smi = ds->priv;
-+-	struct rtl8366_vlan_4k vlan4k;
-+-	int ret;
++-	phylink_set(modes, 2500baseX_Full);
++-}
 +-
-+-	/* Use VLAN nr port + 1 since VLAN0 is not valid */
-+-	if (!smi->ops->is_vlan_valid(smi, port + 1))
-+-		return -EINVAL;
++-static void sfp_quirk_ubnt_uf_instant(const struct sfp_eeprom_id *id,
++-				      unsigned long *modes)
++-{
++-	/* Ubiquiti U-Fiber Instant module claims that support all transceiver
++-	 * types including 10G Ethernet which is not truth. So clear all claimed
++-	 * modes and set only one mode which module supports: 1000baseX_Full.
++-	 */
++-	phylink_zero(modes);
++-	phylink_set(modes, 1000baseX_Full);
++-}
 +-
-+-	dev_info(smi->dev, "%s filtering on port %d\n",
-+-		 vlan_filtering ? "enable" : "disable",
-+-		 port);
++-static const struct sfp_quirk sfp_quirks[] = {
++-	{
++-		// Alcatel Lucent G-010S-P can operate at 2500base-X, but
++-		// incorrectly report 2500MBd NRZ in their EEPROM
++-		.vendor = "ALCATELLUCENT",
++-		.part = "G010SP",
++-		.modes = sfp_quirk_2500basex,
++-	}, {
++-		// Alcatel Lucent G-010S-A can operate at 2500base-X, but
++-		// report 3.2GBd NRZ in their EEPROM
++-		.vendor = "ALCATELLUCENT",
++-		.part = "3FE46541AA",
++-		.modes = sfp_quirk_2500basex,
++-	}, {
++-		// Huawei MA5671A can operate at 2500base-X, but report 1.2GBd
++-		// NRZ in their EEPROM
++-		.vendor = "HUAWEI",
++-		.part = "MA5671A",
++-		.modes = sfp_quirk_2500basex,
++-	}, {
++-		// Lantech 8330-262D-E can operate at 2500base-X, but
++-		// incorrectly report 2500MBd NRZ in their EEPROM
++-		.vendor = "Lantech",
++-		.part = "8330-262D-E",
++-		.modes = sfp_quirk_2500basex,
++-	}, {
++-		.vendor = "UBNT",
++-		.part = "UF-INSTANT",
++-		.modes = sfp_quirk_ubnt_uf_instant,
++-	},
++-};
 +-
-+-	/* TODO:
-+-	 * The hardware support filter ID (FID) 0..7, I have no clue how to
-+-	 * support this in the driver when the callback only says on/off.
-+-	 */
-+-	ret = smi->ops->get_vlan_4k(smi, port + 1, &vlan4k);
-+-	if (ret)
-+-		return ret;
++-static size_t sfp_strlen(const char *str, size_t maxlen)
++-{
++-	size_t size, i;
 +-
-+-	/* Just set the filter to FID 1 for now then */
-+-	ret = rtl8366_set_vlan(smi, port + 1,
-+-			       vlan4k.member,
-+-			       vlan4k.untag,
-+-			       1);
-+-	if (ret)
-+-		return ret;
++-	/* Trailing characters should be filled with space chars */
++-	for (i = 0, size = 0; i < maxlen; i++)
++-		if (str[i] != ' ')
++-			size = i + 1;
 +-
-+-	return 0;
++-	return size;
++-}
++-
++-static bool sfp_match(const char *qs, const char *str, size_t len)
++-{
++-	if (!qs)
++-		return true;
++-	if (strlen(qs) != len)
++-		return false;
++-	return !strncmp(qs, str, len);
++-}
++-
++-static const struct sfp_quirk *sfp_lookup_quirk(const struct sfp_eeprom_id *id)
++-{
++-	const struct sfp_quirk *q;
++-	unsigned int i;
++-	size_t vs, ps;
++-
++-	vs = sfp_strlen(id->base.vendor_name, ARRAY_SIZE(id->base.vendor_name));
++-	ps = sfp_strlen(id->base.vendor_pn, ARRAY_SIZE(id->base.vendor_pn));
++-
++-	for (i = 0, q = sfp_quirks; i < ARRAY_SIZE(sfp_quirks); i++, q++)
++-		if (sfp_match(q->vendor, id->base.vendor_name, vs) &&
++-		    sfp_match(q->part, id->base.vendor_pn, ps))
++-			return q;
++-
++-	return NULL;
 +-}
-+-EXPORT_SYMBOL_GPL(rtl8366_vlan_filtering);
 +-
-+ int rtl8366_vlan_add(struct dsa_switch *ds, int port,
-+ 		     const struct switchdev_obj_port_vlan *vlan,
-+ 		     struct netlink_ext_ack *extack)
-+--- a/drivers/net/dsa/rtl8366rb.c
-++++ b/drivers/net/dsa/rtl8366rb.c
-+@@ -143,6 +143,21 @@
-+ #define RTL8366RB_PHY_NO_OFFSET			9
-+ #define RTL8366RB_PHY_NO_MASK			(0x1f << 9)
-+ 
-++/* VLAN Ingress Control Register 1, one bit per port.
-++ * bit 0 .. 5 will make the switch drop ingress frames without
-++ * VID such as untagged or priority-tagged frames for respective
-++ * port.
-++ * bit 6 .. 11 will make the switch drop ingress frames carrying
-++ * a C-tag with VID != 0 for respective port.
-++ */
-++#define RTL8366RB_VLAN_INGRESS_CTRL1_REG	0x037E
-++#define RTL8366RB_VLAN_INGRESS_CTRL1_DROP(port)	(BIT((port)) | BIT((port) + 6))
-++
-++/* VLAN Ingress Control Register 2, one bit per port.
-++ * bit0 .. bit5 will make the switch drop all ingress frames with
-++ * a VLAN classification that does not include the port is in its
-++ * member set.
-++ */
-+ #define RTL8366RB_VLAN_INGRESS_CTRL2_REG	0x037f
-+ 
-+ /* LED control registers */
-+@@ -321,9 +336,13 @@
 + /**
-+  * struct rtl8366rb - RTL8366RB-specific data
-+  * @max_mtu: per-port max MTU setting
-++ * @pvid_enabled: if PVID is set for respective port
-++ * @vlan_filtering: if VLAN filtering is enabled for respective port
-+  */
-+ struct rtl8366rb {
-+ 	unsigned int max_mtu[RTL8366RB_NUM_PORTS];
-++	bool pvid_enabled[RTL8366RB_NUM_PORTS];
-++	bool vlan_filtering[RTL8366RB_NUM_PORTS];
-+ };
-+ 
-+ static struct rtl8366_mib_counter rtl8366rb_mib_counters[] = {
-+@@ -933,11 +952,13 @@ static int rtl8366rb_setup(struct dsa_sw
-+ 	if (ret)
-+ 		return ret;
-+ 
-+-	/* Discard VLAN tagged packets if the port is not a member of
-+-	 * the VLAN with which the packets is associated.
-+-	 */
-++	/* Accept all packets by default, we enable filtering on-demand */
-++	ret = regmap_write(smi->map, RTL8366RB_VLAN_INGRESS_CTRL1_REG,
-++			   0);
-++	if (ret)
-++		return ret;
-+ 	ret = regmap_write(smi->map, RTL8366RB_VLAN_INGRESS_CTRL2_REG,
-+-			   RTL8366RB_PORT_ALL);
-++			   0);
-+ 	if (ret)
-+ 		return ret;
-+ 
-+@@ -1209,6 +1230,53 @@ rtl8366rb_port_bridge_leave(struct dsa_s
-+ 			   RTL8366RB_PORT_ISO_PORTS(port_bitmap), 0);
++  * sfp_parse_port() - Parse the EEPROM base ID, setting the port type
++  * @bus: a pointer to the &struct sfp_bus structure for the sfp module
++@@ -786,12 +693,13 @@ void sfp_link_down(struct sfp_bus *bus)
 + }
++ EXPORT_SYMBOL_GPL(sfp_link_down);
++ 
++-int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id)
+++int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
+++		      const struct sfp_quirk *quirk)
++ {
++ 	const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
++ 	int ret = 0;
++ 
++-	bus->sfp_quirk = sfp_lookup_quirk(id);
+++	bus->sfp_quirk = quirk;
++ 
++ 	if (ops && ops->module_insert)
++ 		ret = ops->module_insert(bus->upstream, id);
++--- a/drivers/net/phy/sfp.c
+++++ b/drivers/net/phy/sfp.c
++@@ -253,6 +253,8 @@ struct sfp {
++ 	unsigned int module_t_start_up;
++ 	bool tx_fault_ignore;
++ 
+++	const struct sfp_quirk *quirk;
+++
++ #if IS_ENABLED(CONFIG_HWMON)
++ 	struct sfp_diag diag;
++ 	struct delayed_work hwmon_probe;
++@@ -309,6 +311,93 @@ static const struct of_device_id sfp_of_
++ };
++ MODULE_DEVICE_TABLE(of, sfp_of_match);
 + 
-++/**
-++ * rtl8366rb_drop_untagged() - make the switch drop untagged and C-tagged frames
-++ * @smi: SMI state container
-++ * @port: the port to drop untagged and C-tagged frames on
-++ * @drop: whether to drop or pass untagged and C-tagged frames
-++ */
-++static int rtl8366rb_drop_untagged(struct realtek_smi *smi, int port, bool drop)
+++static void sfp_quirk_2500basex(const struct sfp_eeprom_id *id,
+++				unsigned long *modes)
 ++{
-++	return regmap_update_bits(smi->map, RTL8366RB_VLAN_INGRESS_CTRL1_REG,
-++				  RTL8366RB_VLAN_INGRESS_CTRL1_DROP(port),
-++				  drop ? RTL8366RB_VLAN_INGRESS_CTRL1_DROP(port) : 0);
+++	linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseX_Full_BIT, modes);
 ++}
 ++
-++static int rtl8366rb_vlan_filtering(struct dsa_switch *ds, int port,
-++				    bool vlan_filtering,
-++				    struct netlink_ext_ack *extack)
+++static void sfp_quirk_ubnt_uf_instant(const struct sfp_eeprom_id *id,
+++				      unsigned long *modes)
 ++{
-++	struct realtek_smi *smi = ds->priv;
-++	struct rtl8366rb *rb;
-++	int ret;
-++
-++	rb = smi->chip_data;
+++	/* Ubiquiti U-Fiber Instant module claims that support all transceiver
+++	 * types including 10G Ethernet which is not truth. So clear all claimed
+++	 * modes and set only one mode which module supports: 1000baseX_Full.
+++	 */
+++	linkmode_zero(modes);
+++	linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, modes);
+++}
 ++
-++	dev_dbg(smi->dev, "port %d: %s VLAN filtering\n", port,
-++		vlan_filtering ? "enable" : "disable");
+++static const struct sfp_quirk sfp_quirks[] = {
+++	{
+++		// Alcatel Lucent G-010S-P can operate at 2500base-X, but
+++		// incorrectly report 2500MBd NRZ in their EEPROM
+++		.vendor = "ALCATELLUCENT",
+++		.part = "G010SP",
+++		.modes = sfp_quirk_2500basex,
+++	}, {
+++		// Alcatel Lucent G-010S-A can operate at 2500base-X, but
+++		// report 3.2GBd NRZ in their EEPROM
+++		.vendor = "ALCATELLUCENT",
+++		.part = "3FE46541AA",
+++		.modes = sfp_quirk_2500basex,
+++	}, {
+++		// Huawei MA5671A can operate at 2500base-X, but report 1.2GBd
+++		// NRZ in their EEPROM
+++		.vendor = "HUAWEI",
+++		.part = "MA5671A",
+++		.modes = sfp_quirk_2500basex,
+++	}, {
+++		// Lantech 8330-262D-E can operate at 2500base-X, but
+++		// incorrectly report 2500MBd NRZ in their EEPROM
+++		.vendor = "Lantech",
+++		.part = "8330-262D-E",
+++		.modes = sfp_quirk_2500basex,
+++	}, {
+++		.vendor = "UBNT",
+++		.part = "UF-INSTANT",
+++		.modes = sfp_quirk_ubnt_uf_instant,
+++	},
+++};
 ++
-++	/* If the port is not in the member set, the frame will be dropped */
-++	ret = regmap_update_bits(smi->map, RTL8366RB_VLAN_INGRESS_CTRL2_REG,
-++				 BIT(port), vlan_filtering ? BIT(port) : 0);
-++	if (ret)
-++		return ret;
+++static size_t sfp_strlen(const char *str, size_t maxlen)
+++{
+++	size_t size, i;
 ++
-++	/* Keep track if filtering is enabled on each port */
-++	rb->vlan_filtering[port] = vlan_filtering;
+++	/* Trailing characters should be filled with space chars */
+++	for (i = 0, size = 0; i < maxlen; i++)
+++		if (str[i] != ' ')
+++			size = i + 1;
 ++
-++	/* If VLAN filtering is enabled and PVID is also enabled, we must
-++	 * not drop any untagged or C-tagged frames. If we turn off VLAN
-++	 * filtering on a port, we need ti accept any frames.
-++	 */
-++	if (vlan_filtering)
-++		ret = rtl8366rb_drop_untagged(smi, port, !rb->pvid_enabled[port]);
-++	else
-++		ret = rtl8366rb_drop_untagged(smi, port, false);
+++	return size;
+++}
 ++
-++	return ret;
+++static bool sfp_match(const char *qs, const char *str, size_t len)
+++{
+++	if (!qs)
+++		return true;
+++	if (strlen(qs) != len)
+++		return false;
+++	return !strncmp(qs, str, len);
 ++}
 ++
-+ static int rtl8366rb_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
-+ {
-+ 	struct realtek_smi *smi = ds->priv;
-+@@ -1420,14 +1488,34 @@ static int rtl8366rb_get_mc_index(struct
-+ 
-+ static int rtl8366rb_set_mc_index(struct realtek_smi *smi, int port, int index)
-+ {
-++	struct rtl8366rb *rb;
-++	bool pvid_enabled;
-++	int ret;
+++static const struct sfp_quirk *sfp_lookup_quirk(const struct sfp_eeprom_id *id)
+++{
+++	const struct sfp_quirk *q;
+++	unsigned int i;
+++	size_t vs, ps;
 ++
-++	rb = smi->chip_data;
-++	pvid_enabled = !!index;
+++	vs = sfp_strlen(id->base.vendor_name, ARRAY_SIZE(id->base.vendor_name));
+++	ps = sfp_strlen(id->base.vendor_pn, ARRAY_SIZE(id->base.vendor_pn));
 ++
-+ 	if (port >= smi->num_ports || index >= RTL8366RB_NUM_VLANS)
-+ 		return -EINVAL;
-+ 
-+-	return regmap_update_bits(smi->map, RTL8366RB_PORT_VLAN_CTRL_REG(port),
-++	ret = regmap_update_bits(smi->map, RTL8366RB_PORT_VLAN_CTRL_REG(port),
-+ 				RTL8366RB_PORT_VLAN_CTRL_MASK <<
-+ 					RTL8366RB_PORT_VLAN_CTRL_SHIFT(port),
-+ 				(index & RTL8366RB_PORT_VLAN_CTRL_MASK) <<
-+ 					RTL8366RB_PORT_VLAN_CTRL_SHIFT(port));
-++	if (ret)
-++		return ret;
+++	for (i = 0, q = sfp_quirks; i < ARRAY_SIZE(sfp_quirks); i++, q++)
+++		if (sfp_match(q->vendor, id->base.vendor_name, vs) &&
+++		    sfp_match(q->part, id->base.vendor_pn, ps))
+++			return q;
 ++
-++	rb->pvid_enabled[port] = pvid_enabled;
+++	return NULL;
+++}
 ++
-++	/* If VLAN filtering is enabled and PVID is also enabled, we must
-++	 * not drop any untagged or C-tagged frames. Make sure to update the
-++	 * filtering setting.
-++	 */
-++	if (rb->vlan_filtering[port])
-++		ret = rtl8366rb_drop_untagged(smi, port, !pvid_enabled);
++ static unsigned long poll_jiffies;
++ 
++ static unsigned int sfp_gpio_get_state(struct sfp *sfp)
++@@ -1964,6 +2053,8 @@ static int sfp_sm_mod_probe(struct sfp *
++ 	else
++ 		sfp->tx_fault_ignore = false;
++ 
+++	sfp->quirk = sfp_lookup_quirk(&id);
 ++
-++	return ret;
++ 	return 0;
 + }
 + 
-+ static bool rtl8366rb_is_vlan_valid(struct realtek_smi *smi, unsigned int vlan)
-+@@ -1437,7 +1525,7 @@ static bool rtl8366rb_is_vlan_valid(stru
-+ 	if (smi->vlan4k_enabled)
-+ 		max = RTL8366RB_NUM_VIDS - 1;
++@@ -2075,7 +2166,8 @@ static void sfp_sm_module(struct sfp *sf
++ 			break;
 + 
-+-	if (vlan == 0 || vlan > max)
-++	if (vlan > max)
-+ 		return false;
++ 		/* Report the module insertion to the upstream device */
++-		err = sfp_module_insert(sfp->sfp_bus, &sfp->id);
+++		err = sfp_module_insert(sfp->sfp_bus, &sfp->id,
+++					sfp->quirk);
++ 		if (err < 0) {
++ 			sfp_sm_mod_next(sfp, SFP_MOD_ERROR, 0);
++ 			break;
++--- a/drivers/net/phy/sfp.h
+++++ b/drivers/net/phy/sfp.h
++@@ -6,6 +6,12 @@
 + 
-+ 	return true;
-+@@ -1594,7 +1682,7 @@ static const struct dsa_switch_ops rtl83
-+ 	.get_sset_count = rtl8366_get_sset_count,
-+ 	.port_bridge_join = rtl8366rb_port_bridge_join,
-+ 	.port_bridge_leave = rtl8366rb_port_bridge_leave,
-+-	.port_vlan_filtering = rtl8366_vlan_filtering,
-++	.port_vlan_filtering = rtl8366rb_vlan_filtering,
-+ 	.port_vlan_add = rtl8366_vlan_add,
-+ 	.port_vlan_del = rtl8366_vlan_del,
-+ 	.port_enable = rtl8366rb_port_enable,
-diff --git a/target/linux/generic/backport-5.15/774-v5.16-06-net-dsa-rtl8366-Drop-and-depromote-pointless-prints.patch b/target/linux/generic/backport-5.15/774-v5.16-06-net-dsa-rtl8366-Drop-and-depromote-pointless-prints.patch
++ struct sfp;
++ 
+++struct sfp_quirk {
+++	const char *vendor;
+++	const char *part;
+++	void (*modes)(const struct sfp_eeprom_id *id, unsigned long *modes);
+++};
+++
++ struct sfp_socket_ops {
++ 	void (*attach)(struct sfp *sfp);
++ 	void (*detach)(struct sfp *sfp);
++@@ -23,7 +29,8 @@ int sfp_add_phy(struct sfp_bus *bus, str
++ void sfp_remove_phy(struct sfp_bus *bus);
++ void sfp_link_up(struct sfp_bus *bus);
++ void sfp_link_down(struct sfp_bus *bus);
++-int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id);
+++int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
+++		      const struct sfp_quirk *quirk);
++ void sfp_module_remove(struct sfp_bus *bus);
++ int sfp_module_start(struct sfp_bus *bus);
++ void sfp_module_stop(struct sfp_bus *bus);
+diff --git a/target/linux/generic/backport-5.15/785-v6.1-net-sfp-move-Alcatel-Lucent-3FE46541AA-fixup.patch b/target/linux/generic/backport-5.15/785-v6.1-net-sfp-move-Alcatel-Lucent-3FE46541AA-fixup.patch
 new file mode 100644
-index 0000000000..b56032c366
+index 0000000000..aa3112e585
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/774-v5.16-06-net-dsa-rtl8366-Drop-and-depromote-pointless-prints.patch
-@@ -0,0 +1,51 @@
-+From ddb59a5dc42714999c335dab4bf256125ba3120c Mon Sep 17 00:00:00 2001
-+From: Linus Walleij <linus.walleij@linaro.org>
-+Date: Sun, 26 Sep 2021 00:59:29 +0200
-+Subject: [PATCH 06/11] net: dsa: rtl8366: Drop and depromote pointless prints
-+MIME-Version: 1.0
-+Content-Type: text/plain; charset=UTF-8
-+Content-Transfer-Encoding: 8bit
++++ b/target/linux/generic/backport-5.15/785-v6.1-net-sfp-move-Alcatel-Lucent-3FE46541AA-fixup.patch
+@@ -0,0 +1,69 @@
++From 275416754e9a262c97a1ad6f806a4bc6e0464aa2 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Tue, 13 Sep 2022 20:06:37 +0100
++Subject: [PATCH 1/1] net: sfp: move Alcatel Lucent 3FE46541AA fixup
 +
-+We don't need a message for every VLAN association, dbg
-+is fine. The message about adding the DSA or CPU
-+port to a VLAN is directly misleading, this is perfectly
-+fine.
++Add a new fixup mechanism to the SFP quirks, and use it for this
++module.
 +
-+Cc: Vladimir Oltean <olteanv@gmail.com>
-+Cc: Mauri Sandberg <sandberg@mailfence.com>
-+Cc: DENG Qingfang <dqfext@gmail.com>
-+Reviewed-by: Alvin Å ipraga <alsi@bang-olufsen.dk>
-+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 +---
-+ drivers/net/dsa/rtl8366.c | 11 ++++-------
-+ 1 file changed, 4 insertions(+), 7 deletions(-)
++ drivers/net/phy/sfp.c | 14 +++++++++-----
++ drivers/net/phy/sfp.h |  1 +
++ 2 files changed, 10 insertions(+), 5 deletions(-)
 +
-+--- a/drivers/net/dsa/rtl8366.c
-++++ b/drivers/net/dsa/rtl8366.c
-+@@ -318,12 +318,9 @@ int rtl8366_vlan_add(struct dsa_switch *
-+ 		return ret;
-+ 	}
-+ 
-+-	dev_info(smi->dev, "add VLAN %d on port %d, %s, %s\n",
-+-		 vlan->vid, port, untagged ? "untagged" : "tagged",
-+-		 pvid ? " PVID" : "no PVID");
-+-
-+-	if (dsa_is_dsa_port(ds, port) || dsa_is_cpu_port(ds, port))
-+-		dev_err(smi->dev, "port is DSA or CPU port\n");
-++	dev_dbg(smi->dev, "add VLAN %d on port %d, %s, %s\n",
-++		vlan->vid, port, untagged ? "untagged" : "tagged",
-++		pvid ? "PVID" : "no PVID");
++--- a/drivers/net/phy/sfp.c
+++++ b/drivers/net/phy/sfp.c
++@@ -311,6 +311,11 @@ static const struct of_device_id sfp_of_
++ };
++ MODULE_DEVICE_TABLE(of, sfp_of_match);
 + 
-+ 	member |= BIT(port);
+++static void sfp_fixup_long_startup(struct sfp *sfp)
+++{
+++	sfp->module_t_start_up = T_START_UP_BAD_GPON;
+++}
+++
++ static void sfp_quirk_2500basex(const struct sfp_eeprom_id *id,
++ 				unsigned long *modes)
++ {
++@@ -341,6 +346,7 @@ static const struct sfp_quirk sfp_quirks
++ 		.vendor = "ALCATELLUCENT",
++ 		.part = "3FE46541AA",
++ 		.modes = sfp_quirk_2500basex,
+++		.fixup = sfp_fixup_long_startup,
++ 	}, {
++ 		// Huawei MA5671A can operate at 2500base-X, but report 1.2GBd
++ 		// NRZ in their EEPROM
++@@ -2041,11 +2047,7 @@ static int sfp_sm_mod_probe(struct sfp *
++ 	if (sfp->gpio[GPIO_LOS])
++ 		sfp->state_hw_mask |= SFP_F_LOS;
++ 
++-	if (!memcmp(id.base.vendor_name, "ALCATELLUCENT   ", 16) &&
++-	    !memcmp(id.base.vendor_pn, "3FE46541AA      ", 16))
++-		sfp->module_t_start_up = T_START_UP_BAD_GPON;
++-	else
++-		sfp->module_t_start_up = T_START_UP;
+++	sfp->module_t_start_up = T_START_UP;
 + 
-+@@ -356,7 +353,7 @@ int rtl8366_vlan_del(struct dsa_switch *
-+ 	struct realtek_smi *smi = ds->priv;
-+ 	int ret, i;
++ 	if (!memcmp(id.base.vendor_name, "HUAWEI          ", 16) &&
++ 	    !memcmp(id.base.vendor_pn, "MA5671A         ", 16))
++@@ -2054,6 +2056,8 @@ static int sfp_sm_mod_probe(struct sfp *
++ 		sfp->tx_fault_ignore = false;
 + 
-+-	dev_info(smi->dev, "del VLAN %04x on port %d\n", vlan->vid, port);
-++	dev_dbg(smi->dev, "del VLAN %d on port %d\n", vlan->vid, port);
++ 	sfp->quirk = sfp_lookup_quirk(&id);
+++	if (sfp->quirk && sfp->quirk->fixup)
+++		sfp->quirk->fixup(sfp);
 + 
-+ 	for (i = 0; i < smi->num_vlan_mc; i++) {
-+ 		struct rtl8366_vlan_mc vlanmc;
-diff --git a/target/linux/generic/backport-5.15/774-v5.16-07-net-dsa-rtl8366rb-Use-core-filtering-tracking.patch b/target/linux/generic/backport-5.15/774-v5.16-07-net-dsa-rtl8366rb-Use-core-filtering-tracking.patch
-new file mode 100644
-index 0000000000..8cd1df97f2
---- /dev/null
-+++ b/target/linux/generic/backport-5.15/774-v5.16-07-net-dsa-rtl8366rb-Use-core-filtering-tracking.patch
-@@ -0,0 +1,61 @@
-+From 5c9b66f3c8a3f72fa2a58e89a57c6d7afd550bf0 Mon Sep 17 00:00:00 2001
-+From: Linus Walleij <linus.walleij@linaro.org>
-+Date: Wed, 29 Sep 2021 13:23:22 +0200
-+Subject: [PATCH 07/11] net: dsa: rtl8366rb: Use core filtering tracking
-+MIME-Version: 1.0
-+Content-Type: text/plain; charset=UTF-8
-+Content-Transfer-Encoding: 8bit
-+
-+We added a state variable to track whether a certain port
-+was VLAN filtering or not, but we can just inquire the DSA
-+core about this.
-+
-+Cc: Vladimir Oltean <olteanv@gmail.com>
-+Cc: Mauri Sandberg <sandberg@mailfence.com>
-+Cc: DENG Qingfang <dqfext@gmail.com>
-+Cc: Alvin Å ipraga <alsi@bang-olufsen.dk>
-+Cc: Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
-+---
-+ drivers/net/dsa/rtl8366rb.c | 9 ++-------
-+ 1 file changed, 2 insertions(+), 7 deletions(-)
-+
-+--- a/drivers/net/dsa/rtl8366rb.c
-++++ b/drivers/net/dsa/rtl8366rb.c
-+@@ -337,12 +337,10 @@
-+  * struct rtl8366rb - RTL8366RB-specific data
-+  * @max_mtu: per-port max MTU setting
-+  * @pvid_enabled: if PVID is set for respective port
-+- * @vlan_filtering: if VLAN filtering is enabled for respective port
-+  */
-+ struct rtl8366rb {
-+ 	unsigned int max_mtu[RTL8366RB_NUM_PORTS];
-+ 	bool pvid_enabled[RTL8366RB_NUM_PORTS];
-+-	bool vlan_filtering[RTL8366RB_NUM_PORTS];
++ 	return 0;
++ }
++--- a/drivers/net/phy/sfp.h
+++++ b/drivers/net/phy/sfp.h
++@@ -10,6 +10,7 @@ struct sfp_quirk {
++ 	const char *vendor;
++ 	const char *part;
++ 	void (*modes)(const struct sfp_eeprom_id *id, unsigned long *modes);
+++	void (*fixup)(struct sfp *sfp);
 + };
 + 
-+ static struct rtl8366_mib_counter rtl8366rb_mib_counters[] = {
-+@@ -1262,12 +1260,9 @@ static int rtl8366rb_vlan_filtering(stru
-+ 	if (ret)
-+ 		return ret;
-+ 
-+-	/* Keep track if filtering is enabled on each port */
-+-	rb->vlan_filtering[port] = vlan_filtering;
-+-
-+ 	/* If VLAN filtering is enabled and PVID is also enabled, we must
-+ 	 * not drop any untagged or C-tagged frames. If we turn off VLAN
-+-	 * filtering on a port, we need ti accept any frames.
-++	 * filtering on a port, we need to accept any frames.
-+ 	 */
-+ 	if (vlan_filtering)
-+ 		ret = rtl8366rb_drop_untagged(smi, port, !rb->pvid_enabled[port]);
-+@@ -1512,7 +1507,7 @@ static int rtl8366rb_set_mc_index(struct
-+ 	 * not drop any untagged or C-tagged frames. Make sure to update the
-+ 	 * filtering setting.
-+ 	 */
-+-	if (rb->vlan_filtering[port])
-++	if (dsa_port_is_vlan_filtering(dsa_to_port(smi->ds, port)))
-+ 		ret = rtl8366rb_drop_untagged(smi, port, !pvid_enabled);
-+ 
-+ 	return ret;
-diff --git a/target/linux/generic/backport-5.15/774-v5.16-08-net-dsa-rtl8366rb-Support-disabling-learning.patch b/target/linux/generic/backport-5.15/774-v5.16-08-net-dsa-rtl8366rb-Support-disabling-learning.patch
++ struct sfp_socket_ops {
+diff --git a/target/linux/generic/backport-5.15/786-v6.1-net-sfp-move-Huawei-MA5671A-fixup.patch b/target/linux/generic/backport-5.15/786-v6.1-net-sfp-move-Huawei-MA5671A-fixup.patch
 new file mode 100644
-index 0000000000..8306eb5aef
+index 0000000000..14b0f9b8c3
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/774-v5.16-08-net-dsa-rtl8366rb-Support-disabling-learning.patch
-@@ -0,0 +1,115 @@
-+From 831a3d26bea0d14f8563eecf96def660a74a3000 Mon Sep 17 00:00:00 2001
-+From: Linus Walleij <linus.walleij@linaro.org>
-+Date: Tue, 5 Oct 2021 21:47:02 +0200
-+Subject: [PATCH 08/11] net: dsa: rtl8366rb: Support disabling learning
-+MIME-Version: 1.0
-+Content-Type: text/plain; charset=UTF-8
-+Content-Transfer-Encoding: 8bit
++++ b/target/linux/generic/backport-5.15/786-v6.1-net-sfp-move-Huawei-MA5671A-fixup.patch
+@@ -0,0 +1,48 @@
++From 5029be761161374a3624aa7b4670174c35449bf5 Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Tue, 13 Sep 2022 20:06:42 +0100
++Subject: [PATCH 1/1] net: sfp: move Huawei MA5671A fixup
 +
-+The RTL8366RB hardware supports disabling learning per-port
-+so let's make use of this feature. Rename some unfortunately
-+named registers in the process.
++Move this module over to the new fixup mechanism.
 +
-+Suggested-by: Vladimir Oltean <olteanv@gmail.com>
-+Cc: Alvin Å ipraga <alsi@bang-olufsen.dk>
-+Cc: Mauri Sandberg <sandberg@mailfence.com>
-+Cc: Florian Fainelli <f.fainelli@gmail.com>
-+Cc: DENG Qingfang <dqfext@gmail.com>
-+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 +---
-+ drivers/net/dsa/rtl8366rb.c | 50 ++++++++++++++++++++++++++++++++-----
-+ 1 file changed, 44 insertions(+), 6 deletions(-)
++ drivers/net/phy/sfp.c | 12 +++++++-----
++ 1 file changed, 7 insertions(+), 5 deletions(-)
 +
-+--- a/drivers/net/dsa/rtl8366rb.c
-++++ b/drivers/net/dsa/rtl8366rb.c
-+@@ -14,6 +14,7 @@
-+ 
-+ #include <linux/bitops.h>
-+ #include <linux/etherdevice.h>
-++#include <linux/if_bridge.h>
-+ #include <linux/interrupt.h>
-+ #include <linux/irqdomain.h>
-+ #include <linux/irqchip/chained_irq.h>
-+@@ -42,9 +43,12 @@
-+ /* Port Enable Control register */
-+ #define RTL8366RB_PECR				0x0001
-+ 
-+-/* Switch Security Control registers */
-+-#define RTL8366RB_SSCR0				0x0002
-+-#define RTL8366RB_SSCR1				0x0003
-++/* Switch per-port learning disablement register */
-++#define RTL8366RB_PORT_LEARNDIS_CTRL		0x0002
-++
-++/* Security control, actually aging register */
-++#define RTL8366RB_SECURITY_CTRL			0x0003
-++
-+ #define RTL8366RB_SSCR2				0x0004
-+ #define RTL8366RB_SSCR2_DROP_UNKNOWN_DA		BIT(0)
-+ 
-+@@ -927,13 +931,14 @@ static int rtl8366rb_setup(struct dsa_sw
-+ 		/* layer 2 size, see rtl8366rb_change_mtu() */
-+ 		rb->max_mtu[i] = 1532;
-+ 
-+-	/* Enable learning for all ports */
-+-	ret = regmap_write(smi->map, RTL8366RB_SSCR0, 0);
-++	/* Disable learning for all ports */
-++	ret = regmap_write(smi->map, RTL8366RB_PORT_LEARNDIS_CTRL,
-++			   RTL8366RB_PORT_ALL);
-+ 	if (ret)
-+ 		return ret;
-+ 
-+ 	/* Enable auto ageing for all ports */
-+-	ret = regmap_write(smi->map, RTL8366RB_SSCR1, 0);
-++	ret = regmap_write(smi->map, RTL8366RB_SECURITY_CTRL, 0);
-+ 	if (ret)
-+ 		return ret;
-+ 
-+@@ -1272,6 +1277,37 @@ static int rtl8366rb_vlan_filtering(stru
-+ 	return ret;
++--- a/drivers/net/phy/sfp.c
+++++ b/drivers/net/phy/sfp.c
++@@ -316,6 +316,11 @@ static void sfp_fixup_long_startup(struc
++ 	sfp->module_t_start_up = T_START_UP_BAD_GPON;
 + }
 + 
-++static int
-++rtl8366rb_port_pre_bridge_flags(struct dsa_switch *ds, int port,
-++				struct switchdev_brport_flags flags,
-++				struct netlink_ext_ack *extack)
-++{
-++	/* We support enabling/disabling learning */
-++	if (flags.mask & ~(BR_LEARNING))
-++		return -EINVAL;
-++
-++	return 0;
-++}
-++
-++static int
-++rtl8366rb_port_bridge_flags(struct dsa_switch *ds, int port,
-++			    struct switchdev_brport_flags flags,
-++			    struct netlink_ext_ack *extack)
+++static void sfp_fixup_ignore_tx_fault(struct sfp *sfp)
 ++{
-++	struct realtek_smi *smi = ds->priv;
-++	int ret;
-++
-++	if (flags.mask & BR_LEARNING) {
-++		ret = regmap_update_bits(smi->map, RTL8366RB_PORT_LEARNDIS_CTRL,
-++					 BIT(port),
-++					 (flags.val & BR_LEARNING) ? 0 : BIT(port));
-++		if (ret)
-++			return ret;
-++	}
-++
-++	return 0;
+++	sfp->tx_fault_ignore = true;
 ++}
 ++
-+ static int rtl8366rb_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
++ static void sfp_quirk_2500basex(const struct sfp_eeprom_id *id,
++ 				unsigned long *modes)
 + {
-+ 	struct realtek_smi *smi = ds->priv;
-+@@ -1682,6 +1718,8 @@ static const struct dsa_switch_ops rtl83
-+ 	.port_vlan_del = rtl8366_vlan_del,
-+ 	.port_enable = rtl8366rb_port_enable,
-+ 	.port_disable = rtl8366rb_port_disable,
-++	.port_pre_bridge_flags = rtl8366rb_port_pre_bridge_flags,
-++	.port_bridge_flags = rtl8366rb_port_bridge_flags,
-+ 	.port_change_mtu = rtl8366rb_change_mtu,
-+ 	.port_max_mtu = rtl8366rb_max_mtu,
-+ };
-diff --git a/target/linux/generic/backport-5.15/774-v5.16-09-net-dsa-rtl8366rb-Support-fast-aging.patch b/target/linux/generic/backport-5.15/774-v5.16-09-net-dsa-rtl8366rb-Support-fast-aging.patch
++@@ -353,6 +358,7 @@ static const struct sfp_quirk sfp_quirks
++ 		.vendor = "HUAWEI",
++ 		.part = "MA5671A",
++ 		.modes = sfp_quirk_2500basex,
+++		.fixup = sfp_fixup_ignore_tx_fault,
++ 	}, {
++ 		// Lantech 8330-262D-E can operate at 2500base-X, but
++ 		// incorrectly report 2500MBd NRZ in their EEPROM
++@@ -2049,11 +2055,7 @@ static int sfp_sm_mod_probe(struct sfp *
++ 
++ 	sfp->module_t_start_up = T_START_UP;
++ 
++-	if (!memcmp(id.base.vendor_name, "HUAWEI          ", 16) &&
++-	    !memcmp(id.base.vendor_pn, "MA5671A         ", 16))
++-		sfp->tx_fault_ignore = true;
++-	else
++-		sfp->tx_fault_ignore = false;
+++	sfp->tx_fault_ignore = false;
++ 
++ 	sfp->quirk = sfp_lookup_quirk(&id);
++ 	if (sfp->quirk && sfp->quirk->fixup)
+diff --git a/target/linux/generic/backport-5.15/787-v6.1-net-sfp-add-support-for-HALNy-GPON-SFP.patch b/target/linux/generic/backport-5.15/787-v6.1-net-sfp-add-support-for-HALNy-GPON-SFP.patch
 new file mode 100644
-index 0000000000..a74108e23d
+index 0000000000..0c65de5ab8
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/774-v5.16-09-net-dsa-rtl8366rb-Support-fast-aging.patch
-@@ -0,0 +1,57 @@
-+From 8eb13420eb9ab4a4e2ebd612bf5dc9dba0039236 Mon Sep 17 00:00:00 2001
-+From: Linus Walleij <linus.walleij@linaro.org>
-+Date: Tue, 5 Oct 2021 21:47:03 +0200
-+Subject: [PATCH 09/11] net: dsa: rtl8366rb: Support fast aging
-+MIME-Version: 1.0
-+Content-Type: text/plain; charset=UTF-8
-+Content-Transfer-Encoding: 8bit
-+
-+This implements fast aging per-port using the special "security"
-+register, which will flush any learned L2 LUT entries on a port.
++++ b/target/linux/generic/backport-5.15/787-v6.1-net-sfp-add-support-for-HALNy-GPON-SFP.patch
+@@ -0,0 +1,79 @@
++From 73472c830eae5fce2107f7f086f1e6827d215caf Mon Sep 17 00:00:00 2001
++From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
++Date: Tue, 13 Sep 2022 20:06:48 +0100
++Subject: [PATCH 1/1] net: sfp: add support for HALNy GPON SFP
 +
-+The vendor API just enabled setting and clearing this bit, so
-+we set it to age out any entries on the port and then we clear
-+it again.
++Add a quirk for the HALNy HL-GSFP module, which appears to have an
++inverted RX_LOS signal, and maybe uses TX_FAULT as a serial port
++transmit pin. Rather than use these hardware signals, switch to
++using software polling for these status signals.
 +
-+Suggested-by: Vladimir Oltean <olteanv@gmail.com>
-+Cc: Mauri Sandberg <sandberg@mailfence.com>
-+Cc: DENG Qingfang <dqfext@gmail.com>
-+Cc: Florian Fainelli <f.fainelli@gmail.com>
-+Reviewed-by: Alvin Å ipraga <alsi@bang-olufsen.dk>
-+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
-+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
-+Signed-off-by: David S. Miller <davem@davemloft.net>
++Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
++Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 +---
-+ drivers/net/dsa/rtl8366rb.c | 14 ++++++++++++++
-+ 1 file changed, 14 insertions(+)
-+
-+--- a/drivers/net/dsa/rtl8366rb.c
-++++ b/drivers/net/dsa/rtl8366rb.c
-+@@ -1308,6 +1308,19 @@ rtl8366rb_port_bridge_flags(struct dsa_s
-+ 	return 0;
++ drivers/net/phy/sfp-bus.c |  2 +-
++ drivers/net/phy/sfp.c     | 21 ++++++++++++++++++---
++ 2 files changed, 19 insertions(+), 4 deletions(-)
++
++--- a/drivers/net/phy/sfp-bus.c
+++++ b/drivers/net/phy/sfp-bus.c
++@@ -283,7 +283,7 @@ void sfp_parse_support(struct sfp_bus *b
++ 			phylink_set(modes, 2500baseX_Full);
++ 	}
++ 
++-	if (bus->sfp_quirk)
+++	if (bus->sfp_quirk && bus->sfp_quirk->modes)
++ 		bus->sfp_quirk->modes(id, modes);
++ 
++ 	linkmode_or(support, support, modes);
++--- a/drivers/net/phy/sfp.c
+++++ b/drivers/net/phy/sfp.c
++@@ -321,6 +321,15 @@ static void sfp_fixup_ignore_tx_fault(st
++ 	sfp->tx_fault_ignore = true;
 + }
 + 
-++static void
-++rtl8366rb_port_fast_age(struct dsa_switch *ds, int port)
+++static void sfp_fixup_halny_gsfp(struct sfp *sfp)
 ++{
-++	struct realtek_smi *smi = ds->priv;
-++
-++	/* This will age out any learned L2 entries */
-++	regmap_update_bits(smi->map, RTL8366RB_SECURITY_CTRL,
-++			   BIT(port), BIT(port));
-++	/* Restore the normal state of things */
-++	regmap_update_bits(smi->map, RTL8366RB_SECURITY_CTRL,
-++			   BIT(port), 0);
+++	/* Ignore the TX_FAULT and LOS signals on this module.
+++	 * these are possibly used for other purposes on this
+++	 * module, e.g. a serial port.
+++	 */
+++	sfp->state_hw_mask &= ~(SFP_F_TX_FAULT | SFP_F_LOS);
 ++}
 ++
-+ static int rtl8366rb_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
++ static void sfp_quirk_2500basex(const struct sfp_eeprom_id *id,
++ 				unsigned long *modes)
 + {
-+ 	struct realtek_smi *smi = ds->priv;
-+@@ -1720,6 +1733,7 @@ static const struct dsa_switch_ops rtl83
-+ 	.port_disable = rtl8366rb_port_disable,
-+ 	.port_pre_bridge_flags = rtl8366rb_port_pre_bridge_flags,
-+ 	.port_bridge_flags = rtl8366rb_port_bridge_flags,
-++	.port_fast_age = rtl8366rb_port_fast_age,
-+ 	.port_change_mtu = rtl8366rb_change_mtu,
-+ 	.port_max_mtu = rtl8366rb_max_mtu,
++@@ -353,6 +362,10 @@ static const struct sfp_quirk sfp_quirks
++ 		.modes = sfp_quirk_2500basex,
++ 		.fixup = sfp_fixup_long_startup,
++ 	}, {
+++		.vendor = "HALNy",
+++		.part = "HL-GSFP",
+++		.fixup = sfp_fixup_halny_gsfp,
+++	}, {
++ 		// Huawei MA5671A can operate at 2500base-X, but report 1.2GBd
++ 		// NRZ in their EEPROM
++ 		.vendor = "HUAWEI",
++@@ -369,16 +382,18 @@ static const struct sfp_quirk sfp_quirks
++ 		.vendor = "UBNT",
++ 		.part = "UF-INSTANT",
++ 		.modes = sfp_quirk_ubnt_uf_instant,
++-	},
+++	}
 + };
-diff --git a/target/linux/generic/backport-5.15/774-v5.16-10-net-dsa-rtl8366rb-Support-setting-STP-state.patch b/target/linux/generic/backport-5.15/774-v5.16-10-net-dsa-rtl8366rb-Support-setting-STP-state.patch
++ 
++ static size_t sfp_strlen(const char *str, size_t maxlen)
++ {
++ 	size_t size, i;
++ 
++-	/* Trailing characters should be filled with space chars */
+++	/* Trailing characters should be filled with space chars, but
+++	 * some manufacturers can't read SFF-8472 and use NUL.
+++	 */
++ 	for (i = 0, size = 0; i < maxlen; i++)
++-		if (str[i] != ' ')
+++		if (str[i] != ' ' && str[i] != '\0')
++ 			size = i + 1;
++ 
++ 	return size;
+diff --git a/target/linux/generic/backport-5.15/797-v5.17-net-usb-ax88179_178a-add-TSO-feature.patch b/target/linux/generic/backport-5.15/797-v5.17-net-usb-ax88179_178a-add-TSO-feature.patch
 new file mode 100644
-index 0000000000..e787ce9481
+index 0000000000..a2168aaba5
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/774-v5.16-10-net-dsa-rtl8366rb-Support-setting-STP-state.patch
-@@ -0,0 +1,107 @@
-+From 90c855471a89d3e05ecf5b6464bd04abf2c83b70 Mon Sep 17 00:00:00 2001
-+From: Linus Walleij <linus.walleij@linaro.org>
-+Date: Tue, 5 Oct 2021 21:47:04 +0200
-+Subject: [PATCH 10/11] net: dsa: rtl8366rb: Support setting STP state
-+MIME-Version: 1.0
-+Content-Type: text/plain; charset=UTF-8
-+Content-Transfer-Encoding: 8bit
-+
-+This adds support for setting the STP state to the RTL8366RB
-+DSA switch. This rids the following message from the kernel on
-+e.g. OpenWrt:
++++ b/target/linux/generic/backport-5.15/797-v5.17-net-usb-ax88179_178a-add-TSO-feature.patch
+@@ -0,0 +1,68 @@
++From 16b1c4e01c89ba07367461e0bc4cb84993c2d027 Mon Sep 17 00:00:00 2001
++From: Jacky Chou <jackychou@asix.com.tw>
++Date: Mon, 15 Nov 2021 11:49:41 +0800
++Subject: [PATCH] net: usb: ax88179_178a: add TSO feature
 +
-+DSA: failed to set STP state 3 (-95)
++On low-effciency embedded platforms, transmission performance is poor
++due to on Bulk-out with single packet.
++Adding TSO feature improves the transmission performance and reduces
++the number of interrupt caused by Bulk-out complete.
 +
-+Since the RTL8366RB has one STP state register per FID with
-+two bit per port in each, we simply loop over all the FIDs
-+and set the state on all of them.
++Reference to module, net: usb: aqc111.
 +
-+Cc: Vladimir Oltean <olteanv@gmail.com>
-+Cc: Alvin Å ipraga <alsi@bang-olufsen.dk>
-+Cc: Mauri Sandberg <sandberg@mailfence.com>
-+Cc: DENG Qingfang <dqfext@gmail.com>
-+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
-+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
++Signed-off-by: Jacky Chou <jackychou@asix.com.tw>
 +Signed-off-by: David S. Miller <davem@davemloft.net>
 +---
-+ drivers/net/dsa/rtl8366rb.c | 48 +++++++++++++++++++++++++++++++++++++
-+ 1 file changed, 48 insertions(+)
++ drivers/net/usb/ax88179_178a.c | 17 +++++++++++------
++ 1 file changed, 11 insertions(+), 6 deletions(-)
 +
-+--- a/drivers/net/dsa/rtl8366rb.c
-++++ b/drivers/net/dsa/rtl8366rb.c
-+@@ -110,6 +110,18 @@
++--- a/drivers/net/usb/ax88179_178a.c
+++++ b/drivers/net/usb/ax88179_178a.c
++@@ -1377,11 +1377,12 @@ static int ax88179_bind(struct usbnet *d
++ 	dev->mii.phy_id = 0x03;
++ 	dev->mii.supports_gmii = 1;
 + 
-+ #define RTL8366RB_POWER_SAVING_REG	0x0021
++-	dev->net->features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM |
++-			      NETIF_F_RXCSUM;
+++	dev->net->features |= NETIF_F_SG | NETIF_F_IP_CSUM |
+++			      NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM | NETIF_F_TSO;
 + 
-++/* Spanning tree status (STP) control, two bits per port per FID */
-++#define RTL8366RB_STP_STATE_BASE	0x0050 /* 0x0050..0x0057 */
-++#define RTL8366RB_STP_STATE_DISABLED	0x0
-++#define RTL8366RB_STP_STATE_BLOCKING	0x1
-++#define RTL8366RB_STP_STATE_LEARNING	0x2
-++#define RTL8366RB_STP_STATE_FORWARDING	0x3
-++#define RTL8366RB_STP_MASK		GENMASK(1, 0)
-++#define RTL8366RB_STP_STATE(port, state) \
-++	((state) << ((port) * 2))
-++#define RTL8366RB_STP_STATE_MASK(port) \
-++	RTL8366RB_STP_STATE((port), RTL8366RB_STP_MASK)
++-	dev->net->hw_features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM |
++-				 NETIF_F_RXCSUM;
+++	dev->net->hw_features |= dev->net->features;
 ++
-+ /* CPU port control reg */
-+ #define RTL8368RB_CPU_CTRL_REG		0x0061
-+ #define RTL8368RB_CPU_PORTS_MSK		0x00FF
-+@@ -234,6 +246,7 @@
-+ #define RTL8366RB_NUM_LEDGROUPS		4
-+ #define RTL8366RB_NUM_VIDS		4096
-+ #define RTL8366RB_PRIORITYMAX		7
-++#define RTL8366RB_NUM_FIDS		8
-+ #define RTL8366RB_FIDMAX		7
+++	netif_set_gso_max_size(dev->net, 16384);
 + 
-+ #define RTL8366RB_PORT_1		BIT(0) /* In userspace port 0 */
-+@@ -1309,6 +1322,40 @@ rtl8366rb_port_bridge_flags(struct dsa_s
-+ }
++ 	/* Enable checksum offload */
++ 	*tmp = AX_RXCOE_IP | AX_RXCOE_TCP | AX_RXCOE_UDP |
++@@ -1587,17 +1588,19 @@ ax88179_tx_fixup(struct usbnet *dev, str
++ {
++ 	u32 tx_hdr1, tx_hdr2;
++ 	int frame_size = dev->maxpacket;
++-	int mss = skb_shinfo(skb)->gso_size;
++ 	int headroom;
++ 	void *ptr;
 + 
-+ static void
-++rtl8366rb_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
-++{
-++	struct realtek_smi *smi = ds->priv;
-++	u32 val;
-++	int i;
-++
-++	switch (state) {
-++	case BR_STATE_DISABLED:
-++		val = RTL8366RB_STP_STATE_DISABLED;
-++		break;
-++	case BR_STATE_BLOCKING:
-++	case BR_STATE_LISTENING:
-++		val = RTL8366RB_STP_STATE_BLOCKING;
-++		break;
-++	case BR_STATE_LEARNING:
-++		val = RTL8366RB_STP_STATE_LEARNING;
-++		break;
-++	case BR_STATE_FORWARDING:
-++		val = RTL8366RB_STP_STATE_FORWARDING;
-++		break;
-++	default:
-++		dev_err(smi->dev, "unknown bridge state requested\n");
-++		return;
-++	};
++ 	tx_hdr1 = skb->len;
++-	tx_hdr2 = mss;
+++	tx_hdr2 = skb_shinfo(skb)->gso_size; /* Set TSO mss */
++ 	if (((skb->len + 8) % frame_size) == 0)
++ 		tx_hdr2 |= 0x80008000;	/* Enable padding */
++ 
++ 	headroom = skb_headroom(skb) - 8;
++ 
+++	if ((dev->net->features & NETIF_F_SG) && skb_linearize(skb))
+++		return NULL;
 ++
-++	/* Set the same status for the port on all the FIDs */
-++	for (i = 0; i < RTL8366RB_NUM_FIDS; i++) {
-++		regmap_update_bits(smi->map, RTL8366RB_STP_STATE_BASE + i,
-++				   RTL8366RB_STP_STATE_MASK(port),
-++				   RTL8366RB_STP_STATE(port, val));
-++	}
-++}
++ 	if ((skb_header_cloned(skb) || headroom < 0) &&
++ 	    pskb_expand_head(skb, headroom < 0 ? 8 : 0, 0, GFP_ATOMIC)) {
++ 		dev_kfree_skb_any(skb);
++@@ -1608,6 +1611,8 @@ ax88179_tx_fixup(struct usbnet *dev, str
++ 	put_unaligned_le32(tx_hdr1, ptr);
++ 	put_unaligned_le32(tx_hdr2, ptr + 4);
++ 
+++	usbnet_set_skb_tx_stats(skb, (skb_shinfo(skb)->gso_segs ?: 1), 0);
 ++
-++static void
-+ rtl8366rb_port_fast_age(struct dsa_switch *ds, int port)
-+ {
-+ 	struct realtek_smi *smi = ds->priv;
-+@@ -1733,6 +1780,7 @@ static const struct dsa_switch_ops rtl83
-+ 	.port_disable = rtl8366rb_port_disable,
-+ 	.port_pre_bridge_flags = rtl8366rb_port_pre_bridge_flags,
-+ 	.port_bridge_flags = rtl8366rb_port_bridge_flags,
-++	.port_stp_state_set = rtl8366rb_port_stp_state_set,
-+ 	.port_fast_age = rtl8366rb_port_fast_age,
-+ 	.port_change_mtu = rtl8366rb_change_mtu,
-+ 	.port_max_mtu = rtl8366rb_max_mtu,
-diff --git a/target/linux/generic/backport-5.15/800-v5.20-0001-dt-bindings-leds-add-Broadcom-s-BCM63138-controller.patch b/target/linux/generic/backport-5.15/800-v5.20-0001-dt-bindings-leds-add-Broadcom-s-BCM63138-controller.patch
++ 	return skb;
++ }
++ 
+diff --git a/target/linux/generic/backport-5.15/800-v6.0-0001-dt-bindings-leds-add-Broadcom-s-BCM63138-controller.patch b/target/linux/generic/backport-5.15/800-v6.0-0001-dt-bindings-leds-add-Broadcom-s-BCM63138-controller.patch
 new file mode 100644
 index 0000000000..b1072ce640
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/800-v5.20-0001-dt-bindings-leds-add-Broadcom-s-BCM63138-controller.patch
++++ b/target/linux/generic/backport-5.15/800-v6.0-0001-dt-bindings-leds-add-Broadcom-s-BCM63138-controller.patch
 @@ -0,0 +1,125 @@
 +From 13344f8ce8a0d98aa7f5d69ce3b47393c73a343b Mon Sep 17 00:00:00 2001
 +From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
@@ -23673,11 +68435,11 @@ index 0000000000..b1072ce640
 ++            active-low;
 ++        };
 ++    };
-diff --git a/target/linux/generic/backport-5.15/800-v5.20-0002-leds-bcm63138-add-support-for-BCM63138-controller.patch b/target/linux/generic/backport-5.15/800-v5.20-0002-leds-bcm63138-add-support-for-BCM63138-controller.patch
+diff --git a/target/linux/generic/backport-5.15/800-v6.0-0002-leds-bcm63138-add-support-for-BCM63138-controller.patch b/target/linux/generic/backport-5.15/800-v6.0-0002-leds-bcm63138-add-support-for-BCM63138-controller.patch
 new file mode 100644
 index 0000000000..376584574f
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/800-v5.20-0002-leds-bcm63138-add-support-for-BCM63138-controller.patch
++++ b/target/linux/generic/backport-5.15/800-v6.0-0002-leds-bcm63138-add-support-for-BCM63138-controller.patch
 @@ -0,0 +1,356 @@
 +From a0ba692072d89075d0a75c7ad9df31f2c1ee9a1c Mon Sep 17 00:00:00 2001
 +From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
@@ -24035,11 +68797,11 @@ index 0000000000..376584574f
 ++MODULE_AUTHOR("Rafał Miłecki");
 ++MODULE_LICENSE("GPL");
 ++MODULE_DEVICE_TABLE(of, bcm63138_leds_of_match_table);
-diff --git a/target/linux/generic/backport-5.15/801-v5.20-0001-dt-bindings-leds-leds-bcm63138-unify-full-stops-in-d.patch b/target/linux/generic/backport-5.15/801-v5.20-0001-dt-bindings-leds-leds-bcm63138-unify-full-stops-in-d.patch
+diff --git a/target/linux/generic/backport-5.15/801-v6.0-0001-dt-bindings-leds-leds-bcm63138-unify-full-stops-in-d.patch b/target/linux/generic/backport-5.15/801-v6.0-0001-dt-bindings-leds-leds-bcm63138-unify-full-stops-in-d.patch
 new file mode 100644
 index 0000000000..483826abed
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/801-v5.20-0001-dt-bindings-leds-leds-bcm63138-unify-full-stops-in-d.patch
++++ b/target/linux/generic/backport-5.15/801-v6.0-0001-dt-bindings-leds-leds-bcm63138-unify-full-stops-in-d.patch
 @@ -0,0 +1,30 @@
 +From 13b64a0c19059b38150c79d65d350ae44034c5df Mon Sep 17 00:00:00 2001
 +From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
@@ -24071,11 +68833,11 @@ index 0000000000..483826abed
 + 
 +     required:
 +       - reg
-diff --git a/target/linux/generic/backport-5.15/801-v5.20-0002-leds-add-help-info-about-BCM63138-module-name.patch b/target/linux/generic/backport-5.15/801-v5.20-0002-leds-add-help-info-about-BCM63138-module-name.patch
+diff --git a/target/linux/generic/backport-5.15/801-v6.0-0002-leds-add-help-info-about-BCM63138-module-name.patch b/target/linux/generic/backport-5.15/801-v6.0-0002-leds-add-help-info-about-BCM63138-module-name.patch
 new file mode 100644
 index 0000000000..5430b1f084
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/801-v5.20-0002-leds-add-help-info-about-BCM63138-module-name.patch
++++ b/target/linux/generic/backport-5.15/801-v6.0-0002-leds-add-help-info-about-BCM63138-module-name.patch
 @@ -0,0 +1,28 @@
 +From bcc607cdbb1f931111196699426f0cb83bfb296a Mon Sep 17 00:00:00 2001
 +From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
@@ -24105,11 +68867,11 @@ index 0000000000..5430b1f084
 + config LEDS_LGM
 +        tristate "LED support for LGM SoC series"
 +        depends on X86 || COMPILE_TEST
-diff --git a/target/linux/generic/backport-5.15/801-v5.20-0003-leds-leds-bcm63138-get-rid-of-LED_OFF.patch b/target/linux/generic/backport-5.15/801-v5.20-0003-leds-leds-bcm63138-get-rid-of-LED_OFF.patch
+diff --git a/target/linux/generic/backport-5.15/801-v6.0-0003-leds-leds-bcm63138-get-rid-of-LED_OFF.patch b/target/linux/generic/backport-5.15/801-v6.0-0003-leds-leds-bcm63138-get-rid-of-LED_OFF.patch
 new file mode 100644
 index 0000000000..e125a54613
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/801-v5.20-0003-leds-leds-bcm63138-get-rid-of-LED_OFF.patch
++++ b/target/linux/generic/backport-5.15/801-v6.0-0003-leds-leds-bcm63138-get-rid-of-LED_OFF.patch
 @@ -0,0 +1,30 @@
 +From 92cfc71ee2ddfb499ed53e21b28bdf8739bc70bc Mon Sep 17 00:00:00 2001
 +From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
@@ -24141,6 +68903,973 @@ index 0000000000..e125a54613
 + }
 + 
 + /*
+diff --git a/target/linux/generic/backport-5.15/802-v6.1-0001-nvmem-add-driver-handling-U-Boot-environment-variabl.patch b/target/linux/generic/backport-5.15/802-v6.1-0001-nvmem-add-driver-handling-U-Boot-environment-variabl.patch
+new file mode 100644
+index 0000000000..a40c61f929
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/802-v6.1-0001-nvmem-add-driver-handling-U-Boot-environment-variabl.patch
+@@ -0,0 +1,278 @@
++From f955dc14450695564926711cf9fa8e1d5d854302 Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Wed, 15 Jun 2022 21:43:00 +0200
++Subject: [PATCH] nvmem: add driver handling U-Boot environment variables
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++U-Boot stores its setup as environment variables. It's a list of
++key-value pairs stored on flash device with a custom header.
++
++This commit adds an NVMEM driver that:
++1. Provides NVMEM access to environment vars binary data
++2. Extracts variables as NVMEM cells
++
++Current Linux's NVMEM sysfs API allows reading whole NVMEM data block.
++It can be used by user-space tools for reading U-Boot env vars block
++without the hassle of finding its location. Parsing will still need to
++be re-done there.
++
++Kernel-parsed NVMEM cells can be read however by Linux drivers. This may
++be useful for Ethernet drivers for reading device MAC address which is
++often stored as U-Boot env variable.
++
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Reviewed-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
++Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
++---
++
++--- a/drivers/nvmem/Kconfig
+++++ b/drivers/nvmem/Kconfig
++@@ -300,4 +300,17 @@ config NVMEM_BRCM_NVRAM
++ 	  This driver provides support for Broadcom's NVRAM that can be accessed
++ 	  using I/O mapping.
++ 
+++config NVMEM_U_BOOT_ENV
+++	tristate "U-Boot environment variables support"
+++	depends on OF && MTD
+++	select CRC32
+++	help
+++	  U-Boot stores its setup as environment variables. This driver adds
+++	  support for verifying & exporting such data. It also exposes variables
+++	  as NVMEM cells so they can be referenced by other drivers.
+++
+++	  Currently this drivers works only with env variables on top of MTD.
+++
+++	  If compiled as module it will be called nvmem_u-boot-env.
+++
++ endif
++--- a/drivers/nvmem/Makefile
+++++ b/drivers/nvmem/Makefile
++@@ -61,3 +61,5 @@ obj-$(CONFIG_NVMEM_RMEM) 	+= nvmem-rmem.
++ nvmem-rmem-y			:= rmem.o
++ obj-$(CONFIG_NVMEM_BRCM_NVRAM)	+= nvmem_brcm_nvram.o
++ nvmem_brcm_nvram-y		:= brcm_nvram.o
+++obj-$(CONFIG_NVMEM_U_BOOT_ENV)	+= nvmem_u-boot-env.o
+++nvmem_u-boot-env-y		:= u-boot-env.o
++--- /dev/null
+++++ b/drivers/nvmem/u-boot-env.c
++@@ -0,0 +1,218 @@
+++// SPDX-License-Identifier: GPL-2.0-only
+++/*
+++ * Copyright (C) 2022 Rafał Miłecki <rafal@milecki.pl>
+++ */
+++
+++#include <linux/crc32.h>
+++#include <linux/mod_devicetable.h>
+++#include <linux/module.h>
+++#include <linux/mtd/mtd.h>
+++#include <linux/nvmem-consumer.h>
+++#include <linux/nvmem-provider.h>
+++#include <linux/of_device.h>
+++#include <linux/platform_device.h>
+++#include <linux/slab.h>
+++
+++enum u_boot_env_format {
+++	U_BOOT_FORMAT_SINGLE,
+++	U_BOOT_FORMAT_REDUNDANT,
+++};
+++
+++struct u_boot_env {
+++	struct device *dev;
+++	enum u_boot_env_format format;
+++
+++	struct mtd_info *mtd;
+++
+++	/* Cells */
+++	struct nvmem_cell_info *cells;
+++	int ncells;
+++};
+++
+++struct u_boot_env_image_single {
+++	__le32 crc32;
+++	uint8_t data[];
+++} __packed;
+++
+++struct u_boot_env_image_redundant {
+++	__le32 crc32;
+++	u8 mark;
+++	uint8_t data[];
+++} __packed;
+++
+++static int u_boot_env_read(void *context, unsigned int offset, void *val,
+++			   size_t bytes)
+++{
+++	struct u_boot_env *priv = context;
+++	struct device *dev = priv->dev;
+++	size_t bytes_read;
+++	int err;
+++
+++	err = mtd_read(priv->mtd, offset, bytes, &bytes_read, val);
+++	if (err && !mtd_is_bitflip(err)) {
+++		dev_err(dev, "Failed to read from mtd: %d\n", err);
+++		return err;
+++	}
+++
+++	if (bytes_read != bytes) {
+++		dev_err(dev, "Failed to read %zu bytes\n", bytes);
+++		return -EIO;
+++	}
+++
+++	return 0;
+++}
+++
+++static int u_boot_env_add_cells(struct u_boot_env *priv, uint8_t *buf,
+++				size_t data_offset, size_t data_len)
+++{
+++	struct device *dev = priv->dev;
+++	char *data = buf + data_offset;
+++	char *var, *value, *eq;
+++	int idx;
+++
+++	priv->ncells = 0;
+++	for (var = data; var < data + data_len && *var; var += strlen(var) + 1)
+++		priv->ncells++;
+++
+++	priv->cells = devm_kcalloc(dev, priv->ncells, sizeof(*priv->cells), GFP_KERNEL);
+++	if (!priv->cells)
+++		return -ENOMEM;
+++
+++	for (var = data, idx = 0;
+++	     var < data + data_len && *var;
+++	     var = value + strlen(value) + 1, idx++) {
+++		eq = strchr(var, '=');
+++		if (!eq)
+++			break;
+++		*eq = '\0';
+++		value = eq + 1;
+++
+++		priv->cells[idx].name = devm_kstrdup(dev, var, GFP_KERNEL);
+++		if (!priv->cells[idx].name)
+++			return -ENOMEM;
+++		priv->cells[idx].offset = data_offset + value - data;
+++		priv->cells[idx].bytes = strlen(value);
+++	}
+++
+++	if (WARN_ON(idx != priv->ncells))
+++		priv->ncells = idx;
+++
+++	return 0;
+++}
+++
+++static int u_boot_env_parse(struct u_boot_env *priv)
+++{
+++	struct device *dev = priv->dev;
+++	size_t crc32_data_offset;
+++	size_t crc32_data_len;
+++	size_t crc32_offset;
+++	size_t data_offset;
+++	size_t data_len;
+++	uint32_t crc32;
+++	uint32_t calc;
+++	size_t bytes;
+++	uint8_t *buf;
+++	int err;
+++
+++	buf = kcalloc(1, priv->mtd->size, GFP_KERNEL);
+++	if (!buf) {
+++		err = -ENOMEM;
+++		goto err_out;
+++	}
+++
+++	err = mtd_read(priv->mtd, 0, priv->mtd->size, &bytes, buf);
+++	if ((err && !mtd_is_bitflip(err)) || bytes != priv->mtd->size) {
+++		dev_err(dev, "Failed to read from mtd: %d\n", err);
+++		goto err_kfree;
+++	}
+++
+++	switch (priv->format) {
+++	case U_BOOT_FORMAT_SINGLE:
+++		crc32_offset = offsetof(struct u_boot_env_image_single, crc32);
+++		crc32_data_offset = offsetof(struct u_boot_env_image_single, data);
+++		data_offset = offsetof(struct u_boot_env_image_single, data);
+++		break;
+++	case U_BOOT_FORMAT_REDUNDANT:
+++		crc32_offset = offsetof(struct u_boot_env_image_redundant, crc32);
+++		crc32_data_offset = offsetof(struct u_boot_env_image_redundant, mark);
+++		data_offset = offsetof(struct u_boot_env_image_redundant, data);
+++		break;
+++	}
+++	crc32 = le32_to_cpu(*(uint32_t *)(buf + crc32_offset));
+++	crc32_data_len = priv->mtd->size - crc32_data_offset;
+++	data_len = priv->mtd->size - data_offset;
+++
+++	calc = crc32(~0, buf + crc32_data_offset, crc32_data_len) ^ ~0L;
+++	if (calc != crc32) {
+++		dev_err(dev, "Invalid calculated CRC32: 0x%08x (expected: 0x%08x)\n", calc, crc32);
+++		err = -EINVAL;
+++		goto err_kfree;
+++	}
+++
+++	buf[priv->mtd->size - 1] = '\0';
+++	err = u_boot_env_add_cells(priv, buf, data_offset, data_len);
+++	if (err)
+++		dev_err(dev, "Failed to add cells: %d\n", err);
+++
+++err_kfree:
+++	kfree(buf);
+++err_out:
+++	return err;
+++}
+++
+++static int u_boot_env_probe(struct platform_device *pdev)
+++{
+++	struct nvmem_config config = {
+++		.name = "u-boot-env",
+++		.reg_read = u_boot_env_read,
+++	};
+++	struct device *dev = &pdev->dev;
+++	struct device_node *np = dev->of_node;
+++	struct u_boot_env *priv;
+++	int err;
+++
+++	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+++	if (!priv)
+++		return -ENOMEM;
+++	priv->dev = dev;
+++
+++	priv->format = (uintptr_t)of_device_get_match_data(dev);
+++
+++	priv->mtd = of_get_mtd_device_by_node(np);
+++	if (IS_ERR(priv->mtd)) {
+++		dev_err_probe(dev, PTR_ERR(priv->mtd), "Failed to get %pOF MTD\n", np);
+++		return PTR_ERR(priv->mtd);
+++	}
+++
+++	err = u_boot_env_parse(priv);
+++	if (err)
+++		return err;
+++
+++	config.dev = dev;
+++	config.cells = priv->cells;
+++	config.ncells = priv->ncells;
+++	config.priv = priv;
+++	config.size = priv->mtd->size;
+++
+++	return PTR_ERR_OR_ZERO(devm_nvmem_register(dev, &config));
+++}
+++
+++static const struct of_device_id u_boot_env_of_match_table[] = {
+++	{ .compatible = "u-boot,env", .data = (void *)U_BOOT_FORMAT_SINGLE, },
+++	{ .compatible = "u-boot,env-redundant-bool", .data = (void *)U_BOOT_FORMAT_REDUNDANT, },
+++	{ .compatible = "u-boot,env-redundant-count", .data = (void *)U_BOOT_FORMAT_REDUNDANT, },
+++	{},
+++};
+++
+++static struct platform_driver u_boot_env_driver = {
+++	.probe = u_boot_env_probe,
+++	.driver = {
+++		.name = "u_boot_env",
+++		.of_match_table = u_boot_env_of_match_table,
+++	},
+++};
+++module_platform_driver(u_boot_env_driver);
+++
+++MODULE_AUTHOR("Rafał Miłecki");
+++MODULE_LICENSE("GPL");
+++MODULE_DEVICE_TABLE(of, u_boot_env_of_match_table);
+diff --git a/target/linux/generic/backport-5.15/802-v6.1-0002-nvmem-u-boot-env-find-Device-Tree-nodes-for-NVMEM-ce.patch b/target/linux/generic/backport-5.15/802-v6.1-0002-nvmem-u-boot-env-find-Device-Tree-nodes-for-NVMEM-ce.patch
+new file mode 100644
+index 0000000000..3a6b76a221
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/802-v6.1-0002-nvmem-u-boot-env-find-Device-Tree-nodes-for-NVMEM-ce.patch
+@@ -0,0 +1,29 @@
++From d69efcf951df4dcc74a0e1554969c533aec8aa9b Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Thu, 15 Sep 2022 22:06:29 +0200
++Subject: [PATCH] nvmem: u-boot-env: find Device Tree nodes for NVMEM cells
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++DT binding allows specifying NVMEM cells as NVMEM device (provider)
++subnodes. Looks for such subnodes when building NVMEM cells.
++
++This allows NVMEM consumers to use U-Boot environment variables.
++
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
++---
++ drivers/nvmem/u-boot-env.c | 1 +
++ 1 file changed, 1 insertion(+)
++
++--- a/drivers/nvmem/u-boot-env.c
+++++ b/drivers/nvmem/u-boot-env.c
++@@ -92,6 +92,7 @@ static int u_boot_env_add_cells(struct u
++ 			return -ENOMEM;
++ 		priv->cells[idx].offset = data_offset + value - data;
++ 		priv->cells[idx].bytes = strlen(value);
+++		priv->cells[idx].np = of_get_child_by_name(dev->of_node, priv->cells[idx].name);
++ 	}
++ 
++ 	if (WARN_ON(idx != priv->ncells))
+diff --git a/target/linux/generic/backport-5.15/802-v6.1-0003-nvmem-u-boot-env-fix-crc32-casting-type.patch b/target/linux/generic/backport-5.15/802-v6.1-0003-nvmem-u-boot-env-fix-crc32-casting-type.patch
+new file mode 100644
+index 0000000000..6b40557116
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/802-v6.1-0003-nvmem-u-boot-env-fix-crc32-casting-type.patch
+@@ -0,0 +1,30 @@
++From 60bbaad38109684b156e21112322e0a922f92cde Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Thu, 18 Aug 2022 06:38:37 +0200
++Subject: [PATCH] nvmem: u-boot-env: fix crc32 casting type
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++This fixes:
++drivers/nvmem/u-boot-env.c:141:17: sparse: sparse: cast to restricted __le32
++
++Reported-by: kernel test robot <lkp@intel.com>
++Fixes: f955dc1445069 ("nvmem: add driver handling U-Boot environment variables")
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
++---
++ drivers/nvmem/u-boot-env.c | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
++
++--- a/drivers/nvmem/u-boot-env.c
+++++ b/drivers/nvmem/u-boot-env.c
++@@ -139,7 +139,7 @@ static int u_boot_env_parse(struct u_boo
++ 		data_offset = offsetof(struct u_boot_env_image_redundant, data);
++ 		break;
++ 	}
++-	crc32 = le32_to_cpu(*(uint32_t *)(buf + crc32_offset));
+++	crc32 = le32_to_cpu(*(__le32 *)(buf + crc32_offset));
++ 	crc32_data_len = priv->mtd->size - crc32_data_offset;
++ 	data_len = priv->mtd->size - data_offset;
++ 
+diff --git a/target/linux/generic/backport-5.15/803-v5.19-nvmem-core-support-passing-DT-node-in-cell-info.patch b/target/linux/generic/backport-5.15/803-v5.19-nvmem-core-support-passing-DT-node-in-cell-info.patch
+new file mode 100644
+index 0000000000..1acc6947fe
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/803-v5.19-nvmem-core-support-passing-DT-node-in-cell-info.patch
+@@ -0,0 +1,41 @@
++From dbc2f62061c6bfba0aee93161ee3194dcee84bd0 Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Fri, 29 Apr 2022 17:26:46 +0100
++Subject: [PATCH] nvmem: core: support passing DT node in cell info
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++Some hardware may have NVMEM cells described in Device Tree using
++individual nodes. Let drivers pass such nodes to the NVMEM subsystem so
++they can be later used by NVMEM consumers.
++
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
++Link: https://lore.kernel.org/r/20220429162701.2222-2-srinivas.kandagatla@linaro.org
++Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
++---
++ drivers/nvmem/core.c           | 1 +
++ include/linux/nvmem-consumer.h | 1 +
++ 2 files changed, 2 insertions(+)
++
++--- a/drivers/nvmem/core.c
+++++ b/drivers/nvmem/core.c
++@@ -462,6 +462,7 @@ static int nvmem_cell_info_to_nvmem_cell
++ 
++ 	cell->bit_offset = info->bit_offset;
++ 	cell->nbits = info->nbits;
+++	cell->np = info->np;
++ 
++ 	if (cell->nbits)
++ 		cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset,
++--- a/include/linux/nvmem-consumer.h
+++++ b/include/linux/nvmem-consumer.h
++@@ -25,6 +25,7 @@ struct nvmem_cell_info {
++ 	unsigned int		bytes;
++ 	unsigned int		bit_offset;
++ 	unsigned int		nbits;
+++	struct device_node	*np;
++ };
++ 
++ /**
+diff --git a/target/linux/generic/backport-5.15/804-v6.2-0001-nvmem-u-boot-env-fix-crc32_data_offset-on-redundant-.patch b/target/linux/generic/backport-5.15/804-v6.2-0001-nvmem-u-boot-env-fix-crc32_data_offset-on-redundant-.patch
+new file mode 100644
+index 0000000000..69d5a1b845
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/804-v6.2-0001-nvmem-u-boot-env-fix-crc32_data_offset-on-redundant-.patch
+@@ -0,0 +1,56 @@
++From 7a69ff9c9bde03a690ea783970f664782fc303d8 Mon Sep 17 00:00:00 2001
++From: Christian Lamparter <chunkeey@gmail.com>
++Date: Fri, 4 Nov 2022 17:52:03 +0100
++Subject: [PATCH] nvmem: u-boot-env: fix crc32_data_offset on redundant
++ u-boot-env
++
++The Western Digital MyBook Live (PowerPC 464/APM82181)
++has a set of redundant u-boot-env. Loading up the driver
++the following error:
++
++| u_boot_env: Invalid calculated CRC32: 0x4f8f2c86 (expected: 0x98b14514)
++| u_boot_env: probe of partition@1e000 failed with error -22
++
++Looking up the userspace libubootenv utilities source [0],
++it looks like the "mark" or "flag" is not part of the
++crc32 sum... which is unfortunate :(
++
++|static int libuboot_load(struct uboot_ctx *ctx)
++|{
++|[...]
++|       if (ctx->redundant) {
++|		[...]
++|               offsetdata = offsetof(struct uboot_env_redund, data);
++|		[...]					//-----^^
++|       }
++|       usable_envsize = ctx->size - offsetdata;
++|       buf[0] = malloc(bufsize);
++|[...]
++|	for (i = 0; i < copies; i++) {
++|		data = (uint8_t *)(buf[i] + offsetdata);
++|               uint32_t crc;
++|
++|		ret = devread(ctx, i, buf[i]);
++|		[...]
++|		crc = *(uint32_t *)(buf[i] + offsetcrc);
++|               dev->crc = crc32(0, (uint8_t *)data, usable_envsize);
++|
++
++[0] https://github.com/sbabic/libubootenv/blob/master/src/uboot_env.c#L951
++Fixes: d5542923f200 ("nvmem: add driver handling U-Boot environment variables")
++Signed-off-by: Christian Lamparter <chunkeey@gmail.com>
++---
++ drivers/nvmem/u-boot-env.c | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
++
++--- a/drivers/nvmem/u-boot-env.c
+++++ b/drivers/nvmem/u-boot-env.c
++@@ -135,7 +135,7 @@ static int u_boot_env_parse(struct u_boo
++ 		break;
++ 	case U_BOOT_FORMAT_REDUNDANT:
++ 		crc32_offset = offsetof(struct u_boot_env_image_redundant, crc32);
++-		crc32_data_offset = offsetof(struct u_boot_env_image_redundant, mark);
+++		crc32_data_offset = offsetof(struct u_boot_env_image_redundant, data);
++ 		data_offset = offsetof(struct u_boot_env_image_redundant, data);
++ 		break;
++ 	}
+diff --git a/target/linux/generic/backport-5.15/804-v6.2-0002-nvmem-u-boot-env-align-endianness-of-crc32-values.patch b/target/linux/generic/backport-5.15/804-v6.2-0002-nvmem-u-boot-env-align-endianness-of-crc32-values.patch
+new file mode 100644
+index 0000000000..7d6723bb8e
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/804-v6.2-0002-nvmem-u-boot-env-align-endianness-of-crc32-values.patch
+@@ -0,0 +1,47 @@
++From 0e71cac033bb7689c4dfa2e6814191337ef770f5 Mon Sep 17 00:00:00 2001
++From: INAGAKI Hiroshi <musashino.open@gmail.com>
++Date: Thu, 13 Oct 2022 00:51:33 +0900
++Subject: [PATCH] nvmem: u-boot-env: align endianness of crc32 values
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++This patch fixes crc32 error on Big-Endianness system by conversion of
++calculated crc32 value.
++
++Little-Endianness system:
++
++  obtained crc32: Little
++calculated crc32: Little
++
++Big-Endianness system:
++
++  obtained crc32: Little
++calculated crc32: Big
++
++log (APRESIA ApresiaLightGS120GT-SS, RTL8382M, Big-Endianness):
++
++[    8.570000] u_boot_env 18001200.spi:flash@0:partitions:partition@c0000: Invalid calculated CRC32: 0x88cd6f09 (expected: 0x096fcd88)
++[    8.580000] u_boot_env: probe of 18001200.spi:flash@0:partitions:partition@c0000 failed with error -22
++
++Fixes: f955dc1445069 ("nvmem: add driver handling U-Boot environment variables")
++
++Signed-off-by: INAGAKI Hiroshi <musashino.open@gmail.com>
++Acked-by: Rafał Miłecki <rafal@milecki.pl>
++Tested-by: Christian Lamparter <chunkeey@gmail.com>
++Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
++---
++ drivers/nvmem/u-boot-env.c | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
++
++--- a/drivers/nvmem/u-boot-env.c
+++++ b/drivers/nvmem/u-boot-env.c
++@@ -143,7 +143,7 @@ static int u_boot_env_parse(struct u_boo
++ 	crc32_data_len = priv->mtd->size - crc32_data_offset;
++ 	data_len = priv->mtd->size - data_offset;
++ 
++-	calc = crc32(~0, buf + crc32_data_offset, crc32_data_len) ^ ~0L;
+++	calc = le32_to_cpu((__le32)crc32(~0, buf + crc32_data_offset, crc32_data_len) ^ ~0L);
++ 	if (calc != crc32) {
++ 		dev_err(dev, "Invalid calculated CRC32: 0x%08x (expected: 0x%08x)\n", calc, crc32);
++ 		err = -EINVAL;
+diff --git a/target/linux/generic/backport-5.15/804-v6.2-0003-nvmem-u-boot-env-add-Broadcom-format-support.patch b/target/linux/generic/backport-5.15/804-v6.2-0003-nvmem-u-boot-env-add-Broadcom-format-support.patch
+new file mode 100644
+index 0000000000..429b24f0f3
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/804-v6.2-0003-nvmem-u-boot-env-add-Broadcom-format-support.patch
+@@ -0,0 +1,65 @@
++From 5b4eaafbeac472fc19049152f18e88aecb2b2829 Mon Sep 17 00:00:00 2001
++From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
++Date: Mon, 17 Oct 2022 09:17:22 +0200
++Subject: [PATCH] nvmem: u-boot-env: add Broadcom format support
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++Broadcom uses U-Boot for a lot of their bcmbca familiy chipsets. They
++decided to store U-Boot environment data inside U-Boot partition and to
++use a custom header (with "uEnv" magic and env data length).
++
++Add support for Broadcom's specific binding and their custom format.
++
++Ref: 6b0584c19d87 ("dt-bindings: nvmem: u-boot,env: add Broadcom's variant binding")
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
++---
++ drivers/nvmem/u-boot-env.c | 14 ++++++++++++++
++ 1 file changed, 14 insertions(+)
++
++--- a/drivers/nvmem/u-boot-env.c
+++++ b/drivers/nvmem/u-boot-env.c
++@@ -16,6 +16,7 @@
++ enum u_boot_env_format {
++ 	U_BOOT_FORMAT_SINGLE,
++ 	U_BOOT_FORMAT_REDUNDANT,
+++	U_BOOT_FORMAT_BROADCOM,
++ };
++ 
++ struct u_boot_env {
++@@ -40,6 +41,13 @@ struct u_boot_env_image_redundant {
++ 	uint8_t data[];
++ } __packed;
++ 
+++struct u_boot_env_image_broadcom {
+++	__le32 magic;
+++	__le32 len;
+++	__le32 crc32;
+++	uint8_t data[0];
+++} __packed;
+++
++ static int u_boot_env_read(void *context, unsigned int offset, void *val,
++ 			   size_t bytes)
++ {
++@@ -138,6 +146,11 @@ static int u_boot_env_parse(struct u_boo
++ 		crc32_data_offset = offsetof(struct u_boot_env_image_redundant, data);
++ 		data_offset = offsetof(struct u_boot_env_image_redundant, data);
++ 		break;
+++	case U_BOOT_FORMAT_BROADCOM:
+++		crc32_offset = offsetof(struct u_boot_env_image_broadcom, crc32);
+++		crc32_data_offset = offsetof(struct u_boot_env_image_broadcom, data);
+++		data_offset = offsetof(struct u_boot_env_image_broadcom, data);
+++		break;
++ 	}
++ 	crc32 = le32_to_cpu(*(__le32 *)(buf + crc32_offset));
++ 	crc32_data_len = priv->mtd->size - crc32_data_offset;
++@@ -202,6 +215,7 @@ static const struct of_device_id u_boot_
++ 	{ .compatible = "u-boot,env", .data = (void *)U_BOOT_FORMAT_SINGLE, },
++ 	{ .compatible = "u-boot,env-redundant-bool", .data = (void *)U_BOOT_FORMAT_REDUNDANT, },
++ 	{ .compatible = "u-boot,env-redundant-count", .data = (void *)U_BOOT_FORMAT_REDUNDANT, },
+++	{ .compatible = "brcm,env", .data = (void *)U_BOOT_FORMAT_BROADCOM, },
++ 	{},
++ };
++ 
+diff --git a/target/linux/generic/backport-5.15/810-v5.17-net-qmi_wwan-add-ZTE-MF286D-modem-19d2-1485.patch b/target/linux/generic/backport-5.15/810-v5.17-net-qmi_wwan-add-ZTE-MF286D-modem-19d2-1485.patch
+new file mode 100644
+index 0000000000..25802be7e3
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/810-v5.17-net-qmi_wwan-add-ZTE-MF286D-modem-19d2-1485.patch
+@@ -0,0 +1,59 @@
++From 078c6a1cbd4cd7496048786beec2e312577bebbf Mon Sep 17 00:00:00 2001
++From: Pawel Dembicki <paweldembicki@gmail.com>
++Date: Tue, 11 Jan 2022 23:11:32 +0100
++Subject: [PATCH 1/1] net: qmi_wwan: add ZTE MF286D modem 19d2:1485
++MIME-Version: 1.0
++Content-Type: text/plain; charset=UTF-8
++Content-Transfer-Encoding: 8bit
++
++Modem from ZTE MF286D is an Qualcomm MDM9250 based 3G/4G modem.
++
++T:  Bus=02 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#=  3 Spd=5000 MxCh= 0
++D:  Ver= 3.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 9 #Cfgs=  1
++P:  Vendor=19d2 ProdID=1485 Rev=52.87
++S:  Manufacturer=ZTE,Incorporated
++S:  Product=ZTE Technologies MSM
++S:  SerialNumber=MF286DZTED000000
++C:* #Ifs= 7 Cfg#= 1 Atr=80 MxPwr=896mA
++A:  FirstIf#= 0 IfCount= 2 Cls=02(comm.) Sub=06 Prot=00
++I:* If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=02 Prot=ff Driver=rndis_host
++E:  Ad=82(I) Atr=03(Int.) MxPS=   8 Ivl=32ms
++I:* If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=rndis_host
++E:  Ad=81(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms
++E:  Ad=01(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms
++I:* If#= 2 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=ff Driver=option
++E:  Ad=83(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms
++E:  Ad=02(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms
++I:* If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=ff Driver=option
++E:  Ad=85(I) Atr=03(Int.) MxPS=  10 Ivl=32ms
++E:  Ad=84(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms
++E:  Ad=03(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms
++I:* If#= 4 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=ff Driver=option
++E:  Ad=87(I) Atr=03(Int.) MxPS=  10 Ivl=32ms
++E:  Ad=86(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms
++E:  Ad=04(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms
++I:* If#= 5 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=ff Driver=qmi_wwan
++E:  Ad=88(I) Atr=03(Int.) MxPS=   8 Ivl=32ms
++E:  Ad=8e(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms
++E:  Ad=0f(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms
++I:* If#= 6 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=42 Prot=01 Driver=usbfs
++E:  Ad=05(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms
++E:  Ad=89(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms
++
++Signed-off-by: Pawel Dembicki <paweldembicki@gmail.com>
++Acked-by: Bjørn Mork <bjorn@mork.no>
++Signed-off-by: David S. Miller <davem@davemloft.net>
++---
++ drivers/net/usb/qmi_wwan.c | 1 +
++ 1 file changed, 1 insertion(+)
++
++--- a/drivers/net/usb/qmi_wwan.c
+++++ b/drivers/net/usb/qmi_wwan.c
++@@ -1314,6 +1314,7 @@ static const struct usb_device_id produc
++ 	{QMI_FIXED_INTF(0x19d2, 0x1426, 2)},	/* ZTE MF91 */
++ 	{QMI_FIXED_INTF(0x19d2, 0x1428, 2)},	/* Telewell TW-LTE 4G v2 */
++ 	{QMI_FIXED_INTF(0x19d2, 0x1432, 3)},	/* ZTE ME3620 */
+++	{QMI_FIXED_INTF(0x19d2, 0x1485, 5)},	/* ZTE MF286D */
++ 	{QMI_FIXED_INTF(0x19d2, 0x2002, 4)},	/* ZTE (Vodafone) K3765-Z */
++ 	{QMI_FIXED_INTF(0x2001, 0x7e16, 3)},	/* D-Link DWM-221 */
++ 	{QMI_FIXED_INTF(0x2001, 0x7e19, 4)},	/* D-Link DWM-221 B1 */
+diff --git a/target/linux/generic/backport-5.15/821-v5.16-Bluetooth-btusb-Support-public-address-configuration.patch b/target/linux/generic/backport-5.15/821-v5.16-Bluetooth-btusb-Support-public-address-configuration.patch
+new file mode 100644
+index 0000000000..daa3cac4ef
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/821-v5.16-Bluetooth-btusb-Support-public-address-configuration.patch
+@@ -0,0 +1,51 @@
++From 5cb03751455c299b1bf10cb48631bf359cfb11b5 Mon Sep 17 00:00:00 2001
++From: "mark-yw.chen" <mark-yw.chen@mediatek.com>
++Date: Wed, 1 Sep 2021 11:32:25 +0800
++Subject: [PATCH 1/5] Bluetooth: btusb: Support public address configuration
++ for MediaTek Chip.
++
++The MediaTek chip support vendor specific HCI command(0xfc1a) to
++change the public address. Add hdev->set_bdaddr handler for MediaTek
++Chip. After doing a power cycle or MediaTek Bluetooth reset, BD_ADDR
++will bring back the original one.
++
++Signed-off-by: mark-yw.chen <mark-yw.chen@mediatek.com>
++Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
++---
++ drivers/bluetooth/btusb.c | 18 ++++++++++++++++++
++ 1 file changed, 18 insertions(+)
++
++--- a/drivers/bluetooth/btusb.c
+++++ b/drivers/bluetooth/btusb.c
++@@ -2263,6 +2263,23 @@ struct btmtk_section_map {
++ 	};
++ } __packed;
++ 
+++static int btusb_set_bdaddr_mtk(struct hci_dev *hdev, const bdaddr_t *bdaddr)
+++{
+++	struct sk_buff *skb;
+++	long ret;
+++
+++	skb = __hci_cmd_sync(hdev, 0xfc1a, sizeof(bdaddr), bdaddr, HCI_INIT_TIMEOUT);
+++	if (IS_ERR(skb)) {
+++		ret = PTR_ERR(skb);
+++		bt_dev_err(hdev, "changing Mediatek device address failed (%ld)",
+++			   ret);
+++		return ret;
+++	}
+++	kfree_skb(skb);
+++
+++	return 0;
+++}
+++
++ static void btusb_mtk_wmt_recv(struct urb *urb)
++ {
++ 	struct hci_dev *hdev = urb->context;
++@@ -3914,6 +3931,7 @@ static int btusb_probe(struct usb_interf
++ 		hdev->shutdown = btusb_mtk_shutdown;
++ 		hdev->manufacturer = 70;
++ 		hdev->cmd_timeout = btusb_mtk_cmd_timeout;
+++		hdev->set_bdaddr = btusb_set_bdaddr_mtk;
++ 		set_bit(HCI_QUIRK_NON_PERSISTENT_SETUP, &hdev->quirks);
++ 		data->recv_acl = btusb_recv_acl_mtk;
++ 	}
+diff --git a/target/linux/generic/backport-5.15/822-v5.17-Bluetooth-btusb-Fix-application-of-sizeof-to-pointer.patch b/target/linux/generic/backport-5.15/822-v5.17-Bluetooth-btusb-Fix-application-of-sizeof-to-pointer.patch
+new file mode 100644
+index 0000000000..b324cb1aa8
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/822-v5.17-Bluetooth-btusb-Fix-application-of-sizeof-to-pointer.patch
+@@ -0,0 +1,29 @@
++From af774a731f7b4c2a90a8476cd44045ba8d1263ba Mon Sep 17 00:00:00 2001
++From: David Yang <davidcomponentone@gmail.com>
++Date: Wed, 13 Oct 2021 08:56:33 +0800
++Subject: [PATCH 2/5] Bluetooth: btusb: Fix application of sizeof to pointer
++
++The coccinelle check report:
++"./drivers/bluetooth/btusb.c:2239:36-42:
++ERROR: application of sizeof to pointer".
++Using the real size to fix it.
++
++Fixes: 5a87679ffd443 ("Bluetooth: btusb: Support public address configuration for MediaTek Chip.")
++Reported-by: Zeal Robot <zealci@zte.com.cn>
++Signed-off-by: David Yang <davidcomponentone@gmail.com>
++Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
++---
++ drivers/bluetooth/btusb.c | 2 +-
++ 1 file changed, 1 insertion(+), 1 deletion(-)
++
++--- a/drivers/bluetooth/btusb.c
+++++ b/drivers/bluetooth/btusb.c
++@@ -2268,7 +2268,7 @@ static int btusb_set_bdaddr_mtk(struct h
++ 	struct sk_buff *skb;
++ 	long ret;
++ 
++-	skb = __hci_cmd_sync(hdev, 0xfc1a, sizeof(bdaddr), bdaddr, HCI_INIT_TIMEOUT);
+++	skb = __hci_cmd_sync(hdev, 0xfc1a, 6, bdaddr, HCI_INIT_TIMEOUT);
++ 	if (IS_ERR(skb)) {
++ 		ret = PTR_ERR(skb);
++ 		bt_dev_err(hdev, "changing Mediatek device address failed (%ld)",
+diff --git a/target/linux/generic/backport-5.15/823-v5.18-Bluetooth-btusb-Add-a-new-PID-VID-13d3-3567-for-MT79.patch b/target/linux/generic/backport-5.15/823-v5.18-Bluetooth-btusb-Add-a-new-PID-VID-13d3-3567-for-MT79.patch
+new file mode 100644
+index 0000000000..549005c5a7
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/823-v5.18-Bluetooth-btusb-Add-a-new-PID-VID-13d3-3567-for-MT79.patch
+@@ -0,0 +1,70 @@
++From e57186fc02cedff191c469a26cce615371e41740 Mon Sep 17 00:00:00 2001
++From: Yake Yang <yake.yang@mediatek.com>
++Date: Wed, 23 Feb 2022 07:55:59 +0800
++Subject: [PATCH 3/5] Bluetooth: btusb: Add a new PID/VID 13d3/3567 for MT7921
++
++Add VID 13D3 & PID 3567 for MediaTek MT7921 USB Bluetooth chip.
++
++The information in /sys/kernel/debug/usb/devices about the Bluetooth
++device is listed as the below.
++
++T:  Bus=05 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#=  2 Spd=480  MxCh= 0
++D:  Ver= 2.10 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs=  1
++P:  Vendor=13d3 ProdID=3567 Rev= 1.00
++S:  Manufacturer=MediaTek Inc.
++S:  Product=Wireless_Device
++S:  SerialNumber=000000000
++C:* #Ifs= 3 Cfg#= 1 Atr=e0 MxPwr=100mA
++A:  FirstIf#= 0 IfCount= 3 Cls=e0(wlcon) Sub=01 Prot=01
++I:* If#= 0 Alt= 0 #EPs= 3 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=81(I) Atr=03(Int.) MxPS=  16 Ivl=125us
++E:  Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
++E:  Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
++I:* If#= 1 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=   0 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=   0 Ivl=1ms
++I:  If#= 1 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=   9 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=   9 Ivl=1ms
++I:  If#= 1 Alt= 2 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=  17 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=  17 Ivl=1ms
++I:  If#= 1 Alt= 3 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=  25 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=  25 Ivl=1ms
++I:  If#= 1 Alt= 4 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=  33 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=  33 Ivl=1ms
++I:  If#= 1 Alt= 5 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=  49 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=  49 Ivl=1ms
++I:  If#= 1 Alt= 6 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=  63 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=  63 Ivl=1ms
++I:* If#= 2 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=(none)
++E:  Ad=8a(I) Atr=03(Int.) MxPS=  64 Ivl=125us
++E:  Ad=0a(O) Atr=03(Int.) MxPS=  64 Ivl=125us
++I:  If#= 2 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=(none)
++E:  Ad=8a(I) Atr=03(Int.) MxPS=  64 Ivl=125us
++E:  Ad=0a(O) Atr=03(Int.) MxPS=  64 Ivl=125us
++
++Co-developed-by: Sean Wang <sean.wang@mediatek.com>
++Signed-off-by: Sean Wang <sean.wang@mediatek.com>
++Signed-off-by: Yake Yang <yake.yang@mediatek.com>
++Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
++---
++ drivers/bluetooth/btusb.c | 3 +++
++ 1 file changed, 3 insertions(+)
++
++--- a/drivers/bluetooth/btusb.c
+++++ b/drivers/bluetooth/btusb.c
++@@ -460,6 +460,9 @@ static const struct usb_device_id blackl
++ 	{ USB_DEVICE(0x13d3, 0x3564), .driver_info = BTUSB_MEDIATEK |
++ 						     BTUSB_WIDEBAND_SPEECH |
++ 						     BTUSB_VALID_LE_STATES },
+++	{ USB_DEVICE(0x13d3, 0x3567), .driver_info = BTUSB_MEDIATEK |
+++						     BTUSB_WIDEBAND_SPEECH |
+++						     BTUSB_VALID_LE_STATES },
++ 	{ USB_DEVICE(0x0489, 0xe0cd), .driver_info = BTUSB_MEDIATEK |
++ 						     BTUSB_WIDEBAND_SPEECH |
++ 						     BTUSB_VALID_LE_STATES },
+diff --git a/target/linux/generic/backport-5.15/824-v5.19-Bluetooth-btusb-Add-a-new-PID-VID-0489-e0c8-for-MT79.patch b/target/linux/generic/backport-5.15/824-v5.19-Bluetooth-btusb-Add-a-new-PID-VID-0489-e0c8-for-MT79.patch
+new file mode 100644
+index 0000000000..54e3388860
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/824-v5.19-Bluetooth-btusb-Add-a-new-PID-VID-0489-e0c8-for-MT79.patch
+@@ -0,0 +1,68 @@
++From e507366cd1e8e1d4eebe537c08fd142cf0b617fa Mon Sep 17 00:00:00 2001
++From: Sean Wang <sean.wang@mediatek.com>
++Date: Thu, 28 Apr 2022 02:38:39 +0800
++Subject: [PATCH 4/5] Bluetooth: btusb: Add a new PID/VID 0489/e0c8 for MT7921
++
++Add VID 0489 & PID e0c8 for MediaTek MT7921 USB Bluetooth chip.
++
++The information in /sys/kernel/debug/usb/devices about the Bluetooth
++device is listed as the below.
++
++T:  Bus=01 Lev=01 Prnt=01 Port=13 Cnt=03 Dev#=  4 Spd=480  MxCh= 0
++D:  Ver= 2.10 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs=  1
++P:  Vendor=0489 ProdID=e0c8 Rev= 1.00
++S:  Manufacturer=MediaTek Inc.
++S:  Product=Wireless_Device
++S:  SerialNumber=000000000
++C:* #Ifs= 3 Cfg#= 1 Atr=e0 MxPwr=100mA
++A:  FirstIf#= 0 IfCount= 3 Cls=e0(wlcon) Sub=01 Prot=01
++I:* If#= 0 Alt= 0 #EPs= 3 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=81(I) Atr=03(Int.) MxPS=  16 Ivl=125us
++E:  Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
++E:  Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
++I:* If#= 1 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=   0 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=   0 Ivl=1ms
++I:  If#= 1 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=   9 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=   9 Ivl=1ms
++I:  If#= 1 Alt= 2 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=  17 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=  17 Ivl=1ms
++I:  If#= 1 Alt= 3 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=  25 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=  25 Ivl=1ms
++I:  If#= 1 Alt= 4 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=  33 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=  33 Ivl=1ms
++I:  If#= 1 Alt= 5 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=  49 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=  49 Ivl=1ms
++I:  If#= 1 Alt= 6 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=  63 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=  63 Ivl=1ms
++I:* If#= 2 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=(none)
++E:  Ad=8a(I) Atr=03(Int.) MxPS=  64 Ivl=125us
++E:  Ad=0a(O) Atr=03(Int.) MxPS=  64 Ivl=125us
++I:  If#= 2 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=(none)
++E:  Ad=8a(I) Atr=03(Int.) MxPS= 512 Ivl=125us
++E:  Ad=0a(O) Atr=03(Int.) MxPS= 512 Ivl=125us
++
++Signed-off-by: Sean Wang <sean.wang@mediatek.com>
++Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
++---
++ drivers/bluetooth/btusb.c | 3 +++
++ 1 file changed, 3 insertions(+)
++
++--- a/drivers/bluetooth/btusb.c
+++++ b/drivers/bluetooth/btusb.c
++@@ -451,6 +451,9 @@ static const struct usb_device_id blackl
++ 						     BTUSB_VALID_LE_STATES },
++ 
++ 	/* Additional MediaTek MT7921 Bluetooth devices */
+++	{ USB_DEVICE(0x0489, 0xe0c8), .driver_info = BTUSB_MEDIATEK |
+++						     BTUSB_WIDEBAND_SPEECH |
+++						     BTUSB_VALID_LE_STATES },
++ 	{ USB_DEVICE(0x04ca, 0x3802), .driver_info = BTUSB_MEDIATEK |
++ 						     BTUSB_WIDEBAND_SPEECH |
++ 						     BTUSB_VALID_LE_STATES },
+diff --git a/target/linux/generic/backport-5.15/825-v6.1-Bluetooth-btusb-Add-a-new-VID-PID-0e8d-0608-for-MT79.patch b/target/linux/generic/backport-5.15/825-v6.1-Bluetooth-btusb-Add-a-new-VID-PID-0e8d-0608-for-MT79.patch
+new file mode 100644
+index 0000000000..8957ff8e22
+--- /dev/null
++++ b/target/linux/generic/backport-5.15/825-v6.1-Bluetooth-btusb-Add-a-new-VID-PID-0e8d-0608-for-MT79.patch
+@@ -0,0 +1,66 @@
++From be55622ce673f9692cc15d26d77a050cda42a3d3 Mon Sep 17 00:00:00 2001
++From: Daniel Golle <daniel@makrotopia.org>
++Date: Fri, 9 Sep 2022 21:00:30 +0100
++Subject: [PATCH 1/1] Bluetooth: btusb: Add a new VID/PID 0e8d/0608 for MT7921
++
++Add a new PID/VID 0e8d/0608 for MT7921K chip found on AMD RZ608 module.
++
++From /sys/kernel/debug/usb/devices:
++T:  Bus=01 Lev=02 Prnt=02 Port=01 Cnt=01 Dev#=  3 Spd=480  MxCh= 0
++D:  Ver= 2.10 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs=  1
++P:  Vendor=0e8d ProdID=0608 Rev= 1.00
++S:  Manufacturer=MediaTek Inc.
++S:  Product=Wireless_Device
++S:  SerialNumber=000000000
++C:* #Ifs= 3 Cfg#= 1 Atr=e0 MxPwr=100mA
++A:  FirstIf#= 0 IfCount= 3 Cls=e0(wlcon) Sub=01 Prot=01
++I:* If#= 0 Alt= 0 #EPs= 3 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=81(I) Atr=03(Int.) MxPS=  16 Ivl=125us
++E:  Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
++E:  Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
++I:* If#= 1 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=   0 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=   0 Ivl=1ms
++I:  If#= 1 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=   9 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=   9 Ivl=1ms
++I:  If#= 1 Alt= 2 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=  17 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=  17 Ivl=1ms
++I:  If#= 1 Alt= 3 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=  25 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=  25 Ivl=1ms
++I:  If#= 1 Alt= 4 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=  33 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=  33 Ivl=1ms
++I:  If#= 1 Alt= 5 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=  49 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=  49 Ivl=1ms
++I:  If#= 1 Alt= 6 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
++E:  Ad=83(I) Atr=01(Isoc) MxPS=  63 Ivl=1ms
++E:  Ad=03(O) Atr=01(Isoc) MxPS=  63 Ivl=1ms
++I:* If#= 2 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=(none)
++E:  Ad=8a(I) Atr=03(Int.) MxPS=  64 Ivl=125us
++E:  Ad=0a(O) Atr=03(Int.) MxPS=  64 Ivl=125us
++I:  If#= 2 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=(none)
++E:  Ad=8a(I) Atr=03(Int.) MxPS=  64 Ivl=125us
++E:  Ad=0a(O) Atr=03(Int.) MxPS=  64 Ivl=125us
++
++Signed-off-by: Daniel Golle <daniel@makrotopia.org>
++Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
++---
++ drivers/bluetooth/btusb.c | 3 +++
++ 1 file changed, 3 insertions(+)
++
++--- a/drivers/bluetooth/btusb.c
+++++ b/drivers/bluetooth/btusb.c
++@@ -469,6 +469,9 @@ static const struct usb_device_id blackl
++ 	{ USB_DEVICE(0x0489, 0xe0cd), .driver_info = BTUSB_MEDIATEK |
++ 						     BTUSB_WIDEBAND_SPEECH |
++ 						     BTUSB_VALID_LE_STATES },
+++	{ USB_DEVICE(0x0e8d, 0x0608), .driver_info = BTUSB_MEDIATEK |
+++						     BTUSB_WIDEBAND_SPEECH |
+++						     BTUSB_VALID_LE_STATES },
++ 
++ 	/* MediaTek MT7922A Bluetooth devices */
++ 	{ USB_DEVICE(0x0489, 0xe0d8), .driver_info = BTUSB_MEDIATEK |
 diff --git a/target/linux/generic/backport-5.15/860-v5.17-MIPS-ath79-drop-_machine_restart-again.patch b/target/linux/generic/backport-5.15/860-v5.17-MIPS-ath79-drop-_machine_restart-again.patch
 new file mode 100644
 index 0000000000..e9d692b651
@@ -24196,11 +69925,11 @@ index 0000000000..e9d692b651
 + 	_machine_halt = ath79_halt;
 + 	pm_power_off = ath79_halt;
 + }
-diff --git a/target/linux/generic/backport-5.15/870-hwmon-next-hwmon-lm70-Add-ti-tmp125-support.patch b/target/linux/generic/backport-5.15/870-hwmon-next-hwmon-lm70-Add-ti-tmp125-support.patch
+diff --git a/target/linux/generic/backport-5.15/870-v5.18-hwmon-lm70-Add-ti-tmp125-support.patch b/target/linux/generic/backport-5.15/870-v5.18-hwmon-lm70-Add-ti-tmp125-support.patch
 new file mode 100644
 index 0000000000..fabf177628
 --- /dev/null
-+++ b/target/linux/generic/backport-5.15/870-hwmon-next-hwmon-lm70-Add-ti-tmp125-support.patch
++++ b/target/linux/generic/backport-5.15/870-v5.18-hwmon-lm70-Add-ti-tmp125-support.patch
 @@ -0,0 +1,71 @@
 +From 31d8f414e1596ba54a4315418e4c0086fda9e428 Mon Sep 17 00:00:00 2001
 +From: Christian Lamparter <chunkeey@gmail.com>
-- 
GitLab