Skip to content
Snippets Groups Projects
0001-Backport-kernel-5.15-generic.patch 820.41 KiB
From 20c1f9271d0ca07239c8676d0f098d424230535e Mon Sep 17 00:00:00 2001
From: Josef Schlehofer <pepe.schlehofer@gmail.com>
Date: Mon, 4 Jul 2022 14:19:08 +0200
Subject: [PATCH] Generic backport patches 5.15

---
 .../011-kbuild-export-SUBARCH.patch           |   21 +
 ...-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 ++++++
 ...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 +++
 ...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 +
 ...or-setting-affinity-if-no-IRQ-parent.patch |   48 +
 ...ow_offload-handle-netdevice-events-f.patch |  106 +
 ...platform_populate-for-MTD-partitions.patch |   72 +
 ...s-add-support-for-Sercomm-partitions.patch |  302 ++
 ...ce-of-support-for-dynamic-partitions.patch |  106 +
 ...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 ++
 ...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 ++
 ...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-remove-bridge-flow-offload-.patch |   44 +
 ..._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 +
 ..._eth_soc-use-after-free-in-__mtk_ppe.patch |   33 +
 ..._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-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 +
 ..._eth_soc-rely-on-txd_size-in-txd_to_.patch |   39 +
 ..._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-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 +
 ...-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 ++
 ...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 +
 ...hy-at803x-fix-resume-for-QCA8327-phy.patch |  131 +
 ...x-add-DAC-amplitude-fix-for-8327-phy.patch |   91 +
 ...nable-prefer-master-for-83xx-interna.patch |   27 +
 ...hy-at803x-better-describe-debug-regs.patch |  127 +
 ...-dsa-qca8k-add-mac-power-sel-support.patch |   80 +
 ...dsa-qca8k-Add-SGMII-clock-phase-prop.patch |   30 +
 ...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 ++
 ...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 +
 ...qca8k-add-support-for-pws-config-reg.patch |   92 +
 ...dsa-qca8k-document-support-for-qca83.patch |   32 +
 ...et-dsa-qca8k-add-support-for-QCA8328.patch |   78 +
 ...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 ++++
 ...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 +++
 ...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 ++
 ...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 ++
 ...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 +
 ...op-updating-master-MTU-from-master.c.patch |   91 +
 ...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 ++
 ...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 +
 ...ag_qca-enable-promisc_on_master-flag.patch |   27 +
 ...add-define-for-handling-mgmt-Etherne.patch |  110 +
 ...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 ++
 ...qca8k-cache-lo-and-hi-for-mdio-write.patch |  164 +
 ...d-support-for-larger-read-write-size.patch |  206 ++
 ...troduce-qca8k_bulk_read-write-functi.patch |  104 +
 ...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 ++
 ...-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 +
 ...s-add-Broadcom-s-BCM63138-controller.patch |  125 +
 ...-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 +
 ...PS-ath79-drop-_machine_restart-again.patch |   49 +
 ...ext-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
 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
 create mode 100644 target/linux/generic/backport-5.15/050-v5.16-03-mips-bpf-Add-new-eBPF-JIT-for-64-bit-MIPS.patch
 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/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/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/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/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/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
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-03-net-ethernet-mtk_eth_soc-implement-flow-offloading-t.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-04-arm64-dts-mediatek-mt7622-introduce-nodes-for-Wirele.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-05-net-ethernet-mtk_eth_soc-add-ipv6-flow-offload-suppo.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-06-net-ethernet-mtk_eth_soc-support-TC_SETUP_BLOCK-for-.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-07-net-ethernet-mtk_eth_soc-allocate-struct-mtk_ppe-sep.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-08-net-ethernet-mtk_eth_soc-rework-hardware-flow-table-.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-09-net-ethernet-mtk_eth_soc-remove-bridge-flow-offload-.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-10-net-ethernet-mtk_eth_soc-support-creating-mac-addres.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-11-net-ethernet-mtk_eth_soc-wed-fix-sparse-endian-warni.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-12-net-ethernet-mtk_eth_soc-fix-return-value-check-in-m.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-13-net-ethernet-mtk_eth_soc-use-standard-property-for-c.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-14-net-ethernet-mtk_eth_soc-use-after-free-in-__mtk_ppe.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-15-net-ethernet-mtk_eth_soc-add-check-for-allocation-fa.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-16-eth-mtk_eth_soc-silence-the-GCC-12-array-bounds-warn.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-17-net-ethernet-mtk_eth_soc-rely-on-GFP_KERNEL-for-dma_.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-18-net-ethernet-mtk_eth_soc-move-tx-dma-desc-configurat.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-19-net-ethernet-mtk_eth_soc-add-txd_size-to-mtk_soc_dat.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-20-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-mtk_tx_.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-21-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-mtk_des.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-22-net-ethernet-mtk_eth_soc-rely-on-txd_size-in-txd_to_.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-23-net-ethernet-mtk_eth_soc-add-rxd_size-to-mtk_soc_dat.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-24-net-ethernet-mtk_eth_soc-rely-on-txd_size-field-in-m.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-25-net-ethernet-mtk_eth_soc-rely-on-rxd_size-field-in-m.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-26-net-ethernet-mtk_eth_soc-introduce-device-register-m.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-27-net-ethernet-mtk_eth_soc-introduce-MTK_NETSYS_V2-sup.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-28-net-ethernet-mtk_eth_soc-convert-ring-dma-pointer-to.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-29-net-ethernet-mtk_eth_soc-convert-scratch_ring-pointe.patch
 create mode 100644 target/linux/generic/backport-5.15/702-v5.19-30-net-ethernet-mtk_eth_soc-introduce-support-for-mt798.patch
 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/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
 create mode 100644 target/linux/generic/backport-5.15/743-v5.16-0001-net-dsa-b53-Include-all-ports-in-enabled_ports.patch
 create mode 100644 target/linux/generic/backport-5.15/743-v5.16-0002-net-dsa-b53-Drop-BCM5301x-workaround-for-a-wrong-CPU.patch
 create mode 100644 target/linux/generic/backport-5.15/743-v5.16-0003-net-dsa-b53-Improve-flow-control-setup-on-BCM5301x.patch
 create mode 100644 target/linux/generic/backport-5.15/743-v5.16-0004-net-dsa-b53-Drop-unused-cpu_port-field.patch
 create mode 100644 target/linux/generic/backport-5.15/745-v5.16-01-net-phy-at803x-add-support-for-qca-8327-A-variant.patch
 create mode 100644 target/linux/generic/backport-5.15/745-v5.16-02-net-phy-at803x-add-resume-suspend-function-to-qca83x.patch
 create mode 100644 target/linux/generic/backport-5.15/745-v5.16-03-net-phy-at803x-fix-spacing-and-improve-name-for-83xx.patch
 create mode 100644 target/linux/generic/backport-5.15/746-v5.16-01-net-phy-at803x-fix-resume-for-QCA8327-phy.patch
 create mode 100644 target/linux/generic/backport-5.15/746-v5.16-02-net-phy-at803x-add-DAC-amplitude-fix-for-8327-phy.patch
 create mode 100644 target/linux/generic/backport-5.15/746-v5.16-03-net-phy-at803x-enable-prefer-master-for-83xx-interna.patch
 create mode 100644 target/linux/generic/backport-5.15/746-v5.16-04-net-phy-at803x-better-describe-debug-regs.patch
 create mode 100644 target/linux/generic/backport-5.15/747-v5.16-01-dsa-qca8k-add-mac-power-sel-support.patch
 create mode 100644 target/linux/generic/backport-5.15/747-v5.16-02-dt-bindings-net-dsa-qca8k-Add-SGMII-clock-phase-prop.patch
 create mode 100644 target/linux/generic/backport-5.15/747-v5.16-03-net-dsa-qca8k-add-support-for-sgmii-falling-edge.patch
 create mode 100644 target/linux/generic/backport-5.15/747-v5.16-04-dt-bindings-net-dsa-qca8k-Document-support-for-CPU-p.patch
 create mode 100644 target/linux/generic/backport-5.15/747-v5.16-05-net-dsa-qca8k-add-support-for-cpu-port-6.patch
 create mode 100644 target/linux/generic/backport-5.15/747-v5.16-06-net-dsa-qca8k-rework-rgmii-delay-logic-and-scan-for-.patch
 create mode 100644 target/linux/generic/backport-5.15/747-v5.16-07-dt-bindings-net-dsa-qca8k-Document-qca-sgmii-enable-.patch
 create mode 100644 target/linux/generic/backport-5.15/747-v5.16-08-net-dsa-qca8k-add-explicit-SGMII-PLL-enable.patch
 create mode 100644 target/linux/generic/backport-5.15/747-v5.16-09-dt-bindings-net-dsa-qca8k-Document-qca-led-open-drai.patch
 create mode 100644 target/linux/generic/backport-5.15/747-v5.16-10-net-dsa-qca8k-add-support-for-pws-config-reg.patch
 create mode 100644 target/linux/generic/backport-5.15/747-v5.16-11-dt-bindings-net-dsa-qca8k-document-support-for-qca83.patch
 create mode 100644 target/linux/generic/backport-5.15/747-v5.16-12-net-dsa-qca8k-add-support-for-QCA8328.patch
 create mode 100644 target/linux/generic/backport-5.15/747-v5.16-13-net-dsa-qca8k-set-internal-delay-also-for-sgmii.patch
 create mode 100644 target/linux/generic/backport-5.15/747-v5.16-14-net-dsa-qca8k-move-port-config-to-dedicated-struct.patch
 create mode 100644 target/linux/generic/backport-5.15/747-v5.16-15-dt-bindings-net-ipq8064-mdio-fix-warning-with-new-qc.patch
 create mode 100644 target/linux/generic/backport-5.15/747-v5.16-16-dt-bindings-net-dsa-qca8k-convert-to-YAML-schema.patch
 create mode 100644 target/linux/generic/backport-5.15/748-v5.16-net-dsa-qca8k-fix-delay-applied-to-wrong-cpu-in-parse-p.patch
 create mode 100644 target/linux/generic/backport-5.15/749-v5.16-net-dsa-qca8k-tidy-for-loop-in-setup-and-add-cpu-port-c.patch
 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/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
 create mode 100644 target/linux/generic/backport-5.15/774-v5.16-02-net-dsa-rtl8366-Drop-custom-VLAN-set-up.patch
 create mode 100644 target/linux/generic/backport-5.15/774-v5.16-03-net-dsa-rtl8366rb-Rewrite-weird-VLAN-filering-enable.patch
 create mode 100644 target/linux/generic/backport-5.15/774-v5.16-06-net-dsa-rtl8366-Drop-and-depromote-pointless-prints.patch
 create mode 100644 target/linux/generic/backport-5.15/774-v5.16-07-net-dsa-rtl8366rb-Use-core-filtering-tracking.patch
 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/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/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
new file mode 100644
index 0000000000..0aedad4bfd
--- /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>
+---
+ Makefile | 4 ++--
+ 1 file changed, 2 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
new file mode 100644
index 0000000000..82feb7421d
--- /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>
+---
+
+--- 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..3a4d573f80
--- /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>
+---
+
+--- 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..7980659961
--- /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>
+---
+ 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
+ 
+ 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)
++{
++	int reg;
++
++	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;
++
++	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 depth;
++}
++
++/* 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 (!ctx->target)
++		return 0;
++
++	if ((addr ^ pc) & ~MIPS_JMP_MASK)
++		return -1;
++
++	return addr & MIPS_JMP_MASK;
++}
++
++/* Compute the PC-relative offset to relative BPF program offset */
++int get_offset(const struct jit_context *ctx, int off)
++{
++	return (INDEX(ctx->descriptors[ctx->bpf_index + off]) -
++		ctx->jit_index - 1) * sizeof(u32);
++}
++
++/* dst = imm (register width) */
++void emit_mov_i(struct jit_context *ctx, u8 dst, s32 imm)
++{
++	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);
++}
++
++/* 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);
++}
++
++/* 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;
++}
++
++/* Rewrite ALU immediate operation */
++bool rewrite_alu_i(u8 op, s32 imm, u8 *alu, s32 *val)
++{
++	bool act = true;
++
++	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;
++	}
++
++	*alu = op;
++	*val = imm;
++	return act;
++}
++
++/* ALU immediate operation (32-bit) */
++void emit_alu_i(struct jit_context *ctx, u8 dst, s32 imm, u8 op)
++{
++	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);
++}
++
++/* 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);
++		}
++		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);
++}
++
++/* Atomic read-modify-write (32-bit) */
++void emit_atomic_r(struct jit_context *ctx, u8 dst, u8 src, s16 off, u8 code)
++{
++	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 */
++}
++
++/* 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);
++}
++
++/* 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;
++
++	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  */
++
++			emit(ctx, lui, msk, 0xff);       /* msk = 0x00ff0000 */
++			emit(ctx, ori, msk, msk, 0xff);  /* msk = msk | 0xff */
++
++			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);
++}
++
++/* 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;
++}
++
++/* 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;
++}
++
++/* 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;
++
++	/* 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);
++	}
++
++	/*
++	 * 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;
++	}
++
++done:
++	*jit_off = offset;
++	*jit_op = op;
++}
++
++/* 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;
++
++	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);
++}
++
++/* 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);
++}
++
++/* 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);
++
++		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)
++{
++	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;
++	}
++}
++
++/* 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;
++	}
++}
++
++/* Jump always */
++int emit_ja(struct jit_context *ctx, s16 off)
++{
++	int target = get_target(ctx, ctx->bpf_index + off + 1);
++
++	if (target < 0)
++		return -1;
++	emit(ctx, j, target);
++	emit(ctx, nop);
++	return 0;
++}
++
++/* Jump to epilogue */
++int emit_exit(struct jit_context *ctx)
++{
++	int target = get_target(ctx, ctx->program->len);
++
++	if (target < 0)
++		return -1;
++	emit(ctx, j, target);
++	emit(ctx, nop);
++	return 0;
++}
++
++/* 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;
++
++	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;
++
++		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;
++		}
++
++		ret = build_insn(insn, ctx);
++		if (ret < 0)
++			return ret;
++
++		if (ret > 0) {
++			i++;
++			if (ctx->target == NULL)
++				descp[1] = ctx->jit_index;
++		}
++	}
++
++	/* Store the end offset, where the epilogue begins */
++	ctx->descriptors[prog->len] = ctx->jit_index;
++	return 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;
++
++	for (i = 0; i <= prog->len; i++)
++		ctx->descriptors[i] = INDEX(ctx->descriptors[i]) | flag;
++}
++
++static void jit_fill_hole(void *area, unsigned int size)
++{
++	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 */
++}
++
++bool bpf_jit_needs_zext(void)
++{
++	return true;
++}
++
++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;
++
++	/*
++	 * 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;
++	}
++
++	memset(&ctx, 0, sizeof(ctx));
++	ctx.program = prog;
++
++	/*
++	 * 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;
++
++	/* 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;
++
++	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);
++
++	if (WARN_ONCE(ctx.changes > 0, "JIT offsets failed to converge"))
++		goto out_err;
++
++	build_epilogue(&ctx, MIPS_R_RA);
++
++	/* 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;
++
++	/* Actual pass to generate final JIT code */
++	ctx.target = (u32 *)image_ptr;
++	ctx.jit_index = 0;
++
++	/*
++	 * 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);
++
++	/* Populate line info meta data */
++	set_convert_flag(&ctx, false);
++	bpf_prog_fill_jited_linfo(prog, &ctx.descriptors[1]);
++
++	/* 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]);
++
++	if (bpf_jit_enable > 1)
++		bpf_jit_dump(prog->len, image_size, 2, ctx.target);
++
++	prog->bpf_func = (void *)ctx.target;
++	prog->jited = 1;
++	prog->jited_len = image_size;
++
++out:
++	if (tmp_blinded)
++		bpf_jit_prog_release_other(prog, prog == orig_prog ?
++					   tmp : orig_prog);
++	kfree(ctx.descriptors);
++	return prog;
++
++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>
++ */
++
++#ifndef _BPF_JIT_COMP_H
++#define _BPF_JIT_COMP_H
++
++/* 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     */
++
++/*
++ * 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)
++{
++	ctx->accessed |= BIT(reg);
++}
++
++/*
++ * 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);
++}
++
++/*
++ * 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);
++
++/*
++ * 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);
++
++/* Compute the 28-bit jump target address from a BPF program location */
++int get_target(struct jit_context *ctx, u32 loc);
++
++/* Compute the PC-relative offset to relative BPF program offset */
++int get_offset(const struct jit_context *ctx, int off);
++
++/* dst = imm (32-bit) */
++void emit_mov_i(struct jit_context *ctx, u8 dst, s32 imm);
++
++/* dst = src (32-bit) */
++void emit_mov_r(struct jit_context *ctx, u8 dst, u8 src);
++
++/* Validate ALU/ALU64 immediate range */
++bool valid_alu_i(u8 op, s32 imm);
++
++/* Rewrite ALU/ALU64 immediate operation */
++bool rewrite_alu_i(u8 op, s32 imm, u8 *alu, s32 *val);
++
++/* ALU immediate operation (32-bit) */
++void emit_alu_i(struct jit_context *ctx, u8 dst, s32 imm, u8 op);
++
++/* ALU register operation (32-bit) */
++void emit_alu_r(struct jit_context *ctx, u8 dst, u8 src, u8 op);
++
++/* Atomic read-modify-write (32-bit) */
++void emit_atomic_r(struct jit_context *ctx, u8 dst, u8 src, s16 off, u8 code);
++
++/* Atomic compare-and-exchange (32-bit) */
++void emit_cmpxchg_r(struct jit_context *ctx, u8 dst, u8 src, u8 res, s16 off);
++
++/* Swap bytes and truncate a register word or half word */
++void emit_bswap_r(struct jit_context *ctx, u8 dst, u32 width);
++
++/* Validate JMP/JMP32 immediate range */
++bool valid_jmp_i(u8 op, s32 imm);
++
++/* 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);
++
++/* 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);
++
++/* Finish a PC-relative jump operation */
++int finish_jmp(struct jit_context *ctx, u8 jit_op, s16 bpf_off);
++
++/* Conditional JMP/JMP32 immediate */
++void emit_jmp_i(struct jit_context *ctx, u8 dst, s32 imm, s32 off, u8 op);
++
++/* Conditional JMP/JMP32 register */
++void emit_jmp_r(struct jit_context *ctx, u8 dst, u8 src, s32 off, u8 op);
++
++/* Jump always */
++int emit_ja(struct jit_context *ctx, s16 off);
++
++/* Jump to epilogue */
++int emit_exit(struct jit_context *ctx);
++
++/*
++ * 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);
++
++/*
++ * 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);
++
++/*
++ * 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 /* _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>
++ */
++
++#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>
++
++#include "bpf_jit_comp.h"
++
++/* 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
++
++/* Stack is 8-byte aligned in o32 ABI */
++#define MIPS_STACK_ALIGNMENT 8
++
++/*
++ * 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
++
++/* Temporary 64-bit register used by JIT */
++#define JIT_REG_TMP MAX_BPF_JIT_REG
++
++/*
++ * 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
++
++/* CPU registers holding the callee return value */
++#define JIT_RETURN_REGS	  \
++	(BIT(MIPS_R_V0) | \
++	 BIT(MIPS_R_V1))
++
++/* 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))
++
++/* 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))
++
++/* Caller-saved CPU registers */
++#define JIT_CALLER_REGS    \
++	(JIT_RETURN_REGS | \
++	 JIT_ARG_REGS    | \
++	 JIT_STACK_REGS)
++
++/* 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))
++
++/*
++ * 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},
++};
++
++/* 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
++}
++
++/* Get high CPU register for a 64-bit eBPF register mapping */
++static inline u8 hi(const u8 reg[])
++{
++#ifdef __BIG_ENDIAN
++	return reg[1];
++#else
++	return reg[0];
++#endif
++}
++
++/*
++ * 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[])
++{
++	clobber_reg(ctx, reg[0]);
++	clobber_reg(ctx, reg[1]);
++}
++
++/* dst = imm (sign-extended) */
++static void emit_mov_se_i64(struct jit_context *ctx, const u8 dst[], s32 imm)
++{
++	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);
++}
++
++/* Zero extension, if verifier does not do it for us  */
++static void emit_zext_ver(struct jit_context *ctx, const u8 dst[])
++{
++	if (!ctx->program->aux->verifier_zext) {
++		emit(ctx, move, hi(dst), MIPS_R_ZERO);
++		clobber_reg(ctx, hi(dst));
++	}
++}
++
++/* Load delay slot, if ISA mandates it */
++static void emit_load_delay(struct jit_context *ctx)
++{
++	if (!cpu_has_mips_2_3_4_5_r)
++		emit(ctx, nop);
++}
++
++/* ALU immediate operation (64-bit) */
++static void emit_alu_i64(struct jit_context *ctx,
++			 const u8 dst[], s32 imm, u8 op)
++{
++	u8 src = MIPS_R_T6;
++
++	/*
++	 * 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;
++		}
++
++	/* Move immediate to temporary register */
++	emit_mov_i(ctx, src, imm);
++
++	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);
++}
++
++/* ALU register operation (64-bit) */
++static void emit_alu_r64(struct jit_context *ctx,
++			 const u8 dst[], const u8 src[], u8 op)
++{
++	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);
++}
++
++/* ALU invert (64-bit) */
++static void emit_neg_i64(struct jit_context *ctx, const u8 dst[])
++{
++	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);
++
++	clobber_reg64(ctx, dst);
++}
++
++/* ALU shift immediate (64-bit) */
++static void emit_shift_i64(struct jit_context *ctx,
++			   const u8 dst[], u32 imm, u8 op)
++{
++	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);
++}
++
++/* ALU shift register (64-bit) */
++static void emit_shift_r64(struct jit_context *ctx,
++			   const u8 dst[], u8 src, u8 op)
++{
++	u8 t1 = MIPS_R_T8;
++	u8 t2 = MIPS_R_T9;
++
++	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) */
++
++	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;
++	}
++
++	/* +20: Done */
++	clobber_reg64(ctx, dst);
++}
++
++/* ALU mul immediate (64x32-bit) */
++static void emit_mul_i64(struct jit_context *ctx, const u8 dst[], s32 imm)
++{
++	u8 src = MIPS_R_T6;
++	u8 tmp = MIPS_R_T9;
++
++	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));
++		}
++
++		/* hi(dst) = hi(dst) - lo(dst) */
++		if (imm < 0)
++			emit(ctx, subu, hi(dst), hi(dst), lo(dst));
++
++		/* 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);
++		}
++
++		/* hi(dst) += tmp */
++		emit(ctx, addu, hi(dst), hi(dst), tmp);
++		clobber_reg64(ctx, dst);
++		break;
++	}
++}
++
++/* 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;
++
++	/* 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);
++	}
++
++	/* 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);
++	}
++
++	/* acc += tmp */
++	emit(ctx, addu, acc, acc, tmp);
++
++	/* 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);
++	}
++
++	/* hi(dst) = acc + tmp */
++	emit(ctx, addu, hi(dst), acc, tmp);
++	clobber_reg64(ctx, dst);
++}
++
++/* Helper function for 64-bit modulo */
++static u64 jit_mod64(u64 a, u64 b)
++{
++	u64 rem;
++
++	div64_u64_rem(a, b, &rem);
++	return rem;
++}
++
++/* ALU div/mod register (64-bit) */
++static void emit_divmod_r64(struct jit_context *ctx,
++			    const u8 dst[], const u8 src[], u8 op)
++{
++	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;
++
++	/* Push caller-saved registers on stack */
++	push_regs(ctx, ctx->clobbered & JIT_CALLER_REGS,
++		  0, JIT_RESERVED_STACK);
++
++	/* 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);
++	}
++
++	/* 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 */
++
++	/* Store the 64-bit result in dst */
++	emit(ctx, move, dst[0], r0[0]);
++	emit(ctx, move, dst[1], r0[1]);
++
++	/* 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);
++
++	clobber_reg64(ctx, dst);
++	clobber_reg(ctx, MIPS_R_V0);
++	clobber_reg(ctx, MIPS_R_V1);
++	clobber_reg(ctx, MIPS_R_RA);
++}
++
++/* 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;
++
++	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        */
++}
++
++/* Swap half words in a register word */
++static void emit_swap16_r(struct jit_context *ctx, u8 dst, u8 src)
++{
++	u8 tmp = MIPS_R_T9;
++
++	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 */
++}
++
++/* 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;
++
++	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);
++
++			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);
++}
++
++/* 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;
++	}
++}
++
++/* 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);
++}
++
++/* 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;
++	}
++}
++
++/* 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)
++{
++	u32 exclude = 0;
++	u32 addr = 0;
++
++	/* 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);
++
++	/* 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 */
++
++	/* 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);
++}
++
++/* Atomic read-modify-write (64-bit) */
++static void emit_atomic_r64(struct jit_context *ctx,
++			    u8 dst, const u8 src[], s16 off, u8 code)
++{
++	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);
++	/*
++	 * 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);
++
++	/* 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 */
++
++	/* 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);
++}
++
++/*
++ * 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        */
++	}
++	clobber_reg(ctx, rd);
++	clobber_reg(ctx, rs);
++}
++
++/*
++ * 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);
++}
++
++/* 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;
++
++	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  */
++	}
++}
++
++/* 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[])
++{
++	u8 tmp = MIPS_R_T9;
++
++	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      */
++}
++
++/* 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)
++{
++	u8 t1 = MIPS_R_T8;
++	u8 t2 = MIPS_R_T9;
++	u8 cmp;
++
++	/*
++	 * 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 */
++
++	/*
++	 * 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       */
++}
++
++/* 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;
++
++	/*
++	 * 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 */
++
++	/* 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      */
++}
++
++/* Jump immediate (64-bit) */
++static void emit_jmp_i64(struct jit_context *ctx,
++			 const u8 dst[], s32 imm, s32 off, u8 op)
++{
++	u8 tmp = MIPS_R_T6;
++
++	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;
++	}
++}
++
++/* Jump register (64-bit) */
++static void emit_jmp_r64(struct jit_context *ctx,
++			 const u8 dst[], const u8 src[], s32 off, u8 op)
++{
++	u8 t1 = MIPS_R_T6;
++	u8 t2 = MIPS_R_T7;
++
++	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;
++	}
++}
++
++/* Function call */
++static int emit_call(struct jit_context *ctx, const struct bpf_insn *insn)
++{
++	bool fixed;
++	u64 addr;
++
++	/* Decode the call address */
++	if (bpf_jit_get_func_addr(ctx->program, insn, false,
++				  &addr, &fixed) < 0)
++		return -1;
++	if (!fixed)
++		return -1;
++
++	/* Push stack arguments */
++	push_regs(ctx, JIT_STACK_REGS, 0, JIT_RESERVED_STACK);
++
++	/* 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 */
++
++	clobber_reg(ctx, MIPS_R_RA);
++	clobber_reg(ctx, MIPS_R_V0);
++	clobber_reg(ctx, MIPS_R_V1);
++	return 0;
++}
++
++/* Function tail call */
++static int emit_tail_call(struct jit_context *ctx)
++{
++	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;
++
++	/*
++	 * 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
++	 */
++
++	/* 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 */
++
++	/* 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         */
++
++	/* if (prog == 0) goto out */
++	emit(ctx, beqz, t2, get_offset(ctx, 1));  /* PC += off(1) if t2 == 0 */
++	emit(ctx, nop);                           /* Delay slot              */
++
++	/* 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)   */
++
++	/* goto func */
++	build_epilogue(ctx, t1);
++	return 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
++ */
++
++/* 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;
++
++	/*
++	 * 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
++
++	/* === Entry-point for tail calls === */
++
++	/* Zero-extend the 32-bit argument */
++	emit(ctx, move, hi(r1), MIPS_R_ZERO);
++
++	/* If the eBPF frame pointer was accessed it must be saved */
++	if (ctx->accessed & BIT(BPF_REG_FP))
++		clobber_reg64(ctx, fp);
++
++	/* Compute the stack space needed for callee-saved registers */
++	saved = hweight32(ctx->clobbered & JIT_CALLEE_REGS) * sizeof(u32);
++	saved = ALIGN(saved, MIPS_STACK_ALIGNMENT);
++
++	/* Stack space used by eBPF program local data */
++	locals = ALIGN(ctx->program->aux->stack_depth, MIPS_STACK_ALIGNMENT);
++
++	/*
++	 * 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;
++
++	/* Allocate the stack frame */
++	stack = ALIGN(saved + locals + reserved, MIPS_STACK_ALIGNMENT);
++	emit(ctx, addiu, MIPS_R_SP, MIPS_R_SP, -stack);
++
++	/* Store callee-saved registers on stack */
++	push_regs(ctx, ctx->clobbered & JIT_CALLEE_REGS, 0, stack - saved);
++
++	/* Initialize the eBPF frame pointer if accessed */
++	if (ctx->accessed & BIT(BPF_REG_FP))
++		emit(ctx, addiu, lo(fp), MIPS_R_SP, stack - saved);
++
++	ctx->saved_size = saved;
++	ctx->stack_size = stack;
++}
++
++/* 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);
++	/*
++	 * 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
++
++	/* 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);
++}
++
++/* Build one eBPF instruction */
++int build_insn(const struct bpf_insn *insn, struct jit_context *ctx)
++{
++	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;
++
++	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);
++		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;
++
++	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/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>
++
++#include "bpf_jit_comp.h"
++
++/* 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
++
++/* Stack is 16-byte aligned in n64 ABI */
++#define MIPS_STACK_ALIGNMENT 16
++
++/* 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)
++
++/* Number of prologue bytes to skip when doing a tail call */
++#define JIT_TCALL_SKIP 4
++
++/* 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)
++{
++	emit(ctx, sll, dst, src, 0);
++	clobber_reg(ctx, dst);
++}
++
++/* 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);
++}
++
++/* dst = imm (64-bit) */
++static void emit_mov_i64(struct jit_context *ctx, u8 dst, u64 imm64)
++{
++	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;
++
++		for (k = 0; k < 4; k++) {
++			u16 half = imm64 >> (48 - 16 * k);
++
++			if (acc == dst)
++				emit(ctx, dsll, dst, dst, 16);
++
++			if (half) {
++				emit(ctx, ori, dst, acc, half);
++				acc = dst;
++			}
++		}
++	}
++	clobber_reg(ctx, dst);
++}
++
++/* 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);
++}
++
++/* 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);
++}
++
++/* 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;
++
++	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   */
++}
++
++/* 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;
++
++			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    */
++
++			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);
++}
++
++/* 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);
++}
++
++/* 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;
++	}
++	clobber_reg(ctx, dst);
++}
++
++/* 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;
++	}
++}
++
++/* 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;
++
++	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 */
++}
++
++/* Function call */
++static int emit_call(struct jit_context *ctx, const struct bpf_insn *insn)
++{
++	u8 zx = bpf2mips64[JIT_REG_ZX];
++	u8 tmp = MIPS_R_T6;
++	bool fixed;
++	u64 addr;
++
++	/* Decode the call address */
++	if (bpf_jit_get_func_addr(ctx->program, insn, false,
++				  &addr, &fixed) < 0)
++		return -1;
++	if (!fixed)
++		return -1;
++
++	/* Push caller-saved registers on stack */
++	push_regs(ctx, ctx->clobbered & JIT_CALLER_REGS, 0, 0);
++
++	/* Emit function call */
++	emit_mov_i64(ctx, tmp, addr);
++	emit(ctx, jalr, MIPS_R_RA, tmp);
++	emit(ctx, nop); /* Delay slot */
++
++	/* Restore caller-saved registers */
++	pop_regs(ctx, ctx->clobbered & JIT_CALLER_REGS, 0, 0);
++
++	/* 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);
++	}
++
++	clobber_reg(ctx, MIPS_R_RA);
++	clobber_reg(ctx, MIPS_R_V0);
++	clobber_reg(ctx, MIPS_R_V1);
++	return 0;
++}
++
++/* 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;
++
++	/*
++	 * 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
++	 */
++
++	/* 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 (--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)      */
++
++	/* if (prog == 0) goto out */
++	emit(ctx, beqz, tmp, get_offset(ctx, 1)); /* PC += off(1) if tmp == 0*/
++	emit(ctx, nop);                           /* Delay slot              */
++
++	/* 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)      */
++
++	/* goto func */
++	build_epilogue(ctx, tmp);
++	access_reg(ctx, JIT_REG_TC);
++	return 0;
++}
++
++/*
++ * 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
++ */
++
++/* 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;
++
++	/*
++	 * 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));
++
++	/* === Entry-point for tail calls === */
++
++	/*
++	 * 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);
++
++	/* Compute the stack space needed for callee-saved registers */
++	saved = hweight32(ctx->clobbered & JIT_CALLEE_REGS) * sizeof(u64);
++	saved = ALIGN(saved, MIPS_STACK_ALIGNMENT);
++
++	/* Stack space used by eBPF program local data */
++	locals = ALIGN(ctx->program->aux->stack_depth, MIPS_STACK_ALIGNMENT);
++
++	/*
++	 * 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;
++
++	/* Allocate the stack frame */
++	stack = ALIGN(saved + locals + reserved, MIPS_STACK_ALIGNMENT);
++	if (stack)
++		emit(ctx, daddiu, MIPS_R_SP, MIPS_R_SP, -stack);
++
++	/* Store callee-saved registers on stack */
++	push_regs(ctx, ctx->clobbered & JIT_CALLEE_REGS, 0, stack - saved);
++
++	/* Initialize the eBPF frame pointer if accessed */
++	if (ctx->accessed & BIT(BPF_REG_FP))
++		emit(ctx, daddiu, fp, MIPS_R_SP, stack - saved);
++
++	/* 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);
++	}
++
++	ctx->saved_size = saved;
++	ctx->stack_size = stack;
++}
++
++/* 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);
++
++	/* Release the stack frame */
++	if (ctx->stack_size)
++		emit(ctx, daddiu, MIPS_R_SP, MIPS_R_SP, ctx->stack_size);
++
++	/* 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 */
++}
++
++/* 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;
++
++	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
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)
+ {
++	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 {
+ };
+ 
+ /* 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
+ 
+ /*
+  * 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;
+ 
++	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 */
+ }
+ 
+@@ -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
new file mode 100644
index 0000000000..41b3d8139f
--- /dev/null
+++ 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
+
+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.
+
+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: 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
new file mode 100644
index 0000000000..e25c336831
--- /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 @@
+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 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: 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/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
new file mode 100644
index 0000000000..e4c0833ae7
--- /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
+
+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: 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(-)
+
+--- 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>
+
+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.
+
+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/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
+
+Move the code that deals with device events to the core.
+
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+---
+
+--- 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);
+ 
++static int nf_flow_table_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	= nf_flow_table_netdev_event,
++};
++
+ static int __init nf_flow_table_module_init(void)
+ {
+-	return nf_flow_table_offload_init();
++	int ret;
++
++	ret = nf_flow_table_offload_init();
++	if (ret)
++		return ret;
++
++	ret = register_netdevice_notifier(&flow_offload_netdev_notifier);
++	if (ret)
++		nf_flow_table_offload_exit();
++
++	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)
++{
++	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-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)
++{
++	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;
++
++	/* 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/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));
++
+ 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;
++
+ 	/*
+ 	 * 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);
++
+ 		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;
++		}
++	}
++
+ 	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)
++{
++	const struct dsa_device_ops *old_tag_ops = dst->tag_ops;
++	struct dsa_notifier_tag_proto_info info;
++	int err;
++
++	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
++	 */
++	info.tag_ops = tag_ops;
++	err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_CONNECT, &info);
++	if (err && err != -EOPNOTSUPP)
++		goto out_disconnect;
++
++	/* Notify the old tagger about the disconnection from this tree */
++	if (old_tag_ops->disconnect)
++		old_tag_ops->disconnect(dst);
++
++	return 0;
++
++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;
++
++	return 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;
++		}
++
+ 		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;
++
++	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-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;
++	}
++
+ 	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;
++
++disconnect:
++	if (tag_ops->disconnect)
++		tag_ops->disconnect(ds);
++
++	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;
++
++	/* 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;
++	}
++
++	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;
+ }
+ 
+ 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);
++
+ 	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;
++
++	rtnl_lock();
++
++	for (i = 0; i < MTK_MAC_COUNT; i++) {
++		dev = eth->netdev[i];
++
++		if (!dev || !(dev->flags & IFF_UP))
++			continue;
++
++		list_add_tail(&dev->close_list, &dev_list);
++	}
++
++	dev_close_many(&dev_list, false);
++
++	eth->dma_dev = dma_dev;
++
++	list_for_each_entry_safe(dev, tmp, &dev_list, close_list) {
++		list_del_init(&dev->close_list);
++		dev_open(dev, NULL);
++	}
++
++	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);
+ 
+ 	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;
++
++		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)
+ 
++/* 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 {
+ 
+ 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
++
+ 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);
++}
++
++static void
++wed_set(struct mtk_wed_device *dev, u32 reg, u32 mask)
++{
++	return wed_m32(dev, reg, 0, mask);
++}
++
++static void
++wed_clr(struct mtk_wed_device *dev, u32 reg, u32 mask)
++{
++	return wed_m32(dev, reg, mask, 0);
++}
++
++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);
++}
++
++static void
++wdma_set(struct mtk_wed_device *dev, u32 reg, u32 mask)
++{
++	wdma_m32(dev, reg, 0, mask);
++}
++
++static u32
++mtk_wed_read_reset(struct mtk_wed_device *dev)
++{
++	return wed_r32(dev, MTK_WED_RESET);
++}
++
++static void
++mtk_wed_reset(struct mtk_wed_device *dev, u32 mask)
++{
++	u32 status;
++
++	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);
++}
++
++static struct mtk_wed_hw *
++mtk_wed_assign(struct mtk_wed_device *dev)
++{
++	struct mtk_wed_hw *hw;
++
++	hw = hw_list[pci_domain_nr(dev->wlan.pci_dev->bus)];
++	if (!hw || hw->wed_dev)
++		return NULL;
++
++	hw->wed_dev = dev;
++	return hw;
++}
++
++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;
++
++	ring_size = dev->wlan.nbuf & ~(MTK_WED_BUF_PER_PAGE - 1);
++	n_pages = ring_size / MTK_WED_BUF_PER_PAGE;
++
++	page_list = kcalloc(n_pages, sizeof(*page_list), GFP_KERNEL);
++	if (!page_list)
++		return -ENOMEM;
++
++	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;
++		}
++
++		page_list[page_idx++] = page;
++		dma_sync_single_for_cpu(dev->hw->dev, page_phys, PAGE_SIZE,
++					DMA_BIDIRECTIONAL);
++
++		buf = page_to_virt(page);
++		buf_phys = page_phys;
++
++		for (s = 0; s < MTK_WED_BUF_PER_PAGE; s++) {
++			u32 txd_size;
++
++			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->info = 0;
++			desc++;
++
++			buf += MTK_WED_BUF_SIZE;
++			buf_phys += MTK_WED_BUF_SIZE;
++		}
++
++		dma_sync_single_for_device(dev->hw->dev, page_phys, PAGE_SIZE,
++					   DMA_BIDIRECTIONAL);
++	}
++
++	return 0;
++}
++
++static void
++mtk_wed_free_buffer(struct mtk_wed_device *dev)
++{
++	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);
++}
++
++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 void
++mtk_wed_free_tx_rings(struct mtk_wed_device *dev)
++{
++	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]);
++}
++
++static void
++mtk_wed_set_ext_int(struct mtk_wed_device *dev, bool en)
++{
++	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);
++}
++
++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);
++
++	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 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);
++
++	mtk_wed_stop(dev);
++
++	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);
++}
++
++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);
++
++	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);
++}
++
++static void
++mtk_wed_hw_init(struct mtk_wed_device *dev)
++{
++	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);
++
++	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)
++{
++	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;
++	}
++}
++
++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 (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;
++
++	if (wdma_r32(dev, MTK_WDMA_GLO_CFG) &
++	    MTK_WED_WDMA_GLO_CFG_RX_DRV_BUSY)
++		return true;
++
++	if (wed_r32(dev, MTK_WED_CTRL) &
++	    (MTK_WED_CTRL_WED_TX_BM_BUSY | MTK_WED_CTRL_WED_TX_FREE_AGENT_BUSY))
++		return true;
++
++	return false;
++}
++
++static int
++mtk_wed_poll_busy(struct mtk_wed_device *dev)
++{
++	int sleep = 15000;
++	int timeout = 100 * sleep;
++	u32 val;
++
++	return read_poll_timeout(mtk_wed_check_busy, val, !val, sleep,
++				 timeout, false, dev);
++}
++
++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;
++
++		mtk_wed_ring_reset(desc, MTK_WED_TX_RING_SIZE);
++	}
++
++	if (mtk_wed_poll_busy(dev))
++		busy = mtk_wed_check_busy(dev);
++
++	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);
++	}
++
++	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);
++	} 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;
++	}
++
++	mtk_wed_reset(dev, MTK_WED_RESET_TX_FREE_AGENT);
++	mtk_wed_reset(dev, MTK_WED_RESET_TX_BM);
++
++	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);
++	}
++
++}
++
++static int
++mtk_wed_ring_alloc(struct mtk_wed_device *dev, struct mtk_wed_ring *ring,
++		   int size)
++{
++	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);
++
++	return 0;
++}
++
++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];
++
++	if (mtk_wed_ring_alloc(dev, wdma, MTK_WED_WDMA_RING_SIZE))
++		return -ENOMEM;
++
++	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 void
++mtk_wed_start(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);
++
++	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;
++}
++
++static int
++mtk_wed_attach(struct mtk_wed_device *dev)
++	__releases(RCU)
++{
++	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;
++
++	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;
++	}
++
++	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;
++	}
++
++	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
++mtk_wed_irq_set_mask(struct mtk_wed_device *dev, u32 mask)
++{
++	if (!dev->running)
++		return;
++
++	mtk_wed_set_ext_int(dev, !!mask);
++	wed_w32(dev, MTK_WED_INT_MASK, mask);
++}
++
++int mtk_wed_flow_add(int index)
++{
++	struct mtk_wed_hw *hw = hw_list[index];
++	int ret;
++
++	if (!hw || !hw->wed_dev)
++		return -ENODEV;
++
++	if (hw->num_flows) {
++		hw->num_flows++;
++		return 0;
++	}
++
++	mutex_lock(&hw_lock);
++	if (!hw->wed_dev) {
++		ret = -ENODEV;
++		goto out;
++	}
++
++	ret = hw->wed_dev->wlan.offload_enable(hw->wed_dev);
++	if (!ret)
++		hw->num_flows++;
++	mtk_wed_set_ext_int(hw->wed_dev, true);
++
++out:
++	mutex_unlock(&hw_lock);
++
++	return ret;
++}
++
++void mtk_wed_flow_remove(int index)
++{
++	struct mtk_wed_hw *hw = hw_list[index];
++
++	if (!hw)
++		return;
++
++	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);
++}
++
++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;
++
++	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);
++
++	mutex_lock(&hw_lock);
++
++	if (WARN_ON(hw_list[index]))
++		goto unlock;
++
++	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;
++	}
++
++	if (!index) {
++		regmap_write(hw->mirror, 0, 0);
++		regmap_write(hw->mirror, 4, 0);
++	}
++	mtk_wed_hw_add_debugfs(hw);
++
++	hw_list[index] = hw;
++
++unlock:
++	mutex_unlock(&hw_lock);
++}
++
++void mtk_wed_exit(void)
++{
++	int i;
++
++	rcu_assign_pointer(mtk_soc_wed_ops, NULL);
++
++	synchronize_rcu();
++
++	for (i = 0; i < ARRAY_SIZE(hw_list); i++) {
++		struct mtk_wed_hw *hw;
++
++		hw = hw_list[i];
++		if (!hw)
++			continue;
++
++		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> */
++
++#ifndef __MTK_WED_PRIV_H
++#define __MTK_WED_PRIV_H
++
++#include <linux/soc/mediatek/mtk_wed.h>
++#include <linux/debugfs.h>
++#include <linux/regmap.h>
++
++struct mtk_eth;
++
++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;
++};
++
++
++#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);
++}
++
++static inline u32
++wed_r32(struct mtk_wed_device *dev, u32 reg)
++{
++	unsigned int val;
++
++	regmap_read(dev->hw->regs, reg, &val);
++
++	return val;
++}
++
++static inline void
++wdma_w32(struct mtk_wed_device *dev, u32 reg, u32 val)
++{
++	writel(val, dev->hw->wdma + reg);
++}
++
++static inline u32
++wdma_r32(struct mtk_wed_device *dev, u32 reg)
++{
++	return readl(dev->hw->wdma + reg);
++}
++
++static inline u32
++wpdma_tx_r32(struct mtk_wed_device *dev, int ring, u32 reg)
++{
++	if (!dev->tx_ring[ring].wpdma)
++		return 0;
++
++	return readl(dev->tx_ring[ring].wpdma + reg);
++}
++
++static inline void
++wpdma_tx_w32(struct mtk_wed_device *dev, int ring, u32 reg, u32 val)
++{
++	if (!dev->tx_ring[ring].wpdma)
++		return;
++
++	writel(val, dev->tx_ring[ring].wpdma + reg);
++}
++
++static inline u32
++wpdma_txfree_r32(struct mtk_wed_device *dev, u32 reg)
++{
++	if (!dev->txfree_ring.wpdma)
++		return 0;
++
++	return readl(dev->txfree_ring.wpdma + reg);
++}
++
++static inline void
++wpdma_txfree_w32(struct mtk_wed_device *dev, u32 reg, u32 val)
++{
++	if (!dev->txfree_ring.wpdma)
++		return;
++
++	writel(val, dev->txfree_ring.wpdma + reg);
++}
++
++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);
++#else
++static inline void mtk_wed_hw_add_debugfs(struct mtk_wed_hw *hw)
++{
++}
++#endif
++
++#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> */
++
++#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;
++};
++
++enum {
++	DUMP_TYPE_STRING,
++	DUMP_TYPE_WED,
++	DUMP_TYPE_WDMA,
++	DUMP_TYPE_WPDMA_TX,
++	DUMP_TYPE_WPDMA_TXFREE,
++};
++
++#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__ }
++
++#define DUMP_WED(_reg) DUMP_REG(_reg, DUMP_TYPE_WED)
++#define DUMP_WED_RING(_base) DUMP_RING(#_base, MTK_##_base, DUMP_TYPE_WED)
++
++#define DUMP_WDMA(_reg) DUMP_REG(_reg, DUMP_TYPE_WDMA)
++#define DUMP_WDMA_RING(_base) DUMP_RING(#_base, MTK_##_base, DUMP_TYPE_WDMA)
++
++#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)
++
++static void
++print_reg_val(struct seq_file *s, const char *name, u32 val)
++{
++	seq_printf(s, "%-32s %08x\n", name, val);
++}
++
++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;
++
++	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);
++	}
++}
++
++
++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)),
++
++		DUMP_WED(WED_TX_MIB(1)),
++		DUMP_WED_RING(WED_RING_TX(1)),
++
++		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)),
++
++		DUMP_WED(WED_WPDMA_TX_MIB(1)),
++		DUMP_WED_RING(WED_WPDMA_RING_TX(1)),
++		DUMP_WED(WED_WPDMA_TX_COHERENT_MIB(1)),
++
++		DUMP_STR("WPDMA TX"),
++		DUMP_WPDMA_TX_RING(0),
++		DUMP_WPDMA_TX_RING(1),
++
++		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)),
++
++		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)),
++
++		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;
++
++	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;
++
++	*val = regval;
++
++	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..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)
++{
++	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)
+ 
+-#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;
++
++	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)
+ {
++	struct mtk_wdma_info info = {};
+ 	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;
++		*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
+@@ -849,6 +849,7 @@ enum net_device_path_type {
+ 	DEV_PATH_BRIDGE,
+ 	DEV_PATH_PPPOE,
+ 	DEV_PATH_DSA,
++	DEV_PATH_MTK_WDMA,
+ };
+ 
+ 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;
+ 	};
+ };
+ 
+--- 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..f59a364a73
--- /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>
+---
+
+--- 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
+
+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/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..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;
++
++	/* 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..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);
++
++		/* 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;
+ 	}
+@@ -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,
++};
++
++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;
++
+ 			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-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;
+ 
+@@ -2041,7 +2035,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;
+@@ -2050,7 +2044,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-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;
+ 
+-	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
new file mode 100644
index 0000000000..f873b70d0f
--- /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
+
+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;
+@@ -2077,6 +2071,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-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);
++}
++
++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-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
new file mode 100644
index 0000000000..1465d1f35d
--- /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
+
+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
+@@ -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_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-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
new file mode 100644
index 0000000000..973446ec57
--- /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
+
+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)
+@@ -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
new file mode 100644
index 0000000000..29530065a1
--- /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
+
+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)
+ {
+@@ -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)
++{
++	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;
+@@ -1930,6 +2005,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)
+ {
+@@ -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
new file mode 100644
index 0000000000..69e9b381c2
--- /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
+
+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
+@@ -2027,6 +2027,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)
+ {
+@@ -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
new file mode 100644
index 0000000000..bfc77db184
--- /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
+
+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;
+ }
+ 
+@@ -2207,6 +2210,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,
+@@ -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)
+ 
++/* 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-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 *
+ {
+ 	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);
++
++	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();
++
+ 	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();
++
+ 	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
+@@ -267,9 +267,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-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
new file mode 100644
index 0000000000..e6472c61da
--- /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.
+
+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-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;
++
++		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-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
new file mode 100644
index 0000000000..d73b745586
--- /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.
+
+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;
+@@ -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);
+ };
+ 
+ #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;
+@@ -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;
++
++			/* 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;
+@@ -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;
++
++	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-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
new file mode 100644
index 0000000000..6478d580c0
--- /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.
+
+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-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
new file mode 100644
index 0000000000..82c94b385b
--- /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
+
+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-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 */
++
++#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-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
new file mode 100644
index 0000000000..9394a0dabb
--- /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
+
+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-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
new file mode 100644
index 0000000000..459454e03b
--- /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
+
+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-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
new file mode 100644
index 0000000000..7e5dc65730
--- /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
+
+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-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
new file mode 100644
index 0000000000..ad25da30e6
--- /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
+
+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-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
new file mode 100644
index 0000000000..ff8fdca512
--- /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
+
+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
+@@ -2382,6 +2382,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,
+@@ -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,
+ };
+ 
+ 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-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
new file mode 100644
index 0000000000..43656ad79d
--- /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.
+
+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);
+@@ -2393,7 +2591,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 = {
+@@ -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,
+ };
+ 
++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-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
new file mode 100644
index 0000000000..c4bc2b3646
--- /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
+
+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++) {
+@@ -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);
+ }
+ 
+@@ -2608,6 +2708,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:
+@@ -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);
+ 
++	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-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);
+ }
+ 
++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-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"),
+ };
+ 
+-/* 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;
+@@ -3041,6 +3037,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-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
+ }
+ 
+ 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);
+ 
+@@ -3038,6 +3073,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-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;
+ 
+-		/* 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-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
+ }
+ 
+ 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/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..44093eab95
--- /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
+
+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: Tobias Waldekranz <tobias@waldekranz.com>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ net/dsa/switch.c | 38 +++++++++++++++++++++++++-------------
+ 1 file changed, 25 insertions(+), 13 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)
+ {
+-	struct dsa_switch_tree *dst = ds->dst;
+ 	struct netlink_ext_ack extack = {0};
+ 	bool change_vlan_filtering = false;
+ 	bool vlan_filtering;
+ 	int err, port;
+ 
+-	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;
++}
++
++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);
++
++	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);
++
++	err = dsa_switch_sync_vlan_filtering(ds, info);
++	if (err)
++		return err;
++
+ 	return dsa_tag_8021q_bridge_leave(ds, info);
+ }
+ 
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..cdddbcf14e
--- /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:
+
+.---------.   .-----.   .-----.
+|   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>
+---
+ 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);
+ 
+-	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;
++	}
+ 
+ 	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..78570c5e6e
--- /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
+
+Use port isolation registers to configure bridge offloading.
+
+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>
+---
+ drivers/net/dsa/rtl8366rb.c | 86 +++++++++++++++++++++++++++++++++++++
+ 1 file changed, 86 insertions(+)
+
+--- 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 */
+ 
++/* 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
+ 
+@@ -835,6 +842,21 @@ static int rtl8366rb_setup(struct dsa_sw
+ 	if (ret)
+ 		return ret;
+ 
++	/* 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);
+ }
+ 
++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;
++
++	/* 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);
++
++		port_bitmap |= BIT(i);
++	}
++
++	/* 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
++rtl8366rb_port_bridge_leave(struct dsa_switch *ds, int port,
++			    struct net_device *bridge)
++{
++	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 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..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
+ }
+ 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;
+ 	}
+ 
+-	ret = rtl8366_init_vlan(smi);
++	ret = rtl8366_reset_vlan(smi);
+ 	if (ret)
+ 		return ret;
+ 
+@@ -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..2b19752399
--- /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>
+---
+ 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/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);
+ 
+-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)
+ 
++/* 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);
+ }
+ 
++/**
++ * 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)
++{
++	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 rtl8366rb_vlan_filtering(struct dsa_switch *ds, int port,
++				    bool vlan_filtering,
++				    struct netlink_ext_ack *extack)
++{
++	struct realtek_smi *smi = ds->priv;
++	struct rtl8366rb *rb;
++	int ret;
++
++	rb = smi->chip_data;
++
++	dev_dbg(smi->dev, "port %d: %s VLAN filtering\n", port,
++		vlan_filtering ? "enable" : "disable");
++
++	/* 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;
++
++	/* 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.
++	 */
++	if (vlan_filtering)
++		ret = rtl8366rb_drop_untagged(smi, port, !rb->pvid_enabled[port]);
++	else
++		ret = rtl8366rb_drop_untagged(smi, port, false);
++
++	return ret;
++}
++
+ 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;
++
++	rb = smi->chip_data;
++	pvid_enabled = !!index;
++
+ 	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;
++
++	rb->pvid_enabled[port] = pvid_enabled;
++
++	/* 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);
++
++	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);
+ 
+ 	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 @@
+ 
+ #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;
+ }
+ 
++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)
++{
++	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;
++}
++
+ 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;
+ }
+ 
++static void
++rtl8366rb_port_fast_age(struct dsa_switch *ds, int port)
++{
++	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);
++}
++
+ 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,
+ };
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..e787ce9481
--- /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:
+
+DSA: failed to set STP state 3 (-95)
+
+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/rtl8366rb.c | 48 +++++++++++++++++++++++++++++++++++++
+ 1 file changed, 48 insertions(+)
+
+--- a/drivers/net/dsa/rtl8366rb.c
++++ b/drivers/net/dsa/rtl8366rb.c
+@@ -110,6 +110,18 @@
+ 
+ #define RTL8366RB_POWER_SAVING_REG	0x0021
+ 
++/* 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
++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;
++	};
++
++	/* 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));
++	}
++}
++
++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
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
@@ -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>
+Date: Mon, 27 Dec 2021 15:59:04 +0100
+Subject: [PATCH] dt-bindings: leds: add Broadcom's BCM63138 controller
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Broadcom used 2 LEDs hardware blocks for their BCM63xx SoCs:
+1. Older one (BCM6318, BCM6328, BCM6362, BCM63268, BCM6838)
+2. Newer one (BCM6848, BCM6858, BCM63138, BCM63148, BCM63381, BCM68360)
+
+The newer one was also later also used on BCM4908 SoC.
+
+Old block is already documented in the leds-bcm6328.yaml. This binding
+documents the new one which uses different registers & programming. It's
+first used in BCM63138 thus the binding name.
+
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Reviewed-by: Rob Herring <robh@kernel.org>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Pavel Machek <pavel@ucw.cz>
+---
+ .../bindings/leds/leds-bcm63138.yaml          | 95 +++++++++++++++++++
+ 1 file changed, 95 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/leds/leds-bcm63138.yaml
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/leds/leds-bcm63138.yaml
+@@ -0,0 +1,95 @@
++# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/leds/leds-bcm63138.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: Broadcom's BCM63138 LEDs controller
++
++maintainers:
++  - Rafał Miłecki <rafal@milecki.pl>
++
++description: |
++  This LEDs controller was first used on BCM63138 and later reused on BCM4908,
++  BCM6848, BCM6858, BCM63138, BCM63148, BCM63381 and BCM68360 SoCs.
++
++  It supports up to 32 LEDs that can be connected parallelly or serially. It
++  also includes limited support for hardware blinking.
++
++  Binding serially connected LEDs isn't documented yet.
++
++properties:
++  compatible:
++    oneOf:
++      - items:
++          - enum:
++              - brcm,bcm4908-leds
++              - brcm,bcm6848-leds
++              - brcm,bcm6858-leds
++              - brcm,bcm63148-leds
++              - brcm,bcm63381-leds
++              - brcm,bcm68360-leds
++          - const: brcm,bcm63138-leds
++      - const: brcm,bcm63138-leds
++
++  reg:
++    maxItems: 1
++
++  "#address-cells":
++    const: 1
++
++  "#size-cells":
++    const: 0
++
++patternProperties:
++  "^led@[a-f0-9]+$":
++    type: object
++
++    $ref: common.yaml#
++
++    properties:
++      reg:
++        maxItems: 1
++        description: LED pin number
++
++      active-low:
++        type: boolean
++        description: Makes LED active low.
++
++    required:
++      - reg
++
++    unevaluatedProperties: false
++
++required:
++  - reg
++  - "#address-cells"
++  - "#size-cells"
++
++additionalProperties: false
++
++examples:
++  - |
++    #include <dt-bindings/leds/common.h>
++
++    leds@ff800800 {
++        compatible = "brcm,bcm4908-leds", "brcm,bcm63138-leds";
++        reg = <0xff800800 0xdc>;
++
++        #address-cells = <1>;
++        #size-cells = <0>;
++
++        led@0 {
++            reg = <0x0>;
++            function = LED_FUNCTION_POWER;
++            color = <LED_COLOR_ID_GREEN>;
++            default-state = "on";
++        };
++
++        led@3 {
++            reg = <0x3>;
++            function = LED_FUNCTION_STATUS;
++            color = <LED_COLOR_ID_GREEN>;
++            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
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
@@ -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>
+Date: Mon, 27 Dec 2021 15:59:05 +0100
+Subject: [PATCH] leds: bcm63138: add support for BCM63138 controller
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+It's a new controller first introduced in BCM63138 SoC. Later it was
+also used in BCM4908, some BCM68xx and some BCM63xxx SoCs.
+
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Pavel Machek <pavel@ucw.cz>
+---
+ drivers/leds/blink/Kconfig         |  12 ++
+ drivers/leds/blink/Makefile        |   1 +
+ drivers/leds/blink/leds-bcm63138.c | 308 +++++++++++++++++++++++++++++
+ 3 files changed, 321 insertions(+)
+ create mode 100644 drivers/leds/blink/leds-bcm63138.c
+
+--- a/drivers/leds/blink/Kconfig
++++ b/drivers/leds/blink/Kconfig
+@@ -1,3 +1,15 @@
++config LEDS_BCM63138
++	tristate "LED Support for Broadcom BCM63138 SoC"
++	depends on LEDS_CLASS
++	depends on ARCH_BCM4908 || ARCH_BCM_5301X || BCM63XX || COMPILE_TEST
++	depends on HAS_IOMEM
++	depends on OF
++	default ARCH_BCM4908
++	help
++	  This option enables support for LED controller that is part of
++	  BCM63138 SoC. The same hardware block is known to be also used
++	  in BCM4908, BCM6848, BCM6858, BCM63148, BCM63381 and BCM68360.
++
+ config LEDS_LGM
+        tristate "LED support for LGM SoC series"
+        depends on X86 || COMPILE_TEST
+--- a/drivers/leds/blink/Makefile
++++ b/drivers/leds/blink/Makefile
+@@ -1,2 +1,3 @@
+ # SPDX-License-Identifier: GPL-2.0
++obj-$(CONFIG_LEDS_BCM63138)	+= leds-bcm63138.o
+ obj-$(CONFIG_LEDS_LGM)	+= leds-lgm-sso.o
+--- /dev/null
++++ b/drivers/leds/blink/leds-bcm63138.c
+@@ -0,0 +1,308 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2021 Rafał Miłecki <rafal@milecki.pl>
++ */
++#include <linux/delay.h>
++#include <linux/io.h>
++#include <linux/leds.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/pinctrl/consumer.h>
++#include <linux/platform_device.h>
++#include <linux/spinlock.h>
++
++#define BCM63138_MAX_LEDS				32
++#define BCM63138_MAX_BRIGHTNESS				9
++
++#define BCM63138_LED_BITS				4				/* how many bits control a single LED */
++#define BCM63138_LED_MASK				((1 << BCM63138_LED_BITS) - 1)	/* 0xf */
++#define BCM63138_LEDS_PER_REG				(32 / BCM63138_LED_BITS)	/* 8 */
++
++#define BCM63138_GLB_CTRL				0x00
++#define  BCM63138_GLB_CTRL_SERIAL_LED_DATA_PPOL		0x00000002
++#define  BCM63138_GLB_CTRL_SERIAL_LED_EN_POL		0x00000008
++#define BCM63138_MASK					0x04
++#define BCM63138_HW_LED_EN				0x08
++#define BCM63138_SERIAL_LED_SHIFT_SEL			0x0c
++#define BCM63138_FLASH_RATE_CTRL1			0x10
++#define BCM63138_FLASH_RATE_CTRL2			0x14
++#define BCM63138_FLASH_RATE_CTRL3			0x18
++#define BCM63138_FLASH_RATE_CTRL4			0x1c
++#define BCM63138_BRIGHT_CTRL1				0x20
++#define BCM63138_BRIGHT_CTRL2				0x24
++#define BCM63138_BRIGHT_CTRL3				0x28
++#define BCM63138_BRIGHT_CTRL4				0x2c
++#define BCM63138_POWER_LED_CFG				0x30
++#define BCM63138_HW_POLARITY				0xb4
++#define BCM63138_SW_DATA				0xb8
++#define BCM63138_SW_POLARITY				0xbc
++#define BCM63138_PARALLEL_LED_POLARITY			0xc0
++#define BCM63138_SERIAL_LED_POLARITY			0xc4
++#define BCM63138_HW_LED_STATUS				0xc8
++#define BCM63138_FLASH_CTRL_STATUS			0xcc
++#define BCM63138_FLASH_BRT_CTRL				0xd0
++#define BCM63138_FLASH_P_LED_OUT_STATUS			0xd4
++#define BCM63138_FLASH_S_LED_OUT_STATUS			0xd8
++
++struct bcm63138_leds {
++	struct device *dev;
++	void __iomem *base;
++	spinlock_t lock;
++};
++
++struct bcm63138_led {
++	struct bcm63138_leds *leds;
++	struct led_classdev cdev;
++	u32 pin;
++	bool active_low;
++};
++
++/*
++ * I/O access
++ */
++
++static void bcm63138_leds_write(struct bcm63138_leds *leds, unsigned int reg,
++				u32 data)
++{
++	writel(data, leds->base + reg);
++}
++
++static unsigned long bcm63138_leds_read(struct bcm63138_leds *leds,
++					unsigned int reg)
++{
++	return readl(leds->base + reg);
++}
++
++static void bcm63138_leds_update_bits(struct bcm63138_leds *leds,
++				      unsigned int reg, u32 mask, u32 val)
++{
++	WARN_ON(val & ~mask);
++
++	bcm63138_leds_write(leds, reg, (bcm63138_leds_read(leds, reg) & ~mask) | (val & mask));
++}
++
++/*
++ * Helpers
++ */
++
++static void bcm63138_leds_set_flash_rate(struct bcm63138_leds *leds,
++					 struct bcm63138_led *led,
++					 u8 value)
++{
++	int reg_offset = (led->pin >> fls((BCM63138_LEDS_PER_REG - 1))) * 4;
++	int shift = (led->pin & (BCM63138_LEDS_PER_REG - 1)) * BCM63138_LED_BITS;
++
++	bcm63138_leds_update_bits(leds, BCM63138_FLASH_RATE_CTRL1 + reg_offset,
++				  BCM63138_LED_MASK << shift, value << shift);
++}
++
++static void bcm63138_leds_set_bright(struct bcm63138_leds *leds,
++				     struct bcm63138_led *led,
++				     u8 value)
++{
++	int reg_offset = (led->pin >> fls((BCM63138_LEDS_PER_REG - 1))) * 4;
++	int shift = (led->pin & (BCM63138_LEDS_PER_REG - 1)) * BCM63138_LED_BITS;
++
++	bcm63138_leds_update_bits(leds, BCM63138_BRIGHT_CTRL1 + reg_offset,
++				  BCM63138_LED_MASK << shift, value << shift);
++}
++
++static void bcm63138_leds_enable_led(struct bcm63138_leds *leds,
++				     struct bcm63138_led *led,
++				     enum led_brightness value)
++{
++	u32 bit = BIT(led->pin);
++
++	bcm63138_leds_update_bits(leds, BCM63138_SW_DATA, bit,
++				  value == LED_OFF ? 0 : bit);
++}
++
++/*
++ * API callbacks
++ */
++
++static void bcm63138_leds_brightness_set(struct led_classdev *led_cdev,
++					 enum led_brightness value)
++{
++	struct bcm63138_led *led = container_of(led_cdev, struct bcm63138_led, cdev);
++	struct bcm63138_leds *leds = led->leds;
++	unsigned long flags;
++
++	spin_lock_irqsave(&leds->lock, flags);
++
++	bcm63138_leds_enable_led(leds, led, value);
++	if (!value)
++		bcm63138_leds_set_flash_rate(leds, led, 0);
++	else
++		bcm63138_leds_set_bright(leds, led, value);
++
++	spin_unlock_irqrestore(&leds->lock, flags);
++}
++
++static int bcm63138_leds_blink_set(struct led_classdev *led_cdev,
++				   unsigned long *delay_on,
++				   unsigned long *delay_off)
++{
++	struct bcm63138_led *led = container_of(led_cdev, struct bcm63138_led, cdev);
++	struct bcm63138_leds *leds = led->leds;
++	unsigned long flags;
++	u8 value;
++
++	if (!*delay_on && !*delay_off) {
++		*delay_on = 640;
++		*delay_off = 640;
++	}
++
++	if (*delay_on != *delay_off) {
++		dev_dbg(led_cdev->dev, "Blinking at unequal delays is not supported\n");
++		return -EINVAL;
++	}
++
++	switch (*delay_on) {
++	case 1152 ... 1408: /* 1280 ms ± 10% */
++		value = 0x7;
++		break;
++	case 576 ... 704: /* 640 ms ± 10% */
++		value = 0x6;
++		break;
++	case 288 ... 352: /* 320 ms ± 10% */
++		value = 0x5;
++		break;
++	case 126 ... 154: /* 140 ms ± 10% */
++		value = 0x4;
++		break;
++	case 59 ... 72: /* 65 ms ± 10% */
++		value = 0x3;
++		break;
++	default:
++		dev_dbg(led_cdev->dev, "Blinking delay value %lu is unsupported\n",
++			*delay_on);
++		return -EINVAL;
++	}
++
++	spin_lock_irqsave(&leds->lock, flags);
++
++	bcm63138_leds_enable_led(leds, led, BCM63138_MAX_BRIGHTNESS);
++	bcm63138_leds_set_flash_rate(leds, led, value);
++
++	spin_unlock_irqrestore(&leds->lock, flags);
++
++	return 0;
++}
++
++/*
++ * LED driver
++ */
++
++static void bcm63138_leds_create_led(struct bcm63138_leds *leds,
++				     struct device_node *np)
++{
++	struct led_init_data init_data = {
++		.fwnode = of_fwnode_handle(np),
++	};
++	struct device *dev = leds->dev;
++	struct bcm63138_led *led;
++	struct pinctrl *pinctrl;
++	u32 bit;
++	int err;
++
++	led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
++	if (!led) {
++		dev_err(dev, "Failed to alloc LED\n");
++		return;
++	}
++
++	led->leds = leds;
++
++	if (of_property_read_u32(np, "reg", &led->pin)) {
++		dev_err(dev, "Missing \"reg\" property in %pOF\n", np);
++		goto err_free;
++	}
++
++	if (led->pin >= BCM63138_MAX_LEDS) {
++		dev_err(dev, "Invalid \"reg\" value %d\n", led->pin);
++		goto err_free;
++	}
++
++	led->active_low = of_property_read_bool(np, "active-low");
++
++	led->cdev.max_brightness = BCM63138_MAX_BRIGHTNESS;
++	led->cdev.brightness_set = bcm63138_leds_brightness_set;
++	led->cdev.blink_set = bcm63138_leds_blink_set;
++
++	err = devm_led_classdev_register_ext(dev, &led->cdev, &init_data);
++	if (err) {
++		dev_err(dev, "Failed to register LED %pOF: %d\n", np, err);
++		goto err_free;
++	}
++
++	pinctrl = devm_pinctrl_get_select_default(led->cdev.dev);
++	if (IS_ERR(pinctrl) && PTR_ERR(pinctrl) != -ENODEV) {
++		dev_warn(led->cdev.dev, "Failed to select %pOF pinctrl: %ld\n",
++			 np, PTR_ERR(pinctrl));
++	}
++
++	bit = BIT(led->pin);
++	bcm63138_leds_update_bits(leds, BCM63138_PARALLEL_LED_POLARITY, bit,
++				  led->active_low ? 0 : bit);
++	bcm63138_leds_update_bits(leds, BCM63138_HW_LED_EN, bit, 0);
++	bcm63138_leds_set_flash_rate(leds, led, 0);
++	bcm63138_leds_enable_led(leds, led, led->cdev.brightness);
++
++	return;
++
++err_free:
++	devm_kfree(dev, led);
++}
++
++static int bcm63138_leds_probe(struct platform_device *pdev)
++{
++	struct device_node *np = dev_of_node(&pdev->dev);
++	struct device *dev = &pdev->dev;
++	struct bcm63138_leds *leds;
++	struct device_node *child;
++
++	leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL);
++	if (!leds)
++		return -ENOMEM;
++
++	leds->dev = dev;
++
++	leds->base = devm_platform_ioremap_resource(pdev, 0);
++	if (IS_ERR(leds->base))
++		return PTR_ERR(leds->base);
++
++	spin_lock_init(&leds->lock);
++
++	bcm63138_leds_write(leds, BCM63138_GLB_CTRL,
++			    BCM63138_GLB_CTRL_SERIAL_LED_DATA_PPOL |
++			    BCM63138_GLB_CTRL_SERIAL_LED_EN_POL);
++	bcm63138_leds_write(leds, BCM63138_HW_LED_EN, 0);
++	bcm63138_leds_write(leds, BCM63138_SERIAL_LED_POLARITY, 0);
++	bcm63138_leds_write(leds, BCM63138_PARALLEL_LED_POLARITY, 0);
++
++	for_each_available_child_of_node(np, child) {
++		bcm63138_leds_create_led(leds, child);
++	}
++
++	return 0;
++}
++
++static const struct of_device_id bcm63138_leds_of_match_table[] = {
++	{ .compatible = "brcm,bcm63138-leds", },
++	{ },
++};
++
++static struct platform_driver bcm63138_leds_driver = {
++	.probe = bcm63138_leds_probe,
++	.driver = {
++		.name = "leds-bcm63xxx",
++		.of_match_table = bcm63138_leds_of_match_table,
++	},
++};
++
++module_platform_driver(bcm63138_leds_driver);
++
++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
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
@@ -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>
+Date: Sun, 17 Jul 2022 14:42:46 +0200
+Subject: [PATCH] dt-bindings: leds: leds-bcm63138: unify full stops in
+ descriptions
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Description of "reg" doesn't have full stop at the end. It makes sense
+as it's a one-sentence only. Use the same style for "active-low".
+
+Reported-by: Pavel Machek <pavel@ucw.cz>
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Signed-off-by: Pavel Machek <pavel@ucw.cz>
+---
+ Documentation/devicetree/bindings/leds/leds-bcm63138.yaml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/Documentation/devicetree/bindings/leds/leds-bcm63138.yaml
++++ b/Documentation/devicetree/bindings/leds/leds-bcm63138.yaml
+@@ -54,7 +54,7 @@ patternProperties:
+ 
+       active-low:
+         type: boolean
+-        description: Makes LED active low.
++        description: Makes LED active low
+ 
+     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
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
@@ -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>
+Date: Sun, 17 Jul 2022 14:42:47 +0200
+Subject: [PATCH] leds: add help info about BCM63138 module name
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+It's what we do for all other LEDs drivers.
+
+Reported-by: Pavel Machek <pavel@ucw.cz>
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Signed-off-by: Pavel Machek <pavel@ucw.cz>
+---
+ drivers/leds/blink/Kconfig | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/drivers/leds/blink/Kconfig
++++ b/drivers/leds/blink/Kconfig
+@@ -10,6 +10,8 @@ config LEDS_BCM63138
+ 	  BCM63138 SoC. The same hardware block is known to be also used
+ 	  in BCM4908, BCM6848, BCM6858, BCM63148, BCM63381 and BCM68360.
+ 
++	  If compiled as module it will be called leds-bcm63138.
++
+ 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
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
@@ -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>
+Date: Sun, 17 Jul 2022 14:42:48 +0200
+Subject: [PATCH] leds: leds-bcm63138: get rid of LED_OFF
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The whole "enum led_brightness" is marked as obsolete. Replace it with a
+(non-)zero check.
+
+Reported-by: Pavel Machek <pavel@ucw.cz>
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Signed-off-by: Pavel Machek <pavel@ucw.cz>
+---
+ drivers/leds/blink/leds-bcm63138.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/drivers/leds/blink/leds-bcm63138.c
++++ b/drivers/leds/blink/leds-bcm63138.c
+@@ -113,8 +113,7 @@ static void bcm63138_leds_enable_led(str
+ {
+ 	u32 bit = BIT(led->pin);
+ 
+-	bcm63138_leds_update_bits(leds, BCM63138_SW_DATA, bit,
+-				  value == LED_OFF ? 0 : bit);
++	bcm63138_leds_update_bits(leds, BCM63138_SW_DATA, bit, value ? bit : 0);
+ }
+ 
+ /*
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
--- /dev/null
+++ b/target/linux/generic/backport-5.15/860-v5.17-MIPS-ath79-drop-_machine_restart-again.patch
@@ -0,0 +1,49 @@
+From d3115128bdafb62628ab41861a4f06f6d02ac320 Mon Sep 17 00:00:00 2001
+From: Lech Perczak <lech.perczak@gmail.com>
+Date: Mon, 10 Jan 2022 23:48:44 +0100
+Subject: MIPS: ath79: drop _machine_restart again
+
+Commit 81424d0ad0d4 ("MIPS: ath79: Use the reset controller to restart
+OF machines") removed setup of _machine_restart on OF machines to use
+reset handler in reset controller driver.
+While removing remnants of non-OF machines in commit 3a77e0d75eed
+("MIPS: ath79: drop machfiles"), this was introduced again, making it
+impossible to use additional restart handlers registered through device
+tree. Drop setting _machine_restart altogether, and ath79_restart
+function, which is no longer used after this.
+
+Fixes: 3a77e0d75eed ("MIPS: ath79: drop machfiles")
+Cc: John Crispin <john@phrozen.org>
+Cc: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Lech Perczak <lech.perczak@gmail.com>
+Signed-off-by: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
+---
+ arch/mips/ath79/setup.c | 10 ----------
+ 1 file changed, 10 deletions(-)
+
+--- a/arch/mips/ath79/setup.c
++++ b/arch/mips/ath79/setup.c
+@@ -34,15 +34,6 @@
+ 
+ static char ath79_sys_type[ATH79_SYS_TYPE_LEN];
+ 
+-static void ath79_restart(char *command)
+-{
+-	local_irq_disable();
+-	ath79_device_reset_set(AR71XX_RESET_FULL_CHIP);
+-	for (;;)
+-		if (cpu_wait)
+-			cpu_wait();
+-}
+-
+ static void ath79_halt(void)
+ {
+ 	while (1)
+@@ -234,7 +225,6 @@ void __init plat_mem_setup(void)
+ 
+ 	detect_memory_region(0, ATH79_MEM_SIZE_MIN, ATH79_MEM_SIZE_MAX);
+ 
+-	_machine_restart = ath79_restart;
+ 	_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
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
@@ -0,0 +1,71 @@
+From 31d8f414e1596ba54a4315418e4c0086fda9e428 Mon Sep 17 00:00:00 2001
+From: Christian Lamparter <chunkeey@gmail.com>
+Date: Fri, 18 Feb 2022 10:06:43 +0100
+Subject: hwmon: (lm70) Add ti,tmp125 support
+
+The TMP125 is a 2 degree Celsius accurate Digital
+Temperature Sensor with a SPI interface.
+
+The temperature register is a 16-bit, read-only register.
+The MSB (Bit 15) is a leading zero and never set. Bits 14
+to 5 are the 1+9 temperature data bits in a two's
+complement format. Bits 4 to 0 are useless copies of
+Bit 5 value and therefore ignored.
+
+Signed-off-by: Christian Lamparter <chunkeey@gmail.com>
+Link: https://lore.kernel.org/r/43b19cbd4e7f51e9509e561b02b5d8d0e7079fac.1645175187.git.chunkeey@gmail.com
+Signed-off-by: Guenter Roeck <linux@roeck-us.net>
+---
+--- a/drivers/hwmon/lm70.c
++++ b/drivers/hwmon/lm70.c
+@@ -34,6 +34,7 @@
+ #define LM70_CHIP_LM71		2	/* NS LM71 */
+ #define LM70_CHIP_LM74		3	/* NS LM74 */
+ #define LM70_CHIP_TMP122	4	/* TI TMP122/TMP124 */
++#define LM70_CHIP_TMP125	5	/* TI TMP125 */
+ 
+ struct lm70 {
+ 	struct spi_device *spi;
+@@ -87,6 +88,12 @@ static ssize_t temp1_input_show(struct d
+ 	 * LM71:
+ 	 * 14 bits of 2's complement data, discard LSB 2 bits,
+ 	 * resolution 0.0312 degrees celsius.
++	 *
++	 * TMP125:
++	 * MSB/D15 is a leading zero. D14 is the sign-bit. This is
++	 * followed by 9 temperature bits (D13..D5) in 2's complement
++	 * data format with a resolution of 0.25 degrees celsius per unit.
++	 * LSB 5 bits (D4..D0) share the same value as D5 and get discarded.
+ 	 */
+ 	switch (p_lm70->chip) {
+ 	case LM70_CHIP_LM70:
+@@ -102,6 +109,10 @@ static ssize_t temp1_input_show(struct d
+ 	case LM70_CHIP_LM71:
+ 		val = ((int)raw / 4) * 3125 / 100;
+ 		break;
++
++	case LM70_CHIP_TMP125:
++		val = (sign_extend32(raw, 14) / 32) * 250;
++		break;
+ 	}
+ 
+ 	status = sprintf(buf, "%d\n", val); /* millidegrees Celsius */
+@@ -136,6 +147,10 @@ static const struct of_device_id lm70_of
+ 		.data = (void *) LM70_CHIP_TMP122,
+ 	},
+ 	{
++		.compatible = "ti,tmp125",
++		.data = (void *) LM70_CHIP_TMP125,
++	},
++	{
+ 		.compatible = "ti,lm71",
+ 		.data = (void *) LM70_CHIP_LM71,
+ 	},
+@@ -184,6 +199,7 @@ static const struct spi_device_id lm70_i
+ 	{ "lm70",   LM70_CHIP_LM70 },
+ 	{ "tmp121", LM70_CHIP_TMP121 },
+ 	{ "tmp122", LM70_CHIP_TMP122 },
++	{ "tmp125", LM70_CHIP_TMP125 },
+ 	{ "lm71",   LM70_CHIP_LM71 },
+ 	{ "lm74",   LM70_CHIP_LM74 },
+ 	{ },
diff --git a/target/linux/generic/backport-5.15/880-v5.19-cdc_ether-export-usbnet_cdc_zte_rx_fixup.patch b/target/linux/generic/backport-5.15/880-v5.19-cdc_ether-export-usbnet_cdc_zte_rx_fixup.patch
new file mode 100644
index 0000000000..39fdb32773
--- /dev/null
+++ b/target/linux/generic/backport-5.15/880-v5.19-cdc_ether-export-usbnet_cdc_zte_rx_fixup.patch
@@ -0,0 +1,58 @@
+From a79a5613e1907e1bf09bb6ba6fd5ff43b66c1afe Mon Sep 17 00:00:00 2001
+From: Lech Perczak <lech.perczak@gmail.com>
+Date: Fri, 1 Apr 2022 22:03:55 +0200
+Subject: [PATCH 1/3] cdc_ether: export usbnet_cdc_zte_rx_fixup
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Commit bfe9b9d2df66 ("cdc_ether: Improve ZTE MF823/831/910 handling")
+introduces a workaround for certain ZTE modems reporting invalid MAC
+addresses over CDC-ECM.
+The same issue was present on their RNDIS interface,which was fixed in
+commit a5a18bdf7453 ("rndis_host: Set valid random MAC on buggy devices").
+
+However, internal modem of ZTE MF286R router, on its RNDIS interface, also
+exhibits a second issue fixed already in CDC-ECM, of the device not
+respecting configured random MAC address. In order to share the fixup for
+this with rndis_host driver, export the workaround function, which will
+be re-used in the following commit in rndis_host.
+
+Cc: Kristian Evensen <kristian.evensen@gmail.com>
+Cc: Bjørn Mork <bjorn@mork.no>
+Cc: Oliver Neukum <oliver@neukum.org>
+Signed-off-by: Lech Perczak <lech.perczak@gmail.com>
+---
+ drivers/net/usb/cdc_ether.c | 3 ++-
+ include/linux/usb/usbnet.h  | 1 +
+ 2 files changed, 3 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/usb/cdc_ether.c
++++ b/drivers/net/usb/cdc_ether.c
+@@ -479,7 +479,7 @@ static int usbnet_cdc_zte_bind(struct us
+  * device MAC address has been updated). Always set MAC address to that of the
+  * device.
+  */
+-static int usbnet_cdc_zte_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
++int usbnet_cdc_zte_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+ {
+ 	if (skb->len < ETH_HLEN || !(skb->data[0] & 0x02))
+ 		return 1;
+@@ -489,6 +489,7 @@ static int usbnet_cdc_zte_rx_fixup(struc
+ 
+ 	return 1;
+ }
++EXPORT_SYMBOL_GPL(usbnet_cdc_zte_rx_fixup);
+ 
+ /* Ensure correct link state
+  *
+--- a/include/linux/usb/usbnet.h
++++ b/include/linux/usb/usbnet.h
+@@ -214,6 +214,7 @@ extern int usbnet_ether_cdc_bind(struct
+ extern int usbnet_cdc_bind(struct usbnet *, struct usb_interface *);
+ extern void usbnet_cdc_unbind(struct usbnet *, struct usb_interface *);
+ extern void usbnet_cdc_status(struct usbnet *, struct urb *);
++extern int usbnet_cdc_zte_rx_fixup(struct usbnet *dev, struct sk_buff *skb);
+ 
+ /* CDC and RNDIS support the same host-chosen packet filters for IN transfers */
+ #define	DEFAULT_FILTER	(USB_CDC_PACKET_TYPE_BROADCAST \
diff --git a/target/linux/generic/backport-5.15/881-v5.19-rndis_host-enable-the-bogus-MAC-fixup-for-ZTE-device.patch b/target/linux/generic/backport-5.15/881-v5.19-rndis_host-enable-the-bogus-MAC-fixup-for-ZTE-device.patch
new file mode 100644
index 0000000000..7da2280cd9
--- /dev/null
+++ b/target/linux/generic/backport-5.15/881-v5.19-rndis_host-enable-the-bogus-MAC-fixup-for-ZTE-device.patch
@@ -0,0 +1,118 @@
+From aa8aff10e969aca0cb64f5e54ff7489355582667 Mon Sep 17 00:00:00 2001
+From: Lech Perczak <lech.perczak@gmail.com>
+Date: Fri, 1 Apr 2022 22:04:01 +0200
+Subject: [PATCH 2/3] rndis_host: enable the bogus MAC fixup for ZTE devices
+ from cdc_ether
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Certain ZTE modems, namely: MF823. MF831, MF910, built-in modem from
+MF286R, expose both CDC-ECM and RNDIS network interfaces.
+They have a trait of ignoring the locally-administered MAC address
+configured on the interface both in CDC-ECM and RNDIS part,
+and this leads to dropping of incoming traffic by the host.
+However, the workaround was only present in CDC-ECM, and MF286R
+explicitly requires it in RNDIS mode.
+
+Re-use the workaround in rndis_host as well, to fix operation of MF286R
+module, some versions of which expose only the RNDIS interface. Do so by
+introducing new flag, RNDIS_DRIVER_DATA_DST_MAC_FIXUP, and testing for it
+in rndis_rx_fixup. This is required, as RNDIS uses frame batching, and all
+of the packets inside the batch need the fixup. This might introduce a
+performance penalty, because test is done for every returned Ethernet
+frame.
+
+Apply the workaround to both "flavors" of RNDIS interfaces, as older ZTE
+modems, like MF823 found in the wild, report the USB_CLASS_COMM class
+interfaces, while MF286R reports USB_CLASS_WIRELESS_CONTROLLER.
+
+Suggested-by: Bjørn Mork <bjorn@mork.no>
+Cc: Kristian Evensen <kristian.evensen@gmail.com>
+Cc: Oliver Neukum <oliver@neukum.org>
+Signed-off-by: Lech Perczak <lech.perczak@gmail.com>
+---
+ drivers/net/usb/rndis_host.c   | 32 ++++++++++++++++++++++++++++++++
+ include/linux/usb/rndis_host.h |  1 +
+ 2 files changed, 33 insertions(+)
+
+--- a/drivers/net/usb/rndis_host.c
++++ b/drivers/net/usb/rndis_host.c
+@@ -485,10 +485,14 @@ EXPORT_SYMBOL_GPL(rndis_unbind);
+  */
+ int rndis_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+ {
++	bool dst_mac_fixup;
++
+ 	/* This check is no longer done by usbnet */
+ 	if (skb->len < dev->net->hard_header_len)
+ 		return 0;
+ 
++	dst_mac_fixup = !!(dev->driver_info->data & RNDIS_DRIVER_DATA_DST_MAC_FIXUP);
++
+ 	/* peripheral may have batched packets to us... */
+ 	while (likely(skb->len)) {
+ 		struct rndis_data_hdr	*hdr = (void *)skb->data;
+@@ -523,10 +527,17 @@ int rndis_rx_fixup(struct usbnet *dev, s
+ 			break;
+ 		skb_pull(skb, msg_len - sizeof *hdr);
+ 		skb_trim(skb2, data_len);
++
++		if (unlikely(dst_mac_fixup))
++			usbnet_cdc_zte_rx_fixup(dev, skb2);
++
+ 		usbnet_skb_return(dev, skb2);
+ 	}
+ 
+ 	/* caller will usbnet_skb_return the remaining packet */
++	if (unlikely(dst_mac_fixup))
++		usbnet_cdc_zte_rx_fixup(dev, skb);
++
+ 	return 1;
+ }
+ EXPORT_SYMBOL_GPL(rndis_rx_fixup);
+@@ -600,6 +611,17 @@ static const struct driver_info	rndis_po
+ 	.tx_fixup =	rndis_tx_fixup,
+ };
+ 
++static const struct driver_info	zte_rndis_info = {
++	.description =	"ZTE RNDIS device",
++	.flags =	FLAG_ETHER | FLAG_POINTTOPOINT | FLAG_FRAMING_RN | FLAG_NO_SETINT,
++	.data =		RNDIS_DRIVER_DATA_DST_MAC_FIXUP,
++	.bind =		rndis_bind,
++	.unbind =	rndis_unbind,
++	.status =	rndis_status,
++	.rx_fixup =	rndis_rx_fixup,
++	.tx_fixup =	rndis_tx_fixup,
++};
++
+ /*-------------------------------------------------------------------------*/
+ 
+ static const struct usb_device_id	products [] = {
+@@ -614,6 +636,16 @@ static const struct usb_device_id	produc
+ 				      USB_CLASS_COMM, 2 /* ACM */, 0x0ff),
+ 	.driver_info = (unsigned long)&rndis_info,
+ }, {
++	/* ZTE WWAN modules */
++	USB_VENDOR_AND_INTERFACE_INFO(0x19d2,
++				      USB_CLASS_WIRELESS_CONTROLLER, 1, 3),
++	.driver_info = (unsigned long)&zte_rndis_info,
++}, {
++	/* ZTE WWAN modules, ACM flavour */
++	USB_VENDOR_AND_INTERFACE_INFO(0x19d2,
++				      USB_CLASS_COMM, 2 /* ACM */, 0x0ff),
++	.driver_info = (unsigned long)&zte_rndis_info,
++}, {
+ 	/* RNDIS is MSFT's un-official variant of CDC ACM */
+ 	USB_INTERFACE_INFO(USB_CLASS_COMM, 2 /* ACM */, 0x0ff),
+ 	.driver_info = (unsigned long) &rndis_info,
+--- a/include/linux/usb/rndis_host.h
++++ b/include/linux/usb/rndis_host.h
+@@ -197,6 +197,7 @@ struct rndis_keepalive_c {	/* IN (option
+ 
+ /* Flags for driver_info::data */
+ #define RNDIS_DRIVER_DATA_POLL_STATUS	1	/* poll status before control */
++#define RNDIS_DRIVER_DATA_DST_MAC_FIXUP	2	/* device ignores configured MAC address */
+ 
+ extern void rndis_status(struct usbnet *dev, struct urb *urb);
+ extern int
diff --git a/target/linux/generic/backport-5.15/882-v5.19-rndis_host-limit-scope-of-bogus-MAC-address-detectio.patch b/target/linux/generic/backport-5.15/882-v5.19-rndis_host-limit-scope-of-bogus-MAC-address-detectio.patch
new file mode 100644
index 0000000000..ebe7a43fd3
--- /dev/null
+++ b/target/linux/generic/backport-5.15/882-v5.19-rndis_host-limit-scope-of-bogus-MAC-address-detectio.patch
@@ -0,0 +1,63 @@
+From 9bfb4bcda7ba32d73ea322ea56a8ebe32e9247f6 Mon Sep 17 00:00:00 2001
+From: Lech Perczak <lech.perczak@gmail.com>
+Date: Sat, 2 Apr 2022 02:19:57 +0200
+Subject: [PATCH 3/3] rndis_host: limit scope of bogus MAC address detection to
+ ZTE devices
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Reporting of bogus MAC addresses and ignoring configuration of new
+destination address wasn't observed outside of a range of ZTE devices,
+among which this seems to be the common bug. Align rndis_host driver
+with implementation found in cdc_ether, which also limits this workaround
+to ZTE devices.
+
+Suggested-by: Bjørn Mork <bjorn@mork.no>
+Cc: Kristian Evensen <kristian.evensen@gmail.com>
+Cc: Oliver Neukum <oliver@neukum.org>
+Signed-off-by: Lech Perczak <lech.perczak@gmail.com>
+---
+ drivers/net/usb/rndis_host.c | 17 ++++++++++++-----
+ 1 file changed, 12 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/usb/rndis_host.c
++++ b/drivers/net/usb/rndis_host.c
+@@ -418,10 +418,7 @@ generic_rndis_bind(struct usbnet *dev, s
+ 		goto halt_fail_and_release;
+ 	}
+ 
+-	if (bp[0] & 0x02)
+-		eth_hw_addr_random(net);
+-	else
+-		ether_addr_copy(net->dev_addr, bp);
++	ether_addr_copy(net->dev_addr, bp);
+ 
+ 	/* set a nonzero filter to enable data transfers */
+ 	memset(u.set, 0, sizeof *u.set);
+@@ -463,6 +460,16 @@ static int rndis_bind(struct usbnet *dev
+ 	return generic_rndis_bind(dev, intf, FLAG_RNDIS_PHYM_NOT_WIRELESS);
+ }
+ 
++static int zte_rndis_bind(struct usbnet *dev, struct usb_interface *intf)
++{
++	int status = rndis_bind(dev, intf);
++
++	if (!status && (dev->net->dev_addr[0] & 0x02))
++		eth_hw_addr_random(dev->net);
++
++	return status;
++}
++
+ void rndis_unbind(struct usbnet *dev, struct usb_interface *intf)
+ {
+ 	struct rndis_halt	*halt;
+@@ -615,7 +622,7 @@ static const struct driver_info	zte_rndi
+ 	.description =	"ZTE RNDIS device",
+ 	.flags =	FLAG_ETHER | FLAG_POINTTOPOINT | FLAG_FRAMING_RN | FLAG_NO_SETINT,
+ 	.data =		RNDIS_DRIVER_DATA_DST_MAC_FIXUP,
+-	.bind =		rndis_bind,
++	.bind =		zte_rndis_bind,
+ 	.unbind =	rndis_unbind,
+ 	.status =	rndis_status,
+ 	.rx_fixup =	rndis_rx_fixup,
-- 
2.34.1