summaryrefslogtreecommitdiffstats
path: root/target/linux/generic/backport-6.6
diff options
context:
space:
mode:
authorWeijie Gao <hackpascal@gmail.com>2024-01-03 19:57:28 +0800
committerRobert Marko <robimarko@gmail.com>2024-03-11 20:17:25 +0100
commit8a9273d51ede02fe66816ad2132198d592b1491e (patch)
tree8eb933b7aa50d41ac452fce46ea2947bc468907e /target/linux/generic/backport-6.6
parent71360660e68bbc5bdc9cde89fd66ba5104e59106 (diff)
downloadopenwrt-8a9273d51ede02fe66816ad2132198d592b1491e.tar.gz
openwrt-8a9273d51ede02fe66816ad2132198d592b1491e.tar.bz2
openwrt-8a9273d51ede02fe66816ad2132198d592b1491e.zip
generic: copy backport, hack, pending patch and config from 6.1 to 6.6
Copy backport, hack, pending patch and config from 6.1 to 6.6. Signed-off-by: Weijie Gao <hackpascal@gmail.com>
Diffstat (limited to 'target/linux/generic/backport-6.6')
-rw-r--r--target/linux/generic/backport-6.6/020-v6.3-01-UPSTREAM-mm-multi-gen-LRU-rename-lru_gen_struct-to-l.patch352
-rw-r--r--target/linux/generic/backport-6.6/020-v6.3-03-UPSTREAM-mm-multi-gen-LRU-remove-eviction-fairness-s.patch192
-rw-r--r--target/linux/generic/backport-6.6/020-v6.3-04-BACKPORT-mm-multi-gen-LRU-remove-aging-fairness-safe.patch294
-rw-r--r--target/linux/generic/backport-6.6/020-v6.3-05-UPSTREAM-mm-multi-gen-LRU-shuffle-should_run_aging.patch166
-rw-r--r--target/linux/generic/backport-6.6/020-v6.3-06-BACKPORT-mm-multi-gen-LRU-per-node-lru_gen_folio-lis.patch876
-rw-r--r--target/linux/generic/backport-6.6/020-v6.3-07-BACKPORT-mm-multi-gen-LRU-clarify-scan_control-flags.patch202
-rw-r--r--target/linux/generic/backport-6.6/020-v6.3-08-UPSTREAM-mm-multi-gen-LRU-simplify-arch_has_hw_pte_y.patch38
-rw-r--r--target/linux/generic/backport-6.6/020-v6.3-09-UPSTREAM-mm-multi-gen-LRU-avoid-futile-retries.patch92
-rw-r--r--target/linux/generic/backport-6.6/020-v6.3-10-UPSTREAM-mm-add-vma_has_recency.patch191
-rw-r--r--target/linux/generic/backport-6.6/020-v6.3-11-UPSTREAM-mm-support-POSIX_FADV_NOREUSE.patch129
-rw-r--r--target/linux/generic/backport-6.6/020-v6.3-12-UPSTREAM-mm-multi-gen-LRU-section-for-working-set-pr.patch67
-rw-r--r--target/linux/generic/backport-6.6/020-v6.3-13-UPSTREAM-mm-multi-gen-LRU-section-for-rmap-PT-walk-f.patch57
-rw-r--r--target/linux/generic/backport-6.6/020-v6.3-14-UPSTREAM-mm-multi-gen-LRU-section-for-Bloom-filters.patch243
-rw-r--r--target/linux/generic/backport-6.6/020-v6.3-15-UPSTREAM-mm-multi-gen-LRU-section-for-memcg-LRU.patch427
-rw-r--r--target/linux/generic/backport-6.6/020-v6.3-16-UPSTREAM-mm-multi-gen-LRU-improve-lru_gen_exit_memcg.patch40
-rw-r--r--target/linux/generic/backport-6.6/020-v6.3-17-UPSTREAM-mm-multi-gen-LRU-improve-walk_pmd_range.patch135
-rw-r--r--target/linux/generic/backport-6.6/020-v6.3-18-UPSTREAM-mm-multi-gen-LRU-simplify-lru_gen_look_arou.patch148
-rw-r--r--target/linux/generic/backport-6.6/020-v6.4-19-mm-Multi-gen-LRU-remove-wait_event_killable.patch273
-rw-r--r--target/linux/generic/backport-6.6/200-v6.3-bitfield-add-FIELD_PREP_CONST.patch58
-rw-r--r--target/linux/generic/backport-6.6/300-v6.2-powerpc-suppress-some-linker-warnings-in-recent-link.patch63
-rw-r--r--target/linux/generic/backport-6.6/406-v6.2-0001-mtd-core-simplify-a-bit-code-find-partition-matching.patch65
-rw-r--r--target/linux/generic/backport-6.6/406-v6.2-0002-mtd-core-try-to-find-OF-node-for-every-MTD-partition.patch84
-rw-r--r--target/linux/generic/backport-6.6/408-v6.2-mtd-core-set-ROOT_DEV-for-partitions-marked-as-rootf.patch47
-rw-r--r--target/linux/generic/backport-6.6/421-v6.2-mtd-parsers-add-TP-Link-SafeLoader-partitions-table-.patch229
-rw-r--r--target/linux/generic/backport-6.6/423-v6.3-mtd-spinand-macronix-use-scratch-buffer-for-DMA-oper.patch35
-rw-r--r--target/linux/generic/backport-6.6/424-v6.3-mtd-ubi-wire-up-parent-MTD-device.patch33
-rw-r--r--target/linux/generic/backport-6.6/425-v6.3-mtd-ubi-block-wire-up-device-parent.patch49
-rw-r--r--target/linux/generic/backport-6.6/426-v6.4-0004-mtd-core-prepare-mtd_otp_nvmem_add-to-handle-EPROBE_.patch47
-rw-r--r--target/linux/generic/backport-6.6/611-v6.3-net-add-helper-eth_addr_add.patch41
-rw-r--r--target/linux/generic/backport-6.6/701-net-next-net-sfp-add-quirk-for-Fiberstone-GPON-ONU-34-20BI.patch32
-rw-r--r--target/linux/generic/backport-6.6/702-01-v6.7-net-phy-aquantia-move-to-separate-directory.patch2306
-rw-r--r--target/linux/generic/backport-6.6/702-02-v6.7-net-phy-aquantia-move-MMD_VEND-define-to-header.patch183
-rw-r--r--target/linux/generic/backport-6.6/702-03-v6.7-net-phy-aquantia-add-firmware-load-support.patch504
-rw-r--r--target/linux/generic/backport-6.6/703-v6.3-net-mdio-Add-dedicated-C45-API-to-MDIO-bus-drivers.patch326
-rw-r--r--target/linux/generic/backport-6.6/704-v6.6-net-phy-Introduce-PSGMII-PHY-interface-mode.patch105
-rw-r--r--target/linux/generic/backport-6.6/705-v6.4-net-phy-at803x-Replace-of_gpio.h-with-what-indeed-is.patch32
-rw-r--r--target/linux/generic/backport-6.6/706-v6.6-01-net-phy-at803x-support-qca8081-genphy_c45_pma_read_a.patch70
-rw-r--r--target/linux/generic/backport-6.6/706-v6.6-02-net-phy-at803x-merge-qca8081-slave-seed-function.patch73
-rw-r--r--target/linux/generic/backport-6.6/706-v6.6-03-net-phy-at803x-enable-qca8081-slave-seed-conditional.patch76
-rw-r--r--target/linux/generic/backport-6.6/706-v6.6-04-net-phy-at803x-support-qca8081-1G-chip-type.patch48
-rw-r--r--target/linux/generic/backport-6.6/706-v6.6-05-net-phy-at803x-remove-qca8081-1G-fast-retrain-and-sl.patch98
-rw-r--r--target/linux/generic/backport-6.6/706-v6.6-06-net-phy-at803x-add-qca8081-fifo-reset-on-the-link-ch.patch55
-rw-r--r--target/linux/generic/backport-6.6/707-v6.8-02-net-phy-at803x-move-disable-WOL-to-specific-at8031-p.patch69
-rw-r--r--target/linux/generic/backport-6.6/707-v6.8-03-net-phy-at803x-raname-hw_stats-functions-to-qca83xx-.patch129
-rw-r--r--target/linux/generic/backport-6.6/707-v6.8-04-net-phy-at803x-move-qca83xx-specific-check-in-dedica.patch155
-rw-r--r--target/linux/generic/backport-6.6/707-v6.8-05-net-phy-at803x-move-specific-DT-option-for-at8031-to.patch94
-rw-r--r--target/linux/generic/backport-6.6/707-v6.8-06-net-phy-at803x-move-specific-at8031-probe-mode-check.patch78
-rw-r--r--target/linux/generic/backport-6.6/707-v6.8-07-net-phy-at803x-move-specific-at8031-config_init-to-d.patch86
-rw-r--r--target/linux/generic/backport-6.6/707-v6.8-08-net-phy-at803x-move-specific-at8031-WOL-bits-to-dedi.patch92
-rw-r--r--target/linux/generic/backport-6.6/707-v6.8-09-net-phy-at803x-move-specific-at8031-config_intr-to-d.patch78
-rw-r--r--target/linux/generic/backport-6.6/707-v6.8-10-net-phy-at803x-make-at8031-related-DT-functions-name.patch78
-rw-r--r--target/linux/generic/backport-6.6/707-v6.8-11-net-phy-at803x-move-at8031-functions-in-dedicated-se.patch297
-rw-r--r--target/linux/generic/backport-6.6/707-v6.8-12-net-phy-at803x-move-at8035-specific-DT-parse-to-dedi.patch114
-rw-r--r--target/linux/generic/backport-6.6/707-v6.8-13-net-phy-at803x-drop-specific-PHY-ID-check-from-cable.patch219
-rw-r--r--target/linux/generic/backport-6.6/708-v6.8-01-net-phy-at803x-move-specific-qca808x-config_aneg-to-.patch116
-rw-r--r--target/linux/generic/backport-6.6/708-v6.8-02-net-phy-at803x-make-read-specific-status-function-mo.patch97
-rw-r--r--target/linux/generic/backport-6.6/709-v6.8-01-net-phy-at803x-remove-extra-space-after-cast.patch27
-rw-r--r--target/linux/generic/backport-6.6/709-v6.8-02-net-phy-at803x-replace-msleep-1-with-usleep_range.patch38
-rw-r--r--target/linux/generic/backport-6.6/710-v6.8-net-phy-at803x-better-align-function-varibles-to-ope.patch152
-rw-r--r--target/linux/generic/backport-6.6/711-v6.8-01-net-phy-at803x-generalize-cdt-fault-length-function.patch62
-rw-r--r--target/linux/generic/backport-6.6/711-v6.8-02-net-phy-at803x-refactor-qca808x-cable-test-get-statu.patch118
-rw-r--r--target/linux/generic/backport-6.6/711-v6.8-03-net-phy-at803x-add-support-for-cdt-cross-short-test-.patch182
-rw-r--r--target/linux/generic/backport-6.6/711-v6.8-04-net-phy-at803x-make-read_status-more-generic.patch62
-rw-r--r--target/linux/generic/backport-6.6/712-v6.9-net-phy-at803x-add-LED-support-for-qca808x.patch408
-rw-r--r--target/linux/generic/backport-6.6/713-v6.9-01-net-phy-move-at803x-PHY-driver-to-dedicated-director.patch5598
-rw-r--r--target/linux/generic/backport-6.6/713-v6.9-02-net-phy-qcom-create-and-move-functions-to-shared-lib.patch243
-rw-r--r--target/linux/generic/backport-6.6/713-v6.9-03-net-phy-qcom-deatch-qca83xx-PHY-driver-from-at803x.patch638
-rw-r--r--target/linux/generic/backport-6.6/713-v6.9-04-net-phy-qcom-move-additional-functions-to-shared-lib.patch1014
-rw-r--r--target/linux/generic/backport-6.6/713-v6.9-05-net-phy-qcom-detach-qca808x-PHY-driver-from-at803x.patch1936
-rw-r--r--target/linux/generic/backport-6.6/714-net-pcs-add-driver-for-MediaTek-SGMII-PCS.patch394
-rw-r--r--target/linux/generic/backport-6.6/714-v6.8-01-net-phy-make-addr-type-u8-in-phy_package_shared-stru.patch28
-rw-r--r--target/linux/generic/backport-6.6/714-v6.8-02-net-phy-extend-PHY-package-API-to-support-multiple-g.patch341
-rw-r--r--target/linux/generic/backport-6.6/714-v6.8-03-net-phy-restructure-__phy_write-read_mmd-to-helper-a.patch116
-rw-r--r--target/linux/generic/backport-6.6/714-v6.8-04-net-phy-add-support-for-PHY-package-MMD-read-write.patch196
-rw-r--r--target/linux/generic/backport-6.6/715-01-v6.2-net-fman-memac-Add-serdes-support.patch103
-rw-r--r--target/linux/generic/backport-6.6/715-02-v6.2-net-fman-memac-Use-lynx-pcs-driver.patch384
-rw-r--r--target/linux/generic/backport-6.6/715-03-v6.2-net-dpaa-Convert-to-phylink.patch2451
-rw-r--r--target/linux/generic/backport-6.6/715-04-v6.2-net-phylink-provide-phylink_validate_mask_caps-helpe.patch93
-rw-r--r--target/linux/generic/backport-6.6/715-05-v6.2-phylink-require-valid-state-argument-to-phylink_vali.patch39
-rw-r--r--target/linux/generic/backport-6.6/715-06-v6.2-net-phylink-add-phylink_get_link_timer_ns-helper.patch48
-rw-r--r--target/linux/generic/backport-6.6/715-07-v6.2-net-remove-explicit-phylink_generic_validate-referen.patch250
-rw-r--r--target/linux/generic/backport-6.6/715-08-net-sfp-make-sfp_bus_find_fwnode-take-a-const-fwnode.patch48
-rw-r--r--target/linux/generic/backport-6.6/715-09-v6.4-net-pcs-lynx-don-t-print-an_enabled-in-pcs_get_state.patch31
-rw-r--r--target/linux/generic/backport-6.6/715-10-v6.4-net-dpaa2-mac-use-Autoneg-bit-rather-than-an_enabled.patch32
-rw-r--r--target/linux/generic/backport-6.6/715-11-v6.4-net-phylink-support-validated-pause-and-autoneg-in-f.patch64
-rw-r--r--target/linux/generic/backport-6.6/715-12-v6.4-net-phylink-remove-an_enabled.patch177
-rw-r--r--target/linux/generic/backport-6.6/715-13-v6.5-net-phylink-constify-fwnode-arguments.patch88
-rw-r--r--target/linux/generic/backport-6.6/715-14-v6.3-net-phy-constify-fwnode_get_phy_node-fwnode-argument.patch38
-rw-r--r--target/linux/generic/backport-6.6/715-15-v6.4-net-phylink-fix-ksettings_set-ethtool-call.patch44
-rw-r--r--target/linux/generic/backport-6.6/715-16-v6.5-net-sfp-add-support-for-setting-signalling-rate.patch149
-rw-r--r--target/linux/generic/backport-6.6/715-17-v6.5-net-phy-add-helpers-for-comparing-phy-IDs.patch147
-rw-r--r--target/linux/generic/backport-6.6/715-18-v6.5-net-phylink-require-supported_interfaces-to-be-fille.patch71
-rw-r--r--target/linux/generic/backport-6.6/715-19-v6.5-net-phylink-remove-duplicated-linkmode-pause-resolut.patch64
-rw-r--r--target/linux/generic/backport-6.6/715-20-v6.5-net-phylink-add-function-to-resolve-clause-73-negoti.patch76
-rw-r--r--target/linux/generic/backport-6.6/715-21-v6.5-net-phylink-provide-phylink_pcs_config-and-phylink_p.patch100
-rw-r--r--target/linux/generic/backport-6.6/715-23-v6.4-net-phylink-actually-fix-ksettings_set-ethtool-call.patch55
-rw-r--r--target/linux/generic/backport-6.6/715-24-v6.5-net-phylink-add-PCS-negotiation-mode.patch324
-rw-r--r--target/linux/generic/backport-6.6/715-25-v6.5-net-phylink-convert-phylink_mii_c22_pcs_config-to-ne.patch45
-rw-r--r--target/linux/generic/backport-6.6/715-26-v6.5-net-phylink-pass-neg_mode-into-phylink_mii_c22_pcs_c.patch187
-rw-r--r--target/linux/generic/backport-6.6/715-27-v6.5-net-pcs-lynxi-update-PCS-driver-to-use-neg_mode.patch101
-rw-r--r--target/linux/generic/backport-6.6/715-28-v6.4-net-pcs-xpcs-use-Autoneg-bit-rather-than-an_enabled.patch55
-rw-r--r--target/linux/generic/backport-6.6/715-29-v6.4-net-pcs-xpcs-fix-incorrect-number-of-interfaces.patch30
-rw-r--r--target/linux/generic/backport-6.6/715-v6.9-01-net-phy-qcom-qca808x-fix-logic-error-in-LED-brightne.patch36
-rw-r--r--target/linux/generic/backport-6.6/715-v6.9-02-net-phy-qcom-qca808x-default-to-LED-active-High-if-n.patch41
-rw-r--r--target/linux/generic/backport-6.6/716-v6.9-02-net-phy-add-support-for-scanning-PHY-in-PHY-packages.patch211
-rw-r--r--target/linux/generic/backport-6.6/716-v6.9-03-net-phy-add-devm-of_phy_package_join-helper.patch185
-rw-r--r--target/linux/generic/backport-6.6/716-v6.9-04-net-phy-qcom-move-more-function-to-shared-library.patch583
-rw-r--r--target/linux/generic/backport-6.6/716-v6.9-06-net-phy-provide-whether-link-has-changed-in-c37_read.patch100
-rw-r--r--target/linux/generic/backport-6.6/716-v6.9-07-net-phy-qcom-add-support-for-QCA807x-PHY-Family.patch668
-rw-r--r--target/linux/generic/backport-6.6/716-v6.9-08-net-phy-qcom-move-common-qca808x-LED-define-to-share.patch179
-rw-r--r--target/linux/generic/backport-6.6/716-v6.9-09-net-phy-qcom-generalize-some-qca808x-LED-functions.patch172
-rw-r--r--target/linux/generic/backport-6.6/716-v6.9-10-net-phy-qca807x-add-support-for-configurable-LED.patch326
-rw-r--r--target/linux/generic/backport-6.6/717-v6.9-net-phy-qca807x-move-interface-mode-check-to-.config.patch51
-rw-r--r--target/linux/generic/backport-6.6/720-v6.9-net-mdio-ipq4019-add-support-for-clock-frequency-pro.patch205
-rw-r--r--target/linux/generic/backport-6.6/721-v6.7-net-phy-aquantia-drop-wrong-endianness-conversion-fo.patch92
-rw-r--r--target/linux/generic/backport-6.6/724-v6.2-net-ethernet-mtk_eth_soc-enable-flow-offloading-supp.patch26
-rw-r--r--target/linux/generic/backport-6.6/729-01-v6.1-net-ethernet-mtk_wed-introduce-wed-mcu-support.patch591
-rw-r--r--target/linux/generic/backport-6.6/729-02-v6.1-net-ethernet-mtk_wed-introduce-wed-wo-support.patch737
-rw-r--r--target/linux/generic/backport-6.6/729-03-v6.1-net-ethernet-mtk_wed-rename-tx_wdma-array-in-rx_wdma.patch79
-rw-r--r--target/linux/generic/backport-6.6/729-04-v6.1-net-ethernet-mtk_wed-add-configure-wed-wo-support.patch1521
-rw-r--r--target/linux/generic/backport-6.6/729-05-v6.1-net-ethernet-mtk_wed-add-rx-mib-counters.patch149
-rw-r--r--target/linux/generic/backport-6.6/729-07-v6.1-net-ethernet-mtk_eth_soc-remove-cpu_relax-in-mtk_pen.patch36
-rw-r--r--target/linux/generic/backport-6.6/729-09-v6.2-net-ethernet-mtk_wed-add-wcid-overwritten-support-fo.patch80
-rw-r--r--target/linux/generic/backport-6.6/729-10-v6.2-net-ethernet-mtk_wed-return-status-value-in-mtk_wdma.patch85
-rw-r--r--target/linux/generic/backport-6.6/729-11-v6.2-net-ethernet-mtk_wed-move-MTK_WDMA_RESET_IDX_TX-conf.patch52
-rw-r--r--target/linux/generic/backport-6.6/729-12-v6.2-net-ethernet-mtk_wed-update-mtk_wed_stop.patch98
-rw-r--r--target/linux/generic/backport-6.6/729-13-v6.2-net-ethernet-mtk_wed-add-mtk_wed_rx_reset-routine.patch309
-rw-r--r--target/linux/generic/backport-6.6/729-14-v6.2-net-ethernet-mtk_wed-add-reset-to-tx_ring_setup-call.patch103
-rw-r--r--target/linux/generic/backport-6.6/729-15-v6.2-net-ethernet-mtk_wed-fix-sleep-while-atomic-in-mtk_w.patch103
-rw-r--r--target/linux/generic/backport-6.6/729-16-v6.3-net-ethernet-mtk_wed-get-rid-of-queue-lock-for-rx-qu.patch52
-rw-r--r--target/linux/generic/backport-6.6/729-17-v6.3-net-ethernet-mtk_wed-get-rid-of-queue-lock-for-tx-qu.patch75
-rw-r--r--target/linux/generic/backport-6.6/729-18-v6.3-net-ethernet-mtk_eth_soc-introduce-mtk_hw_reset-util.patch70
-rw-r--r--target/linux/generic/backport-6.6/729-19-v6.3-net-ethernet-mtk_eth_soc-introduce-mtk_hw_warm_reset.patch107
-rw-r--r--target/linux/generic/backport-6.6/729-20-v6.3-net-ethernet-mtk_eth_soc-align-reset-procedure-to-ve.patch262
-rw-r--r--target/linux/generic/backport-6.6/729-21-v6.3-net-ethernet-mtk_eth_soc-add-dma-checks-to-mtk_hw_re.patch249
-rw-r--r--target/linux/generic/backport-6.6/729-22-v6.3-net-ethernet-mtk_wed-add-reset-reset_complete-callba.patch124
-rw-r--r--target/linux/generic/backport-6.6/729-23-v6.3-net-ethernet-mtk_wed-add-reset-to-rx_ring_setup-call.patch106
-rw-r--r--target/linux/generic/backport-6.6/730-01-v6.3-net-ethernet-mtk_eth_soc-account-for-vlan-in-rx-head.patch22
-rw-r--r--target/linux/generic/backport-6.6/730-02-v6.3-net-ethernet-mtk_eth_soc-increase-tx-ring-side-for-Q.patch143
-rw-r--r--target/linux/generic/backport-6.6/730-03-v6.3-net-ethernet-mtk_eth_soc-avoid-port_mg-assignment-on.patch52
-rw-r--r--target/linux/generic/backport-6.6/730-04-v6.3-net-ethernet-mtk_eth_soc-implement-multi-queue-suppo.patch654
-rw-r--r--target/linux/generic/backport-6.6/730-05-v6.3-net-dsa-tag_mtk-assign-per-port-queues.patch20
-rw-r--r--target/linux/generic/backport-6.6/730-06-v6.3-net-ethernet-mediatek-ppe-assign-per-port-queues-for.patch93
-rw-r--r--target/linux/generic/backport-6.6/730-08-v6.3-net-dsa-add-support-for-DSA-rx-offloading-via-metada.patch72
-rw-r--r--target/linux/generic/backport-6.6/730-09-v6.3-net-ethernet-mtk_eth_soc-fix-VLAN-rx-hardware-accele.patch192
-rw-r--r--target/linux/generic/backport-6.6/730-12-v6.3-net-ethernet-mtk_eth_soc-disable-hardware-DSA-untagg.patch42
-rw-r--r--target/linux/generic/backport-6.6/730-13-v6.3-net-ethernet-mtk_eth_soc-enable-special-tag-when-any.patch54
-rw-r--r--target/linux/generic/backport-6.6/730-14-v6.3-net-ethernet-mtk_eth_soc-fix-DSA-TX-tag-hwaccel-for-.patch129
-rw-r--r--target/linux/generic/backport-6.6/730-15-v6.3-net-ethernet-mtk_wed-No-need-to-clear-memory-after-a.patch26
-rw-r--r--target/linux/generic/backport-6.6/730-16-v6.3-net-ethernet-mtk_wed-fix-some-possible-NULL-pointer-.patch61
-rw-r--r--target/linux/generic/backport-6.6/730-17-v6.3-net-ethernet-mtk_wed-fix-possible-deadlock-if-mtk_we.patch58
-rw-r--r--target/linux/generic/backport-6.6/730-18-v6.3-net-ethernet-mtk_eth_soc-fix-tx-throughput-regressio.patch31
-rw-r--r--target/linux/generic/backport-6.6/733-v6.2-02-net-mtk_eth_soc-add-definitions-for-PCS.patch55
-rw-r--r--target/linux/generic/backport-6.6/733-v6.2-03-net-mtk_eth_soc-eliminate-unnecessary-error-handling.patch74
-rw-r--r--target/linux/generic/backport-6.6/733-v6.2-04-net-mtk_eth_soc-add-pcs_get_state-implementation.patch46
-rw-r--r--target/linux/generic/backport-6.6/733-v6.2-05-net-mtk_eth_soc-convert-mtk_sgmii-to-use-regmap_upda.patch130
-rw-r--r--target/linux/generic/backport-6.6/733-v6.2-06-net-mtk_eth_soc-add-out-of-band-forcing-of-speed-and.patch52
-rw-r--r--target/linux/generic/backport-6.6/733-v6.2-07-net-mtk_eth_soc-move-PHY-power-up.patch48
-rw-r--r--target/linux/generic/backport-6.6/733-v6.2-08-net-mtk_eth_soc-move-interface-speed-selection.patch48
-rw-r--r--target/linux/generic/backport-6.6/733-v6.2-09-net-mtk_eth_soc-add-advertisement-programming.patch52
-rw-r--r--target/linux/generic/backport-6.6/733-v6.2-10-net-mtk_eth_soc-move-and-correct-link-timer-programm.patch63
-rw-r--r--target/linux/generic/backport-6.6/733-v6.2-11-net-mtk_eth_soc-add-support-for-in-band-802.3z-negot.patch132
-rw-r--r--target/linux/generic/backport-6.6/733-v6.2-12-net-mediatek-sgmii-ensure-the-SGMII-PHY-is-powered-d.patch119
-rw-r--r--target/linux/generic/backport-6.6/733-v6.2-13-net-mediatek-sgmii-fix-duplex-configuration.patch52
-rw-r--r--target/linux/generic/backport-6.6/733-v6.2-14-mtk_sgmii-enable-PCS-polling-to-allow-SFP-work.patch33
-rw-r--r--target/linux/generic/backport-6.6/733-v6.3-15-net-ethernet-mtk_eth_soc-reset-PCS-state.patch48
-rw-r--r--target/linux/generic/backport-6.6/733-v6.3-16-net-ethernet-mtk_eth_soc-only-write-values-if-needed.patch103
-rw-r--r--target/linux/generic/backport-6.6/733-v6.3-18-net-ethernet-mtk_eth_soc-add-support-for-MT7981.patch206
-rw-r--r--target/linux/generic/backport-6.6/733-v6.3-19-net-ethernet-mtk_eth_soc-set-MDIO-bus-clock-frequenc.patch76
-rw-r--r--target/linux/generic/backport-6.6/733-v6.3-20-net-ethernet-mtk_eth_soc-switch-to-external-PCS-driv.patch512
-rw-r--r--target/linux/generic/backport-6.6/733-v6.4-21-net-mtk_eth_soc-use-WO-firmware-for-MT7981.patch46
-rw-r--r--target/linux/generic/backport-6.6/733-v6.4-22-net-ethernet-mtk_eth_soc-fix-NULL-pointer-dereferenc.patch28
-rw-r--r--target/linux/generic/backport-6.6/733-v6.4-23-net-ethernet-mtk_eth_soc-ppe-add-support-for-flow-ac.patch403
-rw-r--r--target/linux/generic/backport-6.6/733-v6.4-24-net-ethernet-mediatek-fix-ppe-flow-accounting-for-v1.patch58
-rw-r--r--target/linux/generic/backport-6.6/733-v6.4-25-net-ethernet-mtk_eth_soc-drop-generic-vlan-rx-offloa.patch201
-rw-r--r--target/linux/generic/backport-6.6/733-v6.5-26-net-ethernet-mtk_eth_soc-always-mtk_get_ib1_pkt_type.patch31
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-01-net-ethernet-mtk_ppe-add-MTK_FOE_ENTRY_V-1-2-_SIZE-m.patch78
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-02-net-ethernet-mtk_eth_soc-remove-incorrect-PLL-config.patch141
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-03-net-ethernet-mtk_eth_soc-remove-mac_pcs_get_state-an.patch81
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-05-net-ethernet-mtk_eth_soc-add-version-in-mtk_soc_data.patch550
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-06-net-ethernet-mtk_eth_soc-increase-MAX_DEVS-to-3.patch29
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-07-net-ethernet-mtk_eth_soc-rely-on-MTK_MAX_DEVS-and-re.patch186
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-08-net-ethernet-mtk_eth_soc-add-NETSYS_V3-version-suppo.patch307
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-09-net-ethernet-mtk_eth_soc-convert-caps-in-mtk_soc_dat.patch193
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-10-net-ethernet-mtk_eth_soc-convert-clock-bitmap-to-u64.patch132
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-11-net-ethernet-mtk_eth_soc-add-basic-support-for-MT798.patch477
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-12-net-ethernet-mtk_eth_soc-enable-page_pool-support-fo.patch27
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-13-net-ethernet-mtk_eth_soc-enable-nft-hw-flowtable_off.patch135
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-14-net-ethernet-mtk_eth_soc-support-per-flow-accounting.patch78
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-15-net-ethernet-mtk_eth_soc-fix-NULL-pointer-on-hw-rese.patch52
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-16-net-ethernet-mtk_eth_soc-fix-register-definitions-fo.patch44
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-17-net-ethernet-mtk_eth_soc-add-reset-bits-for-MT7988.patch188
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-18-net-ethernet-mtk_eth_soc-add-support-for-in-SoC-SRAM.patch254
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-19-net-ethernet-mtk_eth_soc-support-36-bit-DMA-addressi.patch166
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-20-net-ethernet-mtk_eth_soc-fix-uninitialized-variable.patch44
-rw-r--r--target/linux/generic/backport-6.6/750-v6.5-21-net-ethernet-mtk_eth_soc-fix-pse_port-configuration-.patch33
-rw-r--r--target/linux/generic/backport-6.6/751-01-v6.4-net-ethernet-mtk_eth_soc-add-code-for-offloading-flo.patch271
-rw-r--r--target/linux/generic/backport-6.6/751-02-v6.4-net-ethernet-mediatek-mtk_ppe-prefer-newly-added-l2-.patch37
-rw-r--r--target/linux/generic/backport-6.6/751-03-v6.4-net-ethernet-mtk_eth_soc-improve-keeping-track-of-of.patch334
-rw-r--r--target/linux/generic/backport-6.6/751-04-v6.4-net-ethernet-mediatek-fix-ppe-flow-accounting-for-L2.patch342
-rw-r--r--target/linux/generic/backport-6.6/752-01-v6.6-net-ethernet-mtk_wed-add-some-more-info-in-wed_txinf.patch45
-rw-r--r--target/linux/generic/backport-6.6/752-02-v6.6-net-ethernet-mtk_wed-minor-change-in-wed_-tx-rx-info.patch47
-rw-r--r--target/linux/generic/backport-6.6/752-03-v6.6-net-ethernet-mtk_eth_soc-rely-on-mtk_pse_port-defini.patch29
-rw-r--r--target/linux/generic/backport-6.6/752-04-v6.6-net-ethernet-mtk_wed-check-update_wo_rx_stats-in-mtk.patch26
-rw-r--r--target/linux/generic/backport-6.6/752-05-v6.7-net-ethernet-mtk_wed-do-not-assume-offload-callbacks.patch68
-rw-r--r--target/linux/generic/backport-6.6/752-06-v6.7-net-ethernet-mtk_wed-introduce-versioning-utility-ro.patch232
-rw-r--r--target/linux/generic/backport-6.6/752-07-v6.7-net-ethernet-mtk_wed-do-not-configure-rx-offload-if-.patch234
-rw-r--r--target/linux/generic/backport-6.6/752-08-v6.7-net-ethernet-mtk_wed-rename-mtk_rxbm_desc-in-mtk_wed.patch52
-rw-r--r--target/linux/generic/backport-6.6/752-09-v6.7-net-ethernet-mtk_wed-introduce-mtk_wed_buf-structure.patch87
-rw-r--r--target/linux/generic/backport-6.6/752-10-v6.7-net-ethernet-mtk_wed-move-mem_region-array-out-of-mt.patch88
-rw-r--r--target/linux/generic/backport-6.6/752-11-v6.7-net-ethernet-mtk_wed-make-memory-region-optional.patch71
-rw-r--r--target/linux/generic/backport-6.6/752-13-v6.7-net-ethernet-mtk_wed-add-mtk_wed_soc_data-structure.patch217
-rw-r--r--target/linux/generic/backport-6.6/752-14-v6.7-net-ethernet-mtk_wed-introduce-WED-support-for-MT798.patch1280
-rw-r--r--target/linux/generic/backport-6.6/752-15-v6.7-net-ethernet-mtk_wed-refactor-mtk_wed_check_wfdma_rx.patch95
-rw-r--r--target/linux/generic/backport-6.6/752-16-v6.7-net-ethernet-mtk_wed-introduce-partial-AMSDU-offload.patch465
-rw-r--r--target/linux/generic/backport-6.6/752-17-v6.7-net-ethernet-mtk_wed-introduce-hw_rro-support-for-MT.patch483
-rw-r--r--target/linux/generic/backport-6.6/752-18-v6.7-net-ethernet-mtk_wed-debugfs-move-wed_v2-specific-re.patch78
-rw-r--r--target/linux/generic/backport-6.6/752-19-v6.7-net-ethernet-mtk_wed-debugfs-add-WED-3.0-debugfs-ent.patch432
-rw-r--r--target/linux/generic/backport-6.6/752-20-v6.7-net-ethernet-mtk_wed-add-wed-3.0-reset-support.patch587
-rw-r--r--target/linux/generic/backport-6.6/760-v6.9-net-phy-aquantia-add-AQR111-and-AQR111B0-PHY-ID.patch102
-rw-r--r--target/linux/generic/backport-6.6/761-v6.9-net-phy-aquantia-add-AQR113-PHY-ID.patch60
-rw-r--r--target/linux/generic/backport-6.6/762-v6.9-net-phy-aquantia-add-AQR813-PHY-ID.patch59
-rw-r--r--target/linux/generic/backport-6.6/770-net-introduce-napi_is_scheduled-helper.patch96
-rw-r--r--target/linux/generic/backport-6.6/771-v6.7-01-net-stmmac-improve-TX-timer-arm-logic.patch77
-rw-r--r--target/linux/generic/backport-6.6/771-v6.7-02-net-stmmac-move-TX-timer-arm-after-DMA-enable.patch96
-rw-r--r--target/linux/generic/backport-6.6/771-v6.7-03-net-stmmac-increase-TX-coalesce-timer-to-5ms.patch38
-rw-r--r--target/linux/generic/backport-6.6/777-v6.2-04-net-dsa-qca8k-introduce-single-mii-read-write-lo-hi.patch150
-rw-r--r--target/linux/generic/backport-6.6/777-v6.2-05-net-dsa-qca8k-improve-mdio-master-read-write-by-usin.patch73
-rw-r--r--target/linux/generic/backport-6.6/778-v6.3-01-net-dsa-qca8k-add-QCA8K_ATU_TABLE_SIZE-define-for-fd.patch63
-rw-r--r--target/linux/generic/backport-6.6/778-v6.3-02-net-dsa-qca8k-convert-to-regmap-read-write-API.patch261
-rw-r--r--target/linux/generic/backport-6.6/779-v6.5-net-dsa-qca8k-enable-use_single_write-for-qca8xxx.patch78
-rw-r--r--target/linux/generic/backport-6.6/780-v6.6-01-net-dsa-qca8k-make-learning-configurable-and-keep-of.patch146
-rw-r--r--target/linux/generic/backport-6.6/780-v6.6-02-net-dsa-qca8k-limit-user-ports-access-to-the-first-C.patch53
-rw-r--r--target/linux/generic/backport-6.6/780-v6.6-03-net-dsa-qca8k-move-qca8xxx-hol-fixup-to-separate-fun.patch111
-rw-r--r--target/linux/generic/backport-6.6/780-v6.6-04-net-dsa-qca8k-use-dsa_for_each-macro-instead-of-for-.patch158
-rw-r--r--target/linux/generic/backport-6.6/781-v6.6-01-net-dsa-qca8k-fix-regmap-bulk-read-write-methods-on-.patch61
-rw-r--r--target/linux/generic/backport-6.6/788-v6.3-net-dsa-mt7530-use-external-PCS-driver.patch514
-rw-r--r--target/linux/generic/backport-6.6/790-v6.4-0001-net-dsa-mt7530-make-some-noise-if-register-read-fail.patch32
-rw-r--r--target/linux/generic/backport-6.6/790-v6.4-0002-net-dsa-mt7530-refactor-SGMII-PCS-creation.patch111
-rw-r--r--target/linux/generic/backport-6.6/790-v6.4-0003-net-dsa-mt7530-use-unlocked-regmap-accessors.patch74
-rw-r--r--target/linux/generic/backport-6.6/790-v6.4-0004-net-dsa-mt7530-use-regmap-to-access-switch-register-.patch224
-rw-r--r--target/linux/generic/backport-6.6/790-v6.4-0005-net-dsa-mt7530-move-SGMII-PCS-creation-to-mt7530_pro.patch54
-rw-r--r--target/linux/generic/backport-6.6/790-v6.4-0006-net-dsa-mt7530-introduce-mutex-helpers.patch273
-rw-r--r--target/linux/generic/backport-6.6/790-v6.4-0007-net-dsa-mt7530-move-p5_intf_modes-function-to-mt7530.patch75
-rw-r--r--target/linux/generic/backport-6.6/790-v6.4-0008-net-dsa-mt7530-introduce-mt7530_probe_common-helper-.patch155
-rw-r--r--target/linux/generic/backport-6.6/790-v6.4-0009-net-dsa-mt7530-introduce-mt7530_remove_common-helper.patch54
-rw-r--r--target/linux/generic/backport-6.6/790-v6.4-0010-net-dsa-mt7530-introduce-separate-MDIO-driver.patch691
-rw-r--r--target/linux/generic/backport-6.6/790-v6.4-0011-net-dsa-mt7530-skip-locking-if-MDIO-bus-isn-t-presen.patch47
-rw-r--r--target/linux/generic/backport-6.6/790-v6.4-0012-net-dsa-mt7530-introduce-driver-for-MT7988-built-in-.patch421
-rw-r--r--target/linux/generic/backport-6.6/790-v6.4-0013-net-dsa-mt7530-fix-support-for-MT7531BE.patch118
-rw-r--r--target/linux/generic/backport-6.6/791-v6.2-01-net-phy-Add-driver-for-Motorcomm-yt8521-gigabit-ethernet.patch1724
-rw-r--r--target/linux/generic/backport-6.6/791-v6.2-02-net-phy-fix-yt8521-duplicated-argument-to-or.patch49
-rw-r--r--target/linux/generic/backport-6.6/791-v6.2-03-net-phy-add-Motorcomm-YT8531S-phy-id.patch140
-rw-r--r--target/linux/generic/backport-6.6/791-v6.3-04-net-phy-fix-the-spelling-problem-of-Sentinel.patch26
-rw-r--r--target/linux/generic/backport-6.6/791-v6.3-05-net-phy-motorcomm-change-the-phy-id-of-yt8521-and-yt8531s.patch29
-rw-r--r--target/linux/generic/backport-6.6/791-v6.3-06-net-phy-Add-BIT-macro-for-Motorcomm-yt8521-yt8531-gigabit.patch107
-rw-r--r--target/linux/generic/backport-6.6/791-v6.3-07-net-phy-Add-dts-support-for-Motorcomm-yt8521-gigabit.patch343
-rw-r--r--target/linux/generic/backport-6.6/791-v6.3-08-net-phy-Add-dts-support-for-Motorcomm-yt8531s-gigabit.patch100
-rw-r--r--target/linux/generic/backport-6.6/791-v6.3-09-net-phy-Add-driver-for-Motorcomm-yt8531-gigabit-ethernet.patch302
-rw-r--r--target/linux/generic/backport-6.6/791-v6.3-10-net-phy-motorcomm-uninitialized-variables-in.patch34
-rw-r--r--target/linux/generic/backport-6.6/791-v6.6-11-net-phy-motorcomm-Add-pad-drive-strength-cfg-support.patch170
-rw-r--r--target/linux/generic/backport-6.6/792-v6.6-net-phylink-add-pcs_enable-pcs_disable-methods.patch173
-rw-r--r--target/linux/generic/backport-6.6/793-v6.6-net-pcs-lynxi-implement-pcs_disable-op.patch44
-rw-r--r--target/linux/generic/backport-6.6/794-v6.2-net-core-Allow-live-renaming-when-an-interface-is-up.patch136
-rw-r--r--target/linux/generic/backport-6.6/795-v6.3-02-cdc_ether-no-need-to-blacklist-any-r8152-devices.patch158
-rw-r--r--target/linux/generic/backport-6.6/795-v6.3-05-r8152-reduce-the-control-transfer-of-rtl8152_get_ver.patch46
-rw-r--r--target/linux/generic/backport-6.6/795-v6.3-06-r8152-Add-__GFP_NOWARN-to-big-allocations.patch55
-rw-r--r--target/linux/generic/backport-6.6/795-v6.6-08-r8152-adjust-generic_ocp_write-function.patch70
-rw-r--r--target/linux/generic/backport-6.6/795-v6.6-09-r8152-set-bp-in-bulk.patch129
-rw-r--r--target/linux/generic/backport-6.6/795-v6.6-10-eth-r8152-try-to-use-a-normal-budget.patch39
-rw-r--r--target/linux/generic/backport-6.6/795-v6.6-13-r8152-Block-future-register-access-if-register-acces.patch398
-rw-r--r--target/linux/generic/backport-6.6/795-v6.6-14-r8152-break-the-loop-when-the-budget-is-exhausted.patch83
-rw-r--r--target/linux/generic/backport-6.6/795-v6.6-15-net-usb-cdc_ether-add-u-blox-0x1313-composition.patch47
-rw-r--r--target/linux/generic/backport-6.6/795-v6.7-16-r8152-use-napi_gro_frags.patch122
-rw-r--r--target/linux/generic/backport-6.6/800-v6.3-leds-Move-led_init_default_state_get-to-the-global-h.patch39
-rw-r--r--target/linux/generic/backport-6.6/801-v6.4-01-net-dsa-qca8k-move-qca8k_port_to_phy-to-header.patch67
-rw-r--r--target/linux/generic/backport-6.6/801-v6.4-02-net-dsa-qca8k-add-LEDs-basic-support.patch435
-rw-r--r--target/linux/generic/backport-6.6/801-v6.4-03-net-dsa-qca8k-add-LEDs-blink_set-support.patch74
-rw-r--r--target/linux/generic/backport-6.6/801-v6.4-04-leds-Provide-stubs-for-when-CLASS_LED-NEW_LEDS-are-d.patch59
-rw-r--r--target/linux/generic/backport-6.6/801-v6.4-05-net-phy-Add-a-binding-for-PHY-LEDs.patch191
-rw-r--r--target/linux/generic/backport-6.6/801-v6.4-06-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch97
-rw-r--r--target/linux/generic/backport-6.6/801-v6.4-07-net-phy-marvell-Add-software-control-of-the-LEDs.patch112
-rw-r--r--target/linux/generic/backport-6.6/801-v6.4-08-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch73
-rw-r--r--target/linux/generic/backport-6.6/801-v6.4-09-net-phy-marvell-Implement-led_blink_set.patch104
-rw-r--r--target/linux/generic/backport-6.6/802-v6.4-net-phy-marvell-Fix-inconsistent-indenting-in-led_bl.patch38
-rw-r--r--target/linux/generic/backport-6.6/803-v6.5-02-leds-trigger-netdev-Drop-NETDEV_LED_MODE_LINKUP-from.patch87
-rw-r--r--target/linux/generic/backport-6.6/803-v6.5-03-leds-trigger-netdev-Rename-add-namespace-to-netdev-t.patch149
-rw-r--r--target/linux/generic/backport-6.6/803-v6.5-04-leds-trigger-netdev-Convert-device-attr-to-macro.patch82
-rw-r--r--target/linux/generic/backport-6.6/803-v6.5-05-leds-trigger-netdev-Use-mutex-instead-of-spinlocks.patch106
-rw-r--r--target/linux/generic/backport-6.6/804-v6.5-01-leds-add-APIs-for-LEDs-hw-control.patch74
-rw-r--r--target/linux/generic/backport-6.6/804-v6.5-02-leds-add-API-to-get-attached-device-for-LED-hw-contr.patch37
-rw-r--r--target/linux/generic/backport-6.6/804-v6.5-03-Documentation-leds-leds-class-Document-new-Hardware-.patch113
-rw-r--r--target/linux/generic/backport-6.6/804-v6.5-04-leds-trigger-netdev-refactor-code-setting-device-nam.patch69
-rw-r--r--target/linux/generic/backport-6.6/804-v6.5-05-leds-trigger-netdev-introduce-check-for-possible-hw-.patch54
-rw-r--r--target/linux/generic/backport-6.6/804-v6.5-06-leds-trigger-netdev-add-basic-check-for-hw-control-s.patch42
-rw-r--r--target/linux/generic/backport-6.6/804-v6.5-07-leds-trigger-netdev-reject-interval-store-for-hw_con.patch28
-rw-r--r--target/linux/generic/backport-6.6/804-v6.5-08-leds-trigger-netdev-add-support-for-LED-hw-control.patch107
-rw-r--r--target/linux/generic/backport-6.6/804-v6.5-09-leds-trigger-netdev-validate-configured-netdev.patch58
-rw-r--r--target/linux/generic/backport-6.6/804-v6.5-10-leds-trigger-netdev-init-mode-if-hw-control-already-.patch53
-rw-r--r--target/linux/generic/backport-6.6/804-v6.5-11-leds-trigger-netdev-expose-netdev-trigger-modes-in-l.patch54
-rw-r--r--target/linux/generic/backport-6.6/804-v6.5-12-net-dsa-qca8k-implement-hw_control-ops.patch200
-rw-r--r--target/linux/generic/backport-6.6/804-v6.5-13-net-dsa-qca8k-add-op-to-get-ports-netdev.patch70
-rw-r--r--target/linux/generic/backport-6.6/805-v6.5-01-leds-trigger-netdev-add-additional-specific-link-spe.patch242
-rw-r--r--target/linux/generic/backport-6.6/805-v6.5-02-leds-trigger-netdev-add-additional-specific-link-dup.patch138
-rw-r--r--target/linux/generic/backport-6.6/805-v6.5-03-leds-trigger-netdev-expose-hw_control-status-via-sys.patch45
-rw-r--r--target/linux/generic/backport-6.6/806-v6.5-net-dsa-qca8k-add-support-for-additional-modes-for-n.patch64
-rw-r--r--target/linux/generic/backport-6.6/807-v6.5-01-net-dsa-mv88e6xxx-pass-directly-chip-structure-to-mv.patch64
-rw-r--r--target/linux/generic/backport-6.6/807-v6.5-02-net-dsa-mv88e6xxx-use-mv88e6xxx_phy_is_internal-in-m.patch31
-rw-r--r--target/linux/generic/backport-6.6/807-v6.5-03-net-dsa-mv88e6xxx-add-field-to-specify-internal-phys.patch69
-rw-r--r--target/linux/generic/backport-6.6/807-v6.5-04-net-dsa-mv88e6xxx-fix-88E6393X-family-internal-phys-.patch52
-rw-r--r--target/linux/generic/backport-6.6/807-v6.5-05-net-dsa-mv88e6xxx-pass-mv88e6xxx_chip-structure-to-p.patch110
-rw-r--r--target/linux/generic/backport-6.6/807-v6.5-06-net-dsa-mv88e6xxx-enable-support-for-88E6361-switch.patch153
-rw-r--r--target/linux/generic/backport-6.6/808-v6.2-0001-nvmem-stm32-move-STM32MP15_BSEC_NUM_LOWER-in-config.patch82
-rw-r--r--target/linux/generic/backport-6.6/808-v6.2-0002-nvmem-stm32-add-warning-when-upper-OTPs-are-updated.patch34
-rw-r--r--target/linux/generic/backport-6.6/808-v6.2-0003-nvmem-stm32-add-nvmem-type-attribute.patch26
-rw-r--r--target/linux/generic/backport-6.6/808-v6.2-0004-nvmem-stm32-fix-spelling-typo-in-comment.patch27
-rw-r--r--target/linux/generic/backport-6.6/808-v6.2-0005-nvmem-Kconfig-Fix-spelling-mistake-controlls-control.patch27
-rw-r--r--target/linux/generic/backport-6.6/808-v6.2-0006-nvmem-u-boot-env-add-Broadcom-format-support.patch67
-rw-r--r--target/linux/generic/backport-6.6/809-v6.3-0001-nvmem-core-remove-spurious-white-space.patch26
-rw-r--r--target/linux/generic/backport-6.6/809-v6.3-0002-nvmem-core-add-an-index-parameter-to-the-cell.patch180
-rw-r--r--target/linux/generic/backport-6.6/809-v6.3-0003-nvmem-core-move-struct-nvmem_cell_info-to-nvmem-prov.patch78
-rw-r--r--target/linux/generic/backport-6.6/809-v6.3-0004-nvmem-core-drop-the-removal-of-the-cells-in-nvmem_ad.patch65
-rw-r--r--target/linux/generic/backport-6.6/809-v6.3-0005-nvmem-core-add-nvmem_add_one_cell.patch122
-rw-r--r--target/linux/generic/backport-6.6/809-v6.3-0006-nvmem-core-use-nvmem_add_one_cell-in-nvmem_add_cells.patch93
-rw-r--r--target/linux/generic/backport-6.6/809-v6.3-0007-nvmem-stm32-add-OP-TEE-support-for-STM32MP13x.patch562
-rw-r--r--target/linux/generic/backport-6.6/809-v6.3-0008-nvmem-stm32-detect-bsec-pta-presence-for-STM32MP15x.patch85
-rw-r--r--target/linux/generic/backport-6.6/809-v6.3-0009-nvmem-rave-sp-eeprm-fix-kernel-doc-bad-line-warning.patch32
-rw-r--r--target/linux/generic/backport-6.6/809-v6.3-0010-nvmem-qcom-spmi-sdam-register-at-device-init-time.patch43
-rw-r--r--target/linux/generic/backport-6.6/809-v6.3-0011-nvmem-stm32-fix-OPTEE-dependency.patch46
-rw-r--r--target/linux/generic/backport-6.6/810-v6.3-i915-Move-list_count-to-list.h-as-list_count_nodes-f.patch75
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0001-nvmem-xilinx-zynqmp-make-modular.patch35
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0002-nvmem-core-introduce-NVMEM-layouts.patch387
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0003-nvmem-core-handle-the-absence-of-expected-layouts.patch61
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0004-nvmem-core-request-layout-modules-loading.patch52
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0005-nvmem-core-add-per-cell-post-processing.patch86
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0006-nvmem-core-allow-to-modify-a-cell-before-adding-it.patch59
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0007-nvmem-imx-ocotp-replace-global-post-processing-with-.patch81
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0008-nvmem-cell-drop-global-cell_post_process.patch68
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0009-nvmem-core-provide-own-priv-pointer-in-post-process-.patch76
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0010-nvmem-layouts-sl28vpd-Add-new-layout-driver.patch215
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0011-nvmem-layouts-onie-tlv-Add-new-layout-driver.patch306
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0012-nvmem-stm32-romem-mark-OF-related-data-as-maybe-unus.patch32
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0013-nvmem-mtk-efuse-Support-postprocessing-for-GPU-speed.patch120
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0014-nvmem-bcm-ocotp-Use-devm_platform_ioremap_resource.patch39
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0015-nvmem-nintendo-otp-Use-devm_platform_ioremap_resourc.patch39
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0016-nvmem-vf610-ocotp-Use-devm_platform_get_and_ioremap_.patch32
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0017-nvmem-core-support-specifying-both-cell-raw-data-pos.patch115
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0018-nvmem-u-boot-env-post-process-ethaddr-env-variable.patch81
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0019-nvmem-Add-macro-to-register-nvmem-layout-drivers.patch42
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0020-nvmem-layouts-sl28vpd-Use-module_nvmem_layout_driver.patch39
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0021-nvmem-layouts-onie-tlv-Use-module_nvmem_layout_drive.patch39
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0022-nvmem-layouts-onie-tlv-Drop-wrong-module-alias.patch24
-rw-r--r--target/linux/generic/backport-6.6/811-v6.4-0023-nvmem-layouts-sl28vpd-set-varaiable-sl28vpd_layout-s.patch31
-rw-r--r--target/linux/generic/backport-6.6/812-v6.2-firmware-nvram-bcm47xx-support-init-from-IO-memory.patch100
-rw-r--r--target/linux/generic/backport-6.6/813-v6.5-0001-nvmem-imx-ocotp-set-varaiable-imx_ocotp_layout-stora.patch31
-rw-r--r--target/linux/generic/backport-6.6/813-v6.5-0002-nvmem-imx-ocotp-Reverse-MAC-addresses-on-all-i.MX-de.patch71
-rw-r--r--target/linux/generic/backport-6.6/813-v6.5-0003-nvmem-brcm_nvram-add-.read_post_process-for-MACs.patch81
-rw-r--r--target/linux/generic/backport-6.6/813-v6.5-0004-nvmem-rockchip-otp-Add-clks-and-reg_read-to-rockchip.patch166
-rw-r--r--target/linux/generic/backport-6.6/813-v6.5-0005-nvmem-rockchip-otp-Generalize-rockchip_otp_wait_stat.patch62
-rw-r--r--target/linux/generic/backport-6.6/813-v6.5-0006-nvmem-rockchip-otp-Use-devm_reset_control_array_get_.patch31
-rw-r--r--target/linux/generic/backport-6.6/813-v6.5-0007-nvmem-rockchip-otp-Improve-probe-error-handling.patch71
-rw-r--r--target/linux/generic/backport-6.6/813-v6.5-0008-nvmem-rockchip-otp-Add-support-for-RK3588.patch129
-rw-r--r--target/linux/generic/backport-6.6/813-v6.5-0009-nvmem-zynqmp-Switch-xilinx.com-emails-to-amd.com.patch26
-rw-r--r--target/linux/generic/backport-6.6/813-v6.5-0010-nvmem-imx-support-i.MX93-OCOTP.patch230
-rw-r--r--target/linux/generic/backport-6.6/813-v6.5-0011-nvmem-core-add-support-for-fixed-cells-layout.patch96
-rw-r--r--target/linux/generic/backport-6.6/814-v6.6-0001-nvmem-sunxi_sid-Convert-to-devm_platform_ioremap_res.patch36
-rw-r--r--target/linux/generic/backport-6.6/814-v6.6-0002-nvmem-brcm_nvram-Use-devm_platform_get_and_ioremap_r.patch30
-rw-r--r--target/linux/generic/backport-6.6/814-v6.6-0003-nvmem-lpc18xx_otp-Convert-to-devm_platform_ioremap_r.patch34
-rw-r--r--target/linux/generic/backport-6.6/814-v6.6-0004-nvmem-meson-mx-efuse-Convert-to-devm_platform_iorema.patch36
-rw-r--r--target/linux/generic/backport-6.6/814-v6.6-0005-nvmem-rockchip-efuse-Use-devm_platform_get_and_iorem.patch31
-rw-r--r--target/linux/generic/backport-6.6/814-v6.6-0006-nvmem-stm32-romem-Use-devm_platform_get_and_ioremap_.patch30
-rw-r--r--target/linux/generic/backport-6.6/814-v6.6-0007-nvmem-qfprom-do-some-cleanup.patch59
-rw-r--r--target/linux/generic/backport-6.6/814-v6.6-0008-nvmem-uniphier-Use-devm_platform_get_and_ioremap_res.patch29
-rw-r--r--target/linux/generic/backport-6.6/814-v6.6-0009-nvmem-add-new-NXP-QorIQ-eFuse-driver.patch133
-rw-r--r--target/linux/generic/backport-6.6/814-v6.6-0011-nvmem-Kconfig-Fix-typo-drive-driver.patch37
-rw-r--r--target/linux/generic/backport-6.6/814-v6.6-0012-nvmem-sec-qfprom-Add-Qualcomm-secure-QFPROM-support.patch152
-rw-r--r--target/linux/generic/backport-6.6/814-v6.6-0013-nvmem-u-boot-env-Replace-zero-length-array-with-DECL.patch30
-rw-r--r--target/linux/generic/backport-6.6/814-v6.6-0014-nvmem-core-Create-all-cells-before-adding-the-nvmem-.patch40
-rw-r--r--target/linux/generic/backport-6.6/814-v6.6-0015-nvmem-core-Return-NULL-when-no-nvmem-layout-is-found.patch35
-rw-r--r--target/linux/generic/backport-6.6/814-v6.6-0016-nvmem-core-Do-not-open-code-existing-functions.patch29
-rw-r--r--target/linux/generic/backport-6.6/814-v6.6-0017-nvmem-core-Notify-when-a-new-layout-is-registered.patch44
-rw-r--r--target/linux/generic/backport-6.6/815-v6.6-1-leds-turris-omnia-Use-sysfs_emit-instead-of-sprintf.patch29
-rw-r--r--target/linux/generic/backport-6.6/815-v6.7-2-leds-turris-omnia-Make-set_brightness-more-efficient.patch207
-rw-r--r--target/linux/generic/backport-6.6/815-v6.7-3-leds-turris-omnia-Support-HW-controlled-mode-via-pri.patch202
-rw-r--r--target/linux/generic/backport-6.6/815-v6.7-4-leds-turris-omnia-Add-support-for-enabling-disabling.patch244
-rw-r--r--target/linux/generic/backport-6.6/815-v6.7-5-leds-turris-omnia-Fix-brightness-setting-and-trigger.patch167
-rw-r--r--target/linux/generic/backport-6.6/816-v6.7-0001-nvmem-qfprom-Mark-core-clk-as-optional.patch37
-rw-r--r--target/linux/generic/backport-6.6/816-v6.7-0002-nvmem-add-explicit-config-option-to-read-old-syntax-.patch330
-rw-r--r--target/linux/generic/backport-6.6/816-v6.7-0003-nvmem-Use-device_get_match_data.patch77
-rw-r--r--target/linux/generic/backport-6.6/816-v6.7-0004-Revert-nvmem-add-new-config-option.patch77
-rw-r--r--target/linux/generic/backport-6.6/816-v6.7-0005-nvmem-Do-not-expect-fixed-layouts-to-grab-a-layout-d.patch45
-rw-r--r--target/linux/generic/backport-6.6/816-v6.7-0006-nvmem-brcm_nvram-store-a-copy-of-NVRAM-content.patch261
-rw-r--r--target/linux/generic/backport-6.6/818-v6.8-of-device-Export-of_device_make_bus_id.patch140
-rw-r--r--target/linux/generic/backport-6.6/819-v6.8-0001-nvmem-Move-of_nvmem_layout_get_container-in-another-.patch95
-rw-r--r--target/linux/generic/backport-6.6/819-v6.8-0002-nvmem-Create-a-header-for-internal-sharing.patch91
-rw-r--r--target/linux/generic/backport-6.6/819-v6.8-0003-nvmem-Simplify-the-add_cells-hook.patch79
-rw-r--r--target/linux/generic/backport-6.6/819-v6.8-0004-nvmem-Move-and-rename-fixup_cell_info.patch169
-rw-r--r--target/linux/generic/backport-6.6/819-v6.8-0005-nvmem-core-Rework-layouts-to-become-regular-devices.patch763
-rw-r--r--target/linux/generic/backport-6.6/819-v6.8-0006-nvmem-core-Expose-cells-through-sysfs.patch240
-rw-r--r--target/linux/generic/backport-6.6/819-v6.8-0007-nvmem-stm32-add-support-for-STM32MP25-BSEC-to-contro.patch65
-rw-r--r--target/linux/generic/backport-6.6/819-v6.8-0008-nvmem-layouts-refactor-.add_cells-callback-arguments.patch94
-rw-r--r--target/linux/generic/backport-6.6/819-v6.8-0009-nvmem-drop-nvmem_layout_get_match_data.patch72
-rw-r--r--target/linux/generic/backport-6.6/819-v6.8-0010-nvmem-core-add-nvmem_dev_size-helper.patch53
-rw-r--r--target/linux/generic/backport-6.6/819-v6.8-0011-nvmem-u-boot-env-use-nvmem_add_one_cell-nvmem-subsys.patch126
-rw-r--r--target/linux/generic/backport-6.6/819-v6.8-0012-nvmem-u-boot-env-use-nvmem-device-helpers.patch81
-rw-r--r--target/linux/generic/backport-6.6/819-v6.8-0013-nvmem-u-boot-env-improve-coding-style.patch62
-rw-r--r--target/linux/generic/backport-6.6/820-v6.4-net-phy-fix-circular-LEDS_CLASS-dependencies.patch65
-rw-r--r--target/linux/generic/backport-6.6/821-v6.4-net-phy-Fix-reading-LED-reg-property.patch43
-rw-r--r--target/linux/generic/backport-6.6/822-v6.4-net-phy-Manual-remove-LEDs-to-ensure-correct-orderin.patch67
-rw-r--r--target/linux/generic/backport-6.6/824-v6.5-leds-trigger-netdev-Remove-NULL-check-before-dev_-pu.patch44
-rw-r--r--target/linux/generic/backport-6.6/825-v6.5-leds-trigger-netdev-uninitialized-variable-in-netdev.patch31
-rw-r--r--target/linux/generic/backport-6.6/826-v6.6-01-led-trig-netdev-Fix-requesting-offload-device.patch57
-rw-r--r--target/linux/generic/backport-6.6/826-v6.6-02-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch149
-rw-r--r--target/linux/generic/backport-6.6/826-v6.6-03-net-phy-marvell-Add-support-for-offloading-LED-blink.patch344
-rw-r--r--target/linux/generic/backport-6.6/826-v6.6-04-leds-trig-netdev-Disable-offload-on-deactivation-of-.patch31
-rw-r--r--target/linux/generic/backport-6.6/827-v6.3-0001-of-base-add-of_parse_phandle_with_optional_args.patch58
-rw-r--r--target/linux/generic/backport-6.6/827-v6.3-0002-of-property-make-.-cells-optional-for-simple-props.patch34
-rw-r--r--target/linux/generic/backport-6.6/827-v6.3-0003-of-property-add-nvmem-cell-cells-property.patch30
-rw-r--r--target/linux/generic/backport-6.6/827-v6.3-0004-of-device-Ignore-modalias-of-reused-nodes.patch37
-rw-r--r--target/linux/generic/backport-6.6/827-v6.3-0005-of-device-Do-not-ignore-error-code-in-of_device_ueve.patch29
-rw-r--r--target/linux/generic/backport-6.6/828-v6.4-0002-of-Update-of_device_get_modalias.patch103
-rw-r--r--target/linux/generic/backport-6.6/828-v6.4-0003-of-Rename-of_modalias_node.patch173
-rw-r--r--target/linux/generic/backport-6.6/828-v6.4-0004-of-Move-of_modalias-to-module.c.patch160
-rw-r--r--target/linux/generic/backport-6.6/828-v6.4-0005-of-Move-the-request-module-helper-logic-to-module.c.patch131
-rw-r--r--target/linux/generic/backport-6.6/830-00-v6.2-dt-bindings-arm-qcom-document-qcom-msm-id-and-qcom-b.patch207
-rw-r--r--target/linux/generic/backport-6.6/830-01-v6.5-soc-qcom-socinfo-move-SMEM-item-struct-and-defines-t.patch171
-rw-r--r--target/linux/generic/backport-6.6/830-02-v6.5-soc-qcom-smem-Switch-to-EXPORT_SYMBOL_GPL.patch55
-rw-r--r--target/linux/generic/backport-6.6/830-03-v6.5-soc-qcom-smem-introduce-qcom_smem_get_soc_id.patch70
-rw-r--r--target/linux/generic/backport-6.6/830-04-v6.5-cpufreq-qcom-nvmem-use-SoC-ID-s-from-bindings.patch51
-rw-r--r--target/linux/generic/backport-6.6/830-05-v6.5-cpufreq-qcom-nvmem-use-helper-to-get-SMEM-SoC-ID.patch109
-rw-r--r--target/linux/generic/backport-6.6/831-v6.7-rtc-rtc7301-Support-byte-addressed-IO.patch93
-rw-r--r--target/linux/generic/backport-6.6/832-v6.7-net-phy-amd-Support-the-Altima-AMI101L.patch82
-rw-r--r--target/linux/generic/backport-6.6/833-v6.8-leds-core-Add-more-colors-from-DT-bindings-to-led_co.patch.patch29
-rw-r--r--target/linux/generic/backport-6.6/834-v6.8-leds-trigger-netdev-Extend-speeds-up-to-10G.patch111
-rw-r--r--target/linux/generic/backport-6.6/835-v6.9-net-phy-add-support-for-PHY-LEDs-polarity-modes.patch98
-rw-r--r--target/linux/generic/backport-6.6/836-v6.7-leds-trigger-netdev-fix-RTNL-handling-to-prevent-pot.patch170
-rw-r--r--target/linux/generic/backport-6.6/837-v6.4-net-phy-hide-the-PHYLIB_LEDS-knob.patch43
-rw-r--r--target/linux/generic/backport-6.6/838-v6.9-leds-trigger-netdev-Fix-kernel-panic-on-interface-re.patch55
-rw-r--r--target/linux/generic/backport-6.6/890-v6.2-mtd-spinand-winbond-fix-flash-detection.patch40
-rw-r--r--target/linux/generic/backport-6.6/891-v6.2-mtd-spinand-winbond-add-W25N02KV.patch106
-rw-r--r--target/linux/generic/backport-6.6/892-v6.5-mtd-spinand-winbond-Fix-ecc_get_status.patch49
-rw-r--r--target/linux/generic/backport-6.6/894-v6.8-net-ethtool-implement-ethtool_puts.patch139
442 files changed, 73420 insertions, 0 deletions
diff --git a/target/linux/generic/backport-6.6/020-v6.3-01-UPSTREAM-mm-multi-gen-LRU-rename-lru_gen_struct-to-l.patch b/target/linux/generic/backport-6.6/020-v6.3-01-UPSTREAM-mm-multi-gen-LRU-rename-lru_gen_struct-to-l.patch
new file mode 100644
index 0000000000..fe32acc985
--- /dev/null
+++ b/target/linux/generic/backport-6.6/020-v6.3-01-UPSTREAM-mm-multi-gen-LRU-rename-lru_gen_struct-to-l.patch
@@ -0,0 +1,352 @@
+From 8c20e2eb5f2a0175b774134685e4d7bd93e85ff8 Mon Sep 17 00:00:00 2001
+From: Yu Zhao <yuzhao@google.com>
+Date: Wed, 21 Dec 2022 21:18:59 -0700
+Subject: [PATCH 01/19] UPSTREAM: mm: multi-gen LRU: rename lru_gen_struct to
+ lru_gen_folio
+
+Patch series "mm: multi-gen LRU: memcg LRU", v3.
+
+Overview
+========
+
+An memcg LRU is a per-node LRU of memcgs. It is also an LRU of LRUs,
+since each node and memcg combination has an LRU of folios (see
+mem_cgroup_lruvec()).
+
+Its goal is to improve the scalability of global reclaim, which is
+critical to system-wide memory overcommit in data centers. Note that
+memcg reclaim is currently out of scope.
+
+Its memory bloat is a pointer to each lruvec and negligible to each
+pglist_data. In terms of traversing memcgs during global reclaim, it
+improves the best-case complexity from O(n) to O(1) and does not affect
+the worst-case complexity O(n). Therefore, on average, it has a sublinear
+complexity in contrast to the current linear complexity.
+
+The basic structure of an memcg LRU can be understood by an analogy to
+the active/inactive LRU (of folios):
+1. It has the young and the old (generations), i.e., the counterparts
+ to the active and the inactive;
+2. The increment of max_seq triggers promotion, i.e., the counterpart
+ to activation;
+3. Other events trigger similar operations, e.g., offlining an memcg
+ triggers demotion, i.e., the counterpart to deactivation.
+
+In terms of global reclaim, it has two distinct features:
+1. Sharding, which allows each thread to start at a random memcg (in
+ the old generation) and improves parallelism;
+2. Eventual fairness, which allows direct reclaim to bail out at will
+ and reduces latency without affecting fairness over some time.
+
+The commit message in patch 6 details the workflow:
+https://lore.kernel.org/r/20221222041905.2431096-7-yuzhao@google.com/
+
+The following is a simple test to quickly verify its effectiveness.
+
+ Test design:
+ 1. Create multiple memcgs.
+ 2. Each memcg contains a job (fio).
+ 3. All jobs access the same amount of memory randomly.
+ 4. The system does not experience global memory pressure.
+ 5. Periodically write to the root memory.reclaim.
+
+ Desired outcome:
+ 1. All memcgs have similar pgsteal counts, i.e., stddev(pgsteal)
+ over mean(pgsteal) is close to 0%.
+ 2. The total pgsteal is close to the total requested through
+ memory.reclaim, i.e., sum(pgsteal) over sum(requested) is close
+ to 100%.
+
+ Actual outcome [1]:
+ MGLRU off MGLRU on
+ stddev(pgsteal) / mean(pgsteal) 75% 20%
+ sum(pgsteal) / sum(requested) 425% 95%
+
+ ####################################################################
+ MEMCGS=128
+
+ for ((memcg = 0; memcg < $MEMCGS; memcg++)); do
+ mkdir /sys/fs/cgroup/memcg$memcg
+ done
+
+ start() {
+ echo $BASHPID > /sys/fs/cgroup/memcg$memcg/cgroup.procs
+
+ fio -name=memcg$memcg --numjobs=1 --ioengine=mmap \
+ --filename=/dev/zero --size=1920M --rw=randrw \
+ --rate=64m,64m --random_distribution=random \
+ --fadvise_hint=0 --time_based --runtime=10h \
+ --group_reporting --minimal
+ }
+
+ for ((memcg = 0; memcg < $MEMCGS; memcg++)); do
+ start &
+ done
+
+ sleep 600
+
+ for ((i = 0; i < 600; i++)); do
+ echo 256m >/sys/fs/cgroup/memory.reclaim
+ sleep 6
+ done
+
+ for ((memcg = 0; memcg < $MEMCGS; memcg++)); do
+ grep "pgsteal " /sys/fs/cgroup/memcg$memcg/memory.stat
+ done
+ ####################################################################
+
+[1]: This was obtained from running the above script (touches less
+ than 256GB memory) on an EPYC 7B13 with 512GB DRAM for over an
+ hour.
+
+This patch (of 8):
+
+The new name lru_gen_folio will be more distinct from the coming
+lru_gen_memcg.
+
+Link: https://lkml.kernel.org/r/20221222041905.2431096-1-yuzhao@google.com
+Link: https://lkml.kernel.org/r/20221222041905.2431096-2-yuzhao@google.com
+Signed-off-by: Yu Zhao <yuzhao@google.com>
+Cc: Johannes Weiner <hannes@cmpxchg.org>
+Cc: Jonathan Corbet <corbet@lwn.net>
+Cc: Michael Larabel <Michael@MichaelLarabel.com>
+Cc: Michal Hocko <mhocko@kernel.org>
+Cc: Mike Rapoport <rppt@kernel.org>
+Cc: Roman Gushchin <roman.gushchin@linux.dev>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Bug: 274865848
+(cherry picked from commit 391655fe08d1f942359a11148aa9aaf3f99d6d6f)
+Change-Id: I7df67e0e2435ba28f10eaa57d28d98b61a9210a6
+Signed-off-by: T.J. Mercier <tjmercier@google.com>
+---
+ include/linux/mm_inline.h | 4 ++--
+ include/linux/mmzone.h | 6 +++---
+ mm/vmscan.c | 34 +++++++++++++++++-----------------
+ mm/workingset.c | 4 ++--
+ 4 files changed, 24 insertions(+), 24 deletions(-)
+
+--- a/include/linux/mm_inline.h
++++ b/include/linux/mm_inline.h
+@@ -178,7 +178,7 @@ static inline void lru_gen_update_size(s
+ int zone = folio_zonenum(folio);
+ int delta = folio_nr_pages(folio);
+ enum lru_list lru = type * LRU_INACTIVE_FILE;
+- struct lru_gen_struct *lrugen = &lruvec->lrugen;
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+
+ VM_WARN_ON_ONCE(old_gen != -1 && old_gen >= MAX_NR_GENS);
+ VM_WARN_ON_ONCE(new_gen != -1 && new_gen >= MAX_NR_GENS);
+@@ -224,7 +224,7 @@ static inline bool lru_gen_add_folio(str
+ int gen = folio_lru_gen(folio);
+ int type = folio_is_file_lru(folio);
+ int zone = folio_zonenum(folio);
+- struct lru_gen_struct *lrugen = &lruvec->lrugen;
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+
+ VM_WARN_ON_ONCE_FOLIO(gen != -1, folio);
+
+--- a/include/linux/mmzone.h
++++ b/include/linux/mmzone.h
+@@ -404,7 +404,7 @@ enum {
+ * The number of pages in each generation is eventually consistent and therefore
+ * can be transiently negative when reset_batch_size() is pending.
+ */
+-struct lru_gen_struct {
++struct lru_gen_folio {
+ /* the aging increments the youngest generation number */
+ unsigned long max_seq;
+ /* the eviction increments the oldest generation numbers */
+@@ -461,7 +461,7 @@ struct lru_gen_mm_state {
+ struct lru_gen_mm_walk {
+ /* the lruvec under reclaim */
+ struct lruvec *lruvec;
+- /* unstable max_seq from lru_gen_struct */
++ /* unstable max_seq from lru_gen_folio */
+ unsigned long max_seq;
+ /* the next address within an mm to scan */
+ unsigned long next_addr;
+@@ -524,7 +524,7 @@ struct lruvec {
+ unsigned long flags;
+ #ifdef CONFIG_LRU_GEN
+ /* evictable pages divided into generations */
+- struct lru_gen_struct lrugen;
++ struct lru_gen_folio lrugen;
+ /* to concurrently iterate lru_gen_mm_list */
+ struct lru_gen_mm_state mm_state;
+ #endif
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -3190,7 +3190,7 @@ static int get_nr_gens(struct lruvec *lr
+
+ static bool __maybe_unused seq_is_valid(struct lruvec *lruvec)
+ {
+- /* see the comment on lru_gen_struct */
++ /* see the comment on lru_gen_folio */
+ return get_nr_gens(lruvec, LRU_GEN_FILE) >= MIN_NR_GENS &&
+ get_nr_gens(lruvec, LRU_GEN_FILE) <= get_nr_gens(lruvec, LRU_GEN_ANON) &&
+ get_nr_gens(lruvec, LRU_GEN_ANON) <= MAX_NR_GENS;
+@@ -3596,7 +3596,7 @@ struct ctrl_pos {
+ static void read_ctrl_pos(struct lruvec *lruvec, int type, int tier, int gain,
+ struct ctrl_pos *pos)
+ {
+- struct lru_gen_struct *lrugen = &lruvec->lrugen;
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ int hist = lru_hist_from_seq(lrugen->min_seq[type]);
+
+ pos->refaulted = lrugen->avg_refaulted[type][tier] +
+@@ -3611,7 +3611,7 @@ static void read_ctrl_pos(struct lruvec
+ static void reset_ctrl_pos(struct lruvec *lruvec, int type, bool carryover)
+ {
+ int hist, tier;
+- struct lru_gen_struct *lrugen = &lruvec->lrugen;
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ bool clear = carryover ? NR_HIST_GENS == 1 : NR_HIST_GENS > 1;
+ unsigned long seq = carryover ? lrugen->min_seq[type] : lrugen->max_seq + 1;
+
+@@ -3688,7 +3688,7 @@ static int folio_update_gen(struct folio
+ static int folio_inc_gen(struct lruvec *lruvec, struct folio *folio, bool reclaiming)
+ {
+ int type = folio_is_file_lru(folio);
+- struct lru_gen_struct *lrugen = &lruvec->lrugen;
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ int new_gen, old_gen = lru_gen_from_seq(lrugen->min_seq[type]);
+ unsigned long new_flags, old_flags = READ_ONCE(folio->flags);
+
+@@ -3733,7 +3733,7 @@ static void update_batch_size(struct lru
+ static void reset_batch_size(struct lruvec *lruvec, struct lru_gen_mm_walk *walk)
+ {
+ int gen, type, zone;
+- struct lru_gen_struct *lrugen = &lruvec->lrugen;
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+
+ walk->batched = 0;
+
+@@ -4250,7 +4250,7 @@ static bool inc_min_seq(struct lruvec *l
+ {
+ int zone;
+ int remaining = MAX_LRU_BATCH;
+- struct lru_gen_struct *lrugen = &lruvec->lrugen;
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ int new_gen, old_gen = lru_gen_from_seq(lrugen->min_seq[type]);
+
+ if (type == LRU_GEN_ANON && !can_swap)
+@@ -4286,7 +4286,7 @@ static bool try_to_inc_min_seq(struct lr
+ {
+ int gen, type, zone;
+ bool success = false;
+- struct lru_gen_struct *lrugen = &lruvec->lrugen;
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ DEFINE_MIN_SEQ(lruvec);
+
+ VM_WARN_ON_ONCE(!seq_is_valid(lruvec));
+@@ -4307,7 +4307,7 @@ next:
+ ;
+ }
+
+- /* see the comment on lru_gen_struct */
++ /* see the comment on lru_gen_folio */
+ if (can_swap) {
+ min_seq[LRU_GEN_ANON] = min(min_seq[LRU_GEN_ANON], min_seq[LRU_GEN_FILE]);
+ min_seq[LRU_GEN_FILE] = max(min_seq[LRU_GEN_ANON], lrugen->min_seq[LRU_GEN_FILE]);
+@@ -4329,7 +4329,7 @@ static void inc_max_seq(struct lruvec *l
+ {
+ int prev, next;
+ int type, zone;
+- struct lru_gen_struct *lrugen = &lruvec->lrugen;
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+
+ restart:
+ spin_lock_irq(&lruvec->lru_lock);
+@@ -4389,7 +4389,7 @@ static bool try_to_inc_max_seq(struct lr
+ bool success;
+ struct lru_gen_mm_walk *walk;
+ struct mm_struct *mm = NULL;
+- struct lru_gen_struct *lrugen = &lruvec->lrugen;
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+
+ VM_WARN_ON_ONCE(max_seq > READ_ONCE(lrugen->max_seq));
+
+@@ -4454,7 +4454,7 @@ static bool should_run_aging(struct lruv
+ unsigned long old = 0;
+ unsigned long young = 0;
+ unsigned long total = 0;
+- struct lru_gen_struct *lrugen = &lruvec->lrugen;
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+
+ for (type = !can_swap; type < ANON_AND_FILE; type++) {
+@@ -4740,7 +4740,7 @@ static bool sort_folio(struct lruvec *lr
+ int delta = folio_nr_pages(folio);
+ int refs = folio_lru_refs(folio);
+ int tier = lru_tier_from_refs(refs);
+- struct lru_gen_struct *lrugen = &lruvec->lrugen;
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+
+ VM_WARN_ON_ONCE_FOLIO(gen >= MAX_NR_GENS, folio);
+
+@@ -4848,7 +4848,7 @@ static int scan_folios(struct lruvec *lr
+ int scanned = 0;
+ int isolated = 0;
+ int remaining = MAX_LRU_BATCH;
+- struct lru_gen_struct *lrugen = &lruvec->lrugen;
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+
+ VM_WARN_ON_ONCE(!list_empty(list));
+@@ -5249,7 +5249,7 @@ done:
+
+ static bool __maybe_unused state_is_valid(struct lruvec *lruvec)
+ {
+- struct lru_gen_struct *lrugen = &lruvec->lrugen;
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+
+ if (lrugen->enabled) {
+ enum lru_list lru;
+@@ -5531,7 +5531,7 @@ static void lru_gen_seq_show_full(struct
+ int i;
+ int type, tier;
+ int hist = lru_hist_from_seq(seq);
+- struct lru_gen_struct *lrugen = &lruvec->lrugen;
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+
+ for (tier = 0; tier < MAX_NR_TIERS; tier++) {
+ seq_printf(m, " %10d", tier);
+@@ -5581,7 +5581,7 @@ static int lru_gen_seq_show(struct seq_f
+ unsigned long seq;
+ bool full = !debugfs_real_fops(m->file)->write;
+ struct lruvec *lruvec = v;
+- struct lru_gen_struct *lrugen = &lruvec->lrugen;
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ int nid = lruvec_pgdat(lruvec)->node_id;
+ struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+ DEFINE_MAX_SEQ(lruvec);
+@@ -5835,7 +5835,7 @@ void lru_gen_init_lruvec(struct lruvec *
+ {
+ int i;
+ int gen, type, zone;
+- struct lru_gen_struct *lrugen = &lruvec->lrugen;
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+
+ lrugen->max_seq = MIN_NR_GENS + 1;
+ lrugen->enabled = lru_gen_enabled();
+--- a/mm/workingset.c
++++ b/mm/workingset.c
+@@ -223,7 +223,7 @@ static void *lru_gen_eviction(struct fol
+ unsigned long token;
+ unsigned long min_seq;
+ struct lruvec *lruvec;
+- struct lru_gen_struct *lrugen;
++ struct lru_gen_folio *lrugen;
+ int type = folio_is_file_lru(folio);
+ int delta = folio_nr_pages(folio);
+ int refs = folio_lru_refs(folio);
+@@ -252,7 +252,7 @@ static void lru_gen_refault(struct folio
+ unsigned long token;
+ unsigned long min_seq;
+ struct lruvec *lruvec;
+- struct lru_gen_struct *lrugen;
++ struct lru_gen_folio *lrugen;
+ struct mem_cgroup *memcg;
+ struct pglist_data *pgdat;
+ int type = folio_is_file_lru(folio);
diff --git a/target/linux/generic/backport-6.6/020-v6.3-03-UPSTREAM-mm-multi-gen-LRU-remove-eviction-fairness-s.patch b/target/linux/generic/backport-6.6/020-v6.3-03-UPSTREAM-mm-multi-gen-LRU-remove-eviction-fairness-s.patch
new file mode 100644
index 0000000000..e5ad78b61d
--- /dev/null
+++ b/target/linux/generic/backport-6.6/020-v6.3-03-UPSTREAM-mm-multi-gen-LRU-remove-eviction-fairness-s.patch
@@ -0,0 +1,192 @@
+From 14f9a7a15f3d1af351f30e0438fd747b7ac253b0 Mon Sep 17 00:00:00 2001
+From: Yu Zhao <yuzhao@google.com>
+Date: Wed, 21 Dec 2022 21:19:01 -0700
+Subject: [PATCH 03/19] UPSTREAM: mm: multi-gen LRU: remove eviction fairness
+ safeguard
+
+Recall that the eviction consumes the oldest generation: first it
+bucket-sorts folios whose gen counters were updated by the aging and
+reclaims the rest; then it increments lrugen->min_seq.
+
+The current eviction fairness safeguard for global reclaim has a
+dilemma: when there are multiple eligible memcgs, should it continue
+or stop upon meeting the reclaim goal? If it continues, it overshoots
+and increases direct reclaim latency; if it stops, it loses fairness
+between memcgs it has taken memory away from and those it has yet to.
+
+With memcg LRU, the eviction, while ensuring eventual fairness, will
+stop upon meeting its goal. Therefore the current eviction fairness
+safeguard for global reclaim will not be needed.
+
+Note that memcg LRU only applies to global reclaim. For memcg reclaim,
+the eviction will continue, even if it is overshooting. This becomes
+unconditional due to code simplification.
+
+Link: https://lkml.kernel.org/r/20221222041905.2431096-4-yuzhao@google.com
+Signed-off-by: Yu Zhao <yuzhao@google.com>
+Cc: Johannes Weiner <hannes@cmpxchg.org>
+Cc: Jonathan Corbet <corbet@lwn.net>
+Cc: Michael Larabel <Michael@MichaelLarabel.com>
+Cc: Michal Hocko <mhocko@kernel.org>
+Cc: Mike Rapoport <rppt@kernel.org>
+Cc: Roman Gushchin <roman.gushchin@linux.dev>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Bug: 274865848
+(cherry picked from commit a579086c99ed70cc4bfc104348dbe3dd8f2787e6)
+Change-Id: I08ac1b3c90e29cafd0566785aaa4bcdb5db7d22c
+Signed-off-by: T.J. Mercier <tjmercier@google.com>
+---
+ mm/vmscan.c | 81 +++++++++++++++--------------------------------------
+ 1 file changed, 23 insertions(+), 58 deletions(-)
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -448,6 +448,11 @@ static bool cgroup_reclaim(struct scan_c
+ return sc->target_mem_cgroup;
+ }
+
++static bool global_reclaim(struct scan_control *sc)
++{
++ return !sc->target_mem_cgroup || mem_cgroup_is_root(sc->target_mem_cgroup);
++}
++
+ /**
+ * writeback_throttling_sane - is the usual dirty throttling mechanism available?
+ * @sc: scan_control in question
+@@ -498,6 +503,11 @@ static bool cgroup_reclaim(struct scan_c
+ return false;
+ }
+
++static bool global_reclaim(struct scan_control *sc)
++{
++ return true;
++}
++
+ static bool writeback_throttling_sane(struct scan_control *sc)
+ {
+ return true;
+@@ -5005,8 +5015,7 @@ static int isolate_folios(struct lruvec
+ return scanned;
+ }
+
+-static int evict_folios(struct lruvec *lruvec, struct scan_control *sc, int swappiness,
+- bool *need_swapping)
++static int evict_folios(struct lruvec *lruvec, struct scan_control *sc, int swappiness)
+ {
+ int type;
+ int scanned;
+@@ -5095,9 +5104,6 @@ retry:
+ goto retry;
+ }
+
+- if (need_swapping && type == LRU_GEN_ANON)
+- *need_swapping = true;
+-
+ return scanned;
+ }
+
+@@ -5136,67 +5142,26 @@ done:
+ return min_seq[!can_swap] + MIN_NR_GENS <= max_seq ? nr_to_scan : 0;
+ }
+
+-static bool should_abort_scan(struct lruvec *lruvec, unsigned long seq,
+- struct scan_control *sc, bool need_swapping)
++static unsigned long get_nr_to_reclaim(struct scan_control *sc)
+ {
+- int i;
+- DEFINE_MAX_SEQ(lruvec);
+-
+- if (!current_is_kswapd()) {
+- /* age each memcg at most once to ensure fairness */
+- if (max_seq - seq > 1)
+- return true;
+-
+- /* over-swapping can increase allocation latency */
+- if (sc->nr_reclaimed >= sc->nr_to_reclaim && need_swapping)
+- return true;
+-
+- /* give this thread a chance to exit and free its memory */
+- if (fatal_signal_pending(current)) {
+- sc->nr_reclaimed += MIN_LRU_BATCH;
+- return true;
+- }
+-
+- if (cgroup_reclaim(sc))
+- return false;
+- } else if (sc->nr_reclaimed - sc->last_reclaimed < sc->nr_to_reclaim)
+- return false;
+-
+- /* keep scanning at low priorities to ensure fairness */
+- if (sc->priority > DEF_PRIORITY - 2)
+- return false;
+-
+- /*
+- * A minimum amount of work was done under global memory pressure. For
+- * kswapd, it may be overshooting. For direct reclaim, the allocation
+- * may succeed if all suitable zones are somewhat safe. In either case,
+- * it's better to stop now, and restart later if necessary.
+- */
+- for (i = 0; i <= sc->reclaim_idx; i++) {
+- unsigned long wmark;
+- struct zone *zone = lruvec_pgdat(lruvec)->node_zones + i;
+-
+- if (!managed_zone(zone))
+- continue;
+-
+- wmark = current_is_kswapd() ? high_wmark_pages(zone) : low_wmark_pages(zone);
+- if (wmark > zone_page_state(zone, NR_FREE_PAGES))
+- return false;
+- }
++ /* don't abort memcg reclaim to ensure fairness */
++ if (!global_reclaim(sc))
++ return -1;
+
+- sc->nr_reclaimed += MIN_LRU_BATCH;
++ /* discount the previous progress for kswapd */
++ if (current_is_kswapd())
++ return sc->nr_to_reclaim + sc->last_reclaimed;
+
+- return true;
++ return max(sc->nr_to_reclaim, compact_gap(sc->order));
+ }
+
+ static void lru_gen_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
+ {
+ struct blk_plug plug;
+ bool need_aging = false;
+- bool need_swapping = false;
+ unsigned long scanned = 0;
+ unsigned long reclaimed = sc->nr_reclaimed;
+- DEFINE_MAX_SEQ(lruvec);
++ unsigned long nr_to_reclaim = get_nr_to_reclaim(sc);
+
+ lru_add_drain();
+
+@@ -5220,7 +5185,7 @@ static void lru_gen_shrink_lruvec(struct
+ if (!nr_to_scan)
+ goto done;
+
+- delta = evict_folios(lruvec, sc, swappiness, &need_swapping);
++ delta = evict_folios(lruvec, sc, swappiness);
+ if (!delta)
+ goto done;
+
+@@ -5228,7 +5193,7 @@ static void lru_gen_shrink_lruvec(struct
+ if (scanned >= nr_to_scan)
+ break;
+
+- if (should_abort_scan(lruvec, max_seq, sc, need_swapping))
++ if (sc->nr_reclaimed >= nr_to_reclaim)
+ break;
+
+ cond_resched();
+@@ -5678,7 +5643,7 @@ static int run_eviction(struct lruvec *l
+ if (sc->nr_reclaimed >= nr_to_reclaim)
+ return 0;
+
+- if (!evict_folios(lruvec, sc, swappiness, NULL))
++ if (!evict_folios(lruvec, sc, swappiness))
+ return 0;
+
+ cond_resched();
diff --git a/target/linux/generic/backport-6.6/020-v6.3-04-BACKPORT-mm-multi-gen-LRU-remove-aging-fairness-safe.patch b/target/linux/generic/backport-6.6/020-v6.3-04-BACKPORT-mm-multi-gen-LRU-remove-aging-fairness-safe.patch
new file mode 100644
index 0000000000..cb349abcdb
--- /dev/null
+++ b/target/linux/generic/backport-6.6/020-v6.3-04-BACKPORT-mm-multi-gen-LRU-remove-aging-fairness-safe.patch
@@ -0,0 +1,294 @@
+From f3c93d2e37a3c56593d7ccf4f4bcf1b58426fdd8 Mon Sep 17 00:00:00 2001
+From: Yu Zhao <yuzhao@google.com>
+Date: Wed, 21 Dec 2022 21:19:02 -0700
+Subject: [PATCH 04/19] BACKPORT: mm: multi-gen LRU: remove aging fairness
+ safeguard
+
+Recall that the aging produces the youngest generation: first it scans
+for accessed folios and updates their gen counters; then it increments
+lrugen->max_seq.
+
+The current aging fairness safeguard for kswapd uses two passes to
+ensure the fairness to multiple eligible memcgs. On the first pass,
+which is shared with the eviction, it checks whether all eligible
+memcgs are low on cold folios. If so, it requires a second pass, on
+which it ages all those memcgs at the same time.
+
+With memcg LRU, the aging, while ensuring eventual fairness, will run
+when necessary. Therefore the current aging fairness safeguard for
+kswapd will not be needed.
+
+Note that memcg LRU only applies to global reclaim. For memcg reclaim,
+the aging can be unfair to different memcgs, i.e., their
+lrugen->max_seq can be incremented at different paces.
+
+Link: https://lkml.kernel.org/r/20221222041905.2431096-5-yuzhao@google.com
+Signed-off-by: Yu Zhao <yuzhao@google.com>
+Cc: Johannes Weiner <hannes@cmpxchg.org>
+Cc: Jonathan Corbet <corbet@lwn.net>
+Cc: Michael Larabel <Michael@MichaelLarabel.com>
+Cc: Michal Hocko <mhocko@kernel.org>
+Cc: Mike Rapoport <rppt@kernel.org>
+Cc: Roman Gushchin <roman.gushchin@linux.dev>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Bug: 274865848
+(cherry picked from commit 7348cc91821b0cb24dfb00e578047f68299a50ab)
+[TJ: Resolved conflicts with older function signatures for
+min_cgroup_below_min / min_cgroup_below_low]
+Change-Id: I6e36ecfbaaefbc0a56d9a9d5d7cbe404ed7f57a5
+Signed-off-by: T.J. Mercier <tjmercier@google.com>
+---
+ mm/vmscan.c | 126 ++++++++++++++++++++++++----------------------------
+ 1 file changed, 59 insertions(+), 67 deletions(-)
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -136,7 +136,6 @@ struct scan_control {
+
+ #ifdef CONFIG_LRU_GEN
+ /* help kswapd make better choices among multiple memcgs */
+- unsigned int memcgs_need_aging:1;
+ unsigned long last_reclaimed;
+ #endif
+
+@@ -4457,7 +4456,7 @@ done:
+ return true;
+ }
+
+-static bool should_run_aging(struct lruvec *lruvec, unsigned long max_seq, unsigned long *min_seq,
++static bool should_run_aging(struct lruvec *lruvec, unsigned long max_seq,
+ struct scan_control *sc, bool can_swap, unsigned long *nr_to_scan)
+ {
+ int gen, type, zone;
+@@ -4466,6 +4465,13 @@ static bool should_run_aging(struct lruv
+ unsigned long total = 0;
+ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ struct mem_cgroup *memcg = lruvec_memcg(lruvec);
++ DEFINE_MIN_SEQ(lruvec);
++
++ /* whether this lruvec is completely out of cold folios */
++ if (min_seq[!can_swap] + MIN_NR_GENS > max_seq) {
++ *nr_to_scan = 0;
++ return true;
++ }
+
+ for (type = !can_swap; type < ANON_AND_FILE; type++) {
+ unsigned long seq;
+@@ -4494,8 +4500,6 @@ static bool should_run_aging(struct lruv
+ * stalls when the number of generations reaches MIN_NR_GENS. Hence, the
+ * ideal number of generations is MIN_NR_GENS+1.
+ */
+- if (min_seq[!can_swap] + MIN_NR_GENS > max_seq)
+- return true;
+ if (min_seq[!can_swap] + MIN_NR_GENS < max_seq)
+ return false;
+
+@@ -4514,40 +4518,54 @@ static bool should_run_aging(struct lruv
+ return false;
+ }
+
+-static bool age_lruvec(struct lruvec *lruvec, struct scan_control *sc, unsigned long min_ttl)
++static bool lruvec_is_sizable(struct lruvec *lruvec, struct scan_control *sc)
+ {
+- bool need_aging;
+- unsigned long nr_to_scan;
+- int swappiness = get_swappiness(lruvec, sc);
++ int gen, type, zone;
++ unsigned long total = 0;
++ bool can_swap = get_swappiness(lruvec, sc);
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+ DEFINE_MAX_SEQ(lruvec);
+ DEFINE_MIN_SEQ(lruvec);
+
+- VM_WARN_ON_ONCE(sc->memcg_low_reclaim);
++ for (type = !can_swap; type < ANON_AND_FILE; type++) {
++ unsigned long seq;
+
+- mem_cgroup_calculate_protection(NULL, memcg);
++ for (seq = min_seq[type]; seq <= max_seq; seq++) {
++ gen = lru_gen_from_seq(seq);
+
+- if (mem_cgroup_below_min(memcg))
+- return false;
++ for (zone = 0; zone < MAX_NR_ZONES; zone++)
++ total += max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L);
++ }
++ }
+
+- need_aging = should_run_aging(lruvec, max_seq, min_seq, sc, swappiness, &nr_to_scan);
++ /* whether the size is big enough to be helpful */
++ return mem_cgroup_online(memcg) ? (total >> sc->priority) : total;
++}
+
+- if (min_ttl) {
+- int gen = lru_gen_from_seq(min_seq[LRU_GEN_FILE]);
+- unsigned long birth = READ_ONCE(lruvec->lrugen.timestamps[gen]);
++static bool lruvec_is_reclaimable(struct lruvec *lruvec, struct scan_control *sc,
++ unsigned long min_ttl)
++{
++ int gen;
++ unsigned long birth;
++ struct mem_cgroup *memcg = lruvec_memcg(lruvec);
++ DEFINE_MIN_SEQ(lruvec);
+
+- if (time_is_after_jiffies(birth + min_ttl))
+- return false;
++ VM_WARN_ON_ONCE(sc->memcg_low_reclaim);
+
+- /* the size is likely too small to be helpful */
+- if (!nr_to_scan && sc->priority != DEF_PRIORITY)
+- return false;
+- }
++ /* see the comment on lru_gen_folio */
++ gen = lru_gen_from_seq(min_seq[LRU_GEN_FILE]);
++ birth = READ_ONCE(lruvec->lrugen.timestamps[gen]);
+
+- if (need_aging)
+- try_to_inc_max_seq(lruvec, max_seq, sc, swappiness, false);
++ if (time_is_after_jiffies(birth + min_ttl))
++ return false;
+
+- return true;
++ if (!lruvec_is_sizable(lruvec, sc))
++ return false;
++
++ mem_cgroup_calculate_protection(NULL, memcg);
++
++ return !mem_cgroup_below_min(memcg);
+ }
+
+ /* to protect the working set of the last N jiffies */
+@@ -4556,46 +4574,32 @@ static unsigned long lru_gen_min_ttl __r
+ static void lru_gen_age_node(struct pglist_data *pgdat, struct scan_control *sc)
+ {
+ struct mem_cgroup *memcg;
+- bool success = false;
+ unsigned long min_ttl = READ_ONCE(lru_gen_min_ttl);
+
+ VM_WARN_ON_ONCE(!current_is_kswapd());
+
+ sc->last_reclaimed = sc->nr_reclaimed;
+
+- /*
+- * To reduce the chance of going into the aging path, which can be
+- * costly, optimistically skip it if the flag below was cleared in the
+- * eviction path. This improves the overall performance when multiple
+- * memcgs are available.
+- */
+- if (!sc->memcgs_need_aging) {
+- sc->memcgs_need_aging = true;
++ /* check the order to exclude compaction-induced reclaim */
++ if (!min_ttl || sc->order || sc->priority == DEF_PRIORITY)
+ return;
+- }
+-
+- set_mm_walk(pgdat);
+
+ memcg = mem_cgroup_iter(NULL, NULL, NULL);
+ do {
+ struct lruvec *lruvec = mem_cgroup_lruvec(memcg, pgdat);
+
+- if (age_lruvec(lruvec, sc, min_ttl))
+- success = true;
++ if (lruvec_is_reclaimable(lruvec, sc, min_ttl)) {
++ mem_cgroup_iter_break(NULL, memcg);
++ return;
++ }
+
+ cond_resched();
+ } while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)));
+
+- clear_mm_walk();
+-
+- /* check the order to exclude compaction-induced reclaim */
+- if (success || !min_ttl || sc->order)
+- return;
+-
+ /*
+ * The main goal is to OOM kill if every generation from all memcgs is
+ * younger than min_ttl. However, another possibility is all memcgs are
+- * either below min or empty.
++ * either too small or below min.
+ */
+ if (mutex_trylock(&oom_lock)) {
+ struct oom_control oc = {
+@@ -5113,33 +5117,27 @@ retry:
+ * reclaim.
+ */
+ static unsigned long get_nr_to_scan(struct lruvec *lruvec, struct scan_control *sc,
+- bool can_swap, bool *need_aging)
++ bool can_swap)
+ {
+ unsigned long nr_to_scan;
+ struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+ DEFINE_MAX_SEQ(lruvec);
+- DEFINE_MIN_SEQ(lruvec);
+
+ if (mem_cgroup_below_min(memcg) ||
+ (mem_cgroup_below_low(memcg) && !sc->memcg_low_reclaim))
+ return 0;
+
+- *need_aging = should_run_aging(lruvec, max_seq, min_seq, sc, can_swap, &nr_to_scan);
+- if (!*need_aging)
++ if (!should_run_aging(lruvec, max_seq, sc, can_swap, &nr_to_scan))
+ return nr_to_scan;
+
+ /* skip the aging path at the default priority */
+ if (sc->priority == DEF_PRIORITY)
+- goto done;
++ return nr_to_scan;
+
+- /* leave the work to lru_gen_age_node() */
+- if (current_is_kswapd())
+- return 0;
++ try_to_inc_max_seq(lruvec, max_seq, sc, can_swap, false);
+
+- if (try_to_inc_max_seq(lruvec, max_seq, sc, can_swap, false))
+- return nr_to_scan;
+-done:
+- return min_seq[!can_swap] + MIN_NR_GENS <= max_seq ? nr_to_scan : 0;
++ /* skip this lruvec as it's low on cold folios */
++ return 0;
+ }
+
+ static unsigned long get_nr_to_reclaim(struct scan_control *sc)
+@@ -5158,9 +5156,7 @@ static unsigned long get_nr_to_reclaim(s
+ static void lru_gen_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
+ {
+ struct blk_plug plug;
+- bool need_aging = false;
+ unsigned long scanned = 0;
+- unsigned long reclaimed = sc->nr_reclaimed;
+ unsigned long nr_to_reclaim = get_nr_to_reclaim(sc);
+
+ lru_add_drain();
+@@ -5181,13 +5177,13 @@ static void lru_gen_shrink_lruvec(struct
+ else
+ swappiness = 0;
+
+- nr_to_scan = get_nr_to_scan(lruvec, sc, swappiness, &need_aging);
++ nr_to_scan = get_nr_to_scan(lruvec, sc, swappiness);
+ if (!nr_to_scan)
+- goto done;
++ break;
+
+ delta = evict_folios(lruvec, sc, swappiness);
+ if (!delta)
+- goto done;
++ break;
+
+ scanned += delta;
+ if (scanned >= nr_to_scan)
+@@ -5199,10 +5195,6 @@ static void lru_gen_shrink_lruvec(struct
+ cond_resched();
+ }
+
+- /* see the comment in lru_gen_age_node() */
+- if (sc->nr_reclaimed - reclaimed >= MIN_LRU_BATCH && !need_aging)
+- sc->memcgs_need_aging = false;
+-done:
+ clear_mm_walk();
+
+ blk_finish_plug(&plug);
diff --git a/target/linux/generic/backport-6.6/020-v6.3-05-UPSTREAM-mm-multi-gen-LRU-shuffle-should_run_aging.patch b/target/linux/generic/backport-6.6/020-v6.3-05-UPSTREAM-mm-multi-gen-LRU-shuffle-should_run_aging.patch
new file mode 100644
index 0000000000..42caab7c37
--- /dev/null
+++ b/target/linux/generic/backport-6.6/020-v6.3-05-UPSTREAM-mm-multi-gen-LRU-shuffle-should_run_aging.patch
@@ -0,0 +1,166 @@
+From eca3858631e0cbad2ca6e40f788892749428e4cb Mon Sep 17 00:00:00 2001
+From: Yu Zhao <yuzhao@google.com>
+Date: Wed, 21 Dec 2022 21:19:03 -0700
+Subject: [PATCH 05/19] UPSTREAM: mm: multi-gen LRU: shuffle should_run_aging()
+
+Move should_run_aging() next to its only caller left.
+
+Link: https://lkml.kernel.org/r/20221222041905.2431096-6-yuzhao@google.com
+Cc: Johannes Weiner <hannes@cmpxchg.org>
+Cc: Jonathan Corbet <corbet@lwn.net>
+Cc: Michael Larabel <Michael@MichaelLarabel.com>
+Cc: Michal Hocko <mhocko@kernel.org>
+Cc: Mike Rapoport <rppt@kernel.org>
+Cc: Roman Gushchin <roman.gushchin@linux.dev>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Bug: 274865848
+(cherry picked from commit 77d4459a4a1a472b7309e475f962dda87d950abd)
+Signed-off-by: T.J. Mercier <tjmercier@google.com>
+Change-Id: I3b0383fe16b93a783b4d8c0b3a0b325160392576
+Signed-off-by: Yu Zhao <yuzhao@google.com>
+Signed-off-by: T.J. Mercier <tjmercier@google.com>
+---
+ mm/vmscan.c | 124 ++++++++++++++++++++++++++--------------------------
+ 1 file changed, 62 insertions(+), 62 deletions(-)
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -4456,68 +4456,6 @@ done:
+ return true;
+ }
+
+-static bool should_run_aging(struct lruvec *lruvec, unsigned long max_seq,
+- struct scan_control *sc, bool can_swap, unsigned long *nr_to_scan)
+-{
+- int gen, type, zone;
+- unsigned long old = 0;
+- unsigned long young = 0;
+- unsigned long total = 0;
+- struct lru_gen_folio *lrugen = &lruvec->lrugen;
+- struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+- DEFINE_MIN_SEQ(lruvec);
+-
+- /* whether this lruvec is completely out of cold folios */
+- if (min_seq[!can_swap] + MIN_NR_GENS > max_seq) {
+- *nr_to_scan = 0;
+- return true;
+- }
+-
+- for (type = !can_swap; type < ANON_AND_FILE; type++) {
+- unsigned long seq;
+-
+- for (seq = min_seq[type]; seq <= max_seq; seq++) {
+- unsigned long size = 0;
+-
+- gen = lru_gen_from_seq(seq);
+-
+- for (zone = 0; zone < MAX_NR_ZONES; zone++)
+- size += max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L);
+-
+- total += size;
+- if (seq == max_seq)
+- young += size;
+- else if (seq + MIN_NR_GENS == max_seq)
+- old += size;
+- }
+- }
+-
+- /* try to scrape all its memory if this memcg was deleted */
+- *nr_to_scan = mem_cgroup_online(memcg) ? (total >> sc->priority) : total;
+-
+- /*
+- * The aging tries to be lazy to reduce the overhead, while the eviction
+- * stalls when the number of generations reaches MIN_NR_GENS. Hence, the
+- * ideal number of generations is MIN_NR_GENS+1.
+- */
+- if (min_seq[!can_swap] + MIN_NR_GENS < max_seq)
+- return false;
+-
+- /*
+- * It's also ideal to spread pages out evenly, i.e., 1/(MIN_NR_GENS+1)
+- * of the total number of pages for each generation. A reasonable range
+- * for this average portion is [1/MIN_NR_GENS, 1/(MIN_NR_GENS+2)]. The
+- * aging cares about the upper bound of hot pages, while the eviction
+- * cares about the lower bound of cold pages.
+- */
+- if (young * MIN_NR_GENS > total)
+- return true;
+- if (old * (MIN_NR_GENS + 2) < total)
+- return true;
+-
+- return false;
+-}
+-
+ static bool lruvec_is_sizable(struct lruvec *lruvec, struct scan_control *sc)
+ {
+ int gen, type, zone;
+@@ -5111,6 +5049,68 @@ retry:
+ return scanned;
+ }
+
++static bool should_run_aging(struct lruvec *lruvec, unsigned long max_seq,
++ struct scan_control *sc, bool can_swap, unsigned long *nr_to_scan)
++{
++ int gen, type, zone;
++ unsigned long old = 0;
++ unsigned long young = 0;
++ unsigned long total = 0;
++ struct lru_gen_folio *lrugen = &lruvec->lrugen;
++ struct mem_cgroup *memcg = lruvec_memcg(lruvec);
++ DEFINE_MIN_SEQ(lruvec);
++
++ /* whether this lruvec is completely out of cold folios */
++ if (min_seq[!can_swap] + MIN_NR_GENS > max_seq) {
++ *nr_to_scan = 0;
++ return true;
++ }
++
++ for (type = !can_swap; type < ANON_AND_FILE; type++) {
++ unsigned long seq;
++
++ for (seq = min_seq[type]; seq <= max_seq; seq++) {
++ unsigned long size = 0;
++
++ gen = lru_gen_from_seq(seq);
++
++ for (zone = 0; zone < MAX_NR_ZONES; zone++)
++ size += max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L);
++
++ total += size;
++ if (seq == max_seq)
++ young += size;
++ else if (seq + MIN_NR_GENS == max_seq)
++ old += size;
++ }
++ }
++
++ /* try to scrape all its memory if this memcg was deleted */
++ *nr_to_scan = mem_cgroup_online(memcg) ? (total >> sc->priority) : total;
++
++ /*
++ * The aging tries to be lazy to reduce the overhead, while the eviction
++ * stalls when the number of generations reaches MIN_NR_GENS. Hence, the
++ * ideal number of generations is MIN_NR_GENS+1.
++ */
++ if (min_seq[!can_swap] + MIN_NR_GENS < max_seq)
++ return false;
++
++ /*
++ * It's also ideal to spread pages out evenly, i.e., 1/(MIN_NR_GENS+1)
++ * of the total number of pages for each generation. A reasonable range
++ * for this average portion is [1/MIN_NR_GENS, 1/(MIN_NR_GENS+2)]. The
++ * aging cares about the upper bound of hot pages, while the eviction
++ * cares about the lower bound of cold pages.
++ */
++ if (young * MIN_NR_GENS > total)
++ return true;
++ if (old * (MIN_NR_GENS + 2) < total)
++ return true;
++
++ return false;
++}
++
+ /*
+ * For future optimizations:
+ * 1. Defer try_to_inc_max_seq() to workqueues to reduce latency for memcg
diff --git a/target/linux/generic/backport-6.6/020-v6.3-06-BACKPORT-mm-multi-gen-LRU-per-node-lru_gen_folio-lis.patch b/target/linux/generic/backport-6.6/020-v6.3-06-BACKPORT-mm-multi-gen-LRU-per-node-lru_gen_folio-lis.patch
new file mode 100644
index 0000000000..99ec42fe48
--- /dev/null
+++ b/target/linux/generic/backport-6.6/020-v6.3-06-BACKPORT-mm-multi-gen-LRU-per-node-lru_gen_folio-lis.patch
@@ -0,0 +1,876 @@
+From 8ee8571e47aa75221e5fbd4c9c7802fc4244c346 Mon Sep 17 00:00:00 2001
+From: Yu Zhao <yuzhao@google.com>
+Date: Wed, 21 Dec 2022 21:19:04 -0700
+Subject: [PATCH 06/19] BACKPORT: mm: multi-gen LRU: per-node lru_gen_folio
+ lists
+
+For each node, memcgs are divided into two generations: the old and
+the young. For each generation, memcgs are randomly sharded into
+multiple bins to improve scalability. For each bin, an RCU hlist_nulls
+is virtually divided into three segments: the head, the tail and the
+default.
+
+An onlining memcg is added to the tail of a random bin in the old
+generation. The eviction starts at the head of a random bin in the old
+generation. The per-node memcg generation counter, whose reminder (mod
+2) indexes the old generation, is incremented when all its bins become
+empty.
+
+There are four operations:
+1. MEMCG_LRU_HEAD, which moves an memcg to the head of a random bin in
+ its current generation (old or young) and updates its "seg" to
+ "head";
+2. MEMCG_LRU_TAIL, which moves an memcg to the tail of a random bin in
+ its current generation (old or young) and updates its "seg" to
+ "tail";
+3. MEMCG_LRU_OLD, which moves an memcg to the head of a random bin in
+ the old generation, updates its "gen" to "old" and resets its "seg"
+ to "default";
+4. MEMCG_LRU_YOUNG, which moves an memcg to the tail of a random bin
+ in the young generation, updates its "gen" to "young" and resets
+ its "seg" to "default".
+
+The events that trigger the above operations are:
+1. Exceeding the soft limit, which triggers MEMCG_LRU_HEAD;
+2. The first attempt to reclaim an memcg below low, which triggers
+ MEMCG_LRU_TAIL;
+3. The first attempt to reclaim an memcg below reclaimable size
+ threshold, which triggers MEMCG_LRU_TAIL;
+4. The second attempt to reclaim an memcg below reclaimable size
+ threshold, which triggers MEMCG_LRU_YOUNG;
+5. Attempting to reclaim an memcg below min, which triggers
+ MEMCG_LRU_YOUNG;
+6. Finishing the aging on the eviction path, which triggers
+ MEMCG_LRU_YOUNG;
+7. Offlining an memcg, which triggers MEMCG_LRU_OLD.
+
+Note that memcg LRU only applies to global reclaim, and the
+round-robin incrementing of their max_seq counters ensures the
+eventual fairness to all eligible memcgs. For memcg reclaim, it still
+relies on mem_cgroup_iter().
+
+Link: https://lkml.kernel.org/r/20221222041905.2431096-7-yuzhao@google.com
+Signed-off-by: Yu Zhao <yuzhao@google.com>
+Cc: Johannes Weiner <hannes@cmpxchg.org>
+Cc: Jonathan Corbet <corbet@lwn.net>
+Cc: Michael Larabel <Michael@MichaelLarabel.com>
+Cc: Michal Hocko <mhocko@kernel.org>
+Cc: Mike Rapoport <rppt@kernel.org>
+Cc: Roman Gushchin <roman.gushchin@linux.dev>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Bug: 274865848
+(cherry picked from commit e4dde56cd208674ce899b47589f263499e5b8cdc)
+[TJ: Resolved conflicts with older function signatures for
+min_cgroup_below_min / min_cgroup_below_low and includes]
+Change-Id: Idc8a0f635e035d72dd911f807d1224cb47cbd655
+Signed-off-by: T.J. Mercier <tjmercier@google.com>
+---
+ include/linux/memcontrol.h | 10 +
+ include/linux/mm_inline.h | 17 ++
+ include/linux/mmzone.h | 117 +++++++++++-
+ mm/memcontrol.c | 16 ++
+ mm/page_alloc.c | 1 +
+ mm/vmscan.c | 374 +++++++++++++++++++++++++++++++++----
+ 6 files changed, 500 insertions(+), 35 deletions(-)
+
+--- a/include/linux/memcontrol.h
++++ b/include/linux/memcontrol.h
+@@ -795,6 +795,11 @@ static inline void obj_cgroup_put(struct
+ percpu_ref_put(&objcg->refcnt);
+ }
+
++static inline bool mem_cgroup_tryget(struct mem_cgroup *memcg)
++{
++ return !memcg || css_tryget(&memcg->css);
++}
++
+ static inline void mem_cgroup_put(struct mem_cgroup *memcg)
+ {
+ if (memcg)
+@@ -1295,6 +1300,11 @@ static inline void obj_cgroup_put(struct
+ {
+ }
+
++static inline bool mem_cgroup_tryget(struct mem_cgroup *memcg)
++{
++ return true;
++}
++
+ static inline void mem_cgroup_put(struct mem_cgroup *memcg)
+ {
+ }
+--- a/include/linux/mm_inline.h
++++ b/include/linux/mm_inline.h
+@@ -122,6 +122,18 @@ static inline bool lru_gen_in_fault(void
+ return current->in_lru_fault;
+ }
+
++#ifdef CONFIG_MEMCG
++static inline int lru_gen_memcg_seg(struct lruvec *lruvec)
++{
++ return READ_ONCE(lruvec->lrugen.seg);
++}
++#else
++static inline int lru_gen_memcg_seg(struct lruvec *lruvec)
++{
++ return 0;
++}
++#endif
++
+ static inline int lru_gen_from_seq(unsigned long seq)
+ {
+ return seq % MAX_NR_GENS;
+@@ -302,6 +314,11 @@ static inline bool lru_gen_in_fault(void
+ return false;
+ }
+
++static inline int lru_gen_memcg_seg(struct lruvec *lruvec)
++{
++ return 0;
++}
++
+ static inline bool lru_gen_add_folio(struct lruvec *lruvec, struct folio *folio, bool reclaiming)
+ {
+ return false;
+--- a/include/linux/mmzone.h
++++ b/include/linux/mmzone.h
+@@ -7,6 +7,7 @@
+
+ #include <linux/spinlock.h>
+ #include <linux/list.h>
++#include <linux/list_nulls.h>
+ #include <linux/wait.h>
+ #include <linux/bitops.h>
+ #include <linux/cache.h>
+@@ -367,6 +368,15 @@ struct page_vma_mapped_walk;
+ #define LRU_GEN_MASK ((BIT(LRU_GEN_WIDTH) - 1) << LRU_GEN_PGOFF)
+ #define LRU_REFS_MASK ((BIT(LRU_REFS_WIDTH) - 1) << LRU_REFS_PGOFF)
+
++/* see the comment on MEMCG_NR_GENS */
++enum {
++ MEMCG_LRU_NOP,
++ MEMCG_LRU_HEAD,
++ MEMCG_LRU_TAIL,
++ MEMCG_LRU_OLD,
++ MEMCG_LRU_YOUNG,
++};
++
+ #ifdef CONFIG_LRU_GEN
+
+ enum {
+@@ -426,6 +436,14 @@ struct lru_gen_folio {
+ atomic_long_t refaulted[NR_HIST_GENS][ANON_AND_FILE][MAX_NR_TIERS];
+ /* whether the multi-gen LRU is enabled */
+ bool enabled;
++#ifdef CONFIG_MEMCG
++ /* the memcg generation this lru_gen_folio belongs to */
++ u8 gen;
++ /* the list segment this lru_gen_folio belongs to */
++ u8 seg;
++ /* per-node lru_gen_folio list for global reclaim */
++ struct hlist_nulls_node list;
++#endif
+ };
+
+ enum {
+@@ -479,12 +497,87 @@ void lru_gen_init_lruvec(struct lruvec *
+ void lru_gen_look_around(struct page_vma_mapped_walk *pvmw);
+
+ #ifdef CONFIG_MEMCG
++
++/*
++ * For each node, memcgs are divided into two generations: the old and the
++ * young. For each generation, memcgs are randomly sharded into multiple bins
++ * to improve scalability. For each bin, the hlist_nulls is virtually divided
++ * into three segments: the head, the tail and the default.
++ *
++ * An onlining memcg is added to the tail of a random bin in the old generation.
++ * The eviction starts at the head of a random bin in the old generation. The
++ * per-node memcg generation counter, whose reminder (mod MEMCG_NR_GENS) indexes
++ * the old generation, is incremented when all its bins become empty.
++ *
++ * There are four operations:
++ * 1. MEMCG_LRU_HEAD, which moves an memcg to the head of a random bin in its
++ * current generation (old or young) and updates its "seg" to "head";
++ * 2. MEMCG_LRU_TAIL, which moves an memcg to the tail of a random bin in its
++ * current generation (old or young) and updates its "seg" to "tail";
++ * 3. MEMCG_LRU_OLD, which moves an memcg to the head of a random bin in the old
++ * generation, updates its "gen" to "old" and resets its "seg" to "default";
++ * 4. MEMCG_LRU_YOUNG, which moves an memcg to the tail of a random bin in the
++ * young generation, updates its "gen" to "young" and resets its "seg" to
++ * "default".
++ *
++ * The events that trigger the above operations are:
++ * 1. Exceeding the soft limit, which triggers MEMCG_LRU_HEAD;
++ * 2. The first attempt to reclaim an memcg below low, which triggers
++ * MEMCG_LRU_TAIL;
++ * 3. The first attempt to reclaim an memcg below reclaimable size threshold,
++ * which triggers MEMCG_LRU_TAIL;
++ * 4. The second attempt to reclaim an memcg below reclaimable size threshold,
++ * which triggers MEMCG_LRU_YOUNG;
++ * 5. Attempting to reclaim an memcg below min, which triggers MEMCG_LRU_YOUNG;
++ * 6. Finishing the aging on the eviction path, which triggers MEMCG_LRU_YOUNG;
++ * 7. Offlining an memcg, which triggers MEMCG_LRU_OLD.
++ *
++ * Note that memcg LRU only applies to global reclaim, and the round-robin
++ * incrementing of their max_seq counters ensures the eventual fairness to all
++ * eligible memcgs. For memcg reclaim, it still relies on mem_cgroup_iter().
++ */
++#define MEMCG_NR_GENS 2
++#define MEMCG_NR_BINS 8
++
++struct lru_gen_memcg {
++ /* the per-node memcg generation counter */
++ unsigned long seq;
++ /* each memcg has one lru_gen_folio per node */
++ unsigned long nr_memcgs[MEMCG_NR_GENS];
++ /* per-node lru_gen_folio list for global reclaim */
++ struct hlist_nulls_head fifo[MEMCG_NR_GENS][MEMCG_NR_BINS];
++ /* protects the above */
++ spinlock_t lock;
++};
++
++void lru_gen_init_pgdat(struct pglist_data *pgdat);
++
+ void lru_gen_init_memcg(struct mem_cgroup *memcg);
+ void lru_gen_exit_memcg(struct mem_cgroup *memcg);
+-#endif
++void lru_gen_online_memcg(struct mem_cgroup *memcg);
++void lru_gen_offline_memcg(struct mem_cgroup *memcg);
++void lru_gen_release_memcg(struct mem_cgroup *memcg);
++void lru_gen_rotate_memcg(struct lruvec *lruvec, int op);
++
++#else /* !CONFIG_MEMCG */
++
++#define MEMCG_NR_GENS 1
++
++struct lru_gen_memcg {
++};
++
++static inline void lru_gen_init_pgdat(struct pglist_data *pgdat)
++{
++}
++
++#endif /* CONFIG_MEMCG */
+
+ #else /* !CONFIG_LRU_GEN */
+
++static inline void lru_gen_init_pgdat(struct pglist_data *pgdat)
++{
++}
++
+ static inline void lru_gen_init_lruvec(struct lruvec *lruvec)
+ {
+ }
+@@ -494,6 +587,7 @@ static inline void lru_gen_look_around(s
+ }
+
+ #ifdef CONFIG_MEMCG
++
+ static inline void lru_gen_init_memcg(struct mem_cgroup *memcg)
+ {
+ }
+@@ -501,7 +595,24 @@ static inline void lru_gen_init_memcg(st
+ static inline void lru_gen_exit_memcg(struct mem_cgroup *memcg)
+ {
+ }
+-#endif
++
++static inline void lru_gen_online_memcg(struct mem_cgroup *memcg)
++{
++}
++
++static inline void lru_gen_offline_memcg(struct mem_cgroup *memcg)
++{
++}
++
++static inline void lru_gen_release_memcg(struct mem_cgroup *memcg)
++{
++}
++
++static inline void lru_gen_rotate_memcg(struct lruvec *lruvec, int op)
++{
++}
++
++#endif /* CONFIG_MEMCG */
+
+ #endif /* CONFIG_LRU_GEN */
+
+@@ -1219,6 +1330,8 @@ typedef struct pglist_data {
+ #ifdef CONFIG_LRU_GEN
+ /* kswap mm walk data */
+ struct lru_gen_mm_walk mm_walk;
++ /* lru_gen_folio list */
++ struct lru_gen_memcg memcg_lru;
+ #endif
+
+ CACHELINE_PADDING(_pad2_);
+--- a/mm/memcontrol.c
++++ b/mm/memcontrol.c
+@@ -477,6 +477,16 @@ static void mem_cgroup_update_tree(struc
+ struct mem_cgroup_per_node *mz;
+ struct mem_cgroup_tree_per_node *mctz;
+
++ if (lru_gen_enabled()) {
++ struct lruvec *lruvec = &memcg->nodeinfo[nid]->lruvec;
++
++ /* see the comment on MEMCG_NR_GENS */
++ if (soft_limit_excess(memcg) && lru_gen_memcg_seg(lruvec) != MEMCG_LRU_HEAD)
++ lru_gen_rotate_memcg(lruvec, MEMCG_LRU_HEAD);
++
++ return;
++ }
++
+ mctz = soft_limit_tree.rb_tree_per_node[nid];
+ if (!mctz)
+ return;
+@@ -3524,6 +3534,9 @@ unsigned long mem_cgroup_soft_limit_recl
+ struct mem_cgroup_tree_per_node *mctz;
+ unsigned long excess;
+
++ if (lru_gen_enabled())
++ return 0;
++
+ if (order > 0)
+ return 0;
+
+@@ -5387,6 +5400,7 @@ static int mem_cgroup_css_online(struct
+ if (unlikely(mem_cgroup_is_root(memcg)))
+ queue_delayed_work(system_unbound_wq, &stats_flush_dwork,
+ 2UL*HZ);
++ lru_gen_online_memcg(memcg);
+ return 0;
+ offline_kmem:
+ memcg_offline_kmem(memcg);
+@@ -5418,6 +5432,7 @@ static void mem_cgroup_css_offline(struc
+ memcg_offline_kmem(memcg);
+ reparent_shrinker_deferred(memcg);
+ wb_memcg_offline(memcg);
++ lru_gen_offline_memcg(memcg);
+
+ drain_all_stock(memcg);
+
+@@ -5429,6 +5444,7 @@ static void mem_cgroup_css_released(stru
+ struct mem_cgroup *memcg = mem_cgroup_from_css(css);
+
+ invalidate_reclaim_iterators(memcg);
++ lru_gen_release_memcg(memcg);
+ }
+
+ static void mem_cgroup_css_free(struct cgroup_subsys_state *css)
+--- a/mm/page_alloc.c
++++ b/mm/page_alloc.c
+@@ -7943,6 +7943,7 @@ static void __init free_area_init_node(i
+ pgdat_set_deferred_range(pgdat);
+
+ free_area_init_core(pgdat);
++ lru_gen_init_pgdat(pgdat);
+ }
+
+ static void __init free_area_init_memoryless_node(int nid)
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -54,6 +54,8 @@
+ #include <linux/shmem_fs.h>
+ #include <linux/ctype.h>
+ #include <linux/debugfs.h>
++#include <linux/rculist_nulls.h>
++#include <linux/random.h>
+
+ #include <asm/tlbflush.h>
+ #include <asm/div64.h>
+@@ -134,11 +136,6 @@ struct scan_control {
+ /* Always discard instead of demoting to lower tier memory */
+ unsigned int no_demotion:1;
+
+-#ifdef CONFIG_LRU_GEN
+- /* help kswapd make better choices among multiple memcgs */
+- unsigned long last_reclaimed;
+-#endif
+-
+ /* Allocation order */
+ s8 order;
+
+@@ -3160,6 +3157,9 @@ DEFINE_STATIC_KEY_ARRAY_FALSE(lru_gen_ca
+ for ((type) = 0; (type) < ANON_AND_FILE; (type)++) \
+ for ((zone) = 0; (zone) < MAX_NR_ZONES; (zone)++)
+
++#define get_memcg_gen(seq) ((seq) % MEMCG_NR_GENS)
++#define get_memcg_bin(bin) ((bin) % MEMCG_NR_BINS)
++
+ static struct lruvec *get_lruvec(struct mem_cgroup *memcg, int nid)
+ {
+ struct pglist_data *pgdat = NODE_DATA(nid);
+@@ -4442,8 +4442,7 @@ done:
+ if (sc->priority <= DEF_PRIORITY - 2)
+ wait_event_killable(lruvec->mm_state.wait,
+ max_seq < READ_ONCE(lrugen->max_seq));
+-
+- return max_seq < READ_ONCE(lrugen->max_seq);
++ return false;
+ }
+
+ VM_WARN_ON_ONCE(max_seq != READ_ONCE(lrugen->max_seq));
+@@ -4516,8 +4515,6 @@ static void lru_gen_age_node(struct pgli
+
+ VM_WARN_ON_ONCE(!current_is_kswapd());
+
+- sc->last_reclaimed = sc->nr_reclaimed;
+-
+ /* check the order to exclude compaction-induced reclaim */
+ if (!min_ttl || sc->order || sc->priority == DEF_PRIORITY)
+ return;
+@@ -5116,8 +5113,7 @@ static bool should_run_aging(struct lruv
+ * 1. Defer try_to_inc_max_seq() to workqueues to reduce latency for memcg
+ * reclaim.
+ */
+-static unsigned long get_nr_to_scan(struct lruvec *lruvec, struct scan_control *sc,
+- bool can_swap)
++static long get_nr_to_scan(struct lruvec *lruvec, struct scan_control *sc, bool can_swap)
+ {
+ unsigned long nr_to_scan;
+ struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+@@ -5134,10 +5130,8 @@ static unsigned long get_nr_to_scan(stru
+ if (sc->priority == DEF_PRIORITY)
+ return nr_to_scan;
+
+- try_to_inc_max_seq(lruvec, max_seq, sc, can_swap, false);
+-
+ /* skip this lruvec as it's low on cold folios */
+- return 0;
++ return try_to_inc_max_seq(lruvec, max_seq, sc, can_swap, false) ? -1 : 0;
+ }
+
+ static unsigned long get_nr_to_reclaim(struct scan_control *sc)
+@@ -5146,29 +5140,18 @@ static unsigned long get_nr_to_reclaim(s
+ if (!global_reclaim(sc))
+ return -1;
+
+- /* discount the previous progress for kswapd */
+- if (current_is_kswapd())
+- return sc->nr_to_reclaim + sc->last_reclaimed;
+-
+ return max(sc->nr_to_reclaim, compact_gap(sc->order));
+ }
+
+-static void lru_gen_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
++static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
+ {
+- struct blk_plug plug;
++ long nr_to_scan;
+ unsigned long scanned = 0;
+ unsigned long nr_to_reclaim = get_nr_to_reclaim(sc);
+
+- lru_add_drain();
+-
+- blk_start_plug(&plug);
+-
+- set_mm_walk(lruvec_pgdat(lruvec));
+-
+ while (true) {
+ int delta;
+ int swappiness;
+- unsigned long nr_to_scan;
+
+ if (sc->may_swap)
+ swappiness = get_swappiness(lruvec, sc);
+@@ -5178,7 +5161,7 @@ static void lru_gen_shrink_lruvec(struct
+ swappiness = 0;
+
+ nr_to_scan = get_nr_to_scan(lruvec, sc, swappiness);
+- if (!nr_to_scan)
++ if (nr_to_scan <= 0)
+ break;
+
+ delta = evict_folios(lruvec, sc, swappiness);
+@@ -5195,10 +5178,251 @@ static void lru_gen_shrink_lruvec(struct
+ cond_resched();
+ }
+
++ /* whether try_to_inc_max_seq() was successful */
++ return nr_to_scan < 0;
++}
++
++static int shrink_one(struct lruvec *lruvec, struct scan_control *sc)
++{
++ bool success;
++ unsigned long scanned = sc->nr_scanned;
++ unsigned long reclaimed = sc->nr_reclaimed;
++ int seg = lru_gen_memcg_seg(lruvec);
++ struct mem_cgroup *memcg = lruvec_memcg(lruvec);
++ struct pglist_data *pgdat = lruvec_pgdat(lruvec);
++
++ /* see the comment on MEMCG_NR_GENS */
++ if (!lruvec_is_sizable(lruvec, sc))
++ return seg != MEMCG_LRU_TAIL ? MEMCG_LRU_TAIL : MEMCG_LRU_YOUNG;
++
++ mem_cgroup_calculate_protection(NULL, memcg);
++
++ if (mem_cgroup_below_min(memcg))
++ return MEMCG_LRU_YOUNG;
++
++ if (mem_cgroup_below_low(memcg)) {
++ /* see the comment on MEMCG_NR_GENS */
++ if (seg != MEMCG_LRU_TAIL)
++ return MEMCG_LRU_TAIL;
++
++ memcg_memory_event(memcg, MEMCG_LOW);
++ }
++
++ success = try_to_shrink_lruvec(lruvec, sc);
++
++ shrink_slab(sc->gfp_mask, pgdat->node_id, memcg, sc->priority);
++
++ if (!sc->proactive)
++ vmpressure(sc->gfp_mask, memcg, false, sc->nr_scanned - scanned,
++ sc->nr_reclaimed - reclaimed);
++
++ sc->nr_reclaimed += current->reclaim_state->reclaimed_slab;
++ current->reclaim_state->reclaimed_slab = 0;
++
++ return success ? MEMCG_LRU_YOUNG : 0;
++}
++
++#ifdef CONFIG_MEMCG
++
++static void shrink_many(struct pglist_data *pgdat, struct scan_control *sc)
++{
++ int gen;
++ int bin;
++ int first_bin;
++ struct lruvec *lruvec;
++ struct lru_gen_folio *lrugen;
++ const struct hlist_nulls_node *pos;
++ int op = 0;
++ struct mem_cgroup *memcg = NULL;
++ unsigned long nr_to_reclaim = get_nr_to_reclaim(sc);
++
++ bin = first_bin = get_random_u32_below(MEMCG_NR_BINS);
++restart:
++ gen = get_memcg_gen(READ_ONCE(pgdat->memcg_lru.seq));
++
++ rcu_read_lock();
++
++ hlist_nulls_for_each_entry_rcu(lrugen, pos, &pgdat->memcg_lru.fifo[gen][bin], list) {
++ if (op)
++ lru_gen_rotate_memcg(lruvec, op);
++
++ mem_cgroup_put(memcg);
++
++ lruvec = container_of(lrugen, struct lruvec, lrugen);
++ memcg = lruvec_memcg(lruvec);
++
++ if (!mem_cgroup_tryget(memcg)) {
++ op = 0;
++ memcg = NULL;
++ continue;
++ }
++
++ rcu_read_unlock();
++
++ op = shrink_one(lruvec, sc);
++
++ if (sc->nr_reclaimed >= nr_to_reclaim)
++ goto success;
++
++ rcu_read_lock();
++ }
++
++ rcu_read_unlock();
++
++ /* restart if raced with lru_gen_rotate_memcg() */
++ if (gen != get_nulls_value(pos))
++ goto restart;
++
++ /* try the rest of the bins of the current generation */
++ bin = get_memcg_bin(bin + 1);
++ if (bin != first_bin)
++ goto restart;
++success:
++ if (op)
++ lru_gen_rotate_memcg(lruvec, op);
++
++ mem_cgroup_put(memcg);
++}
++
++static void lru_gen_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
++{
++ struct blk_plug plug;
++
++ VM_WARN_ON_ONCE(global_reclaim(sc));
++
++ lru_add_drain();
++
++ blk_start_plug(&plug);
++
++ set_mm_walk(lruvec_pgdat(lruvec));
++
++ if (try_to_shrink_lruvec(lruvec, sc))
++ lru_gen_rotate_memcg(lruvec, MEMCG_LRU_YOUNG);
++
++ clear_mm_walk();
++
++ blk_finish_plug(&plug);
++}
++
++#else /* !CONFIG_MEMCG */
++
++static void shrink_many(struct pglist_data *pgdat, struct scan_control *sc)
++{
++ BUILD_BUG();
++}
++
++static void lru_gen_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
++{
++ BUILD_BUG();
++}
++
++#endif
++
++static void set_initial_priority(struct pglist_data *pgdat, struct scan_control *sc)
++{
++ int priority;
++ unsigned long reclaimable;
++ struct lruvec *lruvec = mem_cgroup_lruvec(NULL, pgdat);
++
++ if (sc->priority != DEF_PRIORITY || sc->nr_to_reclaim < MIN_LRU_BATCH)
++ return;
++ /*
++ * Determine the initial priority based on ((total / MEMCG_NR_GENS) >>
++ * priority) * reclaimed_to_scanned_ratio = nr_to_reclaim, where the
++ * estimated reclaimed_to_scanned_ratio = inactive / total.
++ */
++ reclaimable = node_page_state(pgdat, NR_INACTIVE_FILE);
++ if (get_swappiness(lruvec, sc))
++ reclaimable += node_page_state(pgdat, NR_INACTIVE_ANON);
++
++ reclaimable /= MEMCG_NR_GENS;
++
++ /* round down reclaimable and round up sc->nr_to_reclaim */
++ priority = fls_long(reclaimable) - 1 - fls_long(sc->nr_to_reclaim - 1);
++
++ sc->priority = clamp(priority, 0, DEF_PRIORITY);
++}
++
++static void lru_gen_shrink_node(struct pglist_data *pgdat, struct scan_control *sc)
++{
++ struct blk_plug plug;
++ unsigned long reclaimed = sc->nr_reclaimed;
++
++ VM_WARN_ON_ONCE(!global_reclaim(sc));
++
++ lru_add_drain();
++
++ blk_start_plug(&plug);
++
++ set_mm_walk(pgdat);
++
++ set_initial_priority(pgdat, sc);
++
++ if (current_is_kswapd())
++ sc->nr_reclaimed = 0;
++
++ if (mem_cgroup_disabled())
++ shrink_one(&pgdat->__lruvec, sc);
++ else
++ shrink_many(pgdat, sc);
++
++ if (current_is_kswapd())
++ sc->nr_reclaimed += reclaimed;
++
+ clear_mm_walk();
+
+ blk_finish_plug(&plug);
++
++ /* kswapd should never fail */
++ pgdat->kswapd_failures = 0;
++}
++
++#ifdef CONFIG_MEMCG
++void lru_gen_rotate_memcg(struct lruvec *lruvec, int op)
++{
++ int seg;
++ int old, new;
++ int bin = get_random_u32_below(MEMCG_NR_BINS);
++ struct pglist_data *pgdat = lruvec_pgdat(lruvec);
++
++ spin_lock(&pgdat->memcg_lru.lock);
++
++ VM_WARN_ON_ONCE(hlist_nulls_unhashed(&lruvec->lrugen.list));
++
++ seg = 0;
++ new = old = lruvec->lrugen.gen;
++
++ /* see the comment on MEMCG_NR_GENS */
++ if (op == MEMCG_LRU_HEAD)
++ seg = MEMCG_LRU_HEAD;
++ else if (op == MEMCG_LRU_TAIL)
++ seg = MEMCG_LRU_TAIL;
++ else if (op == MEMCG_LRU_OLD)
++ new = get_memcg_gen(pgdat->memcg_lru.seq);
++ else if (op == MEMCG_LRU_YOUNG)
++ new = get_memcg_gen(pgdat->memcg_lru.seq + 1);
++ else
++ VM_WARN_ON_ONCE(true);
++
++ hlist_nulls_del_rcu(&lruvec->lrugen.list);
++
++ if (op == MEMCG_LRU_HEAD || op == MEMCG_LRU_OLD)
++ hlist_nulls_add_head_rcu(&lruvec->lrugen.list, &pgdat->memcg_lru.fifo[new][bin]);
++ else
++ hlist_nulls_add_tail_rcu(&lruvec->lrugen.list, &pgdat->memcg_lru.fifo[new][bin]);
++
++ pgdat->memcg_lru.nr_memcgs[old]--;
++ pgdat->memcg_lru.nr_memcgs[new]++;
++
++ lruvec->lrugen.gen = new;
++ WRITE_ONCE(lruvec->lrugen.seg, seg);
++
++ if (!pgdat->memcg_lru.nr_memcgs[old] && old == get_memcg_gen(pgdat->memcg_lru.seq))
++ WRITE_ONCE(pgdat->memcg_lru.seq, pgdat->memcg_lru.seq + 1);
++
++ spin_unlock(&pgdat->memcg_lru.lock);
+ }
++#endif
+
+ /******************************************************************************
+ * state change
+@@ -5656,11 +5880,11 @@ static int run_cmd(char cmd, int memcg_i
+
+ if (!mem_cgroup_disabled()) {
+ rcu_read_lock();
++
+ memcg = mem_cgroup_from_id(memcg_id);
+-#ifdef CONFIG_MEMCG
+- if (memcg && !css_tryget(&memcg->css))
++ if (!mem_cgroup_tryget(memcg))
+ memcg = NULL;
+-#endif
++
+ rcu_read_unlock();
+
+ if (!memcg)
+@@ -5808,6 +6032,19 @@ void lru_gen_init_lruvec(struct lruvec *
+ }
+
+ #ifdef CONFIG_MEMCG
++
++void lru_gen_init_pgdat(struct pglist_data *pgdat)
++{
++ int i, j;
++
++ spin_lock_init(&pgdat->memcg_lru.lock);
++
++ for (i = 0; i < MEMCG_NR_GENS; i++) {
++ for (j = 0; j < MEMCG_NR_BINS; j++)
++ INIT_HLIST_NULLS_HEAD(&pgdat->memcg_lru.fifo[i][j], i);
++ }
++}
++
+ void lru_gen_init_memcg(struct mem_cgroup *memcg)
+ {
+ INIT_LIST_HEAD(&memcg->mm_list.fifo);
+@@ -5831,7 +6068,69 @@ void lru_gen_exit_memcg(struct mem_cgrou
+ }
+ }
+ }
+-#endif
++
++void lru_gen_online_memcg(struct mem_cgroup *memcg)
++{
++ int gen;
++ int nid;
++ int bin = get_random_u32_below(MEMCG_NR_BINS);
++
++ for_each_node(nid) {
++ struct pglist_data *pgdat = NODE_DATA(nid);
++ struct lruvec *lruvec = get_lruvec(memcg, nid);
++
++ spin_lock(&pgdat->memcg_lru.lock);
++
++ VM_WARN_ON_ONCE(!hlist_nulls_unhashed(&lruvec->lrugen.list));
++
++ gen = get_memcg_gen(pgdat->memcg_lru.seq);
++
++ hlist_nulls_add_tail_rcu(&lruvec->lrugen.list, &pgdat->memcg_lru.fifo[gen][bin]);
++ pgdat->memcg_lru.nr_memcgs[gen]++;
++
++ lruvec->lrugen.gen = gen;
++
++ spin_unlock(&pgdat->memcg_lru.lock);
++ }
++}
++
++void lru_gen_offline_memcg(struct mem_cgroup *memcg)
++{
++ int nid;
++
++ for_each_node(nid) {
++ struct lruvec *lruvec = get_lruvec(memcg, nid);
++
++ lru_gen_rotate_memcg(lruvec, MEMCG_LRU_OLD);
++ }
++}
++
++void lru_gen_release_memcg(struct mem_cgroup *memcg)
++{
++ int gen;
++ int nid;
++
++ for_each_node(nid) {
++ struct pglist_data *pgdat = NODE_DATA(nid);
++ struct lruvec *lruvec = get_lruvec(memcg, nid);
++
++ spin_lock(&pgdat->memcg_lru.lock);
++
++ VM_WARN_ON_ONCE(hlist_nulls_unhashed(&lruvec->lrugen.list));
++
++ gen = lruvec->lrugen.gen;
++
++ hlist_nulls_del_rcu(&lruvec->lrugen.list);
++ pgdat->memcg_lru.nr_memcgs[gen]--;
++
++ if (!pgdat->memcg_lru.nr_memcgs[gen] && gen == get_memcg_gen(pgdat->memcg_lru.seq))
++ WRITE_ONCE(pgdat->memcg_lru.seq, pgdat->memcg_lru.seq + 1);
++
++ spin_unlock(&pgdat->memcg_lru.lock);
++ }
++}
++
++#endif /* CONFIG_MEMCG */
+
+ static int __init init_lru_gen(void)
+ {
+@@ -5858,6 +6157,10 @@ static void lru_gen_shrink_lruvec(struct
+ {
+ }
+
++static void lru_gen_shrink_node(struct pglist_data *pgdat, struct scan_control *sc)
++{
++}
++
+ #endif /* CONFIG_LRU_GEN */
+
+ static void shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
+@@ -5871,7 +6174,7 @@ static void shrink_lruvec(struct lruvec
+ bool proportional_reclaim;
+ struct blk_plug plug;
+
+- if (lru_gen_enabled()) {
++ if (lru_gen_enabled() && !global_reclaim(sc)) {
+ lru_gen_shrink_lruvec(lruvec, sc);
+ return;
+ }
+@@ -6114,6 +6417,11 @@ static void shrink_node(pg_data_t *pgdat
+ struct lruvec *target_lruvec;
+ bool reclaimable = false;
+
++ if (lru_gen_enabled() && global_reclaim(sc)) {
++ lru_gen_shrink_node(pgdat, sc);
++ return;
++ }
++
+ target_lruvec = mem_cgroup_lruvec(sc->target_mem_cgroup, pgdat);
+
+ again:
diff --git a/target/linux/generic/backport-6.6/020-v6.3-07-BACKPORT-mm-multi-gen-LRU-clarify-scan_control-flags.patch b/target/linux/generic/backport-6.6/020-v6.3-07-BACKPORT-mm-multi-gen-LRU-clarify-scan_control-flags.patch
new file mode 100644
index 0000000000..d60ddb9dcc
--- /dev/null
+++ b/target/linux/generic/backport-6.6/020-v6.3-07-BACKPORT-mm-multi-gen-LRU-clarify-scan_control-flags.patch
@@ -0,0 +1,202 @@
+From 11b14ee8cbbbebd8204609076a9327a1171cd253 Mon Sep 17 00:00:00 2001
+From: Yu Zhao <yuzhao@google.com>
+Date: Wed, 21 Dec 2022 21:19:05 -0700
+Subject: [PATCH 07/19] BACKPORT: mm: multi-gen LRU: clarify scan_control flags
+
+Among the flags in scan_control:
+1. sc->may_swap, which indicates swap constraint due to memsw.max, is
+ supported as usual.
+2. sc->proactive, which indicates reclaim by memory.reclaim, may not
+ opportunistically skip the aging path, since it is considered less
+ latency sensitive.
+3. !(sc->gfp_mask & __GFP_IO), which indicates IO constraint, lowers
+ swappiness to prioritize file LRU, since clean file folios are more
+ likely to exist.
+4. sc->may_writepage and sc->may_unmap, which indicates opportunistic
+ reclaim, are rejected, since unmapped clean folios are already
+ prioritized. Scanning for more of them is likely futile and can
+ cause high reclaim latency when there is a large number of memcgs.
+
+The rest are handled by the existing code.
+
+Link: https://lkml.kernel.org/r/20221222041905.2431096-8-yuzhao@google.com
+Signed-off-by: Yu Zhao <yuzhao@google.com>
+Cc: Johannes Weiner <hannes@cmpxchg.org>
+Cc: Jonathan Corbet <corbet@lwn.net>
+Cc: Michael Larabel <Michael@MichaelLarabel.com>
+Cc: Michal Hocko <mhocko@kernel.org>
+Cc: Mike Rapoport <rppt@kernel.org>
+Cc: Roman Gushchin <roman.gushchin@linux.dev>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Bug: 274865848
+(cherry picked from commit e9d4e1ee788097484606c32122f146d802a9c5fb)
+[TJ: Resolved conflict with older function signature for min_cgroup_below_min, and over
+cdded861182142ac4488a4d64c571107aeb77f53 ("ANDROID: MGLRU: Don't skip anon reclaim if swap low")]
+Change-Id: Ic2e779eaf4e91a3921831b4e2fa10c740dc59d50
+Signed-off-by: T.J. Mercier <tjmercier@google.com>
+---
+ mm/vmscan.c | 55 +++++++++++++++++++++++++++--------------------------
+ 1 file changed, 28 insertions(+), 27 deletions(-)
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -3185,6 +3185,9 @@ static int get_swappiness(struct lruvec
+ struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+ struct pglist_data *pgdat = lruvec_pgdat(lruvec);
+
++ if (!sc->may_swap)
++ return 0;
++
+ if (!can_demote(pgdat->node_id, sc) &&
+ mem_cgroup_get_nr_swap_pages(memcg) < MIN_LRU_BATCH)
+ return 0;
+@@ -4223,7 +4226,7 @@ static void walk_mm(struct lruvec *lruve
+ } while (err == -EAGAIN);
+ }
+
+-static struct lru_gen_mm_walk *set_mm_walk(struct pglist_data *pgdat)
++static struct lru_gen_mm_walk *set_mm_walk(struct pglist_data *pgdat, bool force_alloc)
+ {
+ struct lru_gen_mm_walk *walk = current->reclaim_state->mm_walk;
+
+@@ -4231,7 +4234,7 @@ static struct lru_gen_mm_walk *set_mm_wa
+ VM_WARN_ON_ONCE(walk);
+
+ walk = &pgdat->mm_walk;
+- } else if (!pgdat && !walk) {
++ } else if (!walk && force_alloc) {
+ VM_WARN_ON_ONCE(current_is_kswapd());
+
+ walk = kzalloc(sizeof(*walk), __GFP_HIGH | __GFP_NOMEMALLOC | __GFP_NOWARN);
+@@ -4419,7 +4422,7 @@ static bool try_to_inc_max_seq(struct lr
+ goto done;
+ }
+
+- walk = set_mm_walk(NULL);
++ walk = set_mm_walk(NULL, true);
+ if (!walk) {
+ success = iterate_mm_list_nowalk(lruvec, max_seq);
+ goto done;
+@@ -4488,8 +4491,6 @@ static bool lruvec_is_reclaimable(struct
+ struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+ DEFINE_MIN_SEQ(lruvec);
+
+- VM_WARN_ON_ONCE(sc->memcg_low_reclaim);
+-
+ /* see the comment on lru_gen_folio */
+ gen = lru_gen_from_seq(min_seq[LRU_GEN_FILE]);
+ birth = READ_ONCE(lruvec->lrugen.timestamps[gen]);
+@@ -4753,12 +4754,8 @@ static bool isolate_folio(struct lruvec
+ {
+ bool success;
+
+- /* unmapping inhibited */
+- if (!sc->may_unmap && folio_mapped(folio))
+- return false;
+-
+ /* swapping inhibited */
+- if (!(sc->may_writepage && (sc->gfp_mask & __GFP_IO)) &&
++ if (!(sc->gfp_mask & __GFP_IO) &&
+ (folio_test_dirty(folio) ||
+ (folio_test_anon(folio) && !folio_test_swapcache(folio))))
+ return false;
+@@ -4857,9 +4854,8 @@ static int scan_folios(struct lruvec *lr
+ __count_vm_events(PGSCAN_ANON + type, isolated);
+
+ /*
+- * There might not be eligible pages due to reclaim_idx, may_unmap and
+- * may_writepage. Check the remaining to prevent livelock if it's not
+- * making progress.
++ * There might not be eligible folios due to reclaim_idx. Check the
++ * remaining to prevent livelock if it's not making progress.
+ */
+ return isolated || !remaining ? scanned : 0;
+ }
+@@ -5119,8 +5115,7 @@ static long get_nr_to_scan(struct lruvec
+ struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+ DEFINE_MAX_SEQ(lruvec);
+
+- if (mem_cgroup_below_min(memcg) ||
+- (mem_cgroup_below_low(memcg) && !sc->memcg_low_reclaim))
++ if (mem_cgroup_below_min(memcg))
+ return 0;
+
+ if (!should_run_aging(lruvec, max_seq, sc, can_swap, &nr_to_scan))
+@@ -5148,17 +5143,14 @@ static bool try_to_shrink_lruvec(struct
+ long nr_to_scan;
+ unsigned long scanned = 0;
+ unsigned long nr_to_reclaim = get_nr_to_reclaim(sc);
++ int swappiness = get_swappiness(lruvec, sc);
++
++ /* clean file folios are more likely to exist */
++ if (swappiness && !(sc->gfp_mask & __GFP_IO))
++ swappiness = 1;
+
+ while (true) {
+ int delta;
+- int swappiness;
+-
+- if (sc->may_swap)
+- swappiness = get_swappiness(lruvec, sc);
+- else if (!cgroup_reclaim(sc) && get_swappiness(lruvec, sc))
+- swappiness = 1;
+- else
+- swappiness = 0;
+
+ nr_to_scan = get_nr_to_scan(lruvec, sc, swappiness);
+ if (nr_to_scan <= 0)
+@@ -5289,12 +5281,13 @@ static void lru_gen_shrink_lruvec(struct
+ struct blk_plug plug;
+
+ VM_WARN_ON_ONCE(global_reclaim(sc));
++ VM_WARN_ON_ONCE(!sc->may_writepage || !sc->may_unmap);
+
+ lru_add_drain();
+
+ blk_start_plug(&plug);
+
+- set_mm_walk(lruvec_pgdat(lruvec));
++ set_mm_walk(NULL, sc->proactive);
+
+ if (try_to_shrink_lruvec(lruvec, sc))
+ lru_gen_rotate_memcg(lruvec, MEMCG_LRU_YOUNG);
+@@ -5350,11 +5343,19 @@ static void lru_gen_shrink_node(struct p
+
+ VM_WARN_ON_ONCE(!global_reclaim(sc));
+
++ /*
++ * Unmapped clean folios are already prioritized. Scanning for more of
++ * them is likely futile and can cause high reclaim latency when there
++ * is a large number of memcgs.
++ */
++ if (!sc->may_writepage || !sc->may_unmap)
++ goto done;
++
+ lru_add_drain();
+
+ blk_start_plug(&plug);
+
+- set_mm_walk(pgdat);
++ set_mm_walk(pgdat, sc->proactive);
+
+ set_initial_priority(pgdat, sc);
+
+@@ -5372,7 +5373,7 @@ static void lru_gen_shrink_node(struct p
+ clear_mm_walk();
+
+ blk_finish_plug(&plug);
+-
++done:
+ /* kswapd should never fail */
+ pgdat->kswapd_failures = 0;
+ }
+@@ -5944,7 +5945,7 @@ static ssize_t lru_gen_seq_write(struct
+ set_task_reclaim_state(current, &sc.reclaim_state);
+ flags = memalloc_noreclaim_save();
+ blk_start_plug(&plug);
+- if (!set_mm_walk(NULL)) {
++ if (!set_mm_walk(NULL, true)) {
+ err = -ENOMEM;
+ goto done;
+ }
diff --git a/target/linux/generic/backport-6.6/020-v6.3-08-UPSTREAM-mm-multi-gen-LRU-simplify-arch_has_hw_pte_y.patch b/target/linux/generic/backport-6.6/020-v6.3-08-UPSTREAM-mm-multi-gen-LRU-simplify-arch_has_hw_pte_y.patch
new file mode 100644
index 0000000000..3cbc84f8b8
--- /dev/null
+++ b/target/linux/generic/backport-6.6/020-v6.3-08-UPSTREAM-mm-multi-gen-LRU-simplify-arch_has_hw_pte_y.patch
@@ -0,0 +1,38 @@
+From 25887d48dff860751a06caa4188bfaf6bfb6e4b2 Mon Sep 17 00:00:00 2001
+From: Yu Zhao <yuzhao@google.com>
+Date: Wed, 21 Dec 2022 21:19:06 -0700
+Subject: [PATCH 08/19] UPSTREAM: mm: multi-gen LRU: simplify
+ arch_has_hw_pte_young() check
+
+Scanning page tables when hardware does not set the accessed bit has
+no real use cases.
+
+Link: https://lkml.kernel.org/r/20221222041905.2431096-9-yuzhao@google.com
+Signed-off-by: Yu Zhao <yuzhao@google.com>
+Cc: Johannes Weiner <hannes@cmpxchg.org>
+Cc: Jonathan Corbet <corbet@lwn.net>
+Cc: Michael Larabel <Michael@MichaelLarabel.com>
+Cc: Michal Hocko <mhocko@kernel.org>
+Cc: Mike Rapoport <rppt@kernel.org>
+Cc: Roman Gushchin <roman.gushchin@linux.dev>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Bug: 274865848
+(cherry picked from commit f386e9314025ea99dae639ed2032560a92081430)
+Change-Id: I84d97ab665b4e3bb862a9bc7d72f50dea7191a6b
+Signed-off-by: T.J. Mercier <tjmercier@google.com>
+---
+ mm/vmscan.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -4417,7 +4417,7 @@ static bool try_to_inc_max_seq(struct lr
+ * handful of PTEs. Spreading the work out over a period of time usually
+ * is less efficient, but it avoids bursty page faults.
+ */
+- if (!force_scan && !(arch_has_hw_pte_young() && get_cap(LRU_GEN_MM_WALK))) {
++ if (!arch_has_hw_pte_young() || !get_cap(LRU_GEN_MM_WALK)) {
+ success = iterate_mm_list_nowalk(lruvec, max_seq);
+ goto done;
+ }
diff --git a/target/linux/generic/backport-6.6/020-v6.3-09-UPSTREAM-mm-multi-gen-LRU-avoid-futile-retries.patch b/target/linux/generic/backport-6.6/020-v6.3-09-UPSTREAM-mm-multi-gen-LRU-avoid-futile-retries.patch
new file mode 100644
index 0000000000..c1ad1c538e
--- /dev/null
+++ b/target/linux/generic/backport-6.6/020-v6.3-09-UPSTREAM-mm-multi-gen-LRU-avoid-futile-retries.patch
@@ -0,0 +1,92 @@
+From 620b0ee94455e48d124414cd06d8a53f69fb6453 Mon Sep 17 00:00:00 2001
+From: Yu Zhao <yuzhao@google.com>
+Date: Mon, 13 Feb 2023 00:53:22 -0700
+Subject: [PATCH 09/19] UPSTREAM: mm: multi-gen LRU: avoid futile retries
+
+Recall that the per-node memcg LRU has two generations and they alternate
+when the last memcg (of a given node) is moved from one to the other.
+Each generation is also sharded into multiple bins to improve scalability.
+A reclaimer starts with a random bin (in the old generation) and, if it
+fails, it will retry, i.e., to try the rest of the bins.
+
+If a reclaimer fails with the last memcg, it should move this memcg to the
+young generation first, which causes the generations to alternate, and
+then retry. Otherwise, the retries will be futile because all other bins
+are empty.
+
+Link: https://lkml.kernel.org/r/20230213075322.1416966-1-yuzhao@google.com
+Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
+Signed-off-by: Yu Zhao <yuzhao@google.com>
+Reported-by: T.J. Mercier <tjmercier@google.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Bug: 274865848
+(cherry picked from commit 9f550d78b40da21b4da515db4c37d8d7b12aa1a6)
+Change-Id: Ie92535676b005ec9e7987632b742fdde8d54436f
+Signed-off-by: T.J. Mercier <tjmercier@google.com>
+---
+ mm/vmscan.c | 25 +++++++++++++++----------
+ 1 file changed, 15 insertions(+), 10 deletions(-)
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -5218,18 +5218,20 @@ static int shrink_one(struct lruvec *lru
+
+ static void shrink_many(struct pglist_data *pgdat, struct scan_control *sc)
+ {
++ int op;
+ int gen;
+ int bin;
+ int first_bin;
+ struct lruvec *lruvec;
+ struct lru_gen_folio *lrugen;
++ struct mem_cgroup *memcg;
+ const struct hlist_nulls_node *pos;
+- int op = 0;
+- struct mem_cgroup *memcg = NULL;
+ unsigned long nr_to_reclaim = get_nr_to_reclaim(sc);
+
+ bin = first_bin = get_random_u32_below(MEMCG_NR_BINS);
+ restart:
++ op = 0;
++ memcg = NULL;
+ gen = get_memcg_gen(READ_ONCE(pgdat->memcg_lru.seq));
+
+ rcu_read_lock();
+@@ -5253,14 +5255,22 @@ restart:
+
+ op = shrink_one(lruvec, sc);
+
+- if (sc->nr_reclaimed >= nr_to_reclaim)
+- goto success;
+-
+ rcu_read_lock();
++
++ if (sc->nr_reclaimed >= nr_to_reclaim)
++ break;
+ }
+
+ rcu_read_unlock();
+
++ if (op)
++ lru_gen_rotate_memcg(lruvec, op);
++
++ mem_cgroup_put(memcg);
++
++ if (sc->nr_reclaimed >= nr_to_reclaim)
++ return;
++
+ /* restart if raced with lru_gen_rotate_memcg() */
+ if (gen != get_nulls_value(pos))
+ goto restart;
+@@ -5269,11 +5279,6 @@ restart:
+ bin = get_memcg_bin(bin + 1);
+ if (bin != first_bin)
+ goto restart;
+-success:
+- if (op)
+- lru_gen_rotate_memcg(lruvec, op);
+-
+- mem_cgroup_put(memcg);
+ }
+
+ static void lru_gen_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
diff --git a/target/linux/generic/backport-6.6/020-v6.3-10-UPSTREAM-mm-add-vma_has_recency.patch b/target/linux/generic/backport-6.6/020-v6.3-10-UPSTREAM-mm-add-vma_has_recency.patch
new file mode 100644
index 0000000000..67fe4f96ec
--- /dev/null
+++ b/target/linux/generic/backport-6.6/020-v6.3-10-UPSTREAM-mm-add-vma_has_recency.patch
@@ -0,0 +1,191 @@
+From 70d216c71ff5c5b17dd1da6294f97b91fb6aba7a Mon Sep 17 00:00:00 2001
+From: Yu Zhao <yuzhao@google.com>
+Date: Fri, 30 Dec 2022 14:52:51 -0700
+Subject: [PATCH 10/19] UPSTREAM: mm: add vma_has_recency()
+
+Add vma_has_recency() to indicate whether a VMA may exhibit temporal
+locality that the LRU algorithm relies on.
+
+This function returns false for VMAs marked by VM_SEQ_READ or
+VM_RAND_READ. While the former flag indicates linear access, i.e., a
+special case of spatial locality, both flags indicate a lack of temporal
+locality, i.e., the reuse of an area within a relatively small duration.
+
+"Recency" is chosen over "locality" to avoid confusion between temporal
+and spatial localities.
+
+Before this patch, the active/inactive LRU only ignored the accessed bit
+from VMAs marked by VM_SEQ_READ. After this patch, the active/inactive
+LRU and MGLRU share the same logic: they both ignore the accessed bit if
+vma_has_recency() returns false.
+
+For the active/inactive LRU, the following fio test showed a [6, 8]%
+increase in IOPS when randomly accessing mapped files under memory
+pressure.
+
+ kb=$(awk '/MemTotal/ { print $2 }' /proc/meminfo)
+ kb=$((kb - 8*1024*1024))
+
+ modprobe brd rd_nr=1 rd_size=$kb
+ dd if=/dev/zero of=/dev/ram0 bs=1M
+
+ mkfs.ext4 /dev/ram0
+ mount /dev/ram0 /mnt/
+ swapoff -a
+
+ fio --name=test --directory=/mnt/ --ioengine=mmap --numjobs=8 \
+ --size=8G --rw=randrw --time_based --runtime=10m \
+ --group_reporting
+
+The discussion that led to this patch is here [1]. Additional test
+results are available in that thread.
+
+[1] https://lore.kernel.org/r/Y31s%2FK8T85jh05wH@google.com/
+
+Link: https://lkml.kernel.org/r/20221230215252.2628425-1-yuzhao@google.com
+Change-Id: I291dcb795197659e40e46539cd32b857677c34ad
+Signed-off-by: Yu Zhao <yuzhao@google.com>
+Cc: Alexander Viro <viro@zeniv.linux.org.uk>
+Cc: Andrea Righi <andrea.righi@canonical.com>
+Cc: Johannes Weiner <hannes@cmpxchg.org>
+Cc: Michael Larabel <Michael@MichaelLarabel.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+(cherry picked from commit 8788f6781486769d9598dcaedc3fe0eb12fc3e59)
+Bug: 274865848
+Signed-off-by: T.J. Mercier <tjmercier@google.com>
+---
+ include/linux/mm_inline.h | 8 ++++++++
+ mm/memory.c | 7 +++----
+ mm/rmap.c | 42 +++++++++++++++++----------------------
+ mm/vmscan.c | 5 ++++-
+ 4 files changed, 33 insertions(+), 29 deletions(-)
+
+--- a/include/linux/mm_inline.h
++++ b/include/linux/mm_inline.h
+@@ -600,4 +600,12 @@ pte_install_uffd_wp_if_needed(struct vm_
+ #endif
+ }
+
++static inline bool vma_has_recency(struct vm_area_struct *vma)
++{
++ if (vma->vm_flags & (VM_SEQ_READ | VM_RAND_READ))
++ return false;
++
++ return true;
++}
++
+ #endif
+--- a/mm/memory.c
++++ b/mm/memory.c
+@@ -1445,8 +1445,7 @@ again:
+ force_flush = 1;
+ set_page_dirty(page);
+ }
+- if (pte_young(ptent) &&
+- likely(!(vma->vm_flags & VM_SEQ_READ)))
++ if (pte_young(ptent) && likely(vma_has_recency(vma)))
+ mark_page_accessed(page);
+ }
+ rss[mm_counter(page)]--;
+@@ -5219,8 +5218,8 @@ static inline void mm_account_fault(stru
+ #ifdef CONFIG_LRU_GEN
+ static void lru_gen_enter_fault(struct vm_area_struct *vma)
+ {
+- /* the LRU algorithm doesn't apply to sequential or random reads */
+- current->in_lru_fault = !(vma->vm_flags & (VM_SEQ_READ | VM_RAND_READ));
++ /* the LRU algorithm only applies to accesses with recency */
++ current->in_lru_fault = vma_has_recency(vma);
+ }
+
+ static void lru_gen_exit_fault(void)
+--- a/mm/rmap.c
++++ b/mm/rmap.c
+@@ -823,25 +823,14 @@ static bool folio_referenced_one(struct
+ }
+
+ if (pvmw.pte) {
+- if (lru_gen_enabled() && pte_young(*pvmw.pte) &&
+- !(vma->vm_flags & (VM_SEQ_READ | VM_RAND_READ))) {
++ if (lru_gen_enabled() && pte_young(*pvmw.pte)) {
+ lru_gen_look_around(&pvmw);
+ referenced++;
+ }
+
+ if (ptep_clear_flush_young_notify(vma, address,
+- pvmw.pte)) {
+- /*
+- * Don't treat a reference through
+- * a sequentially read mapping as such.
+- * If the folio has been used in another mapping,
+- * we will catch it; if this other mapping is
+- * already gone, the unmap path will have set
+- * the referenced flag or activated the folio.
+- */
+- if (likely(!(vma->vm_flags & VM_SEQ_READ)))
+- referenced++;
+- }
++ pvmw.pte))
++ referenced++;
+ } else if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE)) {
+ if (pmdp_clear_flush_young_notify(vma, address,
+ pvmw.pmd))
+@@ -875,7 +864,20 @@ static bool invalid_folio_referenced_vma
+ struct folio_referenced_arg *pra = arg;
+ struct mem_cgroup *memcg = pra->memcg;
+
+- if (!mm_match_cgroup(vma->vm_mm, memcg))
++ /*
++ * Ignore references from this mapping if it has no recency. If the
++ * folio has been used in another mapping, we will catch it; if this
++ * other mapping is already gone, the unmap path will have set the
++ * referenced flag or activated the folio in zap_pte_range().
++ */
++ if (!vma_has_recency(vma))
++ return true;
++
++ /*
++ * If we are reclaiming on behalf of a cgroup, skip counting on behalf
++ * of references from different cgroups.
++ */
++ if (memcg && !mm_match_cgroup(vma->vm_mm, memcg))
+ return true;
+
+ return false;
+@@ -906,6 +908,7 @@ int folio_referenced(struct folio *folio
+ .arg = (void *)&pra,
+ .anon_lock = folio_lock_anon_vma_read,
+ .try_lock = true,
++ .invalid_vma = invalid_folio_referenced_vma,
+ };
+
+ *vm_flags = 0;
+@@ -921,15 +924,6 @@ int folio_referenced(struct folio *folio
+ return 1;
+ }
+
+- /*
+- * If we are reclaiming on behalf of a cgroup, skip
+- * counting on behalf of references from different
+- * cgroups
+- */
+- if (memcg) {
+- rwc.invalid_vma = invalid_folio_referenced_vma;
+- }
+-
+ rmap_walk(folio, &rwc);
+ *vm_flags = pra.vm_flags;
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -3778,7 +3778,10 @@ static int should_skip_vma(unsigned long
+ if (is_vm_hugetlb_page(vma))
+ return true;
+
+- if (vma->vm_flags & (VM_LOCKED | VM_SPECIAL | VM_SEQ_READ | VM_RAND_READ))
++ if (!vma_has_recency(vma))
++ return true;
++
++ if (vma->vm_flags & (VM_LOCKED | VM_SPECIAL))
+ return true;
+
+ if (vma == get_gate_vma(vma->vm_mm))
diff --git a/target/linux/generic/backport-6.6/020-v6.3-11-UPSTREAM-mm-support-POSIX_FADV_NOREUSE.patch b/target/linux/generic/backport-6.6/020-v6.3-11-UPSTREAM-mm-support-POSIX_FADV_NOREUSE.patch
new file mode 100644
index 0000000000..f9c39be920
--- /dev/null
+++ b/target/linux/generic/backport-6.6/020-v6.3-11-UPSTREAM-mm-support-POSIX_FADV_NOREUSE.patch
@@ -0,0 +1,129 @@
+From 9ca4e437a24dfc4ec6c362f319eb9850b9eca497 Mon Sep 17 00:00:00 2001
+From: Yu Zhao <yuzhao@google.com>
+Date: Fri, 30 Dec 2022 14:52:52 -0700
+Subject: [PATCH 11/19] UPSTREAM: mm: support POSIX_FADV_NOREUSE
+
+This patch adds POSIX_FADV_NOREUSE to vma_has_recency() so that the LRU
+algorithm can ignore access to mapped files marked by this flag.
+
+The advantages of POSIX_FADV_NOREUSE are:
+1. Unlike MADV_SEQUENTIAL and MADV_RANDOM, it does not alter the
+ default readahead behavior.
+2. Unlike MADV_SEQUENTIAL and MADV_RANDOM, it does not split VMAs and
+ therefore does not take mmap_lock.
+3. Unlike MADV_COLD, setting it has a negligible cost, regardless of
+ how many pages it affects.
+
+Its limitations are:
+1. Like POSIX_FADV_RANDOM and POSIX_FADV_SEQUENTIAL, it currently does
+ not support range. IOW, its scope is the entire file.
+2. It currently does not ignore access through file descriptors.
+ Specifically, for the active/inactive LRU, given a file page shared
+ by two users and one of them having set POSIX_FADV_NOREUSE on the
+ file, this page will be activated upon the second user accessing
+ it. This corner case can be covered by checking POSIX_FADV_NOREUSE
+ before calling folio_mark_accessed() on the read path. But it is
+ considered not worth the effort.
+
+There have been a few attempts to support POSIX_FADV_NOREUSE, e.g., [1].
+This time the goal is to fill a niche: a few desktop applications, e.g.,
+large file transferring and video encoding/decoding, want fast file
+streaming with mmap() rather than direct IO. Among those applications, an
+SVT-AV1 regression was reported when running with MGLRU [2]. The
+following test can reproduce that regression.
+
+ kb=$(awk '/MemTotal/ { print $2 }' /proc/meminfo)
+ kb=$((kb - 8*1024*1024))
+
+ modprobe brd rd_nr=1 rd_size=$kb
+ dd if=/dev/zero of=/dev/ram0 bs=1M
+
+ mkfs.ext4 /dev/ram0
+ mount /dev/ram0 /mnt/
+ swapoff -a
+
+ fallocate -l 8G /mnt/swapfile
+ mkswap /mnt/swapfile
+ swapon /mnt/swapfile
+
+ wget http://ultravideo.cs.tut.fi/video/Bosphorus_3840x2160_120fps_420_8bit_YUV_Y4M.7z
+ 7z e -o/mnt/ Bosphorus_3840x2160_120fps_420_8bit_YUV_Y4M.7z
+ SvtAv1EncApp --preset 12 -w 3840 -h 2160 \
+ -i /mnt/Bosphorus_3840x2160.y4m
+
+For MGLRU, the following change showed a [9-11]% increase in FPS,
+which makes it on par with the active/inactive LRU.
+
+ patch Source/App/EncApp/EbAppMain.c <<EOF
+ 31a32
+ > #include <fcntl.h>
+ 35d35
+ < #include <fcntl.h> /* _O_BINARY */
+ 117a118
+ > posix_fadvise(config->mmap.fd, 0, 0, POSIX_FADV_NOREUSE);
+ EOF
+
+[1] https://lore.kernel.org/r/1308923350-7932-1-git-send-email-andrea@betterlinux.com/
+[2] https://openbenchmarking.org/result/2209259-PTS-MGLRU8GB57
+
+Link: https://lkml.kernel.org/r/20221230215252.2628425-2-yuzhao@google.com
+Change-Id: I0b7f5f971d78014ea1ba44cee6a8ec902a4330d0
+Signed-off-by: Yu Zhao <yuzhao@google.com>
+Cc: Alexander Viro <viro@zeniv.linux.org.uk>
+Cc: Andrea Righi <andrea.righi@canonical.com>
+Cc: Johannes Weiner <hannes@cmpxchg.org>
+Cc: Michael Larabel <Michael@MichaelLarabel.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+(cherry picked from commit 17e810229cb3068b692fa078bd9b3a6527e0866a)
+Bug: 274865848
+Signed-off-by: T.J. Mercier <tjmercier@google.com>
+---
+ include/linux/fs.h | 2 ++
+ include/linux/mm_inline.h | 3 +++
+ mm/fadvise.c | 5 ++++-
+ 3 files changed, 9 insertions(+), 1 deletion(-)
+
+--- a/include/linux/fs.h
++++ b/include/linux/fs.h
+@@ -166,6 +166,8 @@ typedef int (dio_iodone_t)(struct kiocb
+ /* File supports DIRECT IO */
+ #define FMODE_CAN_ODIRECT ((__force fmode_t)0x400000)
+
++#define FMODE_NOREUSE ((__force fmode_t)0x800000)
++
+ /* File was opened by fanotify and shouldn't generate fanotify events */
+ #define FMODE_NONOTIFY ((__force fmode_t)0x4000000)
+
+--- a/include/linux/mm_inline.h
++++ b/include/linux/mm_inline.h
+@@ -605,6 +605,9 @@ static inline bool vma_has_recency(struc
+ if (vma->vm_flags & (VM_SEQ_READ | VM_RAND_READ))
+ return false;
+
++ if (vma->vm_file && (vma->vm_file->f_mode & FMODE_NOREUSE))
++ return false;
++
+ return true;
+ }
+
+--- a/mm/fadvise.c
++++ b/mm/fadvise.c
+@@ -80,7 +80,7 @@ int generic_fadvise(struct file *file, l
+ case POSIX_FADV_NORMAL:
+ file->f_ra.ra_pages = bdi->ra_pages;
+ spin_lock(&file->f_lock);
+- file->f_mode &= ~FMODE_RANDOM;
++ file->f_mode &= ~(FMODE_RANDOM | FMODE_NOREUSE);
+ spin_unlock(&file->f_lock);
+ break;
+ case POSIX_FADV_RANDOM:
+@@ -107,6 +107,9 @@ int generic_fadvise(struct file *file, l
+ force_page_cache_readahead(mapping, file, start_index, nrpages);
+ break;
+ case POSIX_FADV_NOREUSE:
++ spin_lock(&file->f_lock);
++ file->f_mode |= FMODE_NOREUSE;
++ spin_unlock(&file->f_lock);
+ break;
+ case POSIX_FADV_DONTNEED:
+ __filemap_fdatawrite_range(mapping, offset, endbyte,
diff --git a/target/linux/generic/backport-6.6/020-v6.3-12-UPSTREAM-mm-multi-gen-LRU-section-for-working-set-pr.patch b/target/linux/generic/backport-6.6/020-v6.3-12-UPSTREAM-mm-multi-gen-LRU-section-for-working-set-pr.patch
new file mode 100644
index 0000000000..bac2e3e344
--- /dev/null
+++ b/target/linux/generic/backport-6.6/020-v6.3-12-UPSTREAM-mm-multi-gen-LRU-section-for-working-set-pr.patch
@@ -0,0 +1,67 @@
+From 1b5e4c317d80f4826eceb3781702d18d06b14394 Mon Sep 17 00:00:00 2001
+From: "T.J. Alumbaugh" <talumbau@google.com>
+Date: Wed, 18 Jan 2023 00:18:21 +0000
+Subject: [PATCH 12/19] UPSTREAM: mm: multi-gen LRU: section for working set
+ protection
+
+Patch series "mm: multi-gen LRU: improve".
+
+This patch series improves a few MGLRU functions, collects related
+functions, and adds additional documentation.
+
+This patch (of 7):
+
+Add a section for working set protection in the code and the design doc.
+The admin doc already contains its usage.
+
+Link: https://lkml.kernel.org/r/20230118001827.1040870-1-talumbau@google.com
+Link: https://lkml.kernel.org/r/20230118001827.1040870-2-talumbau@google.com
+Change-Id: I65599075fd42951db7739a2ab7cee78516e157b3
+Signed-off-by: T.J. Alumbaugh <talumbau@google.com>
+Cc: Yu Zhao <yuzhao@google.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+(cherry picked from commit 7b8144e63d84716f16a1b929e0c7e03ae5c4d5c1)
+Bug: 274865848
+Signed-off-by: T.J. Mercier <tjmercier@google.com>
+---
+ Documentation/mm/multigen_lru.rst | 15 +++++++++++++++
+ mm/vmscan.c | 4 ++++
+ 2 files changed, 19 insertions(+)
+
+--- a/Documentation/mm/multigen_lru.rst
++++ b/Documentation/mm/multigen_lru.rst
+@@ -141,6 +141,21 @@ loop has detected outlying refaults from
+ this end, the feedback loop uses the first tier as the baseline, for
+ the reason stated earlier.
+
++Working set protection
++----------------------
++Each generation is timestamped at birth. If ``lru_gen_min_ttl`` is
++set, an ``lruvec`` is protected from the eviction when its oldest
++generation was born within ``lru_gen_min_ttl`` milliseconds. In other
++words, it prevents the working set of ``lru_gen_min_ttl`` milliseconds
++from getting evicted. The OOM killer is triggered if this working set
++cannot be kept in memory.
++
++This time-based approach has the following advantages:
++
++1. It is easier to configure because it is agnostic to applications
++ and memory sizes.
++2. It is more reliable because it is directly wired to the OOM killer.
++
+ Summary
+ -------
+ The multi-gen LRU can be disassembled into the following parts:
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -4461,6 +4461,10 @@ done:
+ return true;
+ }
+
++/******************************************************************************
++ * working set protection
++ ******************************************************************************/
++
+ static bool lruvec_is_sizable(struct lruvec *lruvec, struct scan_control *sc)
+ {
+ int gen, type, zone;
diff --git a/target/linux/generic/backport-6.6/020-v6.3-13-UPSTREAM-mm-multi-gen-LRU-section-for-rmap-PT-walk-f.patch b/target/linux/generic/backport-6.6/020-v6.3-13-UPSTREAM-mm-multi-gen-LRU-section-for-rmap-PT-walk-f.patch
new file mode 100644
index 0000000000..c28a1c5d5b
--- /dev/null
+++ b/target/linux/generic/backport-6.6/020-v6.3-13-UPSTREAM-mm-multi-gen-LRU-section-for-rmap-PT-walk-f.patch
@@ -0,0 +1,57 @@
+From 5ddf9d53d375e42af49b744bd7c2f8247c6bce15 Mon Sep 17 00:00:00 2001
+From: "T.J. Alumbaugh" <talumbau@google.com>
+Date: Wed, 18 Jan 2023 00:18:22 +0000
+Subject: [PATCH 13/19] UPSTREAM: mm: multi-gen LRU: section for rmap/PT walk
+ feedback
+
+Add a section for lru_gen_look_around() in the code and the design doc.
+
+Link: https://lkml.kernel.org/r/20230118001827.1040870-3-talumbau@google.com
+Change-Id: I5097af63f61b3b69ec2abee6cdbdc33c296df213
+Signed-off-by: T.J. Alumbaugh <talumbau@google.com>
+Cc: Yu Zhao <yuzhao@google.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+(cherry picked from commit db19a43d9b3a8876552f00f656008206ef9a5efa)
+Bug: 274865848
+Signed-off-by: T.J. Mercier <tjmercier@google.com>
+---
+ Documentation/mm/multigen_lru.rst | 14 ++++++++++++++
+ mm/vmscan.c | 4 ++++
+ 2 files changed, 18 insertions(+)
+
+--- a/Documentation/mm/multigen_lru.rst
++++ b/Documentation/mm/multigen_lru.rst
+@@ -156,6 +156,20 @@ This time-based approach has the followi
+ and memory sizes.
+ 2. It is more reliable because it is directly wired to the OOM killer.
+
++Rmap/PT walk feedback
++---------------------
++Searching the rmap for PTEs mapping each page on an LRU list (to test
++and clear the accessed bit) can be expensive because pages from
++different VMAs (PA space) are not cache friendly to the rmap (VA
++space). For workloads mostly using mapped pages, searching the rmap
++can incur the highest CPU cost in the reclaim path.
++
++``lru_gen_look_around()`` exploits spatial locality to reduce the
++trips into the rmap. It scans the adjacent PTEs of a young PTE and
++promotes hot pages. If the scan was done cacheline efficiently, it
++adds the PMD entry pointing to the PTE table to the Bloom filter. This
++forms a feedback loop between the eviction and the aging.
++
+ Summary
+ -------
+ The multi-gen LRU can be disassembled into the following parts:
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -4555,6 +4555,10 @@ static void lru_gen_age_node(struct pgli
+ }
+ }
+
++/******************************************************************************
++ * rmap/PT walk feedback
++ ******************************************************************************/
++
+ /*
+ * This function exploits spatial locality when shrink_folio_list() walks the
+ * rmap. It scans the adjacent PTEs of a young PTE and promotes hot pages. If
diff --git a/target/linux/generic/backport-6.6/020-v6.3-14-UPSTREAM-mm-multi-gen-LRU-section-for-Bloom-filters.patch b/target/linux/generic/backport-6.6/020-v6.3-14-UPSTREAM-mm-multi-gen-LRU-section-for-Bloom-filters.patch
new file mode 100644
index 0000000000..a7dfb5ffe7
--- /dev/null
+++ b/target/linux/generic/backport-6.6/020-v6.3-14-UPSTREAM-mm-multi-gen-LRU-section-for-Bloom-filters.patch
@@ -0,0 +1,243 @@
+From 397624e12244ec038f51cb1f178ccb7a2ec562e5 Mon Sep 17 00:00:00 2001
+From: "T.J. Alumbaugh" <talumbau@google.com>
+Date: Wed, 18 Jan 2023 00:18:23 +0000
+Subject: [PATCH 14/19] UPSTREAM: mm: multi-gen LRU: section for Bloom filters
+
+Move Bloom filters code into a dedicated section. Improve the design doc
+to explain Bloom filter usage and connection between aging and eviction in
+their use.
+
+Link: https://lkml.kernel.org/r/20230118001827.1040870-4-talumbau@google.com
+Change-Id: I73e866f687c1ed9f5c8538086aa39408b79897db
+Signed-off-by: T.J. Alumbaugh <talumbau@google.com>
+Cc: Yu Zhao <yuzhao@google.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+(cherry picked from commit ccbbbb85945d8f0255aa9dbc1b617017e2294f2c)
+Bug: 274865848
+Signed-off-by: T.J. Mercier <tjmercier@google.com>
+---
+ Documentation/mm/multigen_lru.rst | 16 +++
+ mm/vmscan.c | 180 +++++++++++++++---------------
+ 2 files changed, 108 insertions(+), 88 deletions(-)
+
+--- a/Documentation/mm/multigen_lru.rst
++++ b/Documentation/mm/multigen_lru.rst
+@@ -170,6 +170,22 @@ promotes hot pages. If the scan was done
+ adds the PMD entry pointing to the PTE table to the Bloom filter. This
+ forms a feedback loop between the eviction and the aging.
+
++Bloom Filters
++-------------
++Bloom filters are a space and memory efficient data structure for set
++membership test, i.e., test if an element is not in the set or may be
++in the set.
++
++In the eviction path, specifically, in ``lru_gen_look_around()``, if a
++PMD has a sufficient number of hot pages, its address is placed in the
++filter. In the aging path, set membership means that the PTE range
++will be scanned for young pages.
++
++Note that Bloom filters are probabilistic on set membership. If a test
++is false positive, the cost is an additional scan of a range of PTEs,
++which may yield hot pages anyway. Parameters of the filter itself can
++control the false positive rate in the limit.
++
+ Summary
+ -------
+ The multi-gen LRU can be disassembled into the following parts:
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -3209,6 +3209,98 @@ static bool __maybe_unused seq_is_valid(
+ }
+
+ /******************************************************************************
++ * Bloom filters
++ ******************************************************************************/
++
++/*
++ * Bloom filters with m=1<<15, k=2 and the false positive rates of ~1/5 when
++ * n=10,000 and ~1/2 when n=20,000, where, conventionally, m is the number of
++ * bits in a bitmap, k is the number of hash functions and n is the number of
++ * inserted items.
++ *
++ * Page table walkers use one of the two filters to reduce their search space.
++ * To get rid of non-leaf entries that no longer have enough leaf entries, the
++ * aging uses the double-buffering technique to flip to the other filter each
++ * time it produces a new generation. For non-leaf entries that have enough
++ * leaf entries, the aging carries them over to the next generation in
++ * walk_pmd_range(); the eviction also report them when walking the rmap
++ * in lru_gen_look_around().
++ *
++ * For future optimizations:
++ * 1. It's not necessary to keep both filters all the time. The spare one can be
++ * freed after the RCU grace period and reallocated if needed again.
++ * 2. And when reallocating, it's worth scaling its size according to the number
++ * of inserted entries in the other filter, to reduce the memory overhead on
++ * small systems and false positives on large systems.
++ * 3. Jenkins' hash function is an alternative to Knuth's.
++ */
++#define BLOOM_FILTER_SHIFT 15
++
++static inline int filter_gen_from_seq(unsigned long seq)
++{
++ return seq % NR_BLOOM_FILTERS;
++}
++
++static void get_item_key(void *item, int *key)
++{
++ u32 hash = hash_ptr(item, BLOOM_FILTER_SHIFT * 2);
++
++ BUILD_BUG_ON(BLOOM_FILTER_SHIFT * 2 > BITS_PER_TYPE(u32));
++
++ key[0] = hash & (BIT(BLOOM_FILTER_SHIFT) - 1);
++ key[1] = hash >> BLOOM_FILTER_SHIFT;
++}
++
++static bool test_bloom_filter(struct lruvec *lruvec, unsigned long seq, void *item)
++{
++ int key[2];
++ unsigned long *filter;
++ int gen = filter_gen_from_seq(seq);
++
++ filter = READ_ONCE(lruvec->mm_state.filters[gen]);
++ if (!filter)
++ return true;
++
++ get_item_key(item, key);
++
++ return test_bit(key[0], filter) && test_bit(key[1], filter);
++}
++
++static void update_bloom_filter(struct lruvec *lruvec, unsigned long seq, void *item)
++{
++ int key[2];
++ unsigned long *filter;
++ int gen = filter_gen_from_seq(seq);
++
++ filter = READ_ONCE(lruvec->mm_state.filters[gen]);
++ if (!filter)
++ return;
++
++ get_item_key(item, key);
++
++ if (!test_bit(key[0], filter))
++ set_bit(key[0], filter);
++ if (!test_bit(key[1], filter))
++ set_bit(key[1], filter);
++}
++
++static void reset_bloom_filter(struct lruvec *lruvec, unsigned long seq)
++{
++ unsigned long *filter;
++ int gen = filter_gen_from_seq(seq);
++
++ filter = lruvec->mm_state.filters[gen];
++ if (filter) {
++ bitmap_clear(filter, 0, BIT(BLOOM_FILTER_SHIFT));
++ return;
++ }
++
++ filter = bitmap_zalloc(BIT(BLOOM_FILTER_SHIFT),
++ __GFP_HIGH | __GFP_NOMEMALLOC | __GFP_NOWARN);
++ WRITE_ONCE(lruvec->mm_state.filters[gen], filter);
++}
++
++/******************************************************************************
+ * mm_struct list
+ ******************************************************************************/
+
+@@ -3333,94 +3425,6 @@ void lru_gen_migrate_mm(struct mm_struct
+ }
+ #endif
+
+-/*
+- * Bloom filters with m=1<<15, k=2 and the false positive rates of ~1/5 when
+- * n=10,000 and ~1/2 when n=20,000, where, conventionally, m is the number of
+- * bits in a bitmap, k is the number of hash functions and n is the number of
+- * inserted items.
+- *
+- * Page table walkers use one of the two filters to reduce their search space.
+- * To get rid of non-leaf entries that no longer have enough leaf entries, the
+- * aging uses the double-buffering technique to flip to the other filter each
+- * time it produces a new generation. For non-leaf entries that have enough
+- * leaf entries, the aging carries them over to the next generation in
+- * walk_pmd_range(); the eviction also report them when walking the rmap
+- * in lru_gen_look_around().
+- *
+- * For future optimizations:
+- * 1. It's not necessary to keep both filters all the time. The spare one can be
+- * freed after the RCU grace period and reallocated if needed again.
+- * 2. And when reallocating, it's worth scaling its size according to the number
+- * of inserted entries in the other filter, to reduce the memory overhead on
+- * small systems and false positives on large systems.
+- * 3. Jenkins' hash function is an alternative to Knuth's.
+- */
+-#define BLOOM_FILTER_SHIFT 15
+-
+-static inline int filter_gen_from_seq(unsigned long seq)
+-{
+- return seq % NR_BLOOM_FILTERS;
+-}
+-
+-static void get_item_key(void *item, int *key)
+-{
+- u32 hash = hash_ptr(item, BLOOM_FILTER_SHIFT * 2);
+-
+- BUILD_BUG_ON(BLOOM_FILTER_SHIFT * 2 > BITS_PER_TYPE(u32));
+-
+- key[0] = hash & (BIT(BLOOM_FILTER_SHIFT) - 1);
+- key[1] = hash >> BLOOM_FILTER_SHIFT;
+-}
+-
+-static void reset_bloom_filter(struct lruvec *lruvec, unsigned long seq)
+-{
+- unsigned long *filter;
+- int gen = filter_gen_from_seq(seq);
+-
+- filter = lruvec->mm_state.filters[gen];
+- if (filter) {
+- bitmap_clear(filter, 0, BIT(BLOOM_FILTER_SHIFT));
+- return;
+- }
+-
+- filter = bitmap_zalloc(BIT(BLOOM_FILTER_SHIFT),
+- __GFP_HIGH | __GFP_NOMEMALLOC | __GFP_NOWARN);
+- WRITE_ONCE(lruvec->mm_state.filters[gen], filter);
+-}
+-
+-static void update_bloom_filter(struct lruvec *lruvec, unsigned long seq, void *item)
+-{
+- int key[2];
+- unsigned long *filter;
+- int gen = filter_gen_from_seq(seq);
+-
+- filter = READ_ONCE(lruvec->mm_state.filters[gen]);
+- if (!filter)
+- return;
+-
+- get_item_key(item, key);
+-
+- if (!test_bit(key[0], filter))
+- set_bit(key[0], filter);
+- if (!test_bit(key[1], filter))
+- set_bit(key[1], filter);
+-}
+-
+-static bool test_bloom_filter(struct lruvec *lruvec, unsigned long seq, void *item)
+-{
+- int key[2];
+- unsigned long *filter;
+- int gen = filter_gen_from_seq(seq);
+-
+- filter = READ_ONCE(lruvec->mm_state.filters[gen]);
+- if (!filter)
+- return true;
+-
+- get_item_key(item, key);
+-
+- return test_bit(key[0], filter) && test_bit(key[1], filter);
+-}
+-
+ static void reset_mm_stats(struct lruvec *lruvec, struct lru_gen_mm_walk *walk, bool last)
+ {
+ int i;
diff --git a/target/linux/generic/backport-6.6/020-v6.3-15-UPSTREAM-mm-multi-gen-LRU-section-for-memcg-LRU.patch b/target/linux/generic/backport-6.6/020-v6.3-15-UPSTREAM-mm-multi-gen-LRU-section-for-memcg-LRU.patch
new file mode 100644
index 0000000000..101a0a3757
--- /dev/null
+++ b/target/linux/generic/backport-6.6/020-v6.3-15-UPSTREAM-mm-multi-gen-LRU-section-for-memcg-LRU.patch
@@ -0,0 +1,427 @@
+From 48c916b812652f9453be5bd45a703728926d41ca Mon Sep 17 00:00:00 2001
+From: "T.J. Alumbaugh" <talumbau@google.com>
+Date: Wed, 18 Jan 2023 00:18:24 +0000
+Subject: [PATCH 15/19] UPSTREAM: mm: multi-gen LRU: section for memcg LRU
+
+Move memcg LRU code into a dedicated section. Improve the design doc to
+outline its architecture.
+
+Link: https://lkml.kernel.org/r/20230118001827.1040870-5-talumbau@google.com
+Change-Id: Id252e420cff7a858acb098cf2b3642da5c40f602
+Signed-off-by: T.J. Alumbaugh <talumbau@google.com>
+Cc: Yu Zhao <yuzhao@google.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+(cherry picked from commit 36c7b4db7c942ae9e1b111f0c6b468c8b2e33842)
+Bug: 274865848
+Signed-off-by: T.J. Mercier <tjmercier@google.com>
+---
+ Documentation/mm/multigen_lru.rst | 33 +++-
+ include/linux/mm_inline.h | 17 --
+ include/linux/mmzone.h | 13 +-
+ mm/memcontrol.c | 8 +-
+ mm/vmscan.c | 250 +++++++++++++++++-------------
+ 5 files changed, 178 insertions(+), 143 deletions(-)
+
+--- a/Documentation/mm/multigen_lru.rst
++++ b/Documentation/mm/multigen_lru.rst
+@@ -186,9 +186,40 @@ is false positive, the cost is an additi
+ which may yield hot pages anyway. Parameters of the filter itself can
+ control the false positive rate in the limit.
+
++Memcg LRU
++---------
++An memcg LRU is a per-node LRU of memcgs. It is also an LRU of LRUs,
++since each node and memcg combination has an LRU of folios (see
++``mem_cgroup_lruvec()``). Its goal is to improve the scalability of
++global reclaim, which is critical to system-wide memory overcommit in
++data centers. Note that memcg LRU only applies to global reclaim.
++
++The basic structure of an memcg LRU can be understood by an analogy to
++the active/inactive LRU (of folios):
++
++1. It has the young and the old (generations), i.e., the counterparts
++ to the active and the inactive;
++2. The increment of ``max_seq`` triggers promotion, i.e., the
++ counterpart to activation;
++3. Other events trigger similar operations, e.g., offlining an memcg
++ triggers demotion, i.e., the counterpart to deactivation.
++
++In terms of global reclaim, it has two distinct features:
++
++1. Sharding, which allows each thread to start at a random memcg (in
++ the old generation) and improves parallelism;
++2. Eventual fairness, which allows direct reclaim to bail out at will
++ and reduces latency without affecting fairness over some time.
++
++In terms of traversing memcgs during global reclaim, it improves the
++best-case complexity from O(n) to O(1) and does not affect the
++worst-case complexity O(n). Therefore, on average, it has a sublinear
++complexity.
++
+ Summary
+ -------
+-The multi-gen LRU can be disassembled into the following parts:
++The multi-gen LRU (of folios) can be disassembled into the following
++parts:
+
+ * Generations
+ * Rmap walks
+--- a/include/linux/mm_inline.h
++++ b/include/linux/mm_inline.h
+@@ -122,18 +122,6 @@ static inline bool lru_gen_in_fault(void
+ return current->in_lru_fault;
+ }
+
+-#ifdef CONFIG_MEMCG
+-static inline int lru_gen_memcg_seg(struct lruvec *lruvec)
+-{
+- return READ_ONCE(lruvec->lrugen.seg);
+-}
+-#else
+-static inline int lru_gen_memcg_seg(struct lruvec *lruvec)
+-{
+- return 0;
+-}
+-#endif
+-
+ static inline int lru_gen_from_seq(unsigned long seq)
+ {
+ return seq % MAX_NR_GENS;
+@@ -314,11 +302,6 @@ static inline bool lru_gen_in_fault(void
+ return false;
+ }
+
+-static inline int lru_gen_memcg_seg(struct lruvec *lruvec)
+-{
+- return 0;
+-}
+-
+ static inline bool lru_gen_add_folio(struct lruvec *lruvec, struct folio *folio, bool reclaiming)
+ {
+ return false;
+--- a/include/linux/mmzone.h
++++ b/include/linux/mmzone.h
+@@ -368,15 +368,6 @@ struct page_vma_mapped_walk;
+ #define LRU_GEN_MASK ((BIT(LRU_GEN_WIDTH) - 1) << LRU_GEN_PGOFF)
+ #define LRU_REFS_MASK ((BIT(LRU_REFS_WIDTH) - 1) << LRU_REFS_PGOFF)
+
+-/* see the comment on MEMCG_NR_GENS */
+-enum {
+- MEMCG_LRU_NOP,
+- MEMCG_LRU_HEAD,
+- MEMCG_LRU_TAIL,
+- MEMCG_LRU_OLD,
+- MEMCG_LRU_YOUNG,
+-};
+-
+ #ifdef CONFIG_LRU_GEN
+
+ enum {
+@@ -557,7 +548,7 @@ void lru_gen_exit_memcg(struct mem_cgrou
+ void lru_gen_online_memcg(struct mem_cgroup *memcg);
+ void lru_gen_offline_memcg(struct mem_cgroup *memcg);
+ void lru_gen_release_memcg(struct mem_cgroup *memcg);
+-void lru_gen_rotate_memcg(struct lruvec *lruvec, int op);
++void lru_gen_soft_reclaim(struct lruvec *lruvec);
+
+ #else /* !CONFIG_MEMCG */
+
+@@ -608,7 +599,7 @@ static inline void lru_gen_release_memcg
+ {
+ }
+
+-static inline void lru_gen_rotate_memcg(struct lruvec *lruvec, int op)
++static inline void lru_gen_soft_reclaim(struct lruvec *lruvec)
+ {
+ }
+
+--- a/mm/memcontrol.c
++++ b/mm/memcontrol.c
+@@ -478,12 +478,8 @@ static void mem_cgroup_update_tree(struc
+ struct mem_cgroup_tree_per_node *mctz;
+
+ if (lru_gen_enabled()) {
+- struct lruvec *lruvec = &memcg->nodeinfo[nid]->lruvec;
+-
+- /* see the comment on MEMCG_NR_GENS */
+- if (soft_limit_excess(memcg) && lru_gen_memcg_seg(lruvec) != MEMCG_LRU_HEAD)
+- lru_gen_rotate_memcg(lruvec, MEMCG_LRU_HEAD);
+-
++ if (soft_limit_excess(memcg))
++ lru_gen_soft_reclaim(&memcg->nodeinfo[nid]->lruvec);
+ return;
+ }
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -4692,6 +4692,148 @@ void lru_gen_look_around(struct page_vma
+ }
+
+ /******************************************************************************
++ * memcg LRU
++ ******************************************************************************/
++
++/* see the comment on MEMCG_NR_GENS */
++enum {
++ MEMCG_LRU_NOP,
++ MEMCG_LRU_HEAD,
++ MEMCG_LRU_TAIL,
++ MEMCG_LRU_OLD,
++ MEMCG_LRU_YOUNG,
++};
++
++#ifdef CONFIG_MEMCG
++
++static int lru_gen_memcg_seg(struct lruvec *lruvec)
++{
++ return READ_ONCE(lruvec->lrugen.seg);
++}
++
++static void lru_gen_rotate_memcg(struct lruvec *lruvec, int op)
++{
++ int seg;
++ int old, new;
++ int bin = get_random_u32_below(MEMCG_NR_BINS);
++ struct pglist_data *pgdat = lruvec_pgdat(lruvec);
++
++ spin_lock(&pgdat->memcg_lru.lock);
++
++ VM_WARN_ON_ONCE(hlist_nulls_unhashed(&lruvec->lrugen.list));
++
++ seg = 0;
++ new = old = lruvec->lrugen.gen;
++
++ /* see the comment on MEMCG_NR_GENS */
++ if (op == MEMCG_LRU_HEAD)
++ seg = MEMCG_LRU_HEAD;
++ else if (op == MEMCG_LRU_TAIL)
++ seg = MEMCG_LRU_TAIL;
++ else if (op == MEMCG_LRU_OLD)
++ new = get_memcg_gen(pgdat->memcg_lru.seq);
++ else if (op == MEMCG_LRU_YOUNG)
++ new = get_memcg_gen(pgdat->memcg_lru.seq + 1);
++ else
++ VM_WARN_ON_ONCE(true);
++
++ hlist_nulls_del_rcu(&lruvec->lrugen.list);
++
++ if (op == MEMCG_LRU_HEAD || op == MEMCG_LRU_OLD)
++ hlist_nulls_add_head_rcu(&lruvec->lrugen.list, &pgdat->memcg_lru.fifo[new][bin]);
++ else
++ hlist_nulls_add_tail_rcu(&lruvec->lrugen.list, &pgdat->memcg_lru.fifo[new][bin]);
++
++ pgdat->memcg_lru.nr_memcgs[old]--;
++ pgdat->memcg_lru.nr_memcgs[new]++;
++
++ lruvec->lrugen.gen = new;
++ WRITE_ONCE(lruvec->lrugen.seg, seg);
++
++ if (!pgdat->memcg_lru.nr_memcgs[old] && old == get_memcg_gen(pgdat->memcg_lru.seq))
++ WRITE_ONCE(pgdat->memcg_lru.seq, pgdat->memcg_lru.seq + 1);
++
++ spin_unlock(&pgdat->memcg_lru.lock);
++}
++
++void lru_gen_online_memcg(struct mem_cgroup *memcg)
++{
++ int gen;
++ int nid;
++ int bin = get_random_u32_below(MEMCG_NR_BINS);
++
++ for_each_node(nid) {
++ struct pglist_data *pgdat = NODE_DATA(nid);
++ struct lruvec *lruvec = get_lruvec(memcg, nid);
++
++ spin_lock(&pgdat->memcg_lru.lock);
++
++ VM_WARN_ON_ONCE(!hlist_nulls_unhashed(&lruvec->lrugen.list));
++
++ gen = get_memcg_gen(pgdat->memcg_lru.seq);
++
++ hlist_nulls_add_tail_rcu(&lruvec->lrugen.list, &pgdat->memcg_lru.fifo[gen][bin]);
++ pgdat->memcg_lru.nr_memcgs[gen]++;
++
++ lruvec->lrugen.gen = gen;
++
++ spin_unlock(&pgdat->memcg_lru.lock);
++ }
++}
++
++void lru_gen_offline_memcg(struct mem_cgroup *memcg)
++{
++ int nid;
++
++ for_each_node(nid) {
++ struct lruvec *lruvec = get_lruvec(memcg, nid);
++
++ lru_gen_rotate_memcg(lruvec, MEMCG_LRU_OLD);
++ }
++}
++
++void lru_gen_release_memcg(struct mem_cgroup *memcg)
++{
++ int gen;
++ int nid;
++
++ for_each_node(nid) {
++ struct pglist_data *pgdat = NODE_DATA(nid);
++ struct lruvec *lruvec = get_lruvec(memcg, nid);
++
++ spin_lock(&pgdat->memcg_lru.lock);
++
++ VM_WARN_ON_ONCE(hlist_nulls_unhashed(&lruvec->lrugen.list));
++
++ gen = lruvec->lrugen.gen;
++
++ hlist_nulls_del_rcu(&lruvec->lrugen.list);
++ pgdat->memcg_lru.nr_memcgs[gen]--;
++
++ if (!pgdat->memcg_lru.nr_memcgs[gen] && gen == get_memcg_gen(pgdat->memcg_lru.seq))
++ WRITE_ONCE(pgdat->memcg_lru.seq, pgdat->memcg_lru.seq + 1);
++
++ spin_unlock(&pgdat->memcg_lru.lock);
++ }
++}
++
++void lru_gen_soft_reclaim(struct lruvec *lruvec)
++{
++ /* see the comment on MEMCG_NR_GENS */
++ if (lru_gen_memcg_seg(lruvec) != MEMCG_LRU_HEAD)
++ lru_gen_rotate_memcg(lruvec, MEMCG_LRU_HEAD);
++}
++
++#else /* !CONFIG_MEMCG */
++
++static int lru_gen_memcg_seg(struct lruvec *lruvec)
++{
++ return 0;
++}
++
++#endif
++
++/******************************************************************************
+ * the eviction
+ ******************************************************************************/
+
+@@ -5398,53 +5540,6 @@ done:
+ pgdat->kswapd_failures = 0;
+ }
+
+-#ifdef CONFIG_MEMCG
+-void lru_gen_rotate_memcg(struct lruvec *lruvec, int op)
+-{
+- int seg;
+- int old, new;
+- int bin = get_random_u32_below(MEMCG_NR_BINS);
+- struct pglist_data *pgdat = lruvec_pgdat(lruvec);
+-
+- spin_lock(&pgdat->memcg_lru.lock);
+-
+- VM_WARN_ON_ONCE(hlist_nulls_unhashed(&lruvec->lrugen.list));
+-
+- seg = 0;
+- new = old = lruvec->lrugen.gen;
+-
+- /* see the comment on MEMCG_NR_GENS */
+- if (op == MEMCG_LRU_HEAD)
+- seg = MEMCG_LRU_HEAD;
+- else if (op == MEMCG_LRU_TAIL)
+- seg = MEMCG_LRU_TAIL;
+- else if (op == MEMCG_LRU_OLD)
+- new = get_memcg_gen(pgdat->memcg_lru.seq);
+- else if (op == MEMCG_LRU_YOUNG)
+- new = get_memcg_gen(pgdat->memcg_lru.seq + 1);
+- else
+- VM_WARN_ON_ONCE(true);
+-
+- hlist_nulls_del_rcu(&lruvec->lrugen.list);
+-
+- if (op == MEMCG_LRU_HEAD || op == MEMCG_LRU_OLD)
+- hlist_nulls_add_head_rcu(&lruvec->lrugen.list, &pgdat->memcg_lru.fifo[new][bin]);
+- else
+- hlist_nulls_add_tail_rcu(&lruvec->lrugen.list, &pgdat->memcg_lru.fifo[new][bin]);
+-
+- pgdat->memcg_lru.nr_memcgs[old]--;
+- pgdat->memcg_lru.nr_memcgs[new]++;
+-
+- lruvec->lrugen.gen = new;
+- WRITE_ONCE(lruvec->lrugen.seg, seg);
+-
+- if (!pgdat->memcg_lru.nr_memcgs[old] && old == get_memcg_gen(pgdat->memcg_lru.seq))
+- WRITE_ONCE(pgdat->memcg_lru.seq, pgdat->memcg_lru.seq + 1);
+-
+- spin_unlock(&pgdat->memcg_lru.lock);
+-}
+-#endif
+-
+ /******************************************************************************
+ * state change
+ ******************************************************************************/
+@@ -6090,67 +6185,6 @@ void lru_gen_exit_memcg(struct mem_cgrou
+ }
+ }
+
+-void lru_gen_online_memcg(struct mem_cgroup *memcg)
+-{
+- int gen;
+- int nid;
+- int bin = get_random_u32_below(MEMCG_NR_BINS);
+-
+- for_each_node(nid) {
+- struct pglist_data *pgdat = NODE_DATA(nid);
+- struct lruvec *lruvec = get_lruvec(memcg, nid);
+-
+- spin_lock(&pgdat->memcg_lru.lock);
+-
+- VM_WARN_ON_ONCE(!hlist_nulls_unhashed(&lruvec->lrugen.list));
+-
+- gen = get_memcg_gen(pgdat->memcg_lru.seq);
+-
+- hlist_nulls_add_tail_rcu(&lruvec->lrugen.list, &pgdat->memcg_lru.fifo[gen][bin]);
+- pgdat->memcg_lru.nr_memcgs[gen]++;
+-
+- lruvec->lrugen.gen = gen;
+-
+- spin_unlock(&pgdat->memcg_lru.lock);
+- }
+-}
+-
+-void lru_gen_offline_memcg(struct mem_cgroup *memcg)
+-{
+- int nid;
+-
+- for_each_node(nid) {
+- struct lruvec *lruvec = get_lruvec(memcg, nid);
+-
+- lru_gen_rotate_memcg(lruvec, MEMCG_LRU_OLD);
+- }
+-}
+-
+-void lru_gen_release_memcg(struct mem_cgroup *memcg)
+-{
+- int gen;
+- int nid;
+-
+- for_each_node(nid) {
+- struct pglist_data *pgdat = NODE_DATA(nid);
+- struct lruvec *lruvec = get_lruvec(memcg, nid);
+-
+- spin_lock(&pgdat->memcg_lru.lock);
+-
+- VM_WARN_ON_ONCE(hlist_nulls_unhashed(&lruvec->lrugen.list));
+-
+- gen = lruvec->lrugen.gen;
+-
+- hlist_nulls_del_rcu(&lruvec->lrugen.list);
+- pgdat->memcg_lru.nr_memcgs[gen]--;
+-
+- if (!pgdat->memcg_lru.nr_memcgs[gen] && gen == get_memcg_gen(pgdat->memcg_lru.seq))
+- WRITE_ONCE(pgdat->memcg_lru.seq, pgdat->memcg_lru.seq + 1);
+-
+- spin_unlock(&pgdat->memcg_lru.lock);
+- }
+-}
+-
+ #endif /* CONFIG_MEMCG */
+
+ static int __init init_lru_gen(void)
diff --git a/target/linux/generic/backport-6.6/020-v6.3-16-UPSTREAM-mm-multi-gen-LRU-improve-lru_gen_exit_memcg.patch b/target/linux/generic/backport-6.6/020-v6.3-16-UPSTREAM-mm-multi-gen-LRU-improve-lru_gen_exit_memcg.patch
new file mode 100644
index 0000000000..1ee766f861
--- /dev/null
+++ b/target/linux/generic/backport-6.6/020-v6.3-16-UPSTREAM-mm-multi-gen-LRU-improve-lru_gen_exit_memcg.patch
@@ -0,0 +1,40 @@
+From bec433f29537652ed054148edfd7e2183ddcf7c3 Mon Sep 17 00:00:00 2001
+From: "T.J. Alumbaugh" <talumbau@google.com>
+Date: Wed, 18 Jan 2023 00:18:25 +0000
+Subject: [PATCH 16/19] UPSTREAM: mm: multi-gen LRU: improve
+ lru_gen_exit_memcg()
+
+Add warnings and poison ->next.
+
+Link: https://lkml.kernel.org/r/20230118001827.1040870-6-talumbau@google.com
+Change-Id: I53de9e04c1ae941e122b33cd45d2bbb5f34aae0c
+Signed-off-by: T.J. Alumbaugh <talumbau@google.com>
+Cc: Yu Zhao <yuzhao@google.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+(cherry picked from commit 37cc99979d04cca677c0ad5c0acd1149ec165d1b)
+Bug: 274865848
+Signed-off-by: T.J. Mercier <tjmercier@google.com>
+---
+ mm/vmscan.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -6172,12 +6172,17 @@ void lru_gen_exit_memcg(struct mem_cgrou
+ int i;
+ int nid;
+
++ VM_WARN_ON_ONCE(!list_empty(&memcg->mm_list.fifo));
++
+ for_each_node(nid) {
+ struct lruvec *lruvec = get_lruvec(memcg, nid);
+
++ VM_WARN_ON_ONCE(lruvec->mm_state.nr_walkers);
+ VM_WARN_ON_ONCE(memchr_inv(lruvec->lrugen.nr_pages, 0,
+ sizeof(lruvec->lrugen.nr_pages)));
+
++ lruvec->lrugen.list.next = LIST_POISON1;
++
+ for (i = 0; i < NR_BLOOM_FILTERS; i++) {
+ bitmap_free(lruvec->mm_state.filters[i]);
+ lruvec->mm_state.filters[i] = NULL;
diff --git a/target/linux/generic/backport-6.6/020-v6.3-17-UPSTREAM-mm-multi-gen-LRU-improve-walk_pmd_range.patch b/target/linux/generic/backport-6.6/020-v6.3-17-UPSTREAM-mm-multi-gen-LRU-improve-walk_pmd_range.patch
new file mode 100644
index 0000000000..2273977dc9
--- /dev/null
+++ b/target/linux/generic/backport-6.6/020-v6.3-17-UPSTREAM-mm-multi-gen-LRU-improve-walk_pmd_range.patch
@@ -0,0 +1,135 @@
+From fc0e3b06e0f19917b7ecad7967a72f61d4743644 Mon Sep 17 00:00:00 2001
+From: "T.J. Alumbaugh" <talumbau@google.com>
+Date: Wed, 18 Jan 2023 00:18:26 +0000
+Subject: [PATCH 17/19] UPSTREAM: mm: multi-gen LRU: improve walk_pmd_range()
+
+Improve readability of walk_pmd_range() and walk_pmd_range_locked().
+
+Link: https://lkml.kernel.org/r/20230118001827.1040870-7-talumbau@google.com
+Change-Id: Ia084fbf53fe989673b7804ca8ca520af12d7d52a
+Signed-off-by: T.J. Alumbaugh <talumbau@google.com>
+Cc: Yu Zhao <yuzhao@google.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+(cherry picked from commit b5ff4133617d0eced35b685da0bd0929dd9fabb7)
+Bug: 274865848
+Signed-off-by: T.J. Mercier <tjmercier@google.com>
+---
+ mm/vmscan.c | 40 ++++++++++++++++++++--------------------
+ 1 file changed, 20 insertions(+), 20 deletions(-)
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -3980,8 +3980,8 @@ restart:
+ }
+
+ #if defined(CONFIG_TRANSPARENT_HUGEPAGE) || defined(CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG)
+-static void walk_pmd_range_locked(pud_t *pud, unsigned long next, struct vm_area_struct *vma,
+- struct mm_walk *args, unsigned long *bitmap, unsigned long *start)
++static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area_struct *vma,
++ struct mm_walk *args, unsigned long *bitmap, unsigned long *first)
+ {
+ int i;
+ pmd_t *pmd;
+@@ -3994,18 +3994,19 @@ static void walk_pmd_range_locked(pud_t
+ VM_WARN_ON_ONCE(pud_leaf(*pud));
+
+ /* try to batch at most 1+MIN_LRU_BATCH+1 entries */
+- if (*start == -1) {
+- *start = next;
++ if (*first == -1) {
++ *first = addr;
++ bitmap_zero(bitmap, MIN_LRU_BATCH);
+ return;
+ }
+
+- i = next == -1 ? 0 : pmd_index(next) - pmd_index(*start);
++ i = addr == -1 ? 0 : pmd_index(addr) - pmd_index(*first);
+ if (i && i <= MIN_LRU_BATCH) {
+ __set_bit(i - 1, bitmap);
+ return;
+ }
+
+- pmd = pmd_offset(pud, *start);
++ pmd = pmd_offset(pud, *first);
+
+ ptl = pmd_lockptr(args->mm, pmd);
+ if (!spin_trylock(ptl))
+@@ -4016,15 +4017,16 @@ static void walk_pmd_range_locked(pud_t
+ do {
+ unsigned long pfn;
+ struct folio *folio;
+- unsigned long addr = i ? (*start & PMD_MASK) + i * PMD_SIZE : *start;
++
++ /* don't round down the first address */
++ addr = i ? (*first & PMD_MASK) + i * PMD_SIZE : *first;
+
+ pfn = get_pmd_pfn(pmd[i], vma, addr);
+ if (pfn == -1)
+ goto next;
+
+ if (!pmd_trans_huge(pmd[i])) {
+- if (arch_has_hw_nonleaf_pmd_young() &&
+- get_cap(LRU_GEN_NONLEAF_YOUNG))
++ if (arch_has_hw_nonleaf_pmd_young() && get_cap(LRU_GEN_NONLEAF_YOUNG))
+ pmdp_test_and_clear_young(vma, addr, pmd + i);
+ goto next;
+ }
+@@ -4053,12 +4055,11 @@ next:
+ arch_leave_lazy_mmu_mode();
+ spin_unlock(ptl);
+ done:
+- *start = -1;
+- bitmap_zero(bitmap, MIN_LRU_BATCH);
++ *first = -1;
+ }
+ #else
+-static void walk_pmd_range_locked(pud_t *pud, unsigned long next, struct vm_area_struct *vma,
+- struct mm_walk *args, unsigned long *bitmap, unsigned long *start)
++static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area_struct *vma,
++ struct mm_walk *args, unsigned long *bitmap, unsigned long *first)
+ {
+ }
+ #endif
+@@ -4071,9 +4072,9 @@ static void walk_pmd_range(pud_t *pud, u
+ unsigned long next;
+ unsigned long addr;
+ struct vm_area_struct *vma;
+- unsigned long pos = -1;
++ unsigned long bitmap[BITS_TO_LONGS(MIN_LRU_BATCH)];
++ unsigned long first = -1;
+ struct lru_gen_mm_walk *walk = args->private;
+- unsigned long bitmap[BITS_TO_LONGS(MIN_LRU_BATCH)] = {};
+
+ VM_WARN_ON_ONCE(pud_leaf(*pud));
+
+@@ -4115,18 +4116,17 @@ restart:
+ if (pfn < pgdat->node_start_pfn || pfn >= pgdat_end_pfn(pgdat))
+ continue;
+
+- walk_pmd_range_locked(pud, addr, vma, args, bitmap, &pos);
++ walk_pmd_range_locked(pud, addr, vma, args, bitmap, &first);
+ continue;
+ }
+ #endif
+ walk->mm_stats[MM_NONLEAF_TOTAL]++;
+
+- if (arch_has_hw_nonleaf_pmd_young() &&
+- get_cap(LRU_GEN_NONLEAF_YOUNG)) {
++ if (arch_has_hw_nonleaf_pmd_young() && get_cap(LRU_GEN_NONLEAF_YOUNG)) {
+ if (!pmd_young(val))
+ continue;
+
+- walk_pmd_range_locked(pud, addr, vma, args, bitmap, &pos);
++ walk_pmd_range_locked(pud, addr, vma, args, bitmap, &first);
+ }
+
+ if (!walk->force_scan && !test_bloom_filter(walk->lruvec, walk->max_seq, pmd + i))
+@@ -4143,7 +4143,7 @@ restart:
+ update_bloom_filter(walk->lruvec, walk->max_seq + 1, pmd + i);
+ }
+
+- walk_pmd_range_locked(pud, -1, vma, args, bitmap, &pos);
++ walk_pmd_range_locked(pud, -1, vma, args, bitmap, &first);
+
+ if (i < PTRS_PER_PMD && get_next_vma(PUD_MASK, PMD_SIZE, args, &start, &end))
+ goto restart;
diff --git a/target/linux/generic/backport-6.6/020-v6.3-18-UPSTREAM-mm-multi-gen-LRU-simplify-lru_gen_look_arou.patch b/target/linux/generic/backport-6.6/020-v6.3-18-UPSTREAM-mm-multi-gen-LRU-simplify-lru_gen_look_arou.patch
new file mode 100644
index 0000000000..856199574a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/020-v6.3-18-UPSTREAM-mm-multi-gen-LRU-simplify-lru_gen_look_arou.patch
@@ -0,0 +1,148 @@
+From e604c3ccb4dfbdde2467fccef9bb36170a392695 Mon Sep 17 00:00:00 2001
+From: "T.J. Alumbaugh" <talumbau@google.com>
+Date: Wed, 18 Jan 2023 00:18:27 +0000
+Subject: [PATCH 18/19] UPSTREAM: mm: multi-gen LRU: simplify
+ lru_gen_look_around()
+
+Update the folio generation in place with or without
+current->reclaim_state->mm_walk. The LRU lock is held for longer, if
+mm_walk is NULL and the number of folios to update is more than
+PAGEVEC_SIZE.
+
+This causes a measurable regression from the LRU lock contention during a
+microbencmark. But a tiny regression is not worth the complexity.
+
+Link: https://lkml.kernel.org/r/20230118001827.1040870-8-talumbau@google.com
+Change-Id: I9ce18b4f4062e6c1c13c98ece9422478eb8e1846
+Signed-off-by: T.J. Alumbaugh <talumbau@google.com>
+Cc: Yu Zhao <yuzhao@google.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+(cherry picked from commit abf086721a2f1e6897c57796f7268df1b194c750)
+Bug: 274865848
+Signed-off-by: T.J. Mercier <tjmercier@google.com>
+---
+ mm/vmscan.c | 73 +++++++++++++++++------------------------------------
+ 1 file changed, 23 insertions(+), 50 deletions(-)
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -4573,13 +4573,12 @@ static void lru_gen_age_node(struct pgli
+ void lru_gen_look_around(struct page_vma_mapped_walk *pvmw)
+ {
+ int i;
+- pte_t *pte;
+ unsigned long start;
+ unsigned long end;
+- unsigned long addr;
+ struct lru_gen_mm_walk *walk;
+ int young = 0;
+- unsigned long bitmap[BITS_TO_LONGS(MIN_LRU_BATCH)] = {};
++ pte_t *pte = pvmw->pte;
++ unsigned long addr = pvmw->address;
+ struct folio *folio = pfn_folio(pvmw->pfn);
+ struct mem_cgroup *memcg = folio_memcg(folio);
+ struct pglist_data *pgdat = folio_pgdat(folio);
+@@ -4596,25 +4595,28 @@ void lru_gen_look_around(struct page_vma
+ /* avoid taking the LRU lock under the PTL when possible */
+ walk = current->reclaim_state ? current->reclaim_state->mm_walk : NULL;
+
+- start = max(pvmw->address & PMD_MASK, pvmw->vma->vm_start);
+- end = min(pvmw->address | ~PMD_MASK, pvmw->vma->vm_end - 1) + 1;
++ start = max(addr & PMD_MASK, pvmw->vma->vm_start);
++ end = min(addr | ~PMD_MASK, pvmw->vma->vm_end - 1) + 1;
+
+ if (end - start > MIN_LRU_BATCH * PAGE_SIZE) {
+- if (pvmw->address - start < MIN_LRU_BATCH * PAGE_SIZE / 2)
++ if (addr - start < MIN_LRU_BATCH * PAGE_SIZE / 2)
+ end = start + MIN_LRU_BATCH * PAGE_SIZE;
+- else if (end - pvmw->address < MIN_LRU_BATCH * PAGE_SIZE / 2)
++ else if (end - addr < MIN_LRU_BATCH * PAGE_SIZE / 2)
+ start = end - MIN_LRU_BATCH * PAGE_SIZE;
+ else {
+- start = pvmw->address - MIN_LRU_BATCH * PAGE_SIZE / 2;
+- end = pvmw->address + MIN_LRU_BATCH * PAGE_SIZE / 2;
++ start = addr - MIN_LRU_BATCH * PAGE_SIZE / 2;
++ end = addr + MIN_LRU_BATCH * PAGE_SIZE / 2;
+ }
+ }
+
+- pte = pvmw->pte - (pvmw->address - start) / PAGE_SIZE;
++ /* folio_update_gen() requires stable folio_memcg() */
++ if (!mem_cgroup_trylock_pages(memcg))
++ return;
+
+- rcu_read_lock();
+ arch_enter_lazy_mmu_mode();
+
++ pte -= (addr - start) / PAGE_SIZE;
++
+ for (i = 0, addr = start; addr != end; i++, addr += PAGE_SIZE) {
+ unsigned long pfn;
+
+@@ -4639,56 +4641,27 @@ void lru_gen_look_around(struct page_vma
+ !folio_test_swapcache(folio)))
+ folio_mark_dirty(folio);
+
++ if (walk) {
++ old_gen = folio_update_gen(folio, new_gen);
++ if (old_gen >= 0 && old_gen != new_gen)
++ update_batch_size(walk, folio, old_gen, new_gen);
++
++ continue;
++ }
++
+ old_gen = folio_lru_gen(folio);
+ if (old_gen < 0)
+ folio_set_referenced(folio);
+ else if (old_gen != new_gen)
+- __set_bit(i, bitmap);
++ folio_activate(folio);
+ }
+
+ arch_leave_lazy_mmu_mode();
+- rcu_read_unlock();
++ mem_cgroup_unlock_pages();
+
+ /* feedback from rmap walkers to page table walkers */
+ if (suitable_to_scan(i, young))
+ update_bloom_filter(lruvec, max_seq, pvmw->pmd);
+-
+- if (!walk && bitmap_weight(bitmap, MIN_LRU_BATCH) < PAGEVEC_SIZE) {
+- for_each_set_bit(i, bitmap, MIN_LRU_BATCH) {
+- folio = pfn_folio(pte_pfn(pte[i]));
+- folio_activate(folio);
+- }
+- return;
+- }
+-
+- /* folio_update_gen() requires stable folio_memcg() */
+- if (!mem_cgroup_trylock_pages(memcg))
+- return;
+-
+- if (!walk) {
+- spin_lock_irq(&lruvec->lru_lock);
+- new_gen = lru_gen_from_seq(lruvec->lrugen.max_seq);
+- }
+-
+- for_each_set_bit(i, bitmap, MIN_LRU_BATCH) {
+- folio = pfn_folio(pte_pfn(pte[i]));
+- if (folio_memcg_rcu(folio) != memcg)
+- continue;
+-
+- old_gen = folio_update_gen(folio, new_gen);
+- if (old_gen < 0 || old_gen == new_gen)
+- continue;
+-
+- if (walk)
+- update_batch_size(walk, folio, old_gen, new_gen);
+- else
+- lru_gen_update_size(lruvec, folio, old_gen, new_gen);
+- }
+-
+- if (!walk)
+- spin_unlock_irq(&lruvec->lru_lock);
+-
+- mem_cgroup_unlock_pages();
+ }
+
+ /******************************************************************************
diff --git a/target/linux/generic/backport-6.6/020-v6.4-19-mm-Multi-gen-LRU-remove-wait_event_killable.patch b/target/linux/generic/backport-6.6/020-v6.4-19-mm-Multi-gen-LRU-remove-wait_event_killable.patch
new file mode 100644
index 0000000000..1b0459cdb9
--- /dev/null
+++ b/target/linux/generic/backport-6.6/020-v6.4-19-mm-Multi-gen-LRU-remove-wait_event_killable.patch
@@ -0,0 +1,273 @@
+From 418038c22452df38cde519cc8c662bb15139764a Mon Sep 17 00:00:00 2001
+From: Kalesh Singh <kaleshsingh@google.com>
+Date: Thu, 13 Apr 2023 14:43:26 -0700
+Subject: [PATCH 19/19] mm: Multi-gen LRU: remove wait_event_killable()
+
+Android 14 and later default to MGLRU [1] and field telemetry showed
+occasional long tail latency (>100ms) in the reclaim path.
+
+Tracing revealed priority inversion in the reclaim path. In
+try_to_inc_max_seq(), when high priority tasks were blocked on
+wait_event_killable(), the preemption of the low priority task to call
+wake_up_all() caused those high priority tasks to wait longer than
+necessary. In general, this problem is not different from others of its
+kind, e.g., one caused by mutex_lock(). However, it is specific to MGLRU
+because it introduced the new wait queue lruvec->mm_state.wait.
+
+The purpose of this new wait queue is to avoid the thundering herd
+problem. If many direct reclaimers rush into try_to_inc_max_seq(), only
+one can succeed, i.e., the one to wake up the rest, and the rest who
+failed might cause premature OOM kills if they do not wait. So far there
+is no evidence supporting this scenario, based on how often the wait has
+been hit. And this begs the question how useful the wait queue is in
+practice.
+
+Based on Minchan's recommendation, which is in line with his commit
+6d4675e60135 ("mm: don't be stuck to rmap lock on reclaim path") and the
+rest of the MGLRU code which also uses trylock when possible, remove the
+wait queue.
+
+[1] https://android-review.googlesource.com/q/I7ed7fbfd6ef9ce10053347528125dd98c39e50bf
+
+Link: https://lkml.kernel.org/r/20230413214326.2147568-1-kaleshsingh@google.com
+Fixes: bd74fdaea146 ("mm: multi-gen LRU: support page table walks")
+Signed-off-by: Kalesh Singh <kaleshsingh@google.com>
+Suggested-by: Minchan Kim <minchan@kernel.org>
+Reported-by: Wei Wang <wvw@google.com>
+Acked-by: Yu Zhao <yuzhao@google.com>
+Cc: Minchan Kim <minchan@kernel.org>
+Cc: Jan Alexander Steffens (heftig) <heftig@archlinux.org>
+Cc: Oleksandr Natalenko <oleksandr@natalenko.name>
+Cc: Suleiman Souhlal <suleiman@google.com>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+---
+ include/linux/mmzone.h | 8 +--
+ mm/vmscan.c | 112 +++++++++++++++--------------------------
+ 2 files changed, 42 insertions(+), 78 deletions(-)
+
+--- a/include/linux/mmzone.h
++++ b/include/linux/mmzone.h
+@@ -453,18 +453,14 @@ enum {
+ struct lru_gen_mm_state {
+ /* set to max_seq after each iteration */
+ unsigned long seq;
+- /* where the current iteration continues (inclusive) */
++ /* where the current iteration continues after */
+ struct list_head *head;
+- /* where the last iteration ended (exclusive) */
++ /* where the last iteration ended before */
+ struct list_head *tail;
+- /* to wait for the last page table walker to finish */
+- struct wait_queue_head wait;
+ /* Bloom filters flip after each iteration */
+ unsigned long *filters[NR_BLOOM_FILTERS];
+ /* the mm stats for debugging */
+ unsigned long stats[NR_HIST_GENS][NR_MM_STATS];
+- /* the number of concurrent page table walkers */
+- int nr_walkers;
+ };
+
+ struct lru_gen_mm_walk {
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -3371,18 +3371,13 @@ void lru_gen_del_mm(struct mm_struct *mm
+ if (!lruvec)
+ continue;
+
+- /* where the last iteration ended (exclusive) */
++ /* where the current iteration continues after */
++ if (lruvec->mm_state.head == &mm->lru_gen.list)
++ lruvec->mm_state.head = lruvec->mm_state.head->prev;
++
++ /* where the last iteration ended before */
+ if (lruvec->mm_state.tail == &mm->lru_gen.list)
+ lruvec->mm_state.tail = lruvec->mm_state.tail->next;
+-
+- /* where the current iteration continues (inclusive) */
+- if (lruvec->mm_state.head != &mm->lru_gen.list)
+- continue;
+-
+- lruvec->mm_state.head = lruvec->mm_state.head->next;
+- /* the deletion ends the current iteration */
+- if (lruvec->mm_state.head == &mm_list->fifo)
+- WRITE_ONCE(lruvec->mm_state.seq, lruvec->mm_state.seq + 1);
+ }
+
+ list_del_init(&mm->lru_gen.list);
+@@ -3478,68 +3473,54 @@ static bool iterate_mm_list(struct lruve
+ struct mm_struct **iter)
+ {
+ bool first = false;
+- bool last = true;
++ bool last = false;
+ struct mm_struct *mm = NULL;
+ struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+ struct lru_gen_mm_list *mm_list = get_mm_list(memcg);
+ struct lru_gen_mm_state *mm_state = &lruvec->mm_state;
+
+ /*
+- * There are four interesting cases for this page table walker:
+- * 1. It tries to start a new iteration of mm_list with a stale max_seq;
+- * there is nothing left to do.
+- * 2. It's the first of the current generation, and it needs to reset
+- * the Bloom filter for the next generation.
+- * 3. It reaches the end of mm_list, and it needs to increment
+- * mm_state->seq; the iteration is done.
+- * 4. It's the last of the current generation, and it needs to reset the
+- * mm stats counters for the next generation.
++ * mm_state->seq is incremented after each iteration of mm_list. There
++ * are three interesting cases for this page table walker:
++ * 1. It tries to start a new iteration with a stale max_seq: there is
++ * nothing left to do.
++ * 2. It started the next iteration: it needs to reset the Bloom filter
++ * so that a fresh set of PTE tables can be recorded.
++ * 3. It ended the current iteration: it needs to reset the mm stats
++ * counters and tell its caller to increment max_seq.
+ */
+ spin_lock(&mm_list->lock);
+
+ VM_WARN_ON_ONCE(mm_state->seq + 1 < walk->max_seq);
+- VM_WARN_ON_ONCE(*iter && mm_state->seq > walk->max_seq);
+- VM_WARN_ON_ONCE(*iter && !mm_state->nr_walkers);
+
+- if (walk->max_seq <= mm_state->seq) {
+- if (!*iter)
+- last = false;
++ if (walk->max_seq <= mm_state->seq)
+ goto done;
+- }
+
+- if (!mm_state->nr_walkers) {
+- VM_WARN_ON_ONCE(mm_state->head && mm_state->head != &mm_list->fifo);
++ if (!mm_state->head)
++ mm_state->head = &mm_list->fifo;
+
+- mm_state->head = mm_list->fifo.next;
++ if (mm_state->head == &mm_list->fifo)
+ first = true;
+- }
+-
+- while (!mm && mm_state->head != &mm_list->fifo) {
+- mm = list_entry(mm_state->head, struct mm_struct, lru_gen.list);
+
++ do {
+ mm_state->head = mm_state->head->next;
++ if (mm_state->head == &mm_list->fifo) {
++ WRITE_ONCE(mm_state->seq, mm_state->seq + 1);
++ last = true;
++ break;
++ }
+
+ /* force scan for those added after the last iteration */
+- if (!mm_state->tail || mm_state->tail == &mm->lru_gen.list) {
+- mm_state->tail = mm_state->head;
++ if (!mm_state->tail || mm_state->tail == mm_state->head) {
++ mm_state->tail = mm_state->head->next;
+ walk->force_scan = true;
+ }
+
++ mm = list_entry(mm_state->head, struct mm_struct, lru_gen.list);
+ if (should_skip_mm(mm, walk))
+ mm = NULL;
+- }
+-
+- if (mm_state->head == &mm_list->fifo)
+- WRITE_ONCE(mm_state->seq, mm_state->seq + 1);
++ } while (!mm);
+ done:
+- if (*iter && !mm)
+- mm_state->nr_walkers--;
+- if (!*iter && mm)
+- mm_state->nr_walkers++;
+-
+- if (mm_state->nr_walkers)
+- last = false;
+-
+ if (*iter || last)
+ reset_mm_stats(lruvec, walk, last);
+
+@@ -3567,9 +3548,9 @@ static bool iterate_mm_list_nowalk(struc
+
+ VM_WARN_ON_ONCE(mm_state->seq + 1 < max_seq);
+
+- if (max_seq > mm_state->seq && !mm_state->nr_walkers) {
+- VM_WARN_ON_ONCE(mm_state->head && mm_state->head != &mm_list->fifo);
+-
++ if (max_seq > mm_state->seq) {
++ mm_state->head = NULL;
++ mm_state->tail = NULL;
+ WRITE_ONCE(mm_state->seq, mm_state->seq + 1);
+ reset_mm_stats(lruvec, NULL, true);
+ success = true;
+@@ -4172,10 +4153,6 @@ restart:
+
+ walk_pmd_range(&val, addr, next, args);
+
+- /* a racy check to curtail the waiting time */
+- if (wq_has_sleeper(&walk->lruvec->mm_state.wait))
+- return 1;
+-
+ if (need_resched() || walk->batched >= MAX_LRU_BATCH) {
+ end = (addr | ~PUD_MASK) + 1;
+ goto done;
+@@ -4208,8 +4185,14 @@ static void walk_mm(struct lruvec *lruve
+ walk->next_addr = FIRST_USER_ADDRESS;
+
+ do {
++ DEFINE_MAX_SEQ(lruvec);
++
+ err = -EBUSY;
+
++ /* another thread might have called inc_max_seq() */
++ if (walk->max_seq != max_seq)
++ break;
++
+ /* folio_update_gen() requires stable folio_memcg() */
+ if (!mem_cgroup_trylock_pages(memcg))
+ break;
+@@ -4444,25 +4427,12 @@ static bool try_to_inc_max_seq(struct lr
+ success = iterate_mm_list(lruvec, walk, &mm);
+ if (mm)
+ walk_mm(lruvec, mm, walk);
+-
+- cond_resched();
+ } while (mm);
+ done:
+- if (!success) {
+- if (sc->priority <= DEF_PRIORITY - 2)
+- wait_event_killable(lruvec->mm_state.wait,
+- max_seq < READ_ONCE(lrugen->max_seq));
+- return false;
+- }
++ if (success)
++ inc_max_seq(lruvec, can_swap, force_scan);
+
+- VM_WARN_ON_ONCE(max_seq != READ_ONCE(lrugen->max_seq));
+-
+- inc_max_seq(lruvec, can_swap, force_scan);
+- /* either this sees any waiters or they will see updated max_seq */
+- if (wq_has_sleeper(&lruvec->mm_state.wait))
+- wake_up_all(&lruvec->mm_state.wait);
+-
+- return true;
++ return success;
+ }
+
+ /******************************************************************************
+@@ -6117,7 +6087,6 @@ void lru_gen_init_lruvec(struct lruvec *
+ INIT_LIST_HEAD(&lrugen->folios[gen][type][zone]);
+
+ lruvec->mm_state.seq = MIN_NR_GENS;
+- init_waitqueue_head(&lruvec->mm_state.wait);
+ }
+
+ #ifdef CONFIG_MEMCG
+@@ -6150,7 +6119,6 @@ void lru_gen_exit_memcg(struct mem_cgrou
+ for_each_node(nid) {
+ struct lruvec *lruvec = get_lruvec(memcg, nid);
+
+- VM_WARN_ON_ONCE(lruvec->mm_state.nr_walkers);
+ VM_WARN_ON_ONCE(memchr_inv(lruvec->lrugen.nr_pages, 0,
+ sizeof(lruvec->lrugen.nr_pages)));
+
diff --git a/target/linux/generic/backport-6.6/200-v6.3-bitfield-add-FIELD_PREP_CONST.patch b/target/linux/generic/backport-6.6/200-v6.3-bitfield-add-FIELD_PREP_CONST.patch
new file mode 100644
index 0000000000..2e6d881fe9
--- /dev/null
+++ b/target/linux/generic/backport-6.6/200-v6.3-bitfield-add-FIELD_PREP_CONST.patch
@@ -0,0 +1,58 @@
+From e2192de59e457aef8d1f055a452131f0b3e5d097 Mon Sep 17 00:00:00 2001
+From: Johannes Berg <johannes.berg@intel.com>
+Date: Wed, 18 Jan 2023 14:26:53 +0100
+Subject: [PATCH] bitfield: add FIELD_PREP_CONST()
+
+Neither FIELD_PREP() nor *_encode_bits() can be used
+in constant contexts (such as initializers), but we
+don't want to define shift constants for all masks
+just for use in initializers, and having checks that
+the values fit is also useful.
+
+Therefore, add FIELD_PREP_CONST() which is a smaller
+version of FIELD_PREP() that can only take constant
+arguments and has less friendly (but not less strict)
+error checks, and expands to a constant value.
+
+Signed-off-by: Johannes Berg <johannes.berg@intel.com>
+Link: https://lore.kernel.org/r/20230118142652.53f20593504b.Iaeea0aee77a6493d70e573b4aa55c91c00e01e4b@changeid
+Signed-off-by: Johannes Berg <johannes.berg@intel.com>
+---
+ include/linux/bitfield.h | 26 ++++++++++++++++++++++++++
+ 1 file changed, 26 insertions(+)
+
+--- a/include/linux/bitfield.h
++++ b/include/linux/bitfield.h
+@@ -115,6 +115,32 @@
+ ((typeof(_mask))(_val) << __bf_shf(_mask)) & (_mask); \
+ })
+
++#define __BF_CHECK_POW2(n) BUILD_BUG_ON_ZERO(((n) & ((n) - 1)) != 0)
++
++/**
++ * FIELD_PREP_CONST() - prepare a constant bitfield element
++ * @_mask: shifted mask defining the field's length and position
++ * @_val: value to put in the field
++ *
++ * FIELD_PREP_CONST() masks and shifts up the value. The result should
++ * be combined with other fields of the bitfield using logical OR.
++ *
++ * Unlike FIELD_PREP() this is a constant expression and can therefore
++ * be used in initializers. Error checking is less comfortable for this
++ * version, and non-constant masks cannot be used.
++ */
++#define FIELD_PREP_CONST(_mask, _val) \
++ ( \
++ /* mask must be non-zero */ \
++ BUILD_BUG_ON_ZERO((_mask) == 0) + \
++ /* check if value fits */ \
++ BUILD_BUG_ON_ZERO(~((_mask) >> __bf_shf(_mask)) & (_val)) + \
++ /* check if mask is contiguous */ \
++ __BF_CHECK_POW2((_mask) + (1ULL << __bf_shf(_mask))) + \
++ /* and create the value */ \
++ (((typeof(_mask))(_val) << __bf_shf(_mask)) & (_mask)) \
++ )
++
+ /**
+ * FIELD_GET() - extract a bitfield element
+ * @_mask: shifted mask defining the field's length and position
diff --git a/target/linux/generic/backport-6.6/300-v6.2-powerpc-suppress-some-linker-warnings-in-recent-link.patch b/target/linux/generic/backport-6.6/300-v6.2-powerpc-suppress-some-linker-warnings-in-recent-link.patch
new file mode 100644
index 0000000000..d8d0cf9555
--- /dev/null
+++ b/target/linux/generic/backport-6.6/300-v6.2-powerpc-suppress-some-linker-warnings-in-recent-link.patch
@@ -0,0 +1,63 @@
+From 579aee9fc594af94c242068c011b0233563d4bbf Mon Sep 17 00:00:00 2001
+From: Stephen Rothwell <sfr@canb.auug.org.au>
+Date: Mon, 10 Oct 2022 16:57:21 +1100
+Subject: [PATCH] powerpc: suppress some linker warnings in recent linker
+ versions
+
+This is a follow on from commit
+
+ 0d362be5b142 ("Makefile: link with -z noexecstack --no-warn-rwx-segments")
+
+for arch/powerpc/boot to address wanrings like:
+
+ ld: warning: opal-calls.o: missing .note.GNU-stack section implies executable stack
+ ld: NOTE: This behaviour is deprecated and will be removed in a future version of the linker
+ ld: warning: arch/powerpc/boot/zImage.epapr has a LOAD segment with RWX permissions
+
+This fixes issue https://github.com/linuxppc/issues/issues/417
+
+Signed-off-by: Stephen Rothwell <sfr@canb.auug.org.au>
+Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
+Link: https://lore.kernel.org/r/20221010165721.106267e6@canb.auug.org.au
+---
+ arch/powerpc/boot/wrapper | 15 ++++++++++++++-
+ 1 file changed, 14 insertions(+), 1 deletion(-)
+
+--- a/arch/powerpc/boot/wrapper
++++ b/arch/powerpc/boot/wrapper
+@@ -215,6 +215,11 @@ ld_version()
+ }'
+ }
+
++ld_is_lld()
++{
++ ${CROSS}ld -V 2>&1 | grep -q LLD
++}
++
+ # Do not include PT_INTERP segment when linking pie. Non-pie linking
+ # just ignores this option.
+ LD_VERSION=$(${CROSS}ld --version | ld_version)
+@@ -223,6 +228,14 @@ if [ "$LD_VERSION" -ge "$LD_NO_DL_MIN_VE
+ nodl="--no-dynamic-linker"
+ fi
+
++# suppress some warnings in recent ld versions
++nowarn="-z noexecstack"
++if ! ld_is_lld; then
++ if [ "$LD_VERSION" -ge "$(echo 2.39 | ld_version)" ]; then
++ nowarn="$nowarn --no-warn-rwx-segments"
++ fi
++fi
++
+ platformo=$object/"$platform".o
+ lds=$object/zImage.lds
+ ext=strip
+@@ -504,7 +517,7 @@ if [ "$platform" != "miboot" ]; then
+ text_start="-Ttext $link_address"
+ fi
+ #link everything
+- ${CROSS}ld -m $format -T $lds $text_start $pie $nodl $rodynamic $notext -o "$ofile" $map \
++ ${CROSS}ld -m $format -T $lds $text_start $pie $nodl $nowarn $rodynamic $notext -o "$ofile" $map \
+ $platformo $tmp $object/wrapper.a
+ rm $tmp
+ fi
diff --git a/target/linux/generic/backport-6.6/406-v6.2-0001-mtd-core-simplify-a-bit-code-find-partition-matching.patch b/target/linux/generic/backport-6.6/406-v6.2-0001-mtd-core-simplify-a-bit-code-find-partition-matching.patch
new file mode 100644
index 0000000000..eb6be5ed00
--- /dev/null
+++ b/target/linux/generic/backport-6.6/406-v6.2-0001-mtd-core-simplify-a-bit-code-find-partition-matching.patch
@@ -0,0 +1,65 @@
+From 63db0cb35e1cb3b3c134906d1062f65513fdda2d Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Tue, 4 Oct 2022 10:37:09 +0200
+Subject: [PATCH] mtd: core: simplify (a bit) code find partition-matching
+ dynamic OF node
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+1. Don't hardcode "partition-" string twice
+2. Use simpler logic & use ->name to avoid of_property_read_string()
+3. Use mtd_get_of_node() helper
+
+Cc: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Link: https://lore.kernel.org/linux-mtd/20221004083710.27704-1-zajec5@gmail.com
+---
+ drivers/mtd/mtdcore.c | 16 +++++++---------
+ 1 file changed, 7 insertions(+), 9 deletions(-)
+
+--- a/drivers/mtd/mtdcore.c
++++ b/drivers/mtd/mtdcore.c
+@@ -551,18 +551,16 @@ static void mtd_check_of_node(struct mtd
+ struct device_node *partitions, *parent_dn, *mtd_dn = NULL;
+ const char *pname, *prefix = "partition-";
+ int plen, mtd_name_len, offset, prefix_len;
+- struct mtd_info *parent;
+ bool found = false;
+
+ /* Check if MTD already has a device node */
+- if (dev_of_node(&mtd->dev))
++ if (mtd_get_of_node(mtd))
+ return;
+
+ /* Check if a partitions node exist */
+ if (!mtd_is_partition(mtd))
+ return;
+- parent = mtd->parent;
+- parent_dn = of_node_get(dev_of_node(&parent->dev));
++ parent_dn = of_node_get(mtd_get_of_node(mtd->parent));
+ if (!parent_dn)
+ return;
+
+@@ -575,15 +573,15 @@ static void mtd_check_of_node(struct mtd
+
+ /* Search if a partition is defined with the same name */
+ for_each_child_of_node(partitions, mtd_dn) {
+- offset = 0;
+-
+ /* Skip partition with no/wrong prefix */
+- if (!of_node_name_prefix(mtd_dn, "partition-"))
++ if (!of_node_name_prefix(mtd_dn, prefix))
+ continue;
+
+ /* Label have priority. Check that first */
+- if (of_property_read_string(mtd_dn, "label", &pname)) {
+- of_property_read_string(mtd_dn, "name", &pname);
++ if (!of_property_read_string(mtd_dn, "label", &pname)) {
++ offset = 0;
++ } else {
++ pname = mtd_dn->name;
+ offset = prefix_len;
+ }
+
diff --git a/target/linux/generic/backport-6.6/406-v6.2-0002-mtd-core-try-to-find-OF-node-for-every-MTD-partition.patch b/target/linux/generic/backport-6.6/406-v6.2-0002-mtd-core-try-to-find-OF-node-for-every-MTD-partition.patch
new file mode 100644
index 0000000000..f8d3a370d4
--- /dev/null
+++ b/target/linux/generic/backport-6.6/406-v6.2-0002-mtd-core-try-to-find-OF-node-for-every-MTD-partition.patch
@@ -0,0 +1,84 @@
+From ddb8cefb7af288950447ca6eeeafb09977dab56f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Tue, 4 Oct 2022 10:37:10 +0200
+Subject: [PATCH] mtd: core: try to find OF node for every MTD partition
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+So far this feature was limited to the top-level "nvmem-cells" node.
+There are multiple parsers creating partitions and subpartitions
+dynamically. Extend that code to handle them too.
+
+This allows finding partition-* node for every MTD (sub)partition.
+
+Random example:
+
+partitions {
+ compatible = "brcm,bcm947xx-cfe-partitions";
+
+ partition-firmware {
+ compatible = "brcm,trx";
+
+ partition-loader {
+ };
+ };
+};
+
+Cc: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Link: https://lore.kernel.org/linux-mtd/20221004083710.27704-2-zajec5@gmail.com
+---
+ drivers/mtd/mtdcore.c | 18 ++++++------------
+ 1 file changed, 6 insertions(+), 12 deletions(-)
+
+--- a/drivers/mtd/mtdcore.c
++++ b/drivers/mtd/mtdcore.c
+@@ -551,20 +551,22 @@ static void mtd_check_of_node(struct mtd
+ struct device_node *partitions, *parent_dn, *mtd_dn = NULL;
+ const char *pname, *prefix = "partition-";
+ int plen, mtd_name_len, offset, prefix_len;
+- bool found = false;
+
+ /* Check if MTD already has a device node */
+ if (mtd_get_of_node(mtd))
+ return;
+
+- /* Check if a partitions node exist */
+ if (!mtd_is_partition(mtd))
+ return;
++
+ parent_dn = of_node_get(mtd_get_of_node(mtd->parent));
+ if (!parent_dn)
+ return;
+
+- partitions = of_get_child_by_name(parent_dn, "partitions");
++ if (mtd_is_partition(mtd->parent))
++ partitions = of_node_get(parent_dn);
++ else
++ partitions = of_get_child_by_name(parent_dn, "partitions");
+ if (!partitions)
+ goto exit_parent;
+
+@@ -588,19 +590,11 @@ static void mtd_check_of_node(struct mtd
+ plen = strlen(pname) - offset;
+ if (plen == mtd_name_len &&
+ !strncmp(mtd->name, pname + offset, plen)) {
+- found = true;
++ mtd_set_of_node(mtd, mtd_dn);
+ break;
+ }
+ }
+
+- if (!found)
+- goto exit_partitions;
+-
+- /* Set of_node only for nvmem */
+- if (of_device_is_compatible(mtd_dn, "nvmem-cells"))
+- mtd_set_of_node(mtd, mtd_dn);
+-
+-exit_partitions:
+ of_node_put(partitions);
+ exit_parent:
+ of_node_put(parent_dn);
diff --git a/target/linux/generic/backport-6.6/408-v6.2-mtd-core-set-ROOT_DEV-for-partitions-marked-as-rootf.patch b/target/linux/generic/backport-6.6/408-v6.2-mtd-core-set-ROOT_DEV-for-partitions-marked-as-rootf.patch
new file mode 100644
index 0000000000..5a5e11c8f7
--- /dev/null
+++ b/target/linux/generic/backport-6.6/408-v6.2-mtd-core-set-ROOT_DEV-for-partitions-marked-as-rootf.patch
@@ -0,0 +1,47 @@
+From 26422ac78e9d8767bd4aabfbae616b15edbf6a1b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Sat, 22 Oct 2022 23:13:18 +0200
+Subject: [PATCH] mtd: core: set ROOT_DEV for partitions marked as rootfs in DT
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This adds support for "linux,rootfs" binding that is used to mark flash
+partition containing rootfs. It's useful for devices using device tree
+that don't have bootloader passing root info in cmdline.
+
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Link: https://lore.kernel.org/linux-mtd/20221022211318.32009-2-zajec5@gmail.com
+---
+ drivers/mtd/mtdcore.c | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+
+--- a/drivers/mtd/mtdcore.c
++++ b/drivers/mtd/mtdcore.c
+@@ -28,6 +28,7 @@
+ #include <linux/leds.h>
+ #include <linux/debugfs.h>
+ #include <linux/nvmem-provider.h>
++#include <linux/root_dev.h>
+
+ #include <linux/mtd/mtd.h>
+ #include <linux/mtd/partitions.h>
+@@ -737,6 +738,17 @@ int add_mtd_device(struct mtd_info *mtd)
+ not->add(mtd);
+
+ mutex_unlock(&mtd_table_mutex);
++
++ if (of_find_property(mtd_get_of_node(mtd), "linux,rootfs", NULL)) {
++ if (IS_BUILTIN(CONFIG_MTD)) {
++ pr_info("mtd: setting mtd%d (%s) as root device\n", mtd->index, mtd->name);
++ ROOT_DEV = MKDEV(MTD_BLOCK_MAJOR, mtd->index);
++ } else {
++ pr_warn("mtd: can't set mtd%d (%s) as root device - mtd must be builtin\n",
++ mtd->index, mtd->name);
++ }
++ }
++
+ /* We _know_ we aren't being removed, because
+ our caller is still holding us here. So none
+ of this try_ nonsense, and no bitching about it
diff --git a/target/linux/generic/backport-6.6/421-v6.2-mtd-parsers-add-TP-Link-SafeLoader-partitions-table-.patch b/target/linux/generic/backport-6.6/421-v6.2-mtd-parsers-add-TP-Link-SafeLoader-partitions-table-.patch
new file mode 100644
index 0000000000..e570564a0b
--- /dev/null
+++ b/target/linux/generic/backport-6.6/421-v6.2-mtd-parsers-add-TP-Link-SafeLoader-partitions-table-.patch
@@ -0,0 +1,229 @@
+From aec4d5f5ffd0f0092bd9dc21ea90e0bc237d4b74 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Sat, 15 Oct 2022 11:29:50 +0200
+Subject: [PATCH] mtd: parsers: add TP-Link SafeLoader partitions table parser
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This parser deals with most TP-Link home routers. It reads info about
+partitions and registers them in the MTD subsystem.
+
+Example from TP-Link Archer C5 V2:
+
+spi-nor spi0.0: s25fl128s1 (16384 Kbytes)
+15 tplink-safeloader partitions found on MTD device spi0.0
+Creating 15 MTD partitions on "spi0.0":
+0x000000000000-0x000000040000 : "fs-uboot"
+0x000000040000-0x000000440000 : "os-image"
+0x000000440000-0x000000e40000 : "rootfs"
+0x000000e40000-0x000000e40200 : "default-mac"
+0x000000e40200-0x000000e40400 : "pin"
+0x000000e40400-0x000000e40600 : "product-info"
+0x000000e50000-0x000000e60000 : "partition-table"
+0x000000e60000-0x000000e60200 : "soft-version"
+0x000000e61000-0x000000e70000 : "support-list"
+0x000000e70000-0x000000e80000 : "profile"
+0x000000e80000-0x000000e90000 : "default-config"
+0x000000e90000-0x000000ee0000 : "user-config"
+0x000000ee0000-0x000000fe0000 : "log"
+0x000000fe0000-0x000000ff0000 : "radio_bk"
+0x000000ff0000-0x000001000000 : "radio"
+
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Link: https://lore.kernel.org/linux-mtd/20221015092950.27467-2-zajec5@gmail.com
+---
+ drivers/mtd/parsers/Kconfig | 15 +++
+ drivers/mtd/parsers/Makefile | 1 +
+ drivers/mtd/parsers/tplink_safeloader.c | 150 ++++++++++++++++++++++++
+ 3 files changed, 166 insertions(+)
+ create mode 100644 drivers/mtd/parsers/tplink_safeloader.c
+
+--- a/drivers/mtd/parsers/Kconfig
++++ b/drivers/mtd/parsers/Kconfig
+@@ -123,6 +123,21 @@ config MTD_AFS_PARTS
+ for your particular device. It won't happen automatically. The
+ 'physmap' map driver (CONFIG_MTD_PHYSMAP) does this, for example.
+
++config MTD_PARSER_TPLINK_SAFELOADER
++ tristate "TP-Link Safeloader partitions parser"
++ depends on MTD && (ARCH_BCM_5301X || ATH79 || SOC_MT7620 || SOC_MT7621 || COMPILE_TEST)
++ help
++ TP-Link home routers use flash partitions to store various data. Info
++ about flash space layout is stored in a partitions table using a
++ custom ASCII-based format.
++
++ That format was first found in devices with SafeLoader bootloader and
++ was named after it. Later it was adapted to CFE and U-Boot
++ bootloaders.
++
++ This driver reads partitions table, parses it and creates MTD
++ partitions.
++
+ config MTD_PARSER_TRX
+ tristate "Parser for TRX format partitions"
+ depends on MTD && (BCM47XX || ARCH_BCM_5301X || ARCH_MEDIATEK || RALINK || COMPILE_TEST)
+--- a/drivers/mtd/parsers/Makefile
++++ b/drivers/mtd/parsers/Makefile
+@@ -10,6 +10,7 @@ ofpart-$(CONFIG_MTD_OF_PARTS_BCM4908) +=
+ ofpart-$(CONFIG_MTD_OF_PARTS_LINKSYS_NS)+= ofpart_linksys_ns.o
+ obj-$(CONFIG_MTD_PARSER_IMAGETAG) += parser_imagetag.o
+ obj-$(CONFIG_MTD_AFS_PARTS) += afs.o
++obj-$(CONFIG_MTD_PARSER_TPLINK_SAFELOADER) += tplink_safeloader.o
+ obj-$(CONFIG_MTD_PARSER_TRX) += parser_trx.o
+ obj-$(CONFIG_MTD_SERCOMM_PARTS) += scpart.o
+ obj-$(CONFIG_MTD_SHARPSL_PARTS) += sharpslpart.o
+--- /dev/null
++++ b/drivers/mtd/parsers/tplink_safeloader.c
+@@ -0,0 +1,150 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright © 2022 Rafał Miłecki <rafal@milecki.pl>
++ */
++
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/mtd/mtd.h>
++#include <linux/mtd/partitions.h>
++#include <linux/of.h>
++#include <linux/slab.h>
++
++#define TPLINK_SAFELOADER_DATA_OFFSET 4
++#define TPLINK_SAFELOADER_MAX_PARTS 32
++
++struct safeloader_cmn_header {
++ __be32 size;
++ uint32_t unused;
++} __packed;
++
++static void *mtd_parser_tplink_safeloader_read_table(struct mtd_info *mtd)
++{
++ struct safeloader_cmn_header hdr;
++ struct device_node *np;
++ size_t bytes_read;
++ size_t offset;
++ size_t size;
++ char *buf;
++ int err;
++
++ np = mtd_get_of_node(mtd);
++ if (mtd_is_partition(mtd))
++ of_node_get(np);
++ else
++ np = of_get_child_by_name(np, "partitions");
++
++ if (of_property_read_u32(np, "partitions-table-offset", (u32 *)&offset)) {
++ pr_err("Failed to get partitions table offset\n");
++ goto err_put;
++ }
++
++ err = mtd_read(mtd, offset, sizeof(hdr), &bytes_read, (uint8_t *)&hdr);
++ if (err && !mtd_is_bitflip(err)) {
++ pr_err("Failed to read from %s at 0x%zx\n", mtd->name, offset);
++ goto err_put;
++ }
++
++ size = be32_to_cpu(hdr.size);
++
++ buf = kmalloc(size + 1, GFP_KERNEL);
++ if (!buf)
++ goto err_put;
++
++ err = mtd_read(mtd, offset + sizeof(hdr), size, &bytes_read, buf);
++ if (err && !mtd_is_bitflip(err)) {
++ pr_err("Failed to read from %s at 0x%zx\n", mtd->name, offset + sizeof(hdr));
++ goto err_kfree;
++ }
++
++ buf[size] = '\0';
++
++ of_node_put(np);
++
++ return buf;
++
++err_kfree:
++ kfree(buf);
++err_put:
++ of_node_put(np);
++ return NULL;
++}
++
++static int mtd_parser_tplink_safeloader_parse(struct mtd_info *mtd,
++ const struct mtd_partition **pparts,
++ struct mtd_part_parser_data *data)
++{
++ struct mtd_partition *parts;
++ char name[65];
++ size_t offset;
++ size_t bytes;
++ char *buf;
++ int idx;
++ int err;
++
++ parts = kcalloc(TPLINK_SAFELOADER_MAX_PARTS, sizeof(*parts), GFP_KERNEL);
++ if (!parts) {
++ err = -ENOMEM;
++ goto err_out;
++ }
++
++ buf = mtd_parser_tplink_safeloader_read_table(mtd);
++ if (!buf) {
++ err = -ENOENT;
++ goto err_out;
++ }
++
++ for (idx = 0, offset = TPLINK_SAFELOADER_DATA_OFFSET;
++ idx < TPLINK_SAFELOADER_MAX_PARTS &&
++ sscanf(buf + offset, "partition %64s base 0x%llx size 0x%llx%zn\n",
++ name, &parts[idx].offset, &parts[idx].size, &bytes) == 3;
++ idx++, offset += bytes + 1) {
++ parts[idx].name = kstrdup(name, GFP_KERNEL);
++ if (!parts[idx].name) {
++ err = -ENOMEM;
++ goto err_free;
++ }
++ }
++
++ if (idx == TPLINK_SAFELOADER_MAX_PARTS)
++ pr_warn("Reached maximum number of partitions!\n");
++
++ kfree(buf);
++
++ *pparts = parts;
++
++ return idx;
++
++err_free:
++ for (idx -= 1; idx >= 0; idx--)
++ kfree(parts[idx].name);
++err_out:
++ return err;
++};
++
++static void mtd_parser_tplink_safeloader_cleanup(const struct mtd_partition *pparts,
++ int nr_parts)
++{
++ int i;
++
++ for (i = 0; i < nr_parts; i++)
++ kfree(pparts[i].name);
++
++ kfree(pparts);
++}
++
++static const struct of_device_id mtd_parser_tplink_safeloader_of_match_table[] = {
++ { .compatible = "tplink,safeloader-partitions" },
++ {},
++};
++MODULE_DEVICE_TABLE(of, mtd_parser_tplink_safeloader_of_match_table);
++
++static struct mtd_part_parser mtd_parser_tplink_safeloader = {
++ .parse_fn = mtd_parser_tplink_safeloader_parse,
++ .cleanup = mtd_parser_tplink_safeloader_cleanup,
++ .name = "tplink-safeloader",
++ .of_match_table = mtd_parser_tplink_safeloader_of_match_table,
++};
++module_mtd_part_parser(mtd_parser_tplink_safeloader);
++
++MODULE_LICENSE("GPL");
diff --git a/target/linux/generic/backport-6.6/423-v6.3-mtd-spinand-macronix-use-scratch-buffer-for-DMA-oper.patch b/target/linux/generic/backport-6.6/423-v6.3-mtd-spinand-macronix-use-scratch-buffer-for-DMA-oper.patch
new file mode 100644
index 0000000000..7dbc271725
--- /dev/null
+++ b/target/linux/generic/backport-6.6/423-v6.3-mtd-spinand-macronix-use-scratch-buffer-for-DMA-oper.patch
@@ -0,0 +1,35 @@
+From ebed787a0becb9354f0a23620a5130cccd6c730c Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 19 Jan 2023 03:45:43 +0000
+Subject: [PATCH] mtd: spinand: macronix: use scratch buffer for DMA operation
+
+The mx35lf1ge4ab_get_eccsr() function uses an SPI DMA operation to
+read the eccsr, hence the buffer should not be on stack. Since commit
+380583227c0c7f ("spi: spi-mem: Add extra sanity checks on the op param")
+the kernel emmits a warning and blocks such operations.
+
+Use the scratch buffer to get eccsr instead of trying to directly read
+into a stack-allocated variable.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Dhruva Gole <d-gole@ti.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Link: https://lore.kernel.org/linux-mtd/Y8i85zM0u4XdM46z@makrotopia.org
+---
+ drivers/mtd/nand/spi/macronix.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/mtd/nand/spi/macronix.c
++++ b/drivers/mtd/nand/spi/macronix.c
+@@ -83,9 +83,10 @@ static int mx35lf1ge4ab_ecc_get_status(s
+ * in order to avoid forcing the wear-leveling layer to move
+ * data around if it's not necessary.
+ */
+- if (mx35lf1ge4ab_get_eccsr(spinand, &eccsr))
++ if (mx35lf1ge4ab_get_eccsr(spinand, spinand->scratchbuf))
+ return nanddev_get_ecc_conf(nand)->strength;
+
++ eccsr = *spinand->scratchbuf;
+ if (WARN_ON(eccsr > nanddev_get_ecc_conf(nand)->strength ||
+ !eccsr))
+ return nanddev_get_ecc_conf(nand)->strength;
diff --git a/target/linux/generic/backport-6.6/424-v6.3-mtd-ubi-wire-up-parent-MTD-device.patch b/target/linux/generic/backport-6.6/424-v6.3-mtd-ubi-wire-up-parent-MTD-device.patch
new file mode 100644
index 0000000000..657404196d
--- /dev/null
+++ b/target/linux/generic/backport-6.6/424-v6.3-mtd-ubi-wire-up-parent-MTD-device.patch
@@ -0,0 +1,33 @@
+From 1ecf9e390452e73a362ea7fbde8f3f0db83de856 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 22 Dec 2022 19:33:04 +0000
+Subject: [PATCH] mtd: ubi: wire-up parent MTD device
+
+Wire up the device parent pointer of UBI devices to their lower MTD
+device, typically an MTD partition or whole-chip device.
+
+The most noticeable change is that in sysfs, previously ubi devices
+would be could in /sys/devices/virtual/ubi while after this change they
+would be correctly attached to their parent MTD device, e.g.
+
+/sys/devices/platform/1100d000.spi/spi_master/spi1/spi1.0/mtd/mtd2/ubi0.
+
+Locating UBI devices using /sys/class/ubi/ of course still works as
+well.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: Richard Weinberger <richard@nod.at>
+---
+ drivers/mtd/ubi/build.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/mtd/ubi/build.c
++++ b/drivers/mtd/ubi/build.c
+@@ -929,6 +929,7 @@ int ubi_attach_mtd_dev(struct mtd_info *
+ ubi->dev.release = dev_release;
+ ubi->dev.class = &ubi_class;
+ ubi->dev.groups = ubi_dev_groups;
++ ubi->dev.parent = &mtd->dev;
+
+ ubi->mtd = mtd;
+ ubi->ubi_num = ubi_num;
diff --git a/target/linux/generic/backport-6.6/425-v6.3-mtd-ubi-block-wire-up-device-parent.patch b/target/linux/generic/backport-6.6/425-v6.3-mtd-ubi-block-wire-up-device-parent.patch
new file mode 100644
index 0000000000..48bf986118
--- /dev/null
+++ b/target/linux/generic/backport-6.6/425-v6.3-mtd-ubi-block-wire-up-device-parent.patch
@@ -0,0 +1,49 @@
+From 05b8773ca33253ea562be145cf3145b05ef19f86 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 22 Dec 2022 19:33:31 +0000
+Subject: [PATCH] mtd: ubi: block: wire-up device parent
+
+ubiblock devices were previously only identifyable by their name, but
+not connected to their parent UBI volume device e.g. in sysfs.
+Properly parent ubiblock device as descendant of a UBI volume device
+to reflect device model hierachy.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: Richard Weinberger <richard@nod.at>
+---
+ drivers/mtd/ubi/block.c | 2 +-
+ drivers/mtd/ubi/kapi.c | 1 +
+ include/linux/mtd/ubi.h | 1 +
+ 3 files changed, 3 insertions(+), 1 deletion(-)
+
+--- a/drivers/mtd/ubi/block.c
++++ b/drivers/mtd/ubi/block.c
+@@ -452,7 +452,7 @@ int ubiblock_create(struct ubi_volume_in
+ list_add_tail(&dev->list, &ubiblock_devices);
+
+ /* Must be the last step: anyone can call file ops from now on */
+- ret = add_disk(dev->gd);
++ ret = device_add_disk(vi->dev, dev->gd, NULL);
+ if (ret)
+ goto out_destroy_wq;
+
+--- a/drivers/mtd/ubi/kapi.c
++++ b/drivers/mtd/ubi/kapi.c
+@@ -79,6 +79,7 @@ void ubi_do_get_volume_info(struct ubi_d
+ vi->name_len = vol->name_len;
+ vi->name = vol->name;
+ vi->cdev = vol->cdev.dev;
++ vi->dev = &vol->dev;
+ }
+
+ /**
+--- a/include/linux/mtd/ubi.h
++++ b/include/linux/mtd/ubi.h
+@@ -110,6 +110,7 @@ struct ubi_volume_info {
+ int name_len;
+ const char *name;
+ dev_t cdev;
++ struct device *dev;
+ };
+
+ /**
diff --git a/target/linux/generic/backport-6.6/426-v6.4-0004-mtd-core-prepare-mtd_otp_nvmem_add-to-handle-EPROBE_.patch b/target/linux/generic/backport-6.6/426-v6.4-0004-mtd-core-prepare-mtd_otp_nvmem_add-to-handle-EPROBE_.patch
new file mode 100644
index 0000000000..9ddda420ac
--- /dev/null
+++ b/target/linux/generic/backport-6.6/426-v6.4-0004-mtd-core-prepare-mtd_otp_nvmem_add-to-handle-EPROBE_.patch
@@ -0,0 +1,47 @@
+From 281f7a6c1a33fffcde32001bacbb4f672140fbf9 Mon Sep 17 00:00:00 2001
+From: Michael Walle <michael@walle.cc>
+Date: Wed, 8 Mar 2023 09:20:21 +0100
+Subject: [PATCH] mtd: core: prepare mtd_otp_nvmem_add() to handle
+ -EPROBE_DEFER
+
+NVMEM soon will get the ability for nvmem layouts and these might
+not be ready when nvmem_register() is called and thus it might
+return -EPROBE_DEFER. Don't print the error message in this case.
+
+Signed-off-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Link: https://lore.kernel.org/linux-mtd/20230308082021.870459-4-michael@walle.cc
+---
+ drivers/mtd/mtdcore.c | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+--- a/drivers/mtd/mtdcore.c
++++ b/drivers/mtd/mtdcore.c
+@@ -953,8 +953,8 @@ static int mtd_otp_nvmem_add(struct mtd_
+ nvmem = mtd_otp_nvmem_register(mtd, "user-otp", size,
+ mtd_nvmem_user_otp_reg_read);
+ if (IS_ERR(nvmem)) {
+- dev_err(dev, "Failed to register OTP NVMEM device\n");
+- return PTR_ERR(nvmem);
++ err = PTR_ERR(nvmem);
++ goto err;
+ }
+ mtd->otp_user_nvmem = nvmem;
+ }
+@@ -971,7 +971,6 @@ static int mtd_otp_nvmem_add(struct mtd_
+ nvmem = mtd_otp_nvmem_register(mtd, "factory-otp", size,
+ mtd_nvmem_fact_otp_reg_read);
+ if (IS_ERR(nvmem)) {
+- dev_err(dev, "Failed to register OTP NVMEM device\n");
+ err = PTR_ERR(nvmem);
+ goto err;
+ }
+@@ -983,7 +982,7 @@ static int mtd_otp_nvmem_add(struct mtd_
+
+ err:
+ nvmem_unregister(mtd->otp_user_nvmem);
+- return err;
++ return dev_err_probe(dev, err, "Failed to register OTP NVMEM device\n");
+ }
+
+ /**
diff --git a/target/linux/generic/backport-6.6/611-v6.3-net-add-helper-eth_addr_add.patch b/target/linux/generic/backport-6.6/611-v6.3-net-add-helper-eth_addr_add.patch
new file mode 100644
index 0000000000..28b7b4383e
--- /dev/null
+++ b/target/linux/generic/backport-6.6/611-v6.3-net-add-helper-eth_addr_add.patch
@@ -0,0 +1,41 @@
+From 7390609b0121a1b982c5ecdfcd72dc328e5784ee Mon Sep 17 00:00:00 2001
+From: Michael Walle <michael@walle.cc>
+Date: Mon, 6 Feb 2023 13:43:42 +0000
+Subject: [PATCH] net: add helper eth_addr_add()
+
+Add a helper to add an offset to a ethernet address. This comes in handy
+if you have a base ethernet address for multiple interfaces.
+
+Signed-off-by: Michael Walle <michael@walle.cc>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Acked-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230206134356.839737-9-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/etherdevice.h | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+--- a/include/linux/etherdevice.h
++++ b/include/linux/etherdevice.h
+@@ -508,6 +508,20 @@ static inline void eth_addr_inc(u8 *addr
+ }
+
+ /**
++ * eth_addr_add() - Add (or subtract) an offset to/from the given MAC address.
++ *
++ * @offset: Offset to add.
++ * @addr: Pointer to a six-byte array containing Ethernet address to increment.
++ */
++static inline void eth_addr_add(u8 *addr, long offset)
++{
++ u64 u = ether_addr_to_u64(addr);
++
++ u += offset;
++ u64_to_ether_addr(u, addr);
++}
++
++/**
+ * is_etherdev_addr - Tell if given Ethernet address belongs to the device.
+ * @dev: Pointer to a device structure
+ * @addr: Pointer to a six-byte array containing the Ethernet address
diff --git a/target/linux/generic/backport-6.6/701-net-next-net-sfp-add-quirk-for-Fiberstone-GPON-ONU-34-20BI.patch b/target/linux/generic/backport-6.6/701-net-next-net-sfp-add-quirk-for-Fiberstone-GPON-ONU-34-20BI.patch
new file mode 100644
index 0000000000..56e14c5c0a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/701-net-next-net-sfp-add-quirk-for-Fiberstone-GPON-ONU-34-20BI.patch
@@ -0,0 +1,32 @@
+From d387e34fec407f881fdf165b5d7ec128ebff362f Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 19 Sep 2023 14:47:20 +0200
+Subject: [PATCH] net: sfp: add quirk for Fiberstone GPON-ONU-34-20BI
+
+Fiberstone GPON-ONU-34-20B can operate at 2500base-X, but report 1.2GBd
+NRZ in their EEPROM.
+
+The module also require the ignore tx fault fixup similar to Huawei MA5671A
+as it gets disabled on error messages with serial redirection enabled.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Link: https://lore.kernel.org/r/20230919124720.8210-1-ansuelsmth@gmail.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/sfp.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -393,6 +393,11 @@ static const struct sfp_quirk sfp_quirks
+ SFP_QUIRK("ALCATELLUCENT", "3FE46541AA", sfp_quirk_2500basex,
+ sfp_fixup_long_startup),
+
++ // Fiberstore GPON-ONU-34-20BI can operate at 2500base-X, but report 1.2GBd
++ // NRZ in their EEPROM
++ SFP_QUIRK("FS", "GPON-ONU-34-20BI", sfp_quirk_2500basex,
++ sfp_fixup_ignore_tx_fault),
++
+ SFP_QUIRK_F("HALNy", "HL-GSFP", sfp_fixup_halny_gsfp),
+
+ // HG MXPD-483II-F 2.5G supports 2500Base-X, but incorrectly reports
diff --git a/target/linux/generic/backport-6.6/702-01-v6.7-net-phy-aquantia-move-to-separate-directory.patch b/target/linux/generic/backport-6.6/702-01-v6.7-net-phy-aquantia-move-to-separate-directory.patch
new file mode 100644
index 0000000000..0cad6c53d4
--- /dev/null
+++ b/target/linux/generic/backport-6.6/702-01-v6.7-net-phy-aquantia-move-to-separate-directory.patch
@@ -0,0 +1,2306 @@
+From d2213db3f49bce8e7a87c8de05b9a091f78f654e Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 14 Nov 2023 15:08:41 +0100
+Subject: [PATCH 1/3] net: phy: aquantia: move to separate directory
+
+Move aquantia PHY driver to separate driectory in preparation for
+firmware loading support to keep things tidy.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/Kconfig | 5 +----
+ drivers/net/phy/Makefile | 6 +-----
+ drivers/net/phy/aquantia/Kconfig | 5 +++++
+ drivers/net/phy/aquantia/Makefile | 6 ++++++
+ drivers/net/phy/{ => aquantia}/aquantia.h | 0
+ drivers/net/phy/{ => aquantia}/aquantia_hwmon.c | 0
+ drivers/net/phy/{ => aquantia}/aquantia_main.c | 0
+ 7 files changed, 13 insertions(+), 9 deletions(-)
+ create mode 100644 drivers/net/phy/aquantia/Kconfig
+ create mode 100644 drivers/net/phy/aquantia/Makefile
+ rename drivers/net/phy/{ => aquantia}/aquantia.h (100%)
+ rename drivers/net/phy/{ => aquantia}/aquantia_hwmon.c (100%)
+ rename drivers/net/phy/{ => aquantia}/aquantia_main.c (100%)
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -90,10 +90,7 @@ config ADIN1100_PHY
+ Currently supports the:
+ - ADIN1100 - Robust,Industrial, Low Power 10BASE-T1L Ethernet PHY
+
+-config AQUANTIA_PHY
+- tristate "Aquantia PHYs"
+- help
+- Currently supports the Aquantia AQ1202, AQ2104, AQR105, AQR405
++source "drivers/net/phy/aquantia/Kconfig"
+
+ config AX88796B_PHY
+ tristate "Asix PHYs"
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -33,11 +33,7 @@ obj-y += $(sfp-obj-y) $(sfp-obj-m)
+ obj-$(CONFIG_ADIN_PHY) += adin.o
+ obj-$(CONFIG_ADIN1100_PHY) += adin1100.o
+ obj-$(CONFIG_AMD_PHY) += amd.o
+-aquantia-objs += aquantia_main.o
+-ifdef CONFIG_HWMON
+-aquantia-objs += aquantia_hwmon.o
+-endif
+-obj-$(CONFIG_AQUANTIA_PHY) += aquantia.o
++obj-$(CONFIG_AQUANTIA_PHY) += aquantia/
+ obj-$(CONFIG_AT803X_PHY) += at803x.o
+ obj-$(CONFIG_AX88796B_PHY) += ax88796b.o
+ obj-$(CONFIG_BCM54140_PHY) += bcm54140.o
+--- /dev/null
++++ b/drivers/net/phy/aquantia/Kconfig
+@@ -0,0 +1,5 @@
++# SPDX-License-Identifier: GPL-2.0-only
++config AQUANTIA_PHY
++ tristate "Aquantia PHYs"
++ help
++ Currently supports the Aquantia AQ1202, AQ2104, AQR105, AQR405
+--- /dev/null
++++ b/drivers/net/phy/aquantia/Makefile
+@@ -0,0 +1,6 @@
++# SPDX-License-Identifier: GPL-2.0
++aquantia-objs += aquantia_main.o
++ifdef CONFIG_HWMON
++aquantia-objs += aquantia_hwmon.o
++endif
++obj-$(CONFIG_AQUANTIA_PHY) += aquantia.o
+--- a/drivers/net/phy/aquantia.h
++++ /dev/null
+@@ -1,16 +0,0 @@
+-/* SPDX-License-Identifier: GPL-2.0 */
+-/* HWMON driver for Aquantia PHY
+- *
+- * Author: Nikita Yushchenko <nikita.yoush@cogentembedded.com>
+- * Author: Andrew Lunn <andrew@lunn.ch>
+- * Author: Heiner Kallweit <hkallweit1@gmail.com>
+- */
+-
+-#include <linux/device.h>
+-#include <linux/phy.h>
+-
+-#if IS_REACHABLE(CONFIG_HWMON)
+-int aqr_hwmon_probe(struct phy_device *phydev);
+-#else
+-static inline int aqr_hwmon_probe(struct phy_device *phydev) { return 0; }
+-#endif
+--- /dev/null
++++ b/drivers/net/phy/aquantia/aquantia.h
+@@ -0,0 +1,16 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++/* HWMON driver for Aquantia PHY
++ *
++ * Author: Nikita Yushchenko <nikita.yoush@cogentembedded.com>
++ * Author: Andrew Lunn <andrew@lunn.ch>
++ * Author: Heiner Kallweit <hkallweit1@gmail.com>
++ */
++
++#include <linux/device.h>
++#include <linux/phy.h>
++
++#if IS_REACHABLE(CONFIG_HWMON)
++int aqr_hwmon_probe(struct phy_device *phydev);
++#else
++static inline int aqr_hwmon_probe(struct phy_device *phydev) { return 0; }
++#endif
+--- /dev/null
++++ b/drivers/net/phy/aquantia/aquantia_hwmon.c
+@@ -0,0 +1,250 @@
++// SPDX-License-Identifier: GPL-2.0
++/* HWMON driver for Aquantia PHY
++ *
++ * Author: Nikita Yushchenko <nikita.yoush@cogentembedded.com>
++ * Author: Andrew Lunn <andrew@lunn.ch>
++ * Author: Heiner Kallweit <hkallweit1@gmail.com>
++ */
++
++#include <linux/phy.h>
++#include <linux/device.h>
++#include <linux/ctype.h>
++#include <linux/hwmon.h>
++
++#include "aquantia.h"
++
++/* Vendor specific 1, MDIO_MMD_VEND2 */
++#define VEND1_THERMAL_PROV_HIGH_TEMP_FAIL 0xc421
++#define VEND1_THERMAL_PROV_LOW_TEMP_FAIL 0xc422
++#define VEND1_THERMAL_PROV_HIGH_TEMP_WARN 0xc423
++#define VEND1_THERMAL_PROV_LOW_TEMP_WARN 0xc424
++#define VEND1_THERMAL_STAT1 0xc820
++#define VEND1_THERMAL_STAT2 0xc821
++#define VEND1_THERMAL_STAT2_VALID BIT(0)
++#define VEND1_GENERAL_STAT1 0xc830
++#define VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL BIT(14)
++#define VEND1_GENERAL_STAT1_LOW_TEMP_FAIL BIT(13)
++#define VEND1_GENERAL_STAT1_HIGH_TEMP_WARN BIT(12)
++#define VEND1_GENERAL_STAT1_LOW_TEMP_WARN BIT(11)
++
++#if IS_REACHABLE(CONFIG_HWMON)
++
++static umode_t aqr_hwmon_is_visible(const void *data,
++ enum hwmon_sensor_types type,
++ u32 attr, int channel)
++{
++ if (type != hwmon_temp)
++ return 0;
++
++ switch (attr) {
++ case hwmon_temp_input:
++ case hwmon_temp_min_alarm:
++ case hwmon_temp_max_alarm:
++ case hwmon_temp_lcrit_alarm:
++ case hwmon_temp_crit_alarm:
++ return 0444;
++ case hwmon_temp_min:
++ case hwmon_temp_max:
++ case hwmon_temp_lcrit:
++ case hwmon_temp_crit:
++ return 0644;
++ default:
++ return 0;
++ }
++}
++
++static int aqr_hwmon_get(struct phy_device *phydev, int reg, long *value)
++{
++ int temp = phy_read_mmd(phydev, MDIO_MMD_VEND1, reg);
++
++ if (temp < 0)
++ return temp;
++
++ /* 16 bit value is 2's complement with LSB = 1/256th degree Celsius */
++ *value = (s16)temp * 1000 / 256;
++
++ return 0;
++}
++
++static int aqr_hwmon_set(struct phy_device *phydev, int reg, long value)
++{
++ int temp;
++
++ if (value >= 128000 || value < -128000)
++ return -ERANGE;
++
++ temp = value * 256 / 1000;
++
++ /* temp is in s16 range and we're interested in lower 16 bits only */
++ return phy_write_mmd(phydev, MDIO_MMD_VEND1, reg, (u16)temp);
++}
++
++static int aqr_hwmon_test_bit(struct phy_device *phydev, int reg, int bit)
++{
++ int val = phy_read_mmd(phydev, MDIO_MMD_VEND1, reg);
++
++ if (val < 0)
++ return val;
++
++ return !!(val & bit);
++}
++
++static int aqr_hwmon_status1(struct phy_device *phydev, int bit, long *value)
++{
++ int val = aqr_hwmon_test_bit(phydev, VEND1_GENERAL_STAT1, bit);
++
++ if (val < 0)
++ return val;
++
++ *value = val;
++
++ return 0;
++}
++
++static int aqr_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
++ u32 attr, int channel, long *value)
++{
++ struct phy_device *phydev = dev_get_drvdata(dev);
++ int reg;
++
++ if (type != hwmon_temp)
++ return -EOPNOTSUPP;
++
++ switch (attr) {
++ case hwmon_temp_input:
++ reg = aqr_hwmon_test_bit(phydev, VEND1_THERMAL_STAT2,
++ VEND1_THERMAL_STAT2_VALID);
++ if (reg < 0)
++ return reg;
++ if (!reg)
++ return -EBUSY;
++
++ return aqr_hwmon_get(phydev, VEND1_THERMAL_STAT1, value);
++
++ case hwmon_temp_lcrit:
++ return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_LOW_TEMP_FAIL,
++ value);
++ case hwmon_temp_min:
++ return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_LOW_TEMP_WARN,
++ value);
++ case hwmon_temp_max:
++ return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_WARN,
++ value);
++ case hwmon_temp_crit:
++ return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_FAIL,
++ value);
++ case hwmon_temp_lcrit_alarm:
++ return aqr_hwmon_status1(phydev,
++ VEND1_GENERAL_STAT1_LOW_TEMP_FAIL,
++ value);
++ case hwmon_temp_min_alarm:
++ return aqr_hwmon_status1(phydev,
++ VEND1_GENERAL_STAT1_LOW_TEMP_WARN,
++ value);
++ case hwmon_temp_max_alarm:
++ return aqr_hwmon_status1(phydev,
++ VEND1_GENERAL_STAT1_HIGH_TEMP_WARN,
++ value);
++ case hwmon_temp_crit_alarm:
++ return aqr_hwmon_status1(phydev,
++ VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL,
++ value);
++ default:
++ return -EOPNOTSUPP;
++ }
++}
++
++static int aqr_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
++ u32 attr, int channel, long value)
++{
++ struct phy_device *phydev = dev_get_drvdata(dev);
++
++ if (type != hwmon_temp)
++ return -EOPNOTSUPP;
++
++ switch (attr) {
++ case hwmon_temp_lcrit:
++ return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_LOW_TEMP_FAIL,
++ value);
++ case hwmon_temp_min:
++ return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_LOW_TEMP_WARN,
++ value);
++ case hwmon_temp_max:
++ return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_WARN,
++ value);
++ case hwmon_temp_crit:
++ return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_FAIL,
++ value);
++ default:
++ return -EOPNOTSUPP;
++ }
++}
++
++static const struct hwmon_ops aqr_hwmon_ops = {
++ .is_visible = aqr_hwmon_is_visible,
++ .read = aqr_hwmon_read,
++ .write = aqr_hwmon_write,
++};
++
++static u32 aqr_hwmon_chip_config[] = {
++ HWMON_C_REGISTER_TZ,
++ 0,
++};
++
++static const struct hwmon_channel_info aqr_hwmon_chip = {
++ .type = hwmon_chip,
++ .config = aqr_hwmon_chip_config,
++};
++
++static u32 aqr_hwmon_temp_config[] = {
++ HWMON_T_INPUT |
++ HWMON_T_MAX | HWMON_T_MIN |
++ HWMON_T_MAX_ALARM | HWMON_T_MIN_ALARM |
++ HWMON_T_CRIT | HWMON_T_LCRIT |
++ HWMON_T_CRIT_ALARM | HWMON_T_LCRIT_ALARM,
++ 0,
++};
++
++static const struct hwmon_channel_info aqr_hwmon_temp = {
++ .type = hwmon_temp,
++ .config = aqr_hwmon_temp_config,
++};
++
++static const struct hwmon_channel_info *aqr_hwmon_info[] = {
++ &aqr_hwmon_chip,
++ &aqr_hwmon_temp,
++ NULL,
++};
++
++static const struct hwmon_chip_info aqr_hwmon_chip_info = {
++ .ops = &aqr_hwmon_ops,
++ .info = aqr_hwmon_info,
++};
++
++int aqr_hwmon_probe(struct phy_device *phydev)
++{
++ struct device *dev = &phydev->mdio.dev;
++ struct device *hwmon_dev;
++ char *hwmon_name;
++ int i, j;
++
++ hwmon_name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL);
++ if (!hwmon_name)
++ return -ENOMEM;
++
++ for (i = j = 0; hwmon_name[i]; i++) {
++ if (isalnum(hwmon_name[i])) {
++ if (i != j)
++ hwmon_name[j] = hwmon_name[i];
++ j++;
++ }
++ }
++ hwmon_name[j] = '\0';
++
++ hwmon_dev = devm_hwmon_device_register_with_info(dev, hwmon_name,
++ phydev, &aqr_hwmon_chip_info, NULL);
++
++ return PTR_ERR_OR_ZERO(hwmon_dev);
++}
++
++#endif
+--- /dev/null
++++ b/drivers/net/phy/aquantia/aquantia_main.c
+@@ -0,0 +1,842 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Driver for Aquantia PHY
++ *
++ * Author: Shaohui Xie <Shaohui.Xie@freescale.com>
++ *
++ * Copyright 2015 Freescale Semiconductor, Inc.
++ */
++
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/delay.h>
++#include <linux/bitfield.h>
++#include <linux/phy.h>
++
++#include "aquantia.h"
++
++#define PHY_ID_AQ1202 0x03a1b445
++#define PHY_ID_AQ2104 0x03a1b460
++#define PHY_ID_AQR105 0x03a1b4a2
++#define PHY_ID_AQR106 0x03a1b4d0
++#define PHY_ID_AQR107 0x03a1b4e0
++#define PHY_ID_AQCS109 0x03a1b5c2
++#define PHY_ID_AQR405 0x03a1b4b0
++#define PHY_ID_AQR113C 0x31c31c12
++
++#define MDIO_PHYXS_VEND_IF_STATUS 0xe812
++#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK GENMASK(7, 3)
++#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_KR 0
++#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_KX 1
++#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_XFI 2
++#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_USXGMII 3
++#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_XAUI 4
++#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_SGMII 6
++#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_RXAUI 7
++#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_OCSGMII 10
++
++#define MDIO_AN_VEND_PROV 0xc400
++#define MDIO_AN_VEND_PROV_1000BASET_FULL BIT(15)
++#define MDIO_AN_VEND_PROV_1000BASET_HALF BIT(14)
++#define MDIO_AN_VEND_PROV_5000BASET_FULL BIT(11)
++#define MDIO_AN_VEND_PROV_2500BASET_FULL BIT(10)
++#define MDIO_AN_VEND_PROV_DOWNSHIFT_EN BIT(4)
++#define MDIO_AN_VEND_PROV_DOWNSHIFT_MASK GENMASK(3, 0)
++#define MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT 4
++
++#define MDIO_AN_TX_VEND_STATUS1 0xc800
++#define MDIO_AN_TX_VEND_STATUS1_RATE_MASK GENMASK(3, 1)
++#define MDIO_AN_TX_VEND_STATUS1_10BASET 0
++#define MDIO_AN_TX_VEND_STATUS1_100BASETX 1
++#define MDIO_AN_TX_VEND_STATUS1_1000BASET 2
++#define MDIO_AN_TX_VEND_STATUS1_10GBASET 3
++#define MDIO_AN_TX_VEND_STATUS1_2500BASET 4
++#define MDIO_AN_TX_VEND_STATUS1_5000BASET 5
++#define MDIO_AN_TX_VEND_STATUS1_FULL_DUPLEX BIT(0)
++
++#define MDIO_AN_TX_VEND_INT_STATUS1 0xcc00
++#define MDIO_AN_TX_VEND_INT_STATUS1_DOWNSHIFT BIT(1)
++
++#define MDIO_AN_TX_VEND_INT_STATUS2 0xcc01
++#define MDIO_AN_TX_VEND_INT_STATUS2_MASK BIT(0)
++
++#define MDIO_AN_TX_VEND_INT_MASK2 0xd401
++#define MDIO_AN_TX_VEND_INT_MASK2_LINK BIT(0)
++
++#define MDIO_AN_RX_LP_STAT1 0xe820
++#define MDIO_AN_RX_LP_STAT1_1000BASET_FULL BIT(15)
++#define MDIO_AN_RX_LP_STAT1_1000BASET_HALF BIT(14)
++#define MDIO_AN_RX_LP_STAT1_SHORT_REACH BIT(13)
++#define MDIO_AN_RX_LP_STAT1_AQRATE_DOWNSHIFT BIT(12)
++#define MDIO_AN_RX_LP_STAT1_AQ_PHY BIT(2)
++
++#define MDIO_AN_RX_LP_STAT4 0xe823
++#define MDIO_AN_RX_LP_STAT4_FW_MAJOR GENMASK(15, 8)
++#define MDIO_AN_RX_LP_STAT4_FW_MINOR GENMASK(7, 0)
++
++#define MDIO_AN_RX_VEND_STAT3 0xe832
++#define MDIO_AN_RX_VEND_STAT3_AFR BIT(0)
++
++/* MDIO_MMD_C22EXT */
++#define MDIO_C22EXT_STAT_SGMII_RX_GOOD_FRAMES 0xd292
++#define MDIO_C22EXT_STAT_SGMII_RX_BAD_FRAMES 0xd294
++#define MDIO_C22EXT_STAT_SGMII_RX_FALSE_CARRIER 0xd297
++#define MDIO_C22EXT_STAT_SGMII_TX_GOOD_FRAMES 0xd313
++#define MDIO_C22EXT_STAT_SGMII_TX_BAD_FRAMES 0xd315
++#define MDIO_C22EXT_STAT_SGMII_TX_FALSE_CARRIER 0xd317
++#define MDIO_C22EXT_STAT_SGMII_TX_COLLISIONS 0xd318
++#define MDIO_C22EXT_STAT_SGMII_TX_LINE_COLLISIONS 0xd319
++#define MDIO_C22EXT_STAT_SGMII_TX_FRAME_ALIGN_ERR 0xd31a
++#define MDIO_C22EXT_STAT_SGMII_TX_RUNT_FRAMES 0xd31b
++
++/* Vendor specific 1, MDIO_MMD_VEND1 */
++#define VEND1_GLOBAL_FW_ID 0x0020
++#define VEND1_GLOBAL_FW_ID_MAJOR GENMASK(15, 8)
++#define VEND1_GLOBAL_FW_ID_MINOR GENMASK(7, 0)
++
++#define VEND1_GLOBAL_GEN_STAT2 0xc831
++#define VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG BIT(15)
++
++/* The following registers all have similar layouts; first the registers... */
++#define VEND1_GLOBAL_CFG_10M 0x0310
++#define VEND1_GLOBAL_CFG_100M 0x031b
++#define VEND1_GLOBAL_CFG_1G 0x031c
++#define VEND1_GLOBAL_CFG_2_5G 0x031d
++#define VEND1_GLOBAL_CFG_5G 0x031e
++#define VEND1_GLOBAL_CFG_10G 0x031f
++/* ...and now the fields */
++#define VEND1_GLOBAL_CFG_RATE_ADAPT GENMASK(8, 7)
++#define VEND1_GLOBAL_CFG_RATE_ADAPT_NONE 0
++#define VEND1_GLOBAL_CFG_RATE_ADAPT_USX 1
++#define VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE 2
++
++#define VEND1_GLOBAL_RSVD_STAT1 0xc885
++#define VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID GENMASK(7, 4)
++#define VEND1_GLOBAL_RSVD_STAT1_PROV_ID GENMASK(3, 0)
++
++#define VEND1_GLOBAL_RSVD_STAT9 0xc88d
++#define VEND1_GLOBAL_RSVD_STAT9_MODE GENMASK(7, 0)
++#define VEND1_GLOBAL_RSVD_STAT9_1000BT2 0x23
++
++#define VEND1_GLOBAL_INT_STD_STATUS 0xfc00
++#define VEND1_GLOBAL_INT_VEND_STATUS 0xfc01
++
++#define VEND1_GLOBAL_INT_STD_MASK 0xff00
++#define VEND1_GLOBAL_INT_STD_MASK_PMA1 BIT(15)
++#define VEND1_GLOBAL_INT_STD_MASK_PMA2 BIT(14)
++#define VEND1_GLOBAL_INT_STD_MASK_PCS1 BIT(13)
++#define VEND1_GLOBAL_INT_STD_MASK_PCS2 BIT(12)
++#define VEND1_GLOBAL_INT_STD_MASK_PCS3 BIT(11)
++#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS1 BIT(10)
++#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS2 BIT(9)
++#define VEND1_GLOBAL_INT_STD_MASK_AN1 BIT(8)
++#define VEND1_GLOBAL_INT_STD_MASK_AN2 BIT(7)
++#define VEND1_GLOBAL_INT_STD_MASK_GBE BIT(6)
++#define VEND1_GLOBAL_INT_STD_MASK_ALL BIT(0)
++
++#define VEND1_GLOBAL_INT_VEND_MASK 0xff01
++#define VEND1_GLOBAL_INT_VEND_MASK_PMA BIT(15)
++#define VEND1_GLOBAL_INT_VEND_MASK_PCS BIT(14)
++#define VEND1_GLOBAL_INT_VEND_MASK_PHY_XS BIT(13)
++#define VEND1_GLOBAL_INT_VEND_MASK_AN BIT(12)
++#define VEND1_GLOBAL_INT_VEND_MASK_GBE BIT(11)
++#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL1 BIT(2)
++#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL2 BIT(1)
++#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3 BIT(0)
++
++/* Sleep and timeout for checking if the Processor-Intensive
++ * MDIO operation is finished
++ */
++#define AQR107_OP_IN_PROG_SLEEP 1000
++#define AQR107_OP_IN_PROG_TIMEOUT 100000
++
++struct aqr107_hw_stat {
++ const char *name;
++ int reg;
++ int size;
++};
++
++#define SGMII_STAT(n, r, s) { n, MDIO_C22EXT_STAT_SGMII_ ## r, s }
++static const struct aqr107_hw_stat aqr107_hw_stats[] = {
++ SGMII_STAT("sgmii_rx_good_frames", RX_GOOD_FRAMES, 26),
++ SGMII_STAT("sgmii_rx_bad_frames", RX_BAD_FRAMES, 26),
++ SGMII_STAT("sgmii_rx_false_carrier_events", RX_FALSE_CARRIER, 8),
++ SGMII_STAT("sgmii_tx_good_frames", TX_GOOD_FRAMES, 26),
++ SGMII_STAT("sgmii_tx_bad_frames", TX_BAD_FRAMES, 26),
++ SGMII_STAT("sgmii_tx_false_carrier_events", TX_FALSE_CARRIER, 8),
++ SGMII_STAT("sgmii_tx_collisions", TX_COLLISIONS, 8),
++ SGMII_STAT("sgmii_tx_line_collisions", TX_LINE_COLLISIONS, 8),
++ SGMII_STAT("sgmii_tx_frame_alignment_err", TX_FRAME_ALIGN_ERR, 16),
++ SGMII_STAT("sgmii_tx_runt_frames", TX_RUNT_FRAMES, 22),
++};
++#define AQR107_SGMII_STAT_SZ ARRAY_SIZE(aqr107_hw_stats)
++
++struct aqr107_priv {
++ u64 sgmii_stats[AQR107_SGMII_STAT_SZ];
++};
++
++static int aqr107_get_sset_count(struct phy_device *phydev)
++{
++ return AQR107_SGMII_STAT_SZ;
++}
++
++static void aqr107_get_strings(struct phy_device *phydev, u8 *data)
++{
++ int i;
++
++ for (i = 0; i < AQR107_SGMII_STAT_SZ; i++)
++ strscpy(data + i * ETH_GSTRING_LEN, aqr107_hw_stats[i].name,
++ ETH_GSTRING_LEN);
++}
++
++static u64 aqr107_get_stat(struct phy_device *phydev, int index)
++{
++ const struct aqr107_hw_stat *stat = aqr107_hw_stats + index;
++ int len_l = min(stat->size, 16);
++ int len_h = stat->size - len_l;
++ u64 ret;
++ int val;
++
++ val = phy_read_mmd(phydev, MDIO_MMD_C22EXT, stat->reg);
++ if (val < 0)
++ return U64_MAX;
++
++ ret = val & GENMASK(len_l - 1, 0);
++ if (len_h) {
++ val = phy_read_mmd(phydev, MDIO_MMD_C22EXT, stat->reg + 1);
++ if (val < 0)
++ return U64_MAX;
++
++ ret += (val & GENMASK(len_h - 1, 0)) << 16;
++ }
++
++ return ret;
++}
++
++static void aqr107_get_stats(struct phy_device *phydev,
++ struct ethtool_stats *stats, u64 *data)
++{
++ struct aqr107_priv *priv = phydev->priv;
++ u64 val;
++ int i;
++
++ for (i = 0; i < AQR107_SGMII_STAT_SZ; i++) {
++ val = aqr107_get_stat(phydev, i);
++ if (val == U64_MAX)
++ phydev_err(phydev, "Reading HW Statistics failed for %s\n",
++ aqr107_hw_stats[i].name);
++ else
++ priv->sgmii_stats[i] += val;
++
++ data[i] = priv->sgmii_stats[i];
++ }
++}
++
++static int aqr_config_aneg(struct phy_device *phydev)
++{
++ bool changed = false;
++ u16 reg;
++ int ret;
++
++ if (phydev->autoneg == AUTONEG_DISABLE)
++ return genphy_c45_pma_setup_forced(phydev);
++
++ ret = genphy_c45_an_config_aneg(phydev);
++ if (ret < 0)
++ return ret;
++ if (ret > 0)
++ changed = true;
++
++ /* Clause 45 has no standardized support for 1000BaseT, therefore
++ * use vendor registers for this mode.
++ */
++ reg = 0;
++ if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
++ phydev->advertising))
++ reg |= MDIO_AN_VEND_PROV_1000BASET_FULL;
++
++ if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
++ phydev->advertising))
++ reg |= MDIO_AN_VEND_PROV_1000BASET_HALF;
++
++ /* Handle the case when the 2.5G and 5G speeds are not advertised */
++ if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
++ phydev->advertising))
++ reg |= MDIO_AN_VEND_PROV_2500BASET_FULL;
++
++ if (linkmode_test_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
++ phydev->advertising))
++ reg |= MDIO_AN_VEND_PROV_5000BASET_FULL;
++
++ ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV,
++ MDIO_AN_VEND_PROV_1000BASET_HALF |
++ MDIO_AN_VEND_PROV_1000BASET_FULL |
++ MDIO_AN_VEND_PROV_2500BASET_FULL |
++ MDIO_AN_VEND_PROV_5000BASET_FULL, reg);
++ if (ret < 0)
++ return ret;
++ if (ret > 0)
++ changed = true;
++
++ return genphy_c45_check_and_restart_aneg(phydev, changed);
++}
++
++static int aqr_config_intr(struct phy_device *phydev)
++{
++ bool en = phydev->interrupts == PHY_INTERRUPT_ENABLED;
++ int err;
++
++ if (en) {
++ /* Clear any pending interrupts before enabling them */
++ err = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_STATUS2);
++ if (err < 0)
++ return err;
++ }
++
++ err = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_MASK2,
++ en ? MDIO_AN_TX_VEND_INT_MASK2_LINK : 0);
++ if (err < 0)
++ return err;
++
++ err = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_INT_STD_MASK,
++ en ? VEND1_GLOBAL_INT_STD_MASK_ALL : 0);
++ if (err < 0)
++ return err;
++
++ err = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_INT_VEND_MASK,
++ en ? VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3 |
++ VEND1_GLOBAL_INT_VEND_MASK_AN : 0);
++ if (err < 0)
++ return err;
++
++ if (!en) {
++ /* Clear any pending interrupts after we have disabled them */
++ err = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_STATUS2);
++ if (err < 0)
++ return err;
++ }
++
++ return 0;
++}
++
++static irqreturn_t aqr_handle_interrupt(struct phy_device *phydev)
++{
++ int irq_status;
++
++ irq_status = phy_read_mmd(phydev, MDIO_MMD_AN,
++ MDIO_AN_TX_VEND_INT_STATUS2);
++ if (irq_status < 0) {
++ phy_error(phydev);
++ return IRQ_NONE;
++ }
++
++ if (!(irq_status & MDIO_AN_TX_VEND_INT_STATUS2_MASK))
++ return IRQ_NONE;
++
++ phy_trigger_machine(phydev);
++
++ return IRQ_HANDLED;
++}
++
++static int aqr_read_status(struct phy_device *phydev)
++{
++ int val;
++
++ if (phydev->autoneg == AUTONEG_ENABLE) {
++ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT1);
++ if (val < 0)
++ return val;
++
++ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
++ phydev->lp_advertising,
++ val & MDIO_AN_RX_LP_STAT1_1000BASET_FULL);
++ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
++ phydev->lp_advertising,
++ val & MDIO_AN_RX_LP_STAT1_1000BASET_HALF);
++ }
++
++ return genphy_c45_read_status(phydev);
++}
++
++static int aqr107_read_rate(struct phy_device *phydev)
++{
++ u32 config_reg;
++ int val;
++
++ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_STATUS1);
++ if (val < 0)
++ return val;
++
++ if (val & MDIO_AN_TX_VEND_STATUS1_FULL_DUPLEX)
++ phydev->duplex = DUPLEX_FULL;
++ else
++ phydev->duplex = DUPLEX_HALF;
++
++ switch (FIELD_GET(MDIO_AN_TX_VEND_STATUS1_RATE_MASK, val)) {
++ case MDIO_AN_TX_VEND_STATUS1_10BASET:
++ phydev->speed = SPEED_10;
++ config_reg = VEND1_GLOBAL_CFG_10M;
++ break;
++ case MDIO_AN_TX_VEND_STATUS1_100BASETX:
++ phydev->speed = SPEED_100;
++ config_reg = VEND1_GLOBAL_CFG_100M;
++ break;
++ case MDIO_AN_TX_VEND_STATUS1_1000BASET:
++ phydev->speed = SPEED_1000;
++ config_reg = VEND1_GLOBAL_CFG_1G;
++ break;
++ case MDIO_AN_TX_VEND_STATUS1_2500BASET:
++ phydev->speed = SPEED_2500;
++ config_reg = VEND1_GLOBAL_CFG_2_5G;
++ break;
++ case MDIO_AN_TX_VEND_STATUS1_5000BASET:
++ phydev->speed = SPEED_5000;
++ config_reg = VEND1_GLOBAL_CFG_5G;
++ break;
++ case MDIO_AN_TX_VEND_STATUS1_10GBASET:
++ phydev->speed = SPEED_10000;
++ config_reg = VEND1_GLOBAL_CFG_10G;
++ break;
++ default:
++ phydev->speed = SPEED_UNKNOWN;
++ return 0;
++ }
++
++ val = phy_read_mmd(phydev, MDIO_MMD_VEND1, config_reg);
++ if (val < 0)
++ return val;
++
++ if (FIELD_GET(VEND1_GLOBAL_CFG_RATE_ADAPT, val) ==
++ VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE)
++ phydev->rate_matching = RATE_MATCH_PAUSE;
++ else
++ phydev->rate_matching = RATE_MATCH_NONE;
++
++ return 0;
++}
++
++static int aqr107_read_status(struct phy_device *phydev)
++{
++ int val, ret;
++
++ ret = aqr_read_status(phydev);
++ if (ret)
++ return ret;
++
++ if (!phydev->link || phydev->autoneg == AUTONEG_DISABLE)
++ return 0;
++
++ val = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MDIO_PHYXS_VEND_IF_STATUS);
++ if (val < 0)
++ return val;
++
++ switch (FIELD_GET(MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK, val)) {
++ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_KR:
++ phydev->interface = PHY_INTERFACE_MODE_10GKR;
++ break;
++ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_KX:
++ phydev->interface = PHY_INTERFACE_MODE_1000BASEKX;
++ break;
++ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_XFI:
++ phydev->interface = PHY_INTERFACE_MODE_10GBASER;
++ break;
++ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_USXGMII:
++ phydev->interface = PHY_INTERFACE_MODE_USXGMII;
++ break;
++ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_XAUI:
++ phydev->interface = PHY_INTERFACE_MODE_XAUI;
++ break;
++ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_SGMII:
++ phydev->interface = PHY_INTERFACE_MODE_SGMII;
++ break;
++ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_RXAUI:
++ phydev->interface = PHY_INTERFACE_MODE_RXAUI;
++ break;
++ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_OCSGMII:
++ phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
++ break;
++ default:
++ phydev->interface = PHY_INTERFACE_MODE_NA;
++ break;
++ }
++
++ /* Read possibly downshifted rate from vendor register */
++ return aqr107_read_rate(phydev);
++}
++
++static int aqr107_get_downshift(struct phy_device *phydev, u8 *data)
++{
++ int val, cnt, enable;
++
++ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV);
++ if (val < 0)
++ return val;
++
++ enable = FIELD_GET(MDIO_AN_VEND_PROV_DOWNSHIFT_EN, val);
++ cnt = FIELD_GET(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, val);
++
++ *data = enable && cnt ? cnt : DOWNSHIFT_DEV_DISABLE;
++
++ return 0;
++}
++
++static int aqr107_set_downshift(struct phy_device *phydev, u8 cnt)
++{
++ int val = 0;
++
++ if (!FIELD_FIT(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, cnt))
++ return -E2BIG;
++
++ if (cnt != DOWNSHIFT_DEV_DISABLE) {
++ val = MDIO_AN_VEND_PROV_DOWNSHIFT_EN;
++ val |= FIELD_PREP(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, cnt);
++ }
++
++ return phy_modify_mmd(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV,
++ MDIO_AN_VEND_PROV_DOWNSHIFT_EN |
++ MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, val);
++}
++
++static int aqr107_get_tunable(struct phy_device *phydev,
++ struct ethtool_tunable *tuna, void *data)
++{
++ switch (tuna->id) {
++ case ETHTOOL_PHY_DOWNSHIFT:
++ return aqr107_get_downshift(phydev, data);
++ default:
++ return -EOPNOTSUPP;
++ }
++}
++
++static int aqr107_set_tunable(struct phy_device *phydev,
++ struct ethtool_tunable *tuna, const void *data)
++{
++ switch (tuna->id) {
++ case ETHTOOL_PHY_DOWNSHIFT:
++ return aqr107_set_downshift(phydev, *(const u8 *)data);
++ default:
++ return -EOPNOTSUPP;
++ }
++}
++
++/* If we configure settings whilst firmware is still initializing the chip,
++ * then these settings may be overwritten. Therefore make sure chip
++ * initialization has completed. Use presence of the firmware ID as
++ * indicator for initialization having completed.
++ * The chip also provides a "reset completed" bit, but it's cleared after
++ * read. Therefore function would time out if called again.
++ */
++static int aqr107_wait_reset_complete(struct phy_device *phydev)
++{
++ int val;
++
++ return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
++ VEND1_GLOBAL_FW_ID, val, val != 0,
++ 20000, 2000000, false);
++}
++
++static void aqr107_chip_info(struct phy_device *phydev)
++{
++ u8 fw_major, fw_minor, build_id, prov_id;
++ int val;
++
++ val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_FW_ID);
++ if (val < 0)
++ return;
++
++ fw_major = FIELD_GET(VEND1_GLOBAL_FW_ID_MAJOR, val);
++ fw_minor = FIELD_GET(VEND1_GLOBAL_FW_ID_MINOR, val);
++
++ val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_RSVD_STAT1);
++ if (val < 0)
++ return;
++
++ build_id = FIELD_GET(VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID, val);
++ prov_id = FIELD_GET(VEND1_GLOBAL_RSVD_STAT1_PROV_ID, val);
++
++ phydev_dbg(phydev, "FW %u.%u, Build %u, Provisioning %u\n",
++ fw_major, fw_minor, build_id, prov_id);
++}
++
++static int aqr107_config_init(struct phy_device *phydev)
++{
++ int ret;
++
++ /* Check that the PHY interface type is compatible */
++ if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
++ phydev->interface != PHY_INTERFACE_MODE_1000BASEKX &&
++ phydev->interface != PHY_INTERFACE_MODE_2500BASEX &&
++ phydev->interface != PHY_INTERFACE_MODE_XGMII &&
++ phydev->interface != PHY_INTERFACE_MODE_USXGMII &&
++ phydev->interface != PHY_INTERFACE_MODE_10GKR &&
++ phydev->interface != PHY_INTERFACE_MODE_10GBASER &&
++ phydev->interface != PHY_INTERFACE_MODE_XAUI &&
++ phydev->interface != PHY_INTERFACE_MODE_RXAUI)
++ return -ENODEV;
++
++ WARN(phydev->interface == PHY_INTERFACE_MODE_XGMII,
++ "Your devicetree is out of date, please update it. The AQR107 family doesn't support XGMII, maybe you mean USXGMII.\n");
++
++ ret = aqr107_wait_reset_complete(phydev);
++ if (!ret)
++ aqr107_chip_info(phydev);
++
++ return aqr107_set_downshift(phydev, MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT);
++}
++
++static int aqcs109_config_init(struct phy_device *phydev)
++{
++ int ret;
++
++ /* Check that the PHY interface type is compatible */
++ if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
++ phydev->interface != PHY_INTERFACE_MODE_2500BASEX)
++ return -ENODEV;
++
++ ret = aqr107_wait_reset_complete(phydev);
++ if (!ret)
++ aqr107_chip_info(phydev);
++
++ /* AQCS109 belongs to a chip family partially supporting 10G and 5G.
++ * PMA speed ability bits are the same for all members of the family,
++ * AQCS109 however supports speeds up to 2.5G only.
++ */
++ phy_set_max_speed(phydev, SPEED_2500);
++
++ return aqr107_set_downshift(phydev, MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT);
++}
++
++static void aqr107_link_change_notify(struct phy_device *phydev)
++{
++ u8 fw_major, fw_minor;
++ bool downshift, short_reach, afr;
++ int mode, val;
++
++ if (phydev->state != PHY_RUNNING || phydev->autoneg == AUTONEG_DISABLE)
++ return;
++
++ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT1);
++ /* call failed or link partner is no Aquantia PHY */
++ if (val < 0 || !(val & MDIO_AN_RX_LP_STAT1_AQ_PHY))
++ return;
++
++ short_reach = val & MDIO_AN_RX_LP_STAT1_SHORT_REACH;
++ downshift = val & MDIO_AN_RX_LP_STAT1_AQRATE_DOWNSHIFT;
++
++ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT4);
++ if (val < 0)
++ return;
++
++ fw_major = FIELD_GET(MDIO_AN_RX_LP_STAT4_FW_MAJOR, val);
++ fw_minor = FIELD_GET(MDIO_AN_RX_LP_STAT4_FW_MINOR, val);
++
++ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_VEND_STAT3);
++ if (val < 0)
++ return;
++
++ afr = val & MDIO_AN_RX_VEND_STAT3_AFR;
++
++ phydev_dbg(phydev, "Link partner is Aquantia PHY, FW %u.%u%s%s%s\n",
++ fw_major, fw_minor,
++ short_reach ? ", short reach mode" : "",
++ downshift ? ", fast-retrain downshift advertised" : "",
++ afr ? ", fast reframe advertised" : "");
++
++ val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_RSVD_STAT9);
++ if (val < 0)
++ return;
++
++ mode = FIELD_GET(VEND1_GLOBAL_RSVD_STAT9_MODE, val);
++ if (mode == VEND1_GLOBAL_RSVD_STAT9_1000BT2)
++ phydev_info(phydev, "Aquantia 1000Base-T2 mode active\n");
++}
++
++static int aqr107_wait_processor_intensive_op(struct phy_device *phydev)
++{
++ int val, err;
++
++ /* The datasheet notes to wait at least 1ms after issuing a
++ * processor intensive operation before checking.
++ * We cannot use the 'sleep_before_read' parameter of read_poll_timeout
++ * because that just determines the maximum time slept, not the minimum.
++ */
++ usleep_range(1000, 5000);
++
++ err = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
++ VEND1_GLOBAL_GEN_STAT2, val,
++ !(val & VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG),
++ AQR107_OP_IN_PROG_SLEEP,
++ AQR107_OP_IN_PROG_TIMEOUT, false);
++ if (err) {
++ phydev_err(phydev, "timeout: processor-intensive MDIO operation\n");
++ return err;
++ }
++
++ return 0;
++}
++
++static int aqr107_get_rate_matching(struct phy_device *phydev,
++ phy_interface_t iface)
++{
++ if (iface == PHY_INTERFACE_MODE_10GBASER ||
++ iface == PHY_INTERFACE_MODE_2500BASEX ||
++ iface == PHY_INTERFACE_MODE_NA)
++ return RATE_MATCH_PAUSE;
++ return RATE_MATCH_NONE;
++}
++
++static int aqr107_suspend(struct phy_device *phydev)
++{
++ int err;
++
++ err = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MDIO_CTRL1,
++ MDIO_CTRL1_LPOWER);
++ if (err)
++ return err;
++
++ return aqr107_wait_processor_intensive_op(phydev);
++}
++
++static int aqr107_resume(struct phy_device *phydev)
++{
++ int err;
++
++ err = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MDIO_CTRL1,
++ MDIO_CTRL1_LPOWER);
++ if (err)
++ return err;
++
++ return aqr107_wait_processor_intensive_op(phydev);
++}
++
++static int aqr107_probe(struct phy_device *phydev)
++{
++ phydev->priv = devm_kzalloc(&phydev->mdio.dev,
++ sizeof(struct aqr107_priv), GFP_KERNEL);
++ if (!phydev->priv)
++ return -ENOMEM;
++
++ return aqr_hwmon_probe(phydev);
++}
++
++static struct phy_driver aqr_driver[] = {
++{
++ PHY_ID_MATCH_MODEL(PHY_ID_AQ1202),
++ .name = "Aquantia AQ1202",
++ .config_aneg = aqr_config_aneg,
++ .config_intr = aqr_config_intr,
++ .handle_interrupt = aqr_handle_interrupt,
++ .read_status = aqr_read_status,
++},
++{
++ PHY_ID_MATCH_MODEL(PHY_ID_AQ2104),
++ .name = "Aquantia AQ2104",
++ .config_aneg = aqr_config_aneg,
++ .config_intr = aqr_config_intr,
++ .handle_interrupt = aqr_handle_interrupt,
++ .read_status = aqr_read_status,
++},
++{
++ PHY_ID_MATCH_MODEL(PHY_ID_AQR105),
++ .name = "Aquantia AQR105",
++ .config_aneg = aqr_config_aneg,
++ .config_intr = aqr_config_intr,
++ .handle_interrupt = aqr_handle_interrupt,
++ .read_status = aqr_read_status,
++ .suspend = aqr107_suspend,
++ .resume = aqr107_resume,
++},
++{
++ PHY_ID_MATCH_MODEL(PHY_ID_AQR106),
++ .name = "Aquantia AQR106",
++ .config_aneg = aqr_config_aneg,
++ .config_intr = aqr_config_intr,
++ .handle_interrupt = aqr_handle_interrupt,
++ .read_status = aqr_read_status,
++},
++{
++ PHY_ID_MATCH_MODEL(PHY_ID_AQR107),
++ .name = "Aquantia AQR107",
++ .probe = aqr107_probe,
++ .get_rate_matching = aqr107_get_rate_matching,
++ .config_init = aqr107_config_init,
++ .config_aneg = aqr_config_aneg,
++ .config_intr = aqr_config_intr,
++ .handle_interrupt = aqr_handle_interrupt,
++ .read_status = aqr107_read_status,
++ .get_tunable = aqr107_get_tunable,
++ .set_tunable = aqr107_set_tunable,
++ .suspend = aqr107_suspend,
++ .resume = aqr107_resume,
++ .get_sset_count = aqr107_get_sset_count,
++ .get_strings = aqr107_get_strings,
++ .get_stats = aqr107_get_stats,
++ .link_change_notify = aqr107_link_change_notify,
++},
++{
++ PHY_ID_MATCH_MODEL(PHY_ID_AQCS109),
++ .name = "Aquantia AQCS109",
++ .probe = aqr107_probe,
++ .get_rate_matching = aqr107_get_rate_matching,
++ .config_init = aqcs109_config_init,
++ .config_aneg = aqr_config_aneg,
++ .config_intr = aqr_config_intr,
++ .handle_interrupt = aqr_handle_interrupt,
++ .read_status = aqr107_read_status,
++ .get_tunable = aqr107_get_tunable,
++ .set_tunable = aqr107_set_tunable,
++ .suspend = aqr107_suspend,
++ .resume = aqr107_resume,
++ .get_sset_count = aqr107_get_sset_count,
++ .get_strings = aqr107_get_strings,
++ .get_stats = aqr107_get_stats,
++ .link_change_notify = aqr107_link_change_notify,
++},
++{
++ PHY_ID_MATCH_MODEL(PHY_ID_AQR405),
++ .name = "Aquantia AQR405",
++ .config_aneg = aqr_config_aneg,
++ .config_intr = aqr_config_intr,
++ .handle_interrupt = aqr_handle_interrupt,
++ .read_status = aqr_read_status,
++},
++{
++ PHY_ID_MATCH_MODEL(PHY_ID_AQR113C),
++ .name = "Aquantia AQR113C",
++ .probe = aqr107_probe,
++ .get_rate_matching = aqr107_get_rate_matching,
++ .config_init = aqr107_config_init,
++ .config_aneg = aqr_config_aneg,
++ .config_intr = aqr_config_intr,
++ .handle_interrupt = aqr_handle_interrupt,
++ .read_status = aqr107_read_status,
++ .get_tunable = aqr107_get_tunable,
++ .set_tunable = aqr107_set_tunable,
++ .suspend = aqr107_suspend,
++ .resume = aqr107_resume,
++ .get_sset_count = aqr107_get_sset_count,
++ .get_strings = aqr107_get_strings,
++ .get_stats = aqr107_get_stats,
++ .link_change_notify = aqr107_link_change_notify,
++},
++};
++
++module_phy_driver(aqr_driver);
++
++static struct mdio_device_id __maybe_unused aqr_tbl[] = {
++ { PHY_ID_MATCH_MODEL(PHY_ID_AQ1202) },
++ { PHY_ID_MATCH_MODEL(PHY_ID_AQ2104) },
++ { PHY_ID_MATCH_MODEL(PHY_ID_AQR105) },
++ { PHY_ID_MATCH_MODEL(PHY_ID_AQR106) },
++ { PHY_ID_MATCH_MODEL(PHY_ID_AQR107) },
++ { PHY_ID_MATCH_MODEL(PHY_ID_AQCS109) },
++ { PHY_ID_MATCH_MODEL(PHY_ID_AQR405) },
++ { PHY_ID_MATCH_MODEL(PHY_ID_AQR113C) },
++ { }
++};
++
++MODULE_DEVICE_TABLE(mdio, aqr_tbl);
++
++MODULE_DESCRIPTION("Aquantia PHY driver");
++MODULE_AUTHOR("Shaohui Xie <Shaohui.Xie@freescale.com>");
++MODULE_LICENSE("GPL v2");
+--- a/drivers/net/phy/aquantia_hwmon.c
++++ /dev/null
+@@ -1,250 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0
+-/* HWMON driver for Aquantia PHY
+- *
+- * Author: Nikita Yushchenko <nikita.yoush@cogentembedded.com>
+- * Author: Andrew Lunn <andrew@lunn.ch>
+- * Author: Heiner Kallweit <hkallweit1@gmail.com>
+- */
+-
+-#include <linux/phy.h>
+-#include <linux/device.h>
+-#include <linux/ctype.h>
+-#include <linux/hwmon.h>
+-
+-#include "aquantia.h"
+-
+-/* Vendor specific 1, MDIO_MMD_VEND2 */
+-#define VEND1_THERMAL_PROV_HIGH_TEMP_FAIL 0xc421
+-#define VEND1_THERMAL_PROV_LOW_TEMP_FAIL 0xc422
+-#define VEND1_THERMAL_PROV_HIGH_TEMP_WARN 0xc423
+-#define VEND1_THERMAL_PROV_LOW_TEMP_WARN 0xc424
+-#define VEND1_THERMAL_STAT1 0xc820
+-#define VEND1_THERMAL_STAT2 0xc821
+-#define VEND1_THERMAL_STAT2_VALID BIT(0)
+-#define VEND1_GENERAL_STAT1 0xc830
+-#define VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL BIT(14)
+-#define VEND1_GENERAL_STAT1_LOW_TEMP_FAIL BIT(13)
+-#define VEND1_GENERAL_STAT1_HIGH_TEMP_WARN BIT(12)
+-#define VEND1_GENERAL_STAT1_LOW_TEMP_WARN BIT(11)
+-
+-#if IS_REACHABLE(CONFIG_HWMON)
+-
+-static umode_t aqr_hwmon_is_visible(const void *data,
+- enum hwmon_sensor_types type,
+- u32 attr, int channel)
+-{
+- if (type != hwmon_temp)
+- return 0;
+-
+- switch (attr) {
+- case hwmon_temp_input:
+- case hwmon_temp_min_alarm:
+- case hwmon_temp_max_alarm:
+- case hwmon_temp_lcrit_alarm:
+- case hwmon_temp_crit_alarm:
+- return 0444;
+- case hwmon_temp_min:
+- case hwmon_temp_max:
+- case hwmon_temp_lcrit:
+- case hwmon_temp_crit:
+- return 0644;
+- default:
+- return 0;
+- }
+-}
+-
+-static int aqr_hwmon_get(struct phy_device *phydev, int reg, long *value)
+-{
+- int temp = phy_read_mmd(phydev, MDIO_MMD_VEND1, reg);
+-
+- if (temp < 0)
+- return temp;
+-
+- /* 16 bit value is 2's complement with LSB = 1/256th degree Celsius */
+- *value = (s16)temp * 1000 / 256;
+-
+- return 0;
+-}
+-
+-static int aqr_hwmon_set(struct phy_device *phydev, int reg, long value)
+-{
+- int temp;
+-
+- if (value >= 128000 || value < -128000)
+- return -ERANGE;
+-
+- temp = value * 256 / 1000;
+-
+- /* temp is in s16 range and we're interested in lower 16 bits only */
+- return phy_write_mmd(phydev, MDIO_MMD_VEND1, reg, (u16)temp);
+-}
+-
+-static int aqr_hwmon_test_bit(struct phy_device *phydev, int reg, int bit)
+-{
+- int val = phy_read_mmd(phydev, MDIO_MMD_VEND1, reg);
+-
+- if (val < 0)
+- return val;
+-
+- return !!(val & bit);
+-}
+-
+-static int aqr_hwmon_status1(struct phy_device *phydev, int bit, long *value)
+-{
+- int val = aqr_hwmon_test_bit(phydev, VEND1_GENERAL_STAT1, bit);
+-
+- if (val < 0)
+- return val;
+-
+- *value = val;
+-
+- return 0;
+-}
+-
+-static int aqr_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+- u32 attr, int channel, long *value)
+-{
+- struct phy_device *phydev = dev_get_drvdata(dev);
+- int reg;
+-
+- if (type != hwmon_temp)
+- return -EOPNOTSUPP;
+-
+- switch (attr) {
+- case hwmon_temp_input:
+- reg = aqr_hwmon_test_bit(phydev, VEND1_THERMAL_STAT2,
+- VEND1_THERMAL_STAT2_VALID);
+- if (reg < 0)
+- return reg;
+- if (!reg)
+- return -EBUSY;
+-
+- return aqr_hwmon_get(phydev, VEND1_THERMAL_STAT1, value);
+-
+- case hwmon_temp_lcrit:
+- return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_LOW_TEMP_FAIL,
+- value);
+- case hwmon_temp_min:
+- return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_LOW_TEMP_WARN,
+- value);
+- case hwmon_temp_max:
+- return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_WARN,
+- value);
+- case hwmon_temp_crit:
+- return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_FAIL,
+- value);
+- case hwmon_temp_lcrit_alarm:
+- return aqr_hwmon_status1(phydev,
+- VEND1_GENERAL_STAT1_LOW_TEMP_FAIL,
+- value);
+- case hwmon_temp_min_alarm:
+- return aqr_hwmon_status1(phydev,
+- VEND1_GENERAL_STAT1_LOW_TEMP_WARN,
+- value);
+- case hwmon_temp_max_alarm:
+- return aqr_hwmon_status1(phydev,
+- VEND1_GENERAL_STAT1_HIGH_TEMP_WARN,
+- value);
+- case hwmon_temp_crit_alarm:
+- return aqr_hwmon_status1(phydev,
+- VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL,
+- value);
+- default:
+- return -EOPNOTSUPP;
+- }
+-}
+-
+-static int aqr_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+- u32 attr, int channel, long value)
+-{
+- struct phy_device *phydev = dev_get_drvdata(dev);
+-
+- if (type != hwmon_temp)
+- return -EOPNOTSUPP;
+-
+- switch (attr) {
+- case hwmon_temp_lcrit:
+- return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_LOW_TEMP_FAIL,
+- value);
+- case hwmon_temp_min:
+- return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_LOW_TEMP_WARN,
+- value);
+- case hwmon_temp_max:
+- return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_WARN,
+- value);
+- case hwmon_temp_crit:
+- return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_FAIL,
+- value);
+- default:
+- return -EOPNOTSUPP;
+- }
+-}
+-
+-static const struct hwmon_ops aqr_hwmon_ops = {
+- .is_visible = aqr_hwmon_is_visible,
+- .read = aqr_hwmon_read,
+- .write = aqr_hwmon_write,
+-};
+-
+-static u32 aqr_hwmon_chip_config[] = {
+- HWMON_C_REGISTER_TZ,
+- 0,
+-};
+-
+-static const struct hwmon_channel_info aqr_hwmon_chip = {
+- .type = hwmon_chip,
+- .config = aqr_hwmon_chip_config,
+-};
+-
+-static u32 aqr_hwmon_temp_config[] = {
+- HWMON_T_INPUT |
+- HWMON_T_MAX | HWMON_T_MIN |
+- HWMON_T_MAX_ALARM | HWMON_T_MIN_ALARM |
+- HWMON_T_CRIT | HWMON_T_LCRIT |
+- HWMON_T_CRIT_ALARM | HWMON_T_LCRIT_ALARM,
+- 0,
+-};
+-
+-static const struct hwmon_channel_info aqr_hwmon_temp = {
+- .type = hwmon_temp,
+- .config = aqr_hwmon_temp_config,
+-};
+-
+-static const struct hwmon_channel_info *aqr_hwmon_info[] = {
+- &aqr_hwmon_chip,
+- &aqr_hwmon_temp,
+- NULL,
+-};
+-
+-static const struct hwmon_chip_info aqr_hwmon_chip_info = {
+- .ops = &aqr_hwmon_ops,
+- .info = aqr_hwmon_info,
+-};
+-
+-int aqr_hwmon_probe(struct phy_device *phydev)
+-{
+- struct device *dev = &phydev->mdio.dev;
+- struct device *hwmon_dev;
+- char *hwmon_name;
+- int i, j;
+-
+- hwmon_name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL);
+- if (!hwmon_name)
+- return -ENOMEM;
+-
+- for (i = j = 0; hwmon_name[i]; i++) {
+- if (isalnum(hwmon_name[i])) {
+- if (i != j)
+- hwmon_name[j] = hwmon_name[i];
+- j++;
+- }
+- }
+- hwmon_name[j] = '\0';
+-
+- hwmon_dev = devm_hwmon_device_register_with_info(dev, hwmon_name,
+- phydev, &aqr_hwmon_chip_info, NULL);
+-
+- return PTR_ERR_OR_ZERO(hwmon_dev);
+-}
+-
+-#endif
+--- a/drivers/net/phy/aquantia_main.c
++++ /dev/null
+@@ -1,842 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0
+-/*
+- * Driver for Aquantia PHY
+- *
+- * Author: Shaohui Xie <Shaohui.Xie@freescale.com>
+- *
+- * Copyright 2015 Freescale Semiconductor, Inc.
+- */
+-
+-#include <linux/kernel.h>
+-#include <linux/module.h>
+-#include <linux/delay.h>
+-#include <linux/bitfield.h>
+-#include <linux/phy.h>
+-
+-#include "aquantia.h"
+-
+-#define PHY_ID_AQ1202 0x03a1b445
+-#define PHY_ID_AQ2104 0x03a1b460
+-#define PHY_ID_AQR105 0x03a1b4a2
+-#define PHY_ID_AQR106 0x03a1b4d0
+-#define PHY_ID_AQR107 0x03a1b4e0
+-#define PHY_ID_AQCS109 0x03a1b5c2
+-#define PHY_ID_AQR405 0x03a1b4b0
+-#define PHY_ID_AQR113C 0x31c31c12
+-
+-#define MDIO_PHYXS_VEND_IF_STATUS 0xe812
+-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK GENMASK(7, 3)
+-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_KR 0
+-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_KX 1
+-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_XFI 2
+-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_USXGMII 3
+-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_XAUI 4
+-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_SGMII 6
+-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_RXAUI 7
+-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_OCSGMII 10
+-
+-#define MDIO_AN_VEND_PROV 0xc400
+-#define MDIO_AN_VEND_PROV_1000BASET_FULL BIT(15)
+-#define MDIO_AN_VEND_PROV_1000BASET_HALF BIT(14)
+-#define MDIO_AN_VEND_PROV_5000BASET_FULL BIT(11)
+-#define MDIO_AN_VEND_PROV_2500BASET_FULL BIT(10)
+-#define MDIO_AN_VEND_PROV_DOWNSHIFT_EN BIT(4)
+-#define MDIO_AN_VEND_PROV_DOWNSHIFT_MASK GENMASK(3, 0)
+-#define MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT 4
+-
+-#define MDIO_AN_TX_VEND_STATUS1 0xc800
+-#define MDIO_AN_TX_VEND_STATUS1_RATE_MASK GENMASK(3, 1)
+-#define MDIO_AN_TX_VEND_STATUS1_10BASET 0
+-#define MDIO_AN_TX_VEND_STATUS1_100BASETX 1
+-#define MDIO_AN_TX_VEND_STATUS1_1000BASET 2
+-#define MDIO_AN_TX_VEND_STATUS1_10GBASET 3
+-#define MDIO_AN_TX_VEND_STATUS1_2500BASET 4
+-#define MDIO_AN_TX_VEND_STATUS1_5000BASET 5
+-#define MDIO_AN_TX_VEND_STATUS1_FULL_DUPLEX BIT(0)
+-
+-#define MDIO_AN_TX_VEND_INT_STATUS1 0xcc00
+-#define MDIO_AN_TX_VEND_INT_STATUS1_DOWNSHIFT BIT(1)
+-
+-#define MDIO_AN_TX_VEND_INT_STATUS2 0xcc01
+-#define MDIO_AN_TX_VEND_INT_STATUS2_MASK BIT(0)
+-
+-#define MDIO_AN_TX_VEND_INT_MASK2 0xd401
+-#define MDIO_AN_TX_VEND_INT_MASK2_LINK BIT(0)
+-
+-#define MDIO_AN_RX_LP_STAT1 0xe820
+-#define MDIO_AN_RX_LP_STAT1_1000BASET_FULL BIT(15)
+-#define MDIO_AN_RX_LP_STAT1_1000BASET_HALF BIT(14)
+-#define MDIO_AN_RX_LP_STAT1_SHORT_REACH BIT(13)
+-#define MDIO_AN_RX_LP_STAT1_AQRATE_DOWNSHIFT BIT(12)
+-#define MDIO_AN_RX_LP_STAT1_AQ_PHY BIT(2)
+-
+-#define MDIO_AN_RX_LP_STAT4 0xe823
+-#define MDIO_AN_RX_LP_STAT4_FW_MAJOR GENMASK(15, 8)
+-#define MDIO_AN_RX_LP_STAT4_FW_MINOR GENMASK(7, 0)
+-
+-#define MDIO_AN_RX_VEND_STAT3 0xe832
+-#define MDIO_AN_RX_VEND_STAT3_AFR BIT(0)
+-
+-/* MDIO_MMD_C22EXT */
+-#define MDIO_C22EXT_STAT_SGMII_RX_GOOD_FRAMES 0xd292
+-#define MDIO_C22EXT_STAT_SGMII_RX_BAD_FRAMES 0xd294
+-#define MDIO_C22EXT_STAT_SGMII_RX_FALSE_CARRIER 0xd297
+-#define MDIO_C22EXT_STAT_SGMII_TX_GOOD_FRAMES 0xd313
+-#define MDIO_C22EXT_STAT_SGMII_TX_BAD_FRAMES 0xd315
+-#define MDIO_C22EXT_STAT_SGMII_TX_FALSE_CARRIER 0xd317
+-#define MDIO_C22EXT_STAT_SGMII_TX_COLLISIONS 0xd318
+-#define MDIO_C22EXT_STAT_SGMII_TX_LINE_COLLISIONS 0xd319
+-#define MDIO_C22EXT_STAT_SGMII_TX_FRAME_ALIGN_ERR 0xd31a
+-#define MDIO_C22EXT_STAT_SGMII_TX_RUNT_FRAMES 0xd31b
+-
+-/* Vendor specific 1, MDIO_MMD_VEND1 */
+-#define VEND1_GLOBAL_FW_ID 0x0020
+-#define VEND1_GLOBAL_FW_ID_MAJOR GENMASK(15, 8)
+-#define VEND1_GLOBAL_FW_ID_MINOR GENMASK(7, 0)
+-
+-#define VEND1_GLOBAL_GEN_STAT2 0xc831
+-#define VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG BIT(15)
+-
+-/* The following registers all have similar layouts; first the registers... */
+-#define VEND1_GLOBAL_CFG_10M 0x0310
+-#define VEND1_GLOBAL_CFG_100M 0x031b
+-#define VEND1_GLOBAL_CFG_1G 0x031c
+-#define VEND1_GLOBAL_CFG_2_5G 0x031d
+-#define VEND1_GLOBAL_CFG_5G 0x031e
+-#define VEND1_GLOBAL_CFG_10G 0x031f
+-/* ...and now the fields */
+-#define VEND1_GLOBAL_CFG_RATE_ADAPT GENMASK(8, 7)
+-#define VEND1_GLOBAL_CFG_RATE_ADAPT_NONE 0
+-#define VEND1_GLOBAL_CFG_RATE_ADAPT_USX 1
+-#define VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE 2
+-
+-#define VEND1_GLOBAL_RSVD_STAT1 0xc885
+-#define VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID GENMASK(7, 4)
+-#define VEND1_GLOBAL_RSVD_STAT1_PROV_ID GENMASK(3, 0)
+-
+-#define VEND1_GLOBAL_RSVD_STAT9 0xc88d
+-#define VEND1_GLOBAL_RSVD_STAT9_MODE GENMASK(7, 0)
+-#define VEND1_GLOBAL_RSVD_STAT9_1000BT2 0x23
+-
+-#define VEND1_GLOBAL_INT_STD_STATUS 0xfc00
+-#define VEND1_GLOBAL_INT_VEND_STATUS 0xfc01
+-
+-#define VEND1_GLOBAL_INT_STD_MASK 0xff00
+-#define VEND1_GLOBAL_INT_STD_MASK_PMA1 BIT(15)
+-#define VEND1_GLOBAL_INT_STD_MASK_PMA2 BIT(14)
+-#define VEND1_GLOBAL_INT_STD_MASK_PCS1 BIT(13)
+-#define VEND1_GLOBAL_INT_STD_MASK_PCS2 BIT(12)
+-#define VEND1_GLOBAL_INT_STD_MASK_PCS3 BIT(11)
+-#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS1 BIT(10)
+-#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS2 BIT(9)
+-#define VEND1_GLOBAL_INT_STD_MASK_AN1 BIT(8)
+-#define VEND1_GLOBAL_INT_STD_MASK_AN2 BIT(7)
+-#define VEND1_GLOBAL_INT_STD_MASK_GBE BIT(6)
+-#define VEND1_GLOBAL_INT_STD_MASK_ALL BIT(0)
+-
+-#define VEND1_GLOBAL_INT_VEND_MASK 0xff01
+-#define VEND1_GLOBAL_INT_VEND_MASK_PMA BIT(15)
+-#define VEND1_GLOBAL_INT_VEND_MASK_PCS BIT(14)
+-#define VEND1_GLOBAL_INT_VEND_MASK_PHY_XS BIT(13)
+-#define VEND1_GLOBAL_INT_VEND_MASK_AN BIT(12)
+-#define VEND1_GLOBAL_INT_VEND_MASK_GBE BIT(11)
+-#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL1 BIT(2)
+-#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL2 BIT(1)
+-#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3 BIT(0)
+-
+-/* Sleep and timeout for checking if the Processor-Intensive
+- * MDIO operation is finished
+- */
+-#define AQR107_OP_IN_PROG_SLEEP 1000
+-#define AQR107_OP_IN_PROG_TIMEOUT 100000
+-
+-struct aqr107_hw_stat {
+- const char *name;
+- int reg;
+- int size;
+-};
+-
+-#define SGMII_STAT(n, r, s) { n, MDIO_C22EXT_STAT_SGMII_ ## r, s }
+-static const struct aqr107_hw_stat aqr107_hw_stats[] = {
+- SGMII_STAT("sgmii_rx_good_frames", RX_GOOD_FRAMES, 26),
+- SGMII_STAT("sgmii_rx_bad_frames", RX_BAD_FRAMES, 26),
+- SGMII_STAT("sgmii_rx_false_carrier_events", RX_FALSE_CARRIER, 8),
+- SGMII_STAT("sgmii_tx_good_frames", TX_GOOD_FRAMES, 26),
+- SGMII_STAT("sgmii_tx_bad_frames", TX_BAD_FRAMES, 26),
+- SGMII_STAT("sgmii_tx_false_carrier_events", TX_FALSE_CARRIER, 8),
+- SGMII_STAT("sgmii_tx_collisions", TX_COLLISIONS, 8),
+- SGMII_STAT("sgmii_tx_line_collisions", TX_LINE_COLLISIONS, 8),
+- SGMII_STAT("sgmii_tx_frame_alignment_err", TX_FRAME_ALIGN_ERR, 16),
+- SGMII_STAT("sgmii_tx_runt_frames", TX_RUNT_FRAMES, 22),
+-};
+-#define AQR107_SGMII_STAT_SZ ARRAY_SIZE(aqr107_hw_stats)
+-
+-struct aqr107_priv {
+- u64 sgmii_stats[AQR107_SGMII_STAT_SZ];
+-};
+-
+-static int aqr107_get_sset_count(struct phy_device *phydev)
+-{
+- return AQR107_SGMII_STAT_SZ;
+-}
+-
+-static void aqr107_get_strings(struct phy_device *phydev, u8 *data)
+-{
+- int i;
+-
+- for (i = 0; i < AQR107_SGMII_STAT_SZ; i++)
+- strscpy(data + i * ETH_GSTRING_LEN, aqr107_hw_stats[i].name,
+- ETH_GSTRING_LEN);
+-}
+-
+-static u64 aqr107_get_stat(struct phy_device *phydev, int index)
+-{
+- const struct aqr107_hw_stat *stat = aqr107_hw_stats + index;
+- int len_l = min(stat->size, 16);
+- int len_h = stat->size - len_l;
+- u64 ret;
+- int val;
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_C22EXT, stat->reg);
+- if (val < 0)
+- return U64_MAX;
+-
+- ret = val & GENMASK(len_l - 1, 0);
+- if (len_h) {
+- val = phy_read_mmd(phydev, MDIO_MMD_C22EXT, stat->reg + 1);
+- if (val < 0)
+- return U64_MAX;
+-
+- ret += (val & GENMASK(len_h - 1, 0)) << 16;
+- }
+-
+- return ret;
+-}
+-
+-static void aqr107_get_stats(struct phy_device *phydev,
+- struct ethtool_stats *stats, u64 *data)
+-{
+- struct aqr107_priv *priv = phydev->priv;
+- u64 val;
+- int i;
+-
+- for (i = 0; i < AQR107_SGMII_STAT_SZ; i++) {
+- val = aqr107_get_stat(phydev, i);
+- if (val == U64_MAX)
+- phydev_err(phydev, "Reading HW Statistics failed for %s\n",
+- aqr107_hw_stats[i].name);
+- else
+- priv->sgmii_stats[i] += val;
+-
+- data[i] = priv->sgmii_stats[i];
+- }
+-}
+-
+-static int aqr_config_aneg(struct phy_device *phydev)
+-{
+- bool changed = false;
+- u16 reg;
+- int ret;
+-
+- if (phydev->autoneg == AUTONEG_DISABLE)
+- return genphy_c45_pma_setup_forced(phydev);
+-
+- ret = genphy_c45_an_config_aneg(phydev);
+- if (ret < 0)
+- return ret;
+- if (ret > 0)
+- changed = true;
+-
+- /* Clause 45 has no standardized support for 1000BaseT, therefore
+- * use vendor registers for this mode.
+- */
+- reg = 0;
+- if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+- phydev->advertising))
+- reg |= MDIO_AN_VEND_PROV_1000BASET_FULL;
+-
+- if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+- phydev->advertising))
+- reg |= MDIO_AN_VEND_PROV_1000BASET_HALF;
+-
+- /* Handle the case when the 2.5G and 5G speeds are not advertised */
+- if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+- phydev->advertising))
+- reg |= MDIO_AN_VEND_PROV_2500BASET_FULL;
+-
+- if (linkmode_test_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
+- phydev->advertising))
+- reg |= MDIO_AN_VEND_PROV_5000BASET_FULL;
+-
+- ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV,
+- MDIO_AN_VEND_PROV_1000BASET_HALF |
+- MDIO_AN_VEND_PROV_1000BASET_FULL |
+- MDIO_AN_VEND_PROV_2500BASET_FULL |
+- MDIO_AN_VEND_PROV_5000BASET_FULL, reg);
+- if (ret < 0)
+- return ret;
+- if (ret > 0)
+- changed = true;
+-
+- return genphy_c45_check_and_restart_aneg(phydev, changed);
+-}
+-
+-static int aqr_config_intr(struct phy_device *phydev)
+-{
+- bool en = phydev->interrupts == PHY_INTERRUPT_ENABLED;
+- int err;
+-
+- if (en) {
+- /* Clear any pending interrupts before enabling them */
+- err = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_STATUS2);
+- if (err < 0)
+- return err;
+- }
+-
+- err = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_MASK2,
+- en ? MDIO_AN_TX_VEND_INT_MASK2_LINK : 0);
+- if (err < 0)
+- return err;
+-
+- err = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_INT_STD_MASK,
+- en ? VEND1_GLOBAL_INT_STD_MASK_ALL : 0);
+- if (err < 0)
+- return err;
+-
+- err = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_INT_VEND_MASK,
+- en ? VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3 |
+- VEND1_GLOBAL_INT_VEND_MASK_AN : 0);
+- if (err < 0)
+- return err;
+-
+- if (!en) {
+- /* Clear any pending interrupts after we have disabled them */
+- err = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_STATUS2);
+- if (err < 0)
+- return err;
+- }
+-
+- return 0;
+-}
+-
+-static irqreturn_t aqr_handle_interrupt(struct phy_device *phydev)
+-{
+- int irq_status;
+-
+- irq_status = phy_read_mmd(phydev, MDIO_MMD_AN,
+- MDIO_AN_TX_VEND_INT_STATUS2);
+- if (irq_status < 0) {
+- phy_error(phydev);
+- return IRQ_NONE;
+- }
+-
+- if (!(irq_status & MDIO_AN_TX_VEND_INT_STATUS2_MASK))
+- return IRQ_NONE;
+-
+- phy_trigger_machine(phydev);
+-
+- return IRQ_HANDLED;
+-}
+-
+-static int aqr_read_status(struct phy_device *phydev)
+-{
+- int val;
+-
+- if (phydev->autoneg == AUTONEG_ENABLE) {
+- val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT1);
+- if (val < 0)
+- return val;
+-
+- linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+- phydev->lp_advertising,
+- val & MDIO_AN_RX_LP_STAT1_1000BASET_FULL);
+- linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+- phydev->lp_advertising,
+- val & MDIO_AN_RX_LP_STAT1_1000BASET_HALF);
+- }
+-
+- return genphy_c45_read_status(phydev);
+-}
+-
+-static int aqr107_read_rate(struct phy_device *phydev)
+-{
+- u32 config_reg;
+- int val;
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_STATUS1);
+- if (val < 0)
+- return val;
+-
+- if (val & MDIO_AN_TX_VEND_STATUS1_FULL_DUPLEX)
+- phydev->duplex = DUPLEX_FULL;
+- else
+- phydev->duplex = DUPLEX_HALF;
+-
+- switch (FIELD_GET(MDIO_AN_TX_VEND_STATUS1_RATE_MASK, val)) {
+- case MDIO_AN_TX_VEND_STATUS1_10BASET:
+- phydev->speed = SPEED_10;
+- config_reg = VEND1_GLOBAL_CFG_10M;
+- break;
+- case MDIO_AN_TX_VEND_STATUS1_100BASETX:
+- phydev->speed = SPEED_100;
+- config_reg = VEND1_GLOBAL_CFG_100M;
+- break;
+- case MDIO_AN_TX_VEND_STATUS1_1000BASET:
+- phydev->speed = SPEED_1000;
+- config_reg = VEND1_GLOBAL_CFG_1G;
+- break;
+- case MDIO_AN_TX_VEND_STATUS1_2500BASET:
+- phydev->speed = SPEED_2500;
+- config_reg = VEND1_GLOBAL_CFG_2_5G;
+- break;
+- case MDIO_AN_TX_VEND_STATUS1_5000BASET:
+- phydev->speed = SPEED_5000;
+- config_reg = VEND1_GLOBAL_CFG_5G;
+- break;
+- case MDIO_AN_TX_VEND_STATUS1_10GBASET:
+- phydev->speed = SPEED_10000;
+- config_reg = VEND1_GLOBAL_CFG_10G;
+- break;
+- default:
+- phydev->speed = SPEED_UNKNOWN;
+- return 0;
+- }
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_VEND1, config_reg);
+- if (val < 0)
+- return val;
+-
+- if (FIELD_GET(VEND1_GLOBAL_CFG_RATE_ADAPT, val) ==
+- VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE)
+- phydev->rate_matching = RATE_MATCH_PAUSE;
+- else
+- phydev->rate_matching = RATE_MATCH_NONE;
+-
+- return 0;
+-}
+-
+-static int aqr107_read_status(struct phy_device *phydev)
+-{
+- int val, ret;
+-
+- ret = aqr_read_status(phydev);
+- if (ret)
+- return ret;
+-
+- if (!phydev->link || phydev->autoneg == AUTONEG_DISABLE)
+- return 0;
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MDIO_PHYXS_VEND_IF_STATUS);
+- if (val < 0)
+- return val;
+-
+- switch (FIELD_GET(MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK, val)) {
+- case MDIO_PHYXS_VEND_IF_STATUS_TYPE_KR:
+- phydev->interface = PHY_INTERFACE_MODE_10GKR;
+- break;
+- case MDIO_PHYXS_VEND_IF_STATUS_TYPE_KX:
+- phydev->interface = PHY_INTERFACE_MODE_1000BASEKX;
+- break;
+- case MDIO_PHYXS_VEND_IF_STATUS_TYPE_XFI:
+- phydev->interface = PHY_INTERFACE_MODE_10GBASER;
+- break;
+- case MDIO_PHYXS_VEND_IF_STATUS_TYPE_USXGMII:
+- phydev->interface = PHY_INTERFACE_MODE_USXGMII;
+- break;
+- case MDIO_PHYXS_VEND_IF_STATUS_TYPE_XAUI:
+- phydev->interface = PHY_INTERFACE_MODE_XAUI;
+- break;
+- case MDIO_PHYXS_VEND_IF_STATUS_TYPE_SGMII:
+- phydev->interface = PHY_INTERFACE_MODE_SGMII;
+- break;
+- case MDIO_PHYXS_VEND_IF_STATUS_TYPE_RXAUI:
+- phydev->interface = PHY_INTERFACE_MODE_RXAUI;
+- break;
+- case MDIO_PHYXS_VEND_IF_STATUS_TYPE_OCSGMII:
+- phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+- break;
+- default:
+- phydev->interface = PHY_INTERFACE_MODE_NA;
+- break;
+- }
+-
+- /* Read possibly downshifted rate from vendor register */
+- return aqr107_read_rate(phydev);
+-}
+-
+-static int aqr107_get_downshift(struct phy_device *phydev, u8 *data)
+-{
+- int val, cnt, enable;
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV);
+- if (val < 0)
+- return val;
+-
+- enable = FIELD_GET(MDIO_AN_VEND_PROV_DOWNSHIFT_EN, val);
+- cnt = FIELD_GET(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, val);
+-
+- *data = enable && cnt ? cnt : DOWNSHIFT_DEV_DISABLE;
+-
+- return 0;
+-}
+-
+-static int aqr107_set_downshift(struct phy_device *phydev, u8 cnt)
+-{
+- int val = 0;
+-
+- if (!FIELD_FIT(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, cnt))
+- return -E2BIG;
+-
+- if (cnt != DOWNSHIFT_DEV_DISABLE) {
+- val = MDIO_AN_VEND_PROV_DOWNSHIFT_EN;
+- val |= FIELD_PREP(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, cnt);
+- }
+-
+- return phy_modify_mmd(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV,
+- MDIO_AN_VEND_PROV_DOWNSHIFT_EN |
+- MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, val);
+-}
+-
+-static int aqr107_get_tunable(struct phy_device *phydev,
+- struct ethtool_tunable *tuna, void *data)
+-{
+- switch (tuna->id) {
+- case ETHTOOL_PHY_DOWNSHIFT:
+- return aqr107_get_downshift(phydev, data);
+- default:
+- return -EOPNOTSUPP;
+- }
+-}
+-
+-static int aqr107_set_tunable(struct phy_device *phydev,
+- struct ethtool_tunable *tuna, const void *data)
+-{
+- switch (tuna->id) {
+- case ETHTOOL_PHY_DOWNSHIFT:
+- return aqr107_set_downshift(phydev, *(const u8 *)data);
+- default:
+- return -EOPNOTSUPP;
+- }
+-}
+-
+-/* If we configure settings whilst firmware is still initializing the chip,
+- * then these settings may be overwritten. Therefore make sure chip
+- * initialization has completed. Use presence of the firmware ID as
+- * indicator for initialization having completed.
+- * The chip also provides a "reset completed" bit, but it's cleared after
+- * read. Therefore function would time out if called again.
+- */
+-static int aqr107_wait_reset_complete(struct phy_device *phydev)
+-{
+- int val;
+-
+- return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
+- VEND1_GLOBAL_FW_ID, val, val != 0,
+- 20000, 2000000, false);
+-}
+-
+-static void aqr107_chip_info(struct phy_device *phydev)
+-{
+- u8 fw_major, fw_minor, build_id, prov_id;
+- int val;
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_FW_ID);
+- if (val < 0)
+- return;
+-
+- fw_major = FIELD_GET(VEND1_GLOBAL_FW_ID_MAJOR, val);
+- fw_minor = FIELD_GET(VEND1_GLOBAL_FW_ID_MINOR, val);
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_RSVD_STAT1);
+- if (val < 0)
+- return;
+-
+- build_id = FIELD_GET(VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID, val);
+- prov_id = FIELD_GET(VEND1_GLOBAL_RSVD_STAT1_PROV_ID, val);
+-
+- phydev_dbg(phydev, "FW %u.%u, Build %u, Provisioning %u\n",
+- fw_major, fw_minor, build_id, prov_id);
+-}
+-
+-static int aqr107_config_init(struct phy_device *phydev)
+-{
+- int ret;
+-
+- /* Check that the PHY interface type is compatible */
+- if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
+- phydev->interface != PHY_INTERFACE_MODE_1000BASEKX &&
+- phydev->interface != PHY_INTERFACE_MODE_2500BASEX &&
+- phydev->interface != PHY_INTERFACE_MODE_XGMII &&
+- phydev->interface != PHY_INTERFACE_MODE_USXGMII &&
+- phydev->interface != PHY_INTERFACE_MODE_10GKR &&
+- phydev->interface != PHY_INTERFACE_MODE_10GBASER &&
+- phydev->interface != PHY_INTERFACE_MODE_XAUI &&
+- phydev->interface != PHY_INTERFACE_MODE_RXAUI)
+- return -ENODEV;
+-
+- WARN(phydev->interface == PHY_INTERFACE_MODE_XGMII,
+- "Your devicetree is out of date, please update it. The AQR107 family doesn't support XGMII, maybe you mean USXGMII.\n");
+-
+- ret = aqr107_wait_reset_complete(phydev);
+- if (!ret)
+- aqr107_chip_info(phydev);
+-
+- return aqr107_set_downshift(phydev, MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT);
+-}
+-
+-static int aqcs109_config_init(struct phy_device *phydev)
+-{
+- int ret;
+-
+- /* Check that the PHY interface type is compatible */
+- if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
+- phydev->interface != PHY_INTERFACE_MODE_2500BASEX)
+- return -ENODEV;
+-
+- ret = aqr107_wait_reset_complete(phydev);
+- if (!ret)
+- aqr107_chip_info(phydev);
+-
+- /* AQCS109 belongs to a chip family partially supporting 10G and 5G.
+- * PMA speed ability bits are the same for all members of the family,
+- * AQCS109 however supports speeds up to 2.5G only.
+- */
+- phy_set_max_speed(phydev, SPEED_2500);
+-
+- return aqr107_set_downshift(phydev, MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT);
+-}
+-
+-static void aqr107_link_change_notify(struct phy_device *phydev)
+-{
+- u8 fw_major, fw_minor;
+- bool downshift, short_reach, afr;
+- int mode, val;
+-
+- if (phydev->state != PHY_RUNNING || phydev->autoneg == AUTONEG_DISABLE)
+- return;
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT1);
+- /* call failed or link partner is no Aquantia PHY */
+- if (val < 0 || !(val & MDIO_AN_RX_LP_STAT1_AQ_PHY))
+- return;
+-
+- short_reach = val & MDIO_AN_RX_LP_STAT1_SHORT_REACH;
+- downshift = val & MDIO_AN_RX_LP_STAT1_AQRATE_DOWNSHIFT;
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT4);
+- if (val < 0)
+- return;
+-
+- fw_major = FIELD_GET(MDIO_AN_RX_LP_STAT4_FW_MAJOR, val);
+- fw_minor = FIELD_GET(MDIO_AN_RX_LP_STAT4_FW_MINOR, val);
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_VEND_STAT3);
+- if (val < 0)
+- return;
+-
+- afr = val & MDIO_AN_RX_VEND_STAT3_AFR;
+-
+- phydev_dbg(phydev, "Link partner is Aquantia PHY, FW %u.%u%s%s%s\n",
+- fw_major, fw_minor,
+- short_reach ? ", short reach mode" : "",
+- downshift ? ", fast-retrain downshift advertised" : "",
+- afr ? ", fast reframe advertised" : "");
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_RSVD_STAT9);
+- if (val < 0)
+- return;
+-
+- mode = FIELD_GET(VEND1_GLOBAL_RSVD_STAT9_MODE, val);
+- if (mode == VEND1_GLOBAL_RSVD_STAT9_1000BT2)
+- phydev_info(phydev, "Aquantia 1000Base-T2 mode active\n");
+-}
+-
+-static int aqr107_wait_processor_intensive_op(struct phy_device *phydev)
+-{
+- int val, err;
+-
+- /* The datasheet notes to wait at least 1ms after issuing a
+- * processor intensive operation before checking.
+- * We cannot use the 'sleep_before_read' parameter of read_poll_timeout
+- * because that just determines the maximum time slept, not the minimum.
+- */
+- usleep_range(1000, 5000);
+-
+- err = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
+- VEND1_GLOBAL_GEN_STAT2, val,
+- !(val & VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG),
+- AQR107_OP_IN_PROG_SLEEP,
+- AQR107_OP_IN_PROG_TIMEOUT, false);
+- if (err) {
+- phydev_err(phydev, "timeout: processor-intensive MDIO operation\n");
+- return err;
+- }
+-
+- return 0;
+-}
+-
+-static int aqr107_get_rate_matching(struct phy_device *phydev,
+- phy_interface_t iface)
+-{
+- if (iface == PHY_INTERFACE_MODE_10GBASER ||
+- iface == PHY_INTERFACE_MODE_2500BASEX ||
+- iface == PHY_INTERFACE_MODE_NA)
+- return RATE_MATCH_PAUSE;
+- return RATE_MATCH_NONE;
+-}
+-
+-static int aqr107_suspend(struct phy_device *phydev)
+-{
+- int err;
+-
+- err = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MDIO_CTRL1,
+- MDIO_CTRL1_LPOWER);
+- if (err)
+- return err;
+-
+- return aqr107_wait_processor_intensive_op(phydev);
+-}
+-
+-static int aqr107_resume(struct phy_device *phydev)
+-{
+- int err;
+-
+- err = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MDIO_CTRL1,
+- MDIO_CTRL1_LPOWER);
+- if (err)
+- return err;
+-
+- return aqr107_wait_processor_intensive_op(phydev);
+-}
+-
+-static int aqr107_probe(struct phy_device *phydev)
+-{
+- phydev->priv = devm_kzalloc(&phydev->mdio.dev,
+- sizeof(struct aqr107_priv), GFP_KERNEL);
+- if (!phydev->priv)
+- return -ENOMEM;
+-
+- return aqr_hwmon_probe(phydev);
+-}
+-
+-static struct phy_driver aqr_driver[] = {
+-{
+- PHY_ID_MATCH_MODEL(PHY_ID_AQ1202),
+- .name = "Aquantia AQ1202",
+- .config_aneg = aqr_config_aneg,
+- .config_intr = aqr_config_intr,
+- .handle_interrupt = aqr_handle_interrupt,
+- .read_status = aqr_read_status,
+-},
+-{
+- PHY_ID_MATCH_MODEL(PHY_ID_AQ2104),
+- .name = "Aquantia AQ2104",
+- .config_aneg = aqr_config_aneg,
+- .config_intr = aqr_config_intr,
+- .handle_interrupt = aqr_handle_interrupt,
+- .read_status = aqr_read_status,
+-},
+-{
+- PHY_ID_MATCH_MODEL(PHY_ID_AQR105),
+- .name = "Aquantia AQR105",
+- .config_aneg = aqr_config_aneg,
+- .config_intr = aqr_config_intr,
+- .handle_interrupt = aqr_handle_interrupt,
+- .read_status = aqr_read_status,
+- .suspend = aqr107_suspend,
+- .resume = aqr107_resume,
+-},
+-{
+- PHY_ID_MATCH_MODEL(PHY_ID_AQR106),
+- .name = "Aquantia AQR106",
+- .config_aneg = aqr_config_aneg,
+- .config_intr = aqr_config_intr,
+- .handle_interrupt = aqr_handle_interrupt,
+- .read_status = aqr_read_status,
+-},
+-{
+- PHY_ID_MATCH_MODEL(PHY_ID_AQR107),
+- .name = "Aquantia AQR107",
+- .probe = aqr107_probe,
+- .get_rate_matching = aqr107_get_rate_matching,
+- .config_init = aqr107_config_init,
+- .config_aneg = aqr_config_aneg,
+- .config_intr = aqr_config_intr,
+- .handle_interrupt = aqr_handle_interrupt,
+- .read_status = aqr107_read_status,
+- .get_tunable = aqr107_get_tunable,
+- .set_tunable = aqr107_set_tunable,
+- .suspend = aqr107_suspend,
+- .resume = aqr107_resume,
+- .get_sset_count = aqr107_get_sset_count,
+- .get_strings = aqr107_get_strings,
+- .get_stats = aqr107_get_stats,
+- .link_change_notify = aqr107_link_change_notify,
+-},
+-{
+- PHY_ID_MATCH_MODEL(PHY_ID_AQCS109),
+- .name = "Aquantia AQCS109",
+- .probe = aqr107_probe,
+- .get_rate_matching = aqr107_get_rate_matching,
+- .config_init = aqcs109_config_init,
+- .config_aneg = aqr_config_aneg,
+- .config_intr = aqr_config_intr,
+- .handle_interrupt = aqr_handle_interrupt,
+- .read_status = aqr107_read_status,
+- .get_tunable = aqr107_get_tunable,
+- .set_tunable = aqr107_set_tunable,
+- .suspend = aqr107_suspend,
+- .resume = aqr107_resume,
+- .get_sset_count = aqr107_get_sset_count,
+- .get_strings = aqr107_get_strings,
+- .get_stats = aqr107_get_stats,
+- .link_change_notify = aqr107_link_change_notify,
+-},
+-{
+- PHY_ID_MATCH_MODEL(PHY_ID_AQR405),
+- .name = "Aquantia AQR405",
+- .config_aneg = aqr_config_aneg,
+- .config_intr = aqr_config_intr,
+- .handle_interrupt = aqr_handle_interrupt,
+- .read_status = aqr_read_status,
+-},
+-{
+- PHY_ID_MATCH_MODEL(PHY_ID_AQR113C),
+- .name = "Aquantia AQR113C",
+- .probe = aqr107_probe,
+- .get_rate_matching = aqr107_get_rate_matching,
+- .config_init = aqr107_config_init,
+- .config_aneg = aqr_config_aneg,
+- .config_intr = aqr_config_intr,
+- .handle_interrupt = aqr_handle_interrupt,
+- .read_status = aqr107_read_status,
+- .get_tunable = aqr107_get_tunable,
+- .set_tunable = aqr107_set_tunable,
+- .suspend = aqr107_suspend,
+- .resume = aqr107_resume,
+- .get_sset_count = aqr107_get_sset_count,
+- .get_strings = aqr107_get_strings,
+- .get_stats = aqr107_get_stats,
+- .link_change_notify = aqr107_link_change_notify,
+-},
+-};
+-
+-module_phy_driver(aqr_driver);
+-
+-static struct mdio_device_id __maybe_unused aqr_tbl[] = {
+- { PHY_ID_MATCH_MODEL(PHY_ID_AQ1202) },
+- { PHY_ID_MATCH_MODEL(PHY_ID_AQ2104) },
+- { PHY_ID_MATCH_MODEL(PHY_ID_AQR105) },
+- { PHY_ID_MATCH_MODEL(PHY_ID_AQR106) },
+- { PHY_ID_MATCH_MODEL(PHY_ID_AQR107) },
+- { PHY_ID_MATCH_MODEL(PHY_ID_AQCS109) },
+- { PHY_ID_MATCH_MODEL(PHY_ID_AQR405) },
+- { PHY_ID_MATCH_MODEL(PHY_ID_AQR113C) },
+- { }
+-};
+-
+-MODULE_DEVICE_TABLE(mdio, aqr_tbl);
+-
+-MODULE_DESCRIPTION("Aquantia PHY driver");
+-MODULE_AUTHOR("Shaohui Xie <Shaohui.Xie@freescale.com>");
+-MODULE_LICENSE("GPL v2");
diff --git a/target/linux/generic/backport-6.6/702-02-v6.7-net-phy-aquantia-move-MMD_VEND-define-to-header.patch b/target/linux/generic/backport-6.6/702-02-v6.7-net-phy-aquantia-move-MMD_VEND-define-to-header.patch
new file mode 100644
index 0000000000..2b94522723
--- /dev/null
+++ b/target/linux/generic/backport-6.6/702-02-v6.7-net-phy-aquantia-move-MMD_VEND-define-to-header.patch
@@ -0,0 +1,183 @@
+From e1fbfa4a995d42e02e22b0dff2f8b4fdee1504b3 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 14 Nov 2023 15:08:42 +0100
+Subject: [PATCH 2/3] net: phy: aquantia: move MMD_VEND define to header
+
+Move MMD_VEND define to header to clean things up and in preparation for
+firmware loading support that require some define placed in
+aquantia_main.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/aquantia/aquantia.h | 69 +++++++++++++++++++++++
+ drivers/net/phy/aquantia/aquantia_hwmon.c | 14 -----
+ drivers/net/phy/aquantia/aquantia_main.c | 55 ------------------
+ 3 files changed, 69 insertions(+), 69 deletions(-)
+
+--- a/drivers/net/phy/aquantia/aquantia.h
++++ b/drivers/net/phy/aquantia/aquantia.h
+@@ -9,6 +9,75 @@
+ #include <linux/device.h>
+ #include <linux/phy.h>
+
++/* Vendor specific 1, MDIO_MMD_VEND1 */
++#define VEND1_GLOBAL_FW_ID 0x0020
++#define VEND1_GLOBAL_FW_ID_MAJOR GENMASK(15, 8)
++#define VEND1_GLOBAL_FW_ID_MINOR GENMASK(7, 0)
++
++/* The following registers all have similar layouts; first the registers... */
++#define VEND1_GLOBAL_CFG_10M 0x0310
++#define VEND1_GLOBAL_CFG_100M 0x031b
++#define VEND1_GLOBAL_CFG_1G 0x031c
++#define VEND1_GLOBAL_CFG_2_5G 0x031d
++#define VEND1_GLOBAL_CFG_5G 0x031e
++#define VEND1_GLOBAL_CFG_10G 0x031f
++/* ...and now the fields */
++#define VEND1_GLOBAL_CFG_RATE_ADAPT GENMASK(8, 7)
++#define VEND1_GLOBAL_CFG_RATE_ADAPT_NONE 0
++#define VEND1_GLOBAL_CFG_RATE_ADAPT_USX 1
++#define VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE 2
++
++/* Vendor specific 1, MDIO_MMD_VEND2 */
++#define VEND1_THERMAL_PROV_HIGH_TEMP_FAIL 0xc421
++#define VEND1_THERMAL_PROV_LOW_TEMP_FAIL 0xc422
++#define VEND1_THERMAL_PROV_HIGH_TEMP_WARN 0xc423
++#define VEND1_THERMAL_PROV_LOW_TEMP_WARN 0xc424
++#define VEND1_THERMAL_STAT1 0xc820
++#define VEND1_THERMAL_STAT2 0xc821
++#define VEND1_THERMAL_STAT2_VALID BIT(0)
++#define VEND1_GENERAL_STAT1 0xc830
++#define VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL BIT(14)
++#define VEND1_GENERAL_STAT1_LOW_TEMP_FAIL BIT(13)
++#define VEND1_GENERAL_STAT1_HIGH_TEMP_WARN BIT(12)
++#define VEND1_GENERAL_STAT1_LOW_TEMP_WARN BIT(11)
++
++#define VEND1_GLOBAL_GEN_STAT2 0xc831
++#define VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG BIT(15)
++
++#define VEND1_GLOBAL_RSVD_STAT1 0xc885
++#define VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID GENMASK(7, 4)
++#define VEND1_GLOBAL_RSVD_STAT1_PROV_ID GENMASK(3, 0)
++
++#define VEND1_GLOBAL_RSVD_STAT9 0xc88d
++#define VEND1_GLOBAL_RSVD_STAT9_MODE GENMASK(7, 0)
++#define VEND1_GLOBAL_RSVD_STAT9_1000BT2 0x23
++
++#define VEND1_GLOBAL_INT_STD_STATUS 0xfc00
++#define VEND1_GLOBAL_INT_VEND_STATUS 0xfc01
++
++#define VEND1_GLOBAL_INT_STD_MASK 0xff00
++#define VEND1_GLOBAL_INT_STD_MASK_PMA1 BIT(15)
++#define VEND1_GLOBAL_INT_STD_MASK_PMA2 BIT(14)
++#define VEND1_GLOBAL_INT_STD_MASK_PCS1 BIT(13)
++#define VEND1_GLOBAL_INT_STD_MASK_PCS2 BIT(12)
++#define VEND1_GLOBAL_INT_STD_MASK_PCS3 BIT(11)
++#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS1 BIT(10)
++#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS2 BIT(9)
++#define VEND1_GLOBAL_INT_STD_MASK_AN1 BIT(8)
++#define VEND1_GLOBAL_INT_STD_MASK_AN2 BIT(7)
++#define VEND1_GLOBAL_INT_STD_MASK_GBE BIT(6)
++#define VEND1_GLOBAL_INT_STD_MASK_ALL BIT(0)
++
++#define VEND1_GLOBAL_INT_VEND_MASK 0xff01
++#define VEND1_GLOBAL_INT_VEND_MASK_PMA BIT(15)
++#define VEND1_GLOBAL_INT_VEND_MASK_PCS BIT(14)
++#define VEND1_GLOBAL_INT_VEND_MASK_PHY_XS BIT(13)
++#define VEND1_GLOBAL_INT_VEND_MASK_AN BIT(12)
++#define VEND1_GLOBAL_INT_VEND_MASK_GBE BIT(11)
++#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL1 BIT(2)
++#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL2 BIT(1)
++#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3 BIT(0)
++
+ #if IS_REACHABLE(CONFIG_HWMON)
+ int aqr_hwmon_probe(struct phy_device *phydev);
+ #else
+--- a/drivers/net/phy/aquantia/aquantia_hwmon.c
++++ b/drivers/net/phy/aquantia/aquantia_hwmon.c
+@@ -13,20 +13,6 @@
+
+ #include "aquantia.h"
+
+-/* Vendor specific 1, MDIO_MMD_VEND2 */
+-#define VEND1_THERMAL_PROV_HIGH_TEMP_FAIL 0xc421
+-#define VEND1_THERMAL_PROV_LOW_TEMP_FAIL 0xc422
+-#define VEND1_THERMAL_PROV_HIGH_TEMP_WARN 0xc423
+-#define VEND1_THERMAL_PROV_LOW_TEMP_WARN 0xc424
+-#define VEND1_THERMAL_STAT1 0xc820
+-#define VEND1_THERMAL_STAT2 0xc821
+-#define VEND1_THERMAL_STAT2_VALID BIT(0)
+-#define VEND1_GENERAL_STAT1 0xc830
+-#define VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL BIT(14)
+-#define VEND1_GENERAL_STAT1_LOW_TEMP_FAIL BIT(13)
+-#define VEND1_GENERAL_STAT1_HIGH_TEMP_WARN BIT(12)
+-#define VEND1_GENERAL_STAT1_LOW_TEMP_WARN BIT(11)
+-
+ #if IS_REACHABLE(CONFIG_HWMON)
+
+ static umode_t aqr_hwmon_is_visible(const void *data,
+--- a/drivers/net/phy/aquantia/aquantia_main.c
++++ b/drivers/net/phy/aquantia/aquantia_main.c
+@@ -89,61 +89,6 @@
+ #define MDIO_C22EXT_STAT_SGMII_TX_FRAME_ALIGN_ERR 0xd31a
+ #define MDIO_C22EXT_STAT_SGMII_TX_RUNT_FRAMES 0xd31b
+
+-/* Vendor specific 1, MDIO_MMD_VEND1 */
+-#define VEND1_GLOBAL_FW_ID 0x0020
+-#define VEND1_GLOBAL_FW_ID_MAJOR GENMASK(15, 8)
+-#define VEND1_GLOBAL_FW_ID_MINOR GENMASK(7, 0)
+-
+-#define VEND1_GLOBAL_GEN_STAT2 0xc831
+-#define VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG BIT(15)
+-
+-/* The following registers all have similar layouts; first the registers... */
+-#define VEND1_GLOBAL_CFG_10M 0x0310
+-#define VEND1_GLOBAL_CFG_100M 0x031b
+-#define VEND1_GLOBAL_CFG_1G 0x031c
+-#define VEND1_GLOBAL_CFG_2_5G 0x031d
+-#define VEND1_GLOBAL_CFG_5G 0x031e
+-#define VEND1_GLOBAL_CFG_10G 0x031f
+-/* ...and now the fields */
+-#define VEND1_GLOBAL_CFG_RATE_ADAPT GENMASK(8, 7)
+-#define VEND1_GLOBAL_CFG_RATE_ADAPT_NONE 0
+-#define VEND1_GLOBAL_CFG_RATE_ADAPT_USX 1
+-#define VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE 2
+-
+-#define VEND1_GLOBAL_RSVD_STAT1 0xc885
+-#define VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID GENMASK(7, 4)
+-#define VEND1_GLOBAL_RSVD_STAT1_PROV_ID GENMASK(3, 0)
+-
+-#define VEND1_GLOBAL_RSVD_STAT9 0xc88d
+-#define VEND1_GLOBAL_RSVD_STAT9_MODE GENMASK(7, 0)
+-#define VEND1_GLOBAL_RSVD_STAT9_1000BT2 0x23
+-
+-#define VEND1_GLOBAL_INT_STD_STATUS 0xfc00
+-#define VEND1_GLOBAL_INT_VEND_STATUS 0xfc01
+-
+-#define VEND1_GLOBAL_INT_STD_MASK 0xff00
+-#define VEND1_GLOBAL_INT_STD_MASK_PMA1 BIT(15)
+-#define VEND1_GLOBAL_INT_STD_MASK_PMA2 BIT(14)
+-#define VEND1_GLOBAL_INT_STD_MASK_PCS1 BIT(13)
+-#define VEND1_GLOBAL_INT_STD_MASK_PCS2 BIT(12)
+-#define VEND1_GLOBAL_INT_STD_MASK_PCS3 BIT(11)
+-#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS1 BIT(10)
+-#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS2 BIT(9)
+-#define VEND1_GLOBAL_INT_STD_MASK_AN1 BIT(8)
+-#define VEND1_GLOBAL_INT_STD_MASK_AN2 BIT(7)
+-#define VEND1_GLOBAL_INT_STD_MASK_GBE BIT(6)
+-#define VEND1_GLOBAL_INT_STD_MASK_ALL BIT(0)
+-
+-#define VEND1_GLOBAL_INT_VEND_MASK 0xff01
+-#define VEND1_GLOBAL_INT_VEND_MASK_PMA BIT(15)
+-#define VEND1_GLOBAL_INT_VEND_MASK_PCS BIT(14)
+-#define VEND1_GLOBAL_INT_VEND_MASK_PHY_XS BIT(13)
+-#define VEND1_GLOBAL_INT_VEND_MASK_AN BIT(12)
+-#define VEND1_GLOBAL_INT_VEND_MASK_GBE BIT(11)
+-#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL1 BIT(2)
+-#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL2 BIT(1)
+-#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3 BIT(0)
+-
+ /* Sleep and timeout for checking if the Processor-Intensive
+ * MDIO operation is finished
+ */
diff --git a/target/linux/generic/backport-6.6/702-03-v6.7-net-phy-aquantia-add-firmware-load-support.patch b/target/linux/generic/backport-6.6/702-03-v6.7-net-phy-aquantia-add-firmware-load-support.patch
new file mode 100644
index 0000000000..aa52b3baa6
--- /dev/null
+++ b/target/linux/generic/backport-6.6/702-03-v6.7-net-phy-aquantia-add-firmware-load-support.patch
@@ -0,0 +1,504 @@
+From e93984ebc1c82bd34f7a1b3391efaceee0a8ae96 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Tue, 14 Nov 2023 15:08:43 +0100
+Subject: [PATCH 3/3] net: phy: aquantia: add firmware load support
+
+Aquantia PHY-s require firmware to be loaded before they start operating.
+It can be automatically loaded in case when there is a SPI-NOR connected
+to Aquantia PHY-s or can be loaded from the host via MDIO.
+
+This patch adds support for loading the firmware via MDIO as in most cases
+there is no SPI-NOR being used to save on cost.
+Firmware loading code itself is ported from mainline U-boot with cleanups.
+
+The firmware has mixed values both in big and little endian.
+PHY core itself is big-endian but it expects values to be in little-endian.
+The firmware is little-endian but CRC-16 value for it is stored at the end
+of firmware in big-endian.
+
+It seems the PHY does the conversion internally from firmware that is
+little-endian to the PHY that is big-endian on using the mailbox
+but mailbox returns a big-endian CRC-16 to verify the written data
+integrity.
+
+Co-developed-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/aquantia/Kconfig | 1 +
+ drivers/net/phy/aquantia/Makefile | 2 +-
+ drivers/net/phy/aquantia/aquantia.h | 32 ++
+ drivers/net/phy/aquantia/aquantia_firmware.c | 370 +++++++++++++++++++
+ drivers/net/phy/aquantia/aquantia_main.c | 6 +
+ 5 files changed, 410 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/net/phy/aquantia/aquantia_firmware.c
+
+--- a/drivers/net/phy/aquantia/Kconfig
++++ b/drivers/net/phy/aquantia/Kconfig
+@@ -1,5 +1,6 @@
+ # SPDX-License-Identifier: GPL-2.0-only
+ config AQUANTIA_PHY
+ tristate "Aquantia PHYs"
++ select CRC_CCITT
+ help
+ Currently supports the Aquantia AQ1202, AQ2104, AQR105, AQR405
+--- a/drivers/net/phy/aquantia/Makefile
++++ b/drivers/net/phy/aquantia/Makefile
+@@ -1,5 +1,5 @@
+ # SPDX-License-Identifier: GPL-2.0
+-aquantia-objs += aquantia_main.o
++aquantia-objs += aquantia_main.o aquantia_firmware.o
+ ifdef CONFIG_HWMON
+ aquantia-objs += aquantia_hwmon.o
+ endif
+--- a/drivers/net/phy/aquantia/aquantia.h
++++ b/drivers/net/phy/aquantia/aquantia.h
+@@ -10,10 +10,35 @@
+ #include <linux/phy.h>
+
+ /* Vendor specific 1, MDIO_MMD_VEND1 */
++#define VEND1_GLOBAL_SC 0x0
++#define VEND1_GLOBAL_SC_SOFT_RESET BIT(15)
++#define VEND1_GLOBAL_SC_LOW_POWER BIT(11)
++
+ #define VEND1_GLOBAL_FW_ID 0x0020
+ #define VEND1_GLOBAL_FW_ID_MAJOR GENMASK(15, 8)
+ #define VEND1_GLOBAL_FW_ID_MINOR GENMASK(7, 0)
+
++#define VEND1_GLOBAL_MAILBOX_INTERFACE1 0x0200
++#define VEND1_GLOBAL_MAILBOX_INTERFACE1_EXECUTE BIT(15)
++#define VEND1_GLOBAL_MAILBOX_INTERFACE1_WRITE BIT(14)
++#define VEND1_GLOBAL_MAILBOX_INTERFACE1_CRC_RESET BIT(12)
++#define VEND1_GLOBAL_MAILBOX_INTERFACE1_BUSY BIT(8)
++
++#define VEND1_GLOBAL_MAILBOX_INTERFACE2 0x0201
++#define VEND1_GLOBAL_MAILBOX_INTERFACE3 0x0202
++#define VEND1_GLOBAL_MAILBOX_INTERFACE3_MSW_ADDR_MASK GENMASK(15, 0)
++#define VEND1_GLOBAL_MAILBOX_INTERFACE3_MSW_ADDR(x) FIELD_PREP(VEND1_GLOBAL_MAILBOX_INTERFACE3_MSW_ADDR_MASK, (u16)((x) >> 16))
++#define VEND1_GLOBAL_MAILBOX_INTERFACE4 0x0203
++#define VEND1_GLOBAL_MAILBOX_INTERFACE4_LSW_ADDR_MASK GENMASK(15, 2)
++#define VEND1_GLOBAL_MAILBOX_INTERFACE4_LSW_ADDR(x) FIELD_PREP(VEND1_GLOBAL_MAILBOX_INTERFACE4_LSW_ADDR_MASK, (u16)(x))
++
++#define VEND1_GLOBAL_MAILBOX_INTERFACE5 0x0204
++#define VEND1_GLOBAL_MAILBOX_INTERFACE5_MSW_DATA_MASK GENMASK(15, 0)
++#define VEND1_GLOBAL_MAILBOX_INTERFACE5_MSW_DATA(x) FIELD_PREP(VEND1_GLOBAL_MAILBOX_INTERFACE5_MSW_DATA_MASK, (u16)((x) >> 16))
++#define VEND1_GLOBAL_MAILBOX_INTERFACE6 0x0205
++#define VEND1_GLOBAL_MAILBOX_INTERFACE6_LSW_DATA_MASK GENMASK(15, 0)
++#define VEND1_GLOBAL_MAILBOX_INTERFACE6_LSW_DATA(x) FIELD_PREP(VEND1_GLOBAL_MAILBOX_INTERFACE6_LSW_DATA_MASK, (u16)(x))
++
+ /* The following registers all have similar layouts; first the registers... */
+ #define VEND1_GLOBAL_CFG_10M 0x0310
+ #define VEND1_GLOBAL_CFG_100M 0x031b
+@@ -28,6 +53,11 @@
+ #define VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE 2
+
+ /* Vendor specific 1, MDIO_MMD_VEND2 */
++#define VEND1_GLOBAL_CONTROL2 0xc001
++#define VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_RST BIT(15)
++#define VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_OVD BIT(6)
++#define VEND1_GLOBAL_CONTROL2_UP_RUN_STALL BIT(0)
++
+ #define VEND1_THERMAL_PROV_HIGH_TEMP_FAIL 0xc421
+ #define VEND1_THERMAL_PROV_LOW_TEMP_FAIL 0xc422
+ #define VEND1_THERMAL_PROV_HIGH_TEMP_WARN 0xc423
+@@ -83,3 +113,5 @@ int aqr_hwmon_probe(struct phy_device *p
+ #else
+ static inline int aqr_hwmon_probe(struct phy_device *phydev) { return 0; }
+ #endif
++
++int aqr_firmware_load(struct phy_device *phydev);
+--- /dev/null
++++ b/drivers/net/phy/aquantia/aquantia_firmware.c
+@@ -0,0 +1,370 @@
++// SPDX-License-Identifier: GPL-2.0
++
++#include <linux/bitfield.h>
++#include <linux/of.h>
++#include <linux/firmware.h>
++#include <linux/crc-ccitt.h>
++#include <linux/nvmem-consumer.h>
++
++#include <asm/unaligned.h>
++
++#include "aquantia.h"
++
++#define UP_RESET_SLEEP 100
++
++/* addresses of memory segments in the phy */
++#define DRAM_BASE_ADDR 0x3FFE0000
++#define IRAM_BASE_ADDR 0x40000000
++
++/* firmware image format constants */
++#define VERSION_STRING_SIZE 0x40
++#define VERSION_STRING_OFFSET 0x0200
++/* primary offset is written at an offset from the start of the fw blob */
++#define PRIMARY_OFFSET_OFFSET 0x8
++/* primary offset needs to be then added to a base offset */
++#define PRIMARY_OFFSET_SHIFT 12
++#define PRIMARY_OFFSET(x) ((x) << PRIMARY_OFFSET_SHIFT)
++#define HEADER_OFFSET 0x300
++
++struct aqr_fw_header {
++ u32 padding;
++ u8 iram_offset[3];
++ u8 iram_size[3];
++ u8 dram_offset[3];
++ u8 dram_size[3];
++} __packed;
++
++enum aqr_fw_src {
++ AQR_FW_SRC_NVMEM = 0,
++ AQR_FW_SRC_FS,
++};
++
++static const char * const aqr_fw_src_string[] = {
++ [AQR_FW_SRC_NVMEM] = "NVMEM",
++ [AQR_FW_SRC_FS] = "FS",
++};
++
++/* AQR firmware doesn't have fixed offsets for iram and dram section
++ * but instead provide an header with the offset to use on reading
++ * and parsing the firmware.
++ *
++ * AQR firmware can't be trusted and each offset is validated to be
++ * not negative and be in the size of the firmware itself.
++ */
++static bool aqr_fw_validate_get(size_t size, size_t offset, size_t get_size)
++{
++ return offset + get_size <= size;
++}
++
++static int aqr_fw_get_be16(const u8 *data, size_t offset, size_t size, u16 *value)
++{
++ if (!aqr_fw_validate_get(size, offset, sizeof(u16)))
++ return -EINVAL;
++
++ *value = get_unaligned_be16(data + offset);
++
++ return 0;
++}
++
++static int aqr_fw_get_le16(const u8 *data, size_t offset, size_t size, u16 *value)
++{
++ if (!aqr_fw_validate_get(size, offset, sizeof(u16)))
++ return -EINVAL;
++
++ *value = get_unaligned_le16(data + offset);
++
++ return 0;
++}
++
++static int aqr_fw_get_le24(const u8 *data, size_t offset, size_t size, u32 *value)
++{
++ if (!aqr_fw_validate_get(size, offset, sizeof(u8) * 3))
++ return -EINVAL;
++
++ *value = get_unaligned_le24(data + offset);
++
++ return 0;
++}
++
++/* load data into the phy's memory */
++static int aqr_fw_load_memory(struct phy_device *phydev, u32 addr,
++ const u8 *data, size_t len)
++{
++ u16 crc = 0, up_crc;
++ size_t pos;
++
++ /* PHY expect addr in LE */
++ addr = (__force u32)cpu_to_le32(addr);
++
++ phy_write_mmd(phydev, MDIO_MMD_VEND1,
++ VEND1_GLOBAL_MAILBOX_INTERFACE1,
++ VEND1_GLOBAL_MAILBOX_INTERFACE1_CRC_RESET);
++ phy_write_mmd(phydev, MDIO_MMD_VEND1,
++ VEND1_GLOBAL_MAILBOX_INTERFACE3,
++ VEND1_GLOBAL_MAILBOX_INTERFACE3_MSW_ADDR(addr));
++ phy_write_mmd(phydev, MDIO_MMD_VEND1,
++ VEND1_GLOBAL_MAILBOX_INTERFACE4,
++ VEND1_GLOBAL_MAILBOX_INTERFACE4_LSW_ADDR(addr));
++
++ /* We assume and enforce the size to be word aligned.
++ * If a firmware that is not word aligned is found, please report upstream.
++ */
++ for (pos = 0; pos < len; pos += sizeof(u32)) {
++ u32 word;
++
++ /* FW data is always stored in little-endian */
++ word = get_unaligned((const u32 *)(data + pos));
++
++ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_MAILBOX_INTERFACE5,
++ VEND1_GLOBAL_MAILBOX_INTERFACE5_MSW_DATA(word));
++ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_MAILBOX_INTERFACE6,
++ VEND1_GLOBAL_MAILBOX_INTERFACE6_LSW_DATA(word));
++
++ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_MAILBOX_INTERFACE1,
++ VEND1_GLOBAL_MAILBOX_INTERFACE1_EXECUTE |
++ VEND1_GLOBAL_MAILBOX_INTERFACE1_WRITE);
++
++ /* calculate CRC as we load data to the mailbox.
++ * We convert word to big-endian as PHY is BE and mailbox will
++ * return a BE CRC.
++ */
++ word = (__force u32)cpu_to_be32(word);
++ crc = crc_ccitt_false(crc, (u8 *)&word, sizeof(word));
++ }
++
++ up_crc = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_MAILBOX_INTERFACE2);
++ if (crc != up_crc) {
++ phydev_err(phydev, "CRC mismatch: calculated 0x%04x PHY 0x%04x\n",
++ crc, up_crc);
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
++static int aqr_fw_boot(struct phy_device *phydev, const u8 *data, size_t size,
++ enum aqr_fw_src fw_src)
++{
++ u16 calculated_crc, read_crc, read_primary_offset;
++ u32 iram_offset = 0, iram_size = 0;
++ u32 dram_offset = 0, dram_size = 0;
++ char version[VERSION_STRING_SIZE];
++ u32 primary_offset = 0;
++ int ret;
++
++ /* extract saved CRC at the end of the fw
++ * CRC is saved in big-endian as PHY is BE
++ */
++ ret = aqr_fw_get_be16(data, size - sizeof(u16), size, &read_crc);
++ if (ret) {
++ phydev_err(phydev, "bad firmware CRC in firmware\n");
++ return ret;
++ }
++ calculated_crc = crc_ccitt_false(0, data, size - sizeof(u16));
++ if (read_crc != calculated_crc) {
++ phydev_err(phydev, "bad firmware CRC: file 0x%04x calculated 0x%04x\n",
++ read_crc, calculated_crc);
++ return -EINVAL;
++ }
++
++ /* Get the primary offset to extract DRAM and IRAM sections. */
++ ret = aqr_fw_get_le16(data, PRIMARY_OFFSET_OFFSET, size, &read_primary_offset);
++ if (ret) {
++ phydev_err(phydev, "bad primary offset in firmware\n");
++ return ret;
++ }
++ primary_offset = PRIMARY_OFFSET(read_primary_offset);
++
++ /* Find the DRAM and IRAM sections within the firmware file.
++ * Make sure the fw_header is correctly in the firmware.
++ */
++ if (!aqr_fw_validate_get(size, primary_offset + HEADER_OFFSET,
++ sizeof(struct aqr_fw_header))) {
++ phydev_err(phydev, "bad fw_header in firmware\n");
++ return -EINVAL;
++ }
++
++ /* offset are in LE and values needs to be converted to cpu endian */
++ ret = aqr_fw_get_le24(data, primary_offset + HEADER_OFFSET +
++ offsetof(struct aqr_fw_header, iram_offset),
++ size, &iram_offset);
++ if (ret) {
++ phydev_err(phydev, "bad iram offset in firmware\n");
++ return ret;
++ }
++ ret = aqr_fw_get_le24(data, primary_offset + HEADER_OFFSET +
++ offsetof(struct aqr_fw_header, iram_size),
++ size, &iram_size);
++ if (ret) {
++ phydev_err(phydev, "invalid iram size in firmware\n");
++ return ret;
++ }
++ ret = aqr_fw_get_le24(data, primary_offset + HEADER_OFFSET +
++ offsetof(struct aqr_fw_header, dram_offset),
++ size, &dram_offset);
++ if (ret) {
++ phydev_err(phydev, "bad dram offset in firmware\n");
++ return ret;
++ }
++ ret = aqr_fw_get_le24(data, primary_offset + HEADER_OFFSET +
++ offsetof(struct aqr_fw_header, dram_size),
++ size, &dram_size);
++ if (ret) {
++ phydev_err(phydev, "invalid dram size in firmware\n");
++ return ret;
++ }
++
++ /* Increment the offset with the primary offset.
++ * Validate iram/dram offset and size.
++ */
++ iram_offset += primary_offset;
++ if (iram_size % sizeof(u32)) {
++ phydev_err(phydev, "iram size if not aligned to word size. Please report this upstream!\n");
++ return -EINVAL;
++ }
++ if (!aqr_fw_validate_get(size, iram_offset, iram_size)) {
++ phydev_err(phydev, "invalid iram offset for iram size\n");
++ return -EINVAL;
++ }
++
++ dram_offset += primary_offset;
++ if (dram_size % sizeof(u32)) {
++ phydev_err(phydev, "dram size if not aligned to word size. Please report this upstream!\n");
++ return -EINVAL;
++ }
++ if (!aqr_fw_validate_get(size, dram_offset, dram_size)) {
++ phydev_err(phydev, "invalid iram offset for iram size\n");
++ return -EINVAL;
++ }
++
++ phydev_dbg(phydev, "primary %d IRAM offset=%d size=%d DRAM offset=%d size=%d\n",
++ primary_offset, iram_offset, iram_size, dram_offset, dram_size);
++
++ if (!aqr_fw_validate_get(size, dram_offset + VERSION_STRING_OFFSET,
++ VERSION_STRING_SIZE)) {
++ phydev_err(phydev, "invalid version in firmware\n");
++ return -EINVAL;
++ }
++ strscpy(version, (char *)data + dram_offset + VERSION_STRING_OFFSET,
++ VERSION_STRING_SIZE);
++ if (version[0] == '\0') {
++ phydev_err(phydev, "invalid version in firmware\n");
++ return -EINVAL;
++ }
++ phydev_info(phydev, "loading firmware version '%s' from '%s'\n", version,
++ aqr_fw_src_string[fw_src]);
++
++ /* stall the microcprocessor */
++ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_CONTROL2,
++ VEND1_GLOBAL_CONTROL2_UP_RUN_STALL | VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_OVD);
++
++ phydev_dbg(phydev, "loading DRAM 0x%08x from offset=%d size=%d\n",
++ DRAM_BASE_ADDR, dram_offset, dram_size);
++ ret = aqr_fw_load_memory(phydev, DRAM_BASE_ADDR, data + dram_offset,
++ dram_size);
++ if (ret)
++ return ret;
++
++ phydev_dbg(phydev, "loading IRAM 0x%08x from offset=%d size=%d\n",
++ IRAM_BASE_ADDR, iram_offset, iram_size);
++ ret = aqr_fw_load_memory(phydev, IRAM_BASE_ADDR, data + iram_offset,
++ iram_size);
++ if (ret)
++ return ret;
++
++ /* make sure soft reset and low power mode are clear */
++ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_SC,
++ VEND1_GLOBAL_SC_SOFT_RESET | VEND1_GLOBAL_SC_LOW_POWER);
++
++ /* Release the microprocessor. UP_RESET must be held for 100 usec. */
++ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_CONTROL2,
++ VEND1_GLOBAL_CONTROL2_UP_RUN_STALL |
++ VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_OVD |
++ VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_RST);
++ usleep_range(UP_RESET_SLEEP, UP_RESET_SLEEP * 2);
++
++ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_CONTROL2,
++ VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_OVD);
++
++ return 0;
++}
++
++static int aqr_firmware_load_nvmem(struct phy_device *phydev)
++{
++ struct nvmem_cell *cell;
++ size_t size;
++ u8 *buf;
++ int ret;
++
++ cell = nvmem_cell_get(&phydev->mdio.dev, "firmware");
++ if (IS_ERR(cell))
++ return PTR_ERR(cell);
++
++ buf = nvmem_cell_read(cell, &size);
++ if (IS_ERR(buf)) {
++ ret = PTR_ERR(buf);
++ goto exit;
++ }
++
++ ret = aqr_fw_boot(phydev, buf, size, AQR_FW_SRC_NVMEM);
++ if (ret)
++ phydev_err(phydev, "firmware loading failed: %d\n", ret);
++
++ kfree(buf);
++exit:
++ nvmem_cell_put(cell);
++
++ return ret;
++}
++
++static int aqr_firmware_load_fs(struct phy_device *phydev)
++{
++ struct device *dev = &phydev->mdio.dev;
++ const struct firmware *fw;
++ const char *fw_name;
++ int ret;
++
++ ret = of_property_read_string(dev->of_node, "firmware-name",
++ &fw_name);
++ if (ret)
++ return ret;
++
++ ret = request_firmware(&fw, fw_name, dev);
++ if (ret) {
++ phydev_err(phydev, "failed to find FW file %s (%d)\n",
++ fw_name, ret);
++ return ret;
++ }
++
++ ret = aqr_fw_boot(phydev, fw->data, fw->size, AQR_FW_SRC_FS);
++ if (ret)
++ phydev_err(phydev, "firmware loading failed: %d\n", ret);
++
++ release_firmware(fw);
++
++ return ret;
++}
++
++int aqr_firmware_load(struct phy_device *phydev)
++{
++ int ret;
++
++ /* Check if the firmware is not already loaded by pooling
++ * the current version returned by the PHY. If 0 is returned,
++ * no firmware is loaded.
++ */
++ ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_FW_ID);
++ if (ret > 0)
++ goto exit;
++
++ ret = aqr_firmware_load_nvmem(phydev);
++ if (!ret)
++ goto exit;
++
++ ret = aqr_firmware_load_fs(phydev);
++ if (ret)
++ return ret;
++
++exit:
++ return 0;
++}
+--- a/drivers/net/phy/aquantia/aquantia_main.c
++++ b/drivers/net/phy/aquantia/aquantia_main.c
+@@ -656,11 +656,17 @@ static int aqr107_resume(struct phy_devi
+
+ static int aqr107_probe(struct phy_device *phydev)
+ {
++ int ret;
++
+ phydev->priv = devm_kzalloc(&phydev->mdio.dev,
+ sizeof(struct aqr107_priv), GFP_KERNEL);
+ if (!phydev->priv)
+ return -ENOMEM;
+
++ ret = aqr_firmware_load(phydev);
++ if (ret)
++ return ret;
++
+ return aqr_hwmon_probe(phydev);
+ }
+
diff --git a/target/linux/generic/backport-6.6/703-v6.3-net-mdio-Add-dedicated-C45-API-to-MDIO-bus-drivers.patch b/target/linux/generic/backport-6.6/703-v6.3-net-mdio-Add-dedicated-C45-API-to-MDIO-bus-drivers.patch
new file mode 100644
index 0000000000..ff186742ba
--- /dev/null
+++ b/target/linux/generic/backport-6.6/703-v6.3-net-mdio-Add-dedicated-C45-API-to-MDIO-bus-drivers.patch
@@ -0,0 +1,326 @@
+From 4e4aafcddbbfcdd6eed5780e190fcbfac8b4685a Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 9 Jan 2023 16:30:41 +0100
+Subject: [PATCH] net: mdio: Add dedicated C45 API to MDIO bus drivers
+
+Currently C22 and C45 transactions are mixed over a combined API calls
+which make use of a special bit in the reg address to indicate if a
+C45 transaction should be performed. This makes it impossible to know
+if the bus driver actually supports C45. Additionally, many C22 only
+drivers don't return -EOPNOTSUPP when asked to perform a C45
+transaction, they mistaking perform a C22 transaction.
+
+This is the first step to cleanly separate C22 from C45. To maintain
+backwards compatibility until all drivers which are capable of
+performing C45 are converted to this new API, the helper functions
+will fall back to the older API if the new API is not
+supported. Eventually this fallback will be removed.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/mdio_bus.c | 189 +++++++++++++++++++++++++++++++++++++
+ include/linux/mdio.h | 39 ++++----
+ include/linux/phy.h | 5 +
+ 3 files changed, 214 insertions(+), 19 deletions(-)
+
+--- a/drivers/net/phy/mdio_bus.c
++++ b/drivers/net/phy/mdio_bus.c
+@@ -832,6 +832,100 @@ int __mdiobus_modify_changed(struct mii_
+ EXPORT_SYMBOL_GPL(__mdiobus_modify_changed);
+
+ /**
++ * __mdiobus_c45_read - Unlocked version of the mdiobus_c45_read function
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @devad: device address to read
++ * @regnum: register number to read
++ *
++ * Read a MDIO bus register. Caller must hold the mdio bus lock.
++ *
++ * NOTE: MUST NOT be called from interrupt context.
++ */
++int __mdiobus_c45_read(struct mii_bus *bus, int addr, int devad, u32 regnum)
++{
++ int retval;
++
++ lockdep_assert_held_once(&bus->mdio_lock);
++
++ if (bus->read_c45)
++ retval = bus->read_c45(bus, addr, devad, regnum);
++ else
++ retval = bus->read(bus, addr, mdiobus_c45_addr(devad, regnum));
++
++ trace_mdio_access(bus, 1, addr, regnum, retval, retval);
++ mdiobus_stats_acct(&bus->stats[addr], true, retval);
++
++ return retval;
++}
++EXPORT_SYMBOL(__mdiobus_c45_read);
++
++/**
++ * __mdiobus_c45_write - Unlocked version of the mdiobus_write function
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @devad: device address to read
++ * @regnum: register number to write
++ * @val: value to write to @regnum
++ *
++ * Write a MDIO bus register. Caller must hold the mdio bus lock.
++ *
++ * NOTE: MUST NOT be called from interrupt context.
++ */
++int __mdiobus_c45_write(struct mii_bus *bus, int addr, int devad, u32 regnum,
++ u16 val)
++{
++ int err;
++
++ lockdep_assert_held_once(&bus->mdio_lock);
++
++ if (bus->write_c45)
++ err = bus->write_c45(bus, addr, devad, regnum, val);
++ else
++ err = bus->write(bus, addr, mdiobus_c45_addr(devad, regnum),
++ val);
++
++ trace_mdio_access(bus, 0, addr, regnum, val, err);
++ mdiobus_stats_acct(&bus->stats[addr], false, err);
++
++ return err;
++}
++EXPORT_SYMBOL(__mdiobus_c45_write);
++
++/**
++ * __mdiobus_c45_modify_changed - Unlocked version of the mdiobus_modify function
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @devad: device address to read
++ * @regnum: register number to modify
++ * @mask: bit mask of bits to clear
++ * @set: bit mask of bits to set
++ *
++ * Read, modify, and if any change, write the register value back to the
++ * device. Any error returns a negative number.
++ *
++ * NOTE: MUST NOT be called from interrupt context.
++ */
++static int __mdiobus_c45_modify_changed(struct mii_bus *bus, int addr,
++ int devad, u32 regnum, u16 mask,
++ u16 set)
++{
++ int new, ret;
++
++ ret = __mdiobus_c45_read(bus, addr, devad, regnum);
++ if (ret < 0)
++ return ret;
++
++ new = (ret & ~mask) | set;
++ if (new == ret)
++ return 0;
++
++ ret = __mdiobus_c45_write(bus, addr, devad, regnum, new);
++
++ return ret < 0 ? ret : 1;
++}
++
++/**
+ * mdiobus_read_nested - Nested version of the mdiobus_read function
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+@@ -879,6 +973,29 @@ int mdiobus_read(struct mii_bus *bus, in
+ EXPORT_SYMBOL(mdiobus_read);
+
+ /**
++ * mdiobus_c45_read - Convenience function for reading a given MII mgmt register
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @devad: device address to read
++ * @regnum: register number to read
++ *
++ * NOTE: MUST NOT be called from interrupt context,
++ * because the bus read/write functions may wait for an interrupt
++ * to conclude the operation.
++ */
++int mdiobus_c45_read(struct mii_bus *bus, int addr, int devad, u32 regnum)
++{
++ int retval;
++
++ mutex_lock(&bus->mdio_lock);
++ retval = __mdiobus_c45_read(bus, addr, devad, regnum);
++ mutex_unlock(&bus->mdio_lock);
++
++ return retval;
++}
++EXPORT_SYMBOL(mdiobus_c45_read);
++
++/**
+ * mdiobus_write_nested - Nested version of the mdiobus_write function
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+@@ -928,6 +1045,31 @@ int mdiobus_write(struct mii_bus *bus, i
+ EXPORT_SYMBOL(mdiobus_write);
+
+ /**
++ * mdiobus_c45_write - Convenience function for writing a given MII mgmt register
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @devad: device address to read
++ * @regnum: register number to write
++ * @val: value to write to @regnum
++ *
++ * NOTE: MUST NOT be called from interrupt context,
++ * because the bus read/write functions may wait for an interrupt
++ * to conclude the operation.
++ */
++int mdiobus_c45_write(struct mii_bus *bus, int addr, int devad, u32 regnum,
++ u16 val)
++{
++ int err;
++
++ mutex_lock(&bus->mdio_lock);
++ err = __mdiobus_c45_write(bus, addr, devad, regnum, val);
++ mutex_unlock(&bus->mdio_lock);
++
++ return err;
++}
++EXPORT_SYMBOL(mdiobus_c45_write);
++
++/**
+ * mdiobus_modify - Convenience function for modifying a given mdio device
+ * register
+ * @bus: the mii_bus struct
+@@ -949,6 +1091,30 @@ int mdiobus_modify(struct mii_bus *bus,
+ EXPORT_SYMBOL_GPL(mdiobus_modify);
+
+ /**
++ * mdiobus_c45_modify - Convenience function for modifying a given mdio device
++ * register
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @devad: device address to read
++ * @regnum: register number to write
++ * @mask: bit mask of bits to clear
++ * @set: bit mask of bits to set
++ */
++int mdiobus_c45_modify(struct mii_bus *bus, int addr, int devad, u32 regnum,
++ u16 mask, u16 set)
++{
++ int err;
++
++ mutex_lock(&bus->mdio_lock);
++ err = __mdiobus_c45_modify_changed(bus, addr, devad, regnum,
++ mask, set);
++ mutex_unlock(&bus->mdio_lock);
++
++ return err < 0 ? err : 0;
++}
++EXPORT_SYMBOL_GPL(mdiobus_c45_modify);
++
++/**
+ * mdiobus_modify_changed - Convenience function for modifying a given mdio
+ * device register and returning if it changed
+ * @bus: the mii_bus struct
+@@ -971,6 +1137,29 @@ int mdiobus_modify_changed(struct mii_bu
+ EXPORT_SYMBOL_GPL(mdiobus_modify_changed);
+
+ /**
++ * mdiobus_c45_modify_changed - Convenience function for modifying a given mdio
++ * device register and returning if it changed
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @devad: device address to read
++ * @regnum: register number to write
++ * @mask: bit mask of bits to clear
++ * @set: bit mask of bits to set
++ */
++int mdiobus_c45_modify_changed(struct mii_bus *bus, int devad, int addr,
++ u32 regnum, u16 mask, u16 set)
++{
++ int err;
++
++ mutex_lock(&bus->mdio_lock);
++ err = __mdiobus_c45_modify_changed(bus, addr, devad, regnum, mask, set);
++ mutex_unlock(&bus->mdio_lock);
++
++ return err;
++}
++EXPORT_SYMBOL_GPL(mdiobus_c45_modify_changed);
++
++/**
+ * mdio_bus_match - determine if given MDIO driver supports the given
+ * MDIO device
+ * @dev: target MDIO device
+--- a/include/linux/mdio.h
++++ b/include/linux/mdio.h
+@@ -423,6 +423,17 @@ int mdiobus_modify(struct mii_bus *bus,
+ u16 set);
+ int mdiobus_modify_changed(struct mii_bus *bus, int addr, u32 regnum,
+ u16 mask, u16 set);
++int __mdiobus_c45_read(struct mii_bus *bus, int addr, int devad, u32 regnum);
++int mdiobus_c45_read(struct mii_bus *bus, int addr, int devad, u32 regnum);
++int __mdiobus_c45_write(struct mii_bus *bus, int addr, int devad, u32 regnum,
++ u16 val);
++int mdiobus_c45_write(struct mii_bus *bus, int addr, int devad, u32 regnum,
++ u16 val);
++int mdiobus_c45_modify(struct mii_bus *bus, int addr, int devad, u32 regnum,
++ u16 mask, u16 set);
++
++int mdiobus_c45_modify_changed(struct mii_bus *bus, int addr, int devad,
++ u32 regnum, u16 mask, u16 set);
+
+ static inline int mdiodev_read(struct mdio_device *mdiodev, u32 regnum)
+ {
+@@ -463,29 +474,19 @@ static inline u16 mdiobus_c45_devad(u32
+ return FIELD_GET(MII_DEVADDR_C45_MASK, regnum);
+ }
+
+-static inline int __mdiobus_c45_read(struct mii_bus *bus, int prtad, int devad,
+- u16 regnum)
+-{
+- return __mdiobus_read(bus, prtad, mdiobus_c45_addr(devad, regnum));
+-}
+-
+-static inline int __mdiobus_c45_write(struct mii_bus *bus, int prtad, int devad,
+- u16 regnum, u16 val)
+-{
+- return __mdiobus_write(bus, prtad, mdiobus_c45_addr(devad, regnum),
+- val);
+-}
+-
+-static inline int mdiobus_c45_read(struct mii_bus *bus, int prtad, int devad,
+- u16 regnum)
++static inline int mdiodev_c45_modify(struct mdio_device *mdiodev, int devad,
++ u32 regnum, u16 mask, u16 set)
+ {
+- return mdiobus_read(bus, prtad, mdiobus_c45_addr(devad, regnum));
++ return mdiobus_c45_modify(mdiodev->bus, mdiodev->addr, devad, regnum,
++ mask, set);
+ }
+
+-static inline int mdiobus_c45_write(struct mii_bus *bus, int prtad, int devad,
+- u16 regnum, u16 val)
++static inline int mdiodev_c45_modify_changed(struct mdio_device *mdiodev,
++ int devad, u32 regnum, u16 mask,
++ u16 set)
+ {
+- return mdiobus_write(bus, prtad, mdiobus_c45_addr(devad, regnum), val);
++ return mdiobus_c45_modify_changed(mdiodev->bus, mdiodev->addr, devad,
++ regnum, mask, set);
+ }
+
+ int mdiobus_register_device(struct mdio_device *mdiodev);
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -364,6 +364,11 @@ struct mii_bus {
+ int (*read)(struct mii_bus *bus, int addr, int regnum);
+ /** @write: Perform a write transfer on the bus */
+ int (*write)(struct mii_bus *bus, int addr, int regnum, u16 val);
++ /** @read_c45: Perform a C45 read transfer on the bus */
++ int (*read_c45)(struct mii_bus *bus, int addr, int devnum, int regnum);
++ /** @write_c45: Perform a C45 write transfer on the bus */
++ int (*write_c45)(struct mii_bus *bus, int addr, int devnum,
++ int regnum, u16 val);
+ /** @reset: Perform a reset of the bus */
+ int (*reset)(struct mii_bus *bus);
+
diff --git a/target/linux/generic/backport-6.6/704-v6.6-net-phy-Introduce-PSGMII-PHY-interface-mode.patch b/target/linux/generic/backport-6.6/704-v6.6-net-phy-Introduce-PSGMII-PHY-interface-mode.patch
new file mode 100644
index 0000000000..80210e6da1
--- /dev/null
+++ b/target/linux/generic/backport-6.6/704-v6.6-net-phy-Introduce-PSGMII-PHY-interface-mode.patch
@@ -0,0 +1,105 @@
+From 9a0e95e34e9c0a713ddfd48c3a88a20d2bdfd514 Mon Sep 17 00:00:00 2001
+From: Gabor Juhos <j4g8y7@gmail.com>
+Date: Fri, 11 Aug 2023 13:10:07 +0200
+Subject: [PATCH] net: phy: Introduce PSGMII PHY interface mode
+
+The PSGMII interface is similar to QSGMII. The main difference
+is that the PSGMII interface combines five SGMII lines into a
+single link while in QSGMII only four lines are combined.
+
+Similarly to the QSGMII, this interface mode might also needs
+special handling within the MAC driver.
+
+It is commonly used by Qualcomm with their QCA807x PHY series and
+modern WiSoC-s.
+
+Add definitions for the PHY layer to allow to express this type
+of connection between the MAC and PHY.
+
+Signed-off-by: Gabor Juhos <j4g8y7@gmail.com>
+Signed-off-by: Robert Marko <robert.marko@sartura.hr>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ Documentation/networking/phy.rst | 4 ++++
+ drivers/net/phy/phy-core.c | 2 ++
+ drivers/net/phy/phylink.c | 3 +++
+ include/linux/phy.h | 4 ++++
+ 4 files changed, 13 insertions(+)
+
+--- a/Documentation/networking/phy.rst
++++ b/Documentation/networking/phy.rst
+@@ -323,6 +323,10 @@ Some of the interface modes are describe
+ contrast with the 1000BASE-X phy mode used for Clause 38 and 39 PMDs, this
+ interface mode has different autonegotiation and only supports full duplex.
+
++``PHY_INTERFACE_MODE_PSGMII``
++ This is the Penta SGMII mode, it is similar to QSGMII but it combines 5
++ SGMII lines into a single link compared to 4 on QSGMII.
++
+ Pause frames / flow control
+ ===========================
+
+--- a/drivers/net/phy/phy-core.c
++++ b/drivers/net/phy/phy-core.c
+@@ -140,6 +140,8 @@ int phy_interface_num_ports(phy_interfac
+ case PHY_INTERFACE_MODE_QSGMII:
+ case PHY_INTERFACE_MODE_QUSGMII:
+ return 4;
++ case PHY_INTERFACE_MODE_PSGMII:
++ return 5;
+ case PHY_INTERFACE_MODE_MAX:
+ WARN_ONCE(1, "PHY_INTERFACE_MODE_MAX isn't a valid interface mode");
+ return 0;
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -187,6 +187,7 @@ static int phylink_interface_max_speed(p
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII:
++ case PHY_INTERFACE_MODE_PSGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
+ case PHY_INTERFACE_MODE_QUSGMII:
+ case PHY_INTERFACE_MODE_SGMII:
+@@ -448,6 +449,7 @@ unsigned long phylink_get_capabilities(p
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII:
++ case PHY_INTERFACE_MODE_PSGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
+ case PHY_INTERFACE_MODE_QUSGMII:
+ case PHY_INTERFACE_MODE_SGMII:
+@@ -814,6 +816,7 @@ static int phylink_parse_mode(struct phy
+
+ switch (pl->link_config.interface) {
+ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_PSGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
+ case PHY_INTERFACE_MODE_QUSGMII:
+ case PHY_INTERFACE_MODE_RGMII:
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -103,6 +103,7 @@ extern const int phy_10gbit_features_arr
+ * @PHY_INTERFACE_MODE_XGMII: 10 gigabit media-independent interface
+ * @PHY_INTERFACE_MODE_XLGMII:40 gigabit media-independent interface
+ * @PHY_INTERFACE_MODE_MOCA: Multimedia over Coax
++ * @PHY_INTERFACE_MODE_PSGMII: Penta SGMII
+ * @PHY_INTERFACE_MODE_QSGMII: Quad SGMII
+ * @PHY_INTERFACE_MODE_TRGMII: Turbo RGMII
+ * @PHY_INTERFACE_MODE_100BASEX: 100 BaseX
+@@ -140,6 +141,7 @@ typedef enum {
+ PHY_INTERFACE_MODE_XGMII,
+ PHY_INTERFACE_MODE_XLGMII,
+ PHY_INTERFACE_MODE_MOCA,
++ PHY_INTERFACE_MODE_PSGMII,
+ PHY_INTERFACE_MODE_QSGMII,
+ PHY_INTERFACE_MODE_TRGMII,
+ PHY_INTERFACE_MODE_100BASEX,
+@@ -247,6 +249,8 @@ static inline const char *phy_modes(phy_
+ return "xlgmii";
+ case PHY_INTERFACE_MODE_MOCA:
+ return "moca";
++ case PHY_INTERFACE_MODE_PSGMII:
++ return "psgmii";
+ case PHY_INTERFACE_MODE_QSGMII:
+ return "qsgmii";
+ case PHY_INTERFACE_MODE_TRGMII:
diff --git a/target/linux/generic/backport-6.6/705-v6.4-net-phy-at803x-Replace-of_gpio.h-with-what-indeed-is.patch b/target/linux/generic/backport-6.6/705-v6.4-net-phy-at803x-Replace-of_gpio.h-with-what-indeed-is.patch
new file mode 100644
index 0000000000..7cacb71b18
--- /dev/null
+++ b/target/linux/generic/backport-6.6/705-v6.4-net-phy-at803x-Replace-of_gpio.h-with-what-indeed-is.patch
@@ -0,0 +1,32 @@
+From a593a2fcfdfb92cfd0ffc54bc81b07e6bfaaaf46 Mon Sep 17 00:00:00 2001
+From: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+Date: Thu, 16 Mar 2023 14:08:26 +0200
+Subject: [PATCH] net: phy: at803x: Replace of_gpio.h with what indeed is used
+
+of_gpio.h in this driver is solely used as a proxy to other headers.
+This is incorrect usage of the of_gpio.h. Replace it .h with what
+indeed is used in the code.
+
+Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.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, 1 insertion(+), 2 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -13,12 +13,11 @@
+ #include <linux/netdevice.h>
+ #include <linux/etherdevice.h>
+ #include <linux/ethtool_netlink.h>
+-#include <linux/of_gpio.h>
+ #include <linux/bitfield.h>
+-#include <linux/gpio/consumer.h>
+ #include <linux/regulator/of_regulator.h>
+ #include <linux/regulator/driver.h>
+ #include <linux/regulator/consumer.h>
++#include <linux/of.h>
+ #include <linux/phylink.h>
+ #include <linux/sfp.h>
+ #include <dt-bindings/net/qca-ar803x.h>
diff --git a/target/linux/generic/backport-6.6/706-v6.6-01-net-phy-at803x-support-qca8081-genphy_c45_pma_read_a.patch b/target/linux/generic/backport-6.6/706-v6.6-01-net-phy-at803x-support-qca8081-genphy_c45_pma_read_a.patch
new file mode 100644
index 0000000000..dab4928960
--- /dev/null
+++ b/target/linux/generic/backport-6.6/706-v6.6-01-net-phy-at803x-support-qca8081-genphy_c45_pma_read_a.patch
@@ -0,0 +1,70 @@
+From 8b8bc13d89a7d23d14b0e041c73f037c9db997b1 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Sun, 16 Jul 2023 16:49:19 +0800
+Subject: [PATCH 1/6] net: phy: at803x: support qca8081
+ genphy_c45_pma_read_abilities
+
+qca8081 PHY supports to use genphy_c45_pma_read_abilities for
+getting the PHY features supported except for the autoneg ability
+
+but autoneg ability exists in MDIO_STAT1 instead of MMD7.1, add it
+manually after calling genphy_c45_pma_read_abilities.
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 28 ++++++++++++++++++----------
+ 1 file changed, 18 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -902,15 +902,6 @@ static int at803x_get_features(struct ph
+ if (err)
+ return err;
+
+- if (phydev->drv->phy_id == QCA8081_PHY_ID) {
+- err = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_NG_EXTABLE);
+- if (err < 0)
+- return err;
+-
+- linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported,
+- err & MDIO_PMA_NG_EXTABLE_2_5GBT);
+- }
+-
+ if (phydev->drv->phy_id != ATH8031_PHY_ID)
+ return 0;
+
+@@ -1996,6 +1987,23 @@ static int qca808x_cable_test_get_status
+ return 0;
+ }
+
++static int qca808x_get_features(struct phy_device *phydev)
++{
++ int ret;
++
++ ret = genphy_c45_pma_read_abilities(phydev);
++ if (ret)
++ return ret;
++
++ /* The autoneg ability is not existed in bit3 of MMD7.1,
++ * but it is supported by qca808x PHY, so we add it here
++ * manually.
++ */
++ linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
++
++ return 0;
++}
++
+ static struct phy_driver at803x_driver[] = {
+ {
+ /* Qualcomm Atheros AR8035 */
+@@ -2163,7 +2171,7 @@ static struct phy_driver at803x_driver[]
+ .set_tunable = at803x_set_tunable,
+ .set_wol = at803x_set_wol,
+ .get_wol = at803x_get_wol,
+- .get_features = at803x_get_features,
++ .get_features = qca808x_get_features,
+ .config_aneg = at803x_config_aneg,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
diff --git a/target/linux/generic/backport-6.6/706-v6.6-02-net-phy-at803x-merge-qca8081-slave-seed-function.patch b/target/linux/generic/backport-6.6/706-v6.6-02-net-phy-at803x-merge-qca8081-slave-seed-function.patch
new file mode 100644
index 0000000000..28874068ce
--- /dev/null
+++ b/target/linux/generic/backport-6.6/706-v6.6-02-net-phy-at803x-merge-qca8081-slave-seed-function.patch
@@ -0,0 +1,73 @@
+From f3db55ae860a82e1224a909072783ef850e5d228 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Sun, 16 Jul 2023 16:49:20 +0800
+Subject: [PATCH 2/6] net: phy: at803x: merge qca8081 slave seed function
+
+merge the seed enablement and seed value configuration into
+one function, since the random seed value is needed to be
+configured when the seed is enabled.
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 29 +++++++++--------------------
+ 1 file changed, 9 insertions(+), 20 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -1730,24 +1730,19 @@ static int qca808x_phy_fast_retrain_conf
+ return 0;
+ }
+
+-static int qca808x_phy_ms_random_seed_set(struct phy_device *phydev)
+-{
+- u16 seed_value = prandom_u32_max(QCA808X_MASTER_SLAVE_SEED_RANGE);
+-
+- return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
+- QCA808X_MASTER_SLAVE_SEED_CFG,
+- FIELD_PREP(QCA808X_MASTER_SLAVE_SEED_CFG, seed_value));
+-}
+-
+ static int qca808x_phy_ms_seed_enable(struct phy_device *phydev, bool enable)
+ {
+- u16 seed_enable = 0;
++ u16 seed_value;
+
+- if (enable)
+- seed_enable = QCA808X_MASTER_SLAVE_SEED_ENABLE;
++ if (!enable)
++ return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
++ QCA808X_MASTER_SLAVE_SEED_ENABLE, 0);
+
++ seed_value = prandom_u32_max(QCA808X_MASTER_SLAVE_SEED_RANGE);
+ return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
+- QCA808X_MASTER_SLAVE_SEED_ENABLE, seed_enable);
++ QCA808X_MASTER_SLAVE_SEED_CFG | QCA808X_MASTER_SLAVE_SEED_ENABLE,
++ FIELD_PREP(QCA808X_MASTER_SLAVE_SEED_CFG, seed_value) |
++ QCA808X_MASTER_SLAVE_SEED_ENABLE);
+ }
+
+ static int qca808x_config_init(struct phy_device *phydev)
+@@ -1771,12 +1766,7 @@ static int qca808x_config_init(struct ph
+ if (ret)
+ return ret;
+
+- /* Configure lower ramdom seed to make phy linked as slave mode */
+- ret = qca808x_phy_ms_random_seed_set(phydev);
+- if (ret)
+- return ret;
+-
+- /* Enable seed */
++ /* Enable seed and configure lower ramdom seed to make phy linked as slave mode */
+ ret = qca808x_phy_ms_seed_enable(phydev, true);
+ if (ret)
+ return ret;
+@@ -1821,7 +1811,6 @@ static int qca808x_read_status(struct ph
+ if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR) {
+ qca808x_phy_ms_seed_enable(phydev, false);
+ } else {
+- qca808x_phy_ms_random_seed_set(phydev);
+ qca808x_phy_ms_seed_enable(phydev, true);
+ }
+ }
diff --git a/target/linux/generic/backport-6.6/706-v6.6-03-net-phy-at803x-enable-qca8081-slave-seed-conditional.patch b/target/linux/generic/backport-6.6/706-v6.6-03-net-phy-at803x-enable-qca8081-slave-seed-conditional.patch
new file mode 100644
index 0000000000..b91d2cab30
--- /dev/null
+++ b/target/linux/generic/backport-6.6/706-v6.6-03-net-phy-at803x-enable-qca8081-slave-seed-conditional.patch
@@ -0,0 +1,76 @@
+From 7cc3209558002d95c0d45a1276ba4f5f741eec42 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Sun, 16 Jul 2023 16:49:21 +0800
+Subject: [PATCH 3/6] net: phy: at803x: enable qca8081 slave seed conditionally
+
+qca8081 is the single port PHY, the slave prefer mode is used
+by default.
+
+if the phy master perfer mode is configured, the slave seed
+configuration should not be enabled, since the slave seed
+enablement is for making PHY linked as slave mode easily.
+
+disable slave seed if the master mode is preferred.
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.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
+@@ -1745,6 +1745,12 @@ static int qca808x_phy_ms_seed_enable(st
+ QCA808X_MASTER_SLAVE_SEED_ENABLE);
+ }
+
++static bool qca808x_is_prefer_master(struct phy_device *phydev)
++{
++ return (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_FORCE) ||
++ (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_PREFERRED);
++}
++
+ static int qca808x_config_init(struct phy_device *phydev)
+ {
+ int ret;
+@@ -1766,11 +1772,17 @@ static int qca808x_config_init(struct ph
+ if (ret)
+ return ret;
+
+- /* Enable seed and configure lower ramdom seed to make phy linked as slave mode */
+- ret = qca808x_phy_ms_seed_enable(phydev, true);
+- if (ret)
++ ret = genphy_read_master_slave(phydev);
++ if (ret < 0)
+ return ret;
+
++ if (!qca808x_is_prefer_master(phydev)) {
++ /* Enable seed and configure lower ramdom seed to make phy linked as slave mode */
++ ret = qca808x_phy_ms_seed_enable(phydev, true);
++ if (ret)
++ return ret;
++ }
++
+ /* Configure adc threshold as 100mv for the link 10M */
+ return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_ADC_THRESHOLD,
+ QCA808X_ADC_THRESHOLD_MASK, QCA808X_ADC_THRESHOLD_100MV);
+@@ -1802,13 +1814,16 @@ static int qca808x_read_status(struct ph
+ phydev->interface = PHY_INTERFACE_MODE_SGMII;
+ } else {
+ /* generate seed as a lower random value to make PHY linked as SLAVE easily,
+- * except for master/slave configuration fault detected.
++ * except for master/slave configuration fault detected or the master mode
++ * preferred.
++ *
+ * the reason for not putting this code into the function link_change_notify is
+ * the corner case where the link partner is also the qca8081 PHY and the seed
+ * value is configured as the same value, the link can't be up and no link change
+ * occurs.
+ */
+- if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR) {
++ if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR ||
++ qca808x_is_prefer_master(phydev)) {
+ qca808x_phy_ms_seed_enable(phydev, false);
+ } else {
+ qca808x_phy_ms_seed_enable(phydev, true);
diff --git a/target/linux/generic/backport-6.6/706-v6.6-04-net-phy-at803x-support-qca8081-1G-chip-type.patch b/target/linux/generic/backport-6.6/706-v6.6-04-net-phy-at803x-support-qca8081-1G-chip-type.patch
new file mode 100644
index 0000000000..b8db89d853
--- /dev/null
+++ b/target/linux/generic/backport-6.6/706-v6.6-04-net-phy-at803x-support-qca8081-1G-chip-type.patch
@@ -0,0 +1,48 @@
+From fea7cfb83d1a2782e39cd101dd44ed2548539de5 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Sun, 16 Jul 2023 16:49:22 +0800
+Subject: [PATCH 4/6] net: phy: at803x: support qca8081 1G chip type
+
+The qca8081 1G chip version does not support 2.5 capability, which
+is distinguished from qca8081 2.5G chip according to the bit0 of
+register mmd7.0x901d, the 1G version chip also has the same PHY ID
+as the normal qca8081 2.5G chip.
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+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
+@@ -272,6 +272,10 @@
+ #define QCA808X_CDT_STATUS_STAT_OPEN 2
+ #define QCA808X_CDT_STATUS_STAT_SHORT 3
+
++/* QCA808X 1G chip type */
++#define QCA808X_PHY_MMD7_CHIP_TYPE 0x901d
++#define QCA808X_PHY_CHIP_TYPE_1G BIT(0)
++
+ MODULE_DESCRIPTION("Qualcomm Atheros AR803x and QCA808X PHY driver");
+ MODULE_AUTHOR("Matus Ujhelyi");
+ MODULE_LICENSE("GPL");
+@@ -2005,6 +2009,17 @@ static int qca808x_get_features(struct p
+ */
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
+
++ /* As for the qca8081 1G version chip, the 2500baseT ability is also
++ * existed in the bit0 of MMD1.21, we need to remove it manually if
++ * it is the qca8081 1G chip according to the bit0 of MMD7.0x901d.
++ */
++ ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_CHIP_TYPE);
++ if (ret < 0)
++ return ret;
++
++ if (QCA808X_PHY_CHIP_TYPE_1G & ret)
++ linkmode_clear_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
++
+ return 0;
+ }
+
diff --git a/target/linux/generic/backport-6.6/706-v6.6-05-net-phy-at803x-remove-qca8081-1G-fast-retrain-and-sl.patch b/target/linux/generic/backport-6.6/706-v6.6-05-net-phy-at803x-remove-qca8081-1G-fast-retrain-and-sl.patch
new file mode 100644
index 0000000000..b54a86e5f5
--- /dev/null
+++ b/target/linux/generic/backport-6.6/706-v6.6-05-net-phy-at803x-remove-qca8081-1G-fast-retrain-and-sl.patch
@@ -0,0 +1,98 @@
+From df9401ff3e6eeaa42bfb06761967f1b71f5afce7 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Sun, 16 Jul 2023 16:49:23 +0800
+Subject: [PATCH 5/6] net: phy: at803x: remove qca8081 1G fast retrain and
+ slave seed config
+
+The fast retrain and slave seed configs are only applicable when the 2.5G
+ability is supported.
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 50 +++++++++++++++++++++++++---------------
+ 1 file changed, 32 insertions(+), 18 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -1755,6 +1755,11 @@ static bool qca808x_is_prefer_master(str
+ (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_PREFERRED);
+ }
+
++static bool qca808x_has_fast_retrain_or_slave_seed(struct phy_device *phydev)
++{
++ return linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
++}
++
+ static int qca808x_config_init(struct phy_device *phydev)
+ {
+ int ret;
+@@ -1771,20 +1776,24 @@ static int qca808x_config_init(struct ph
+ if (ret)
+ return ret;
+
+- /* Config the fast retrain for the link 2500M */
+- ret = qca808x_phy_fast_retrain_config(phydev);
+- if (ret)
+- return ret;
+-
+- ret = genphy_read_master_slave(phydev);
+- if (ret < 0)
+- return ret;
+-
+- if (!qca808x_is_prefer_master(phydev)) {
+- /* Enable seed and configure lower ramdom seed to make phy linked as slave mode */
+- ret = qca808x_phy_ms_seed_enable(phydev, true);
++ if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
++ /* Config the fast retrain for the link 2500M */
++ ret = qca808x_phy_fast_retrain_config(phydev);
+ if (ret)
+ return ret;
++
++ ret = genphy_read_master_slave(phydev);
++ if (ret < 0)
++ return ret;
++
++ if (!qca808x_is_prefer_master(phydev)) {
++ /* Enable seed and configure lower ramdom seed to make phy
++ * linked as slave mode.
++ */
++ ret = qca808x_phy_ms_seed_enable(phydev, true);
++ if (ret)
++ return ret;
++ }
+ }
+
+ /* Configure adc threshold as 100mv for the link 10M */
+@@ -1826,11 +1835,13 @@ static int qca808x_read_status(struct ph
+ * value is configured as the same value, the link can't be up and no link change
+ * occurs.
+ */
+- if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR ||
+- qca808x_is_prefer_master(phydev)) {
+- qca808x_phy_ms_seed_enable(phydev, false);
+- } else {
+- qca808x_phy_ms_seed_enable(phydev, true);
++ if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
++ if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR ||
++ qca808x_is_prefer_master(phydev)) {
++ qca808x_phy_ms_seed_enable(phydev, false);
++ } else {
++ qca808x_phy_ms_seed_enable(phydev, true);
++ }
+ }
+ }
+
+@@ -1845,7 +1856,10 @@ static int qca808x_soft_reset(struct phy
+ if (ret < 0)
+ return ret;
+
+- return qca808x_phy_ms_seed_enable(phydev, true);
++ if (qca808x_has_fast_retrain_or_slave_seed(phydev))
++ ret = qca808x_phy_ms_seed_enable(phydev, true);
++
++ return ret;
+ }
+
+ static bool qca808x_cdt_fault_length_valid(int cdt_code)
diff --git a/target/linux/generic/backport-6.6/706-v6.6-06-net-phy-at803x-add-qca8081-fifo-reset-on-the-link-ch.patch b/target/linux/generic/backport-6.6/706-v6.6-06-net-phy-at803x-add-qca8081-fifo-reset-on-the-link-ch.patch
new file mode 100644
index 0000000000..e382cf7876
--- /dev/null
+++ b/target/linux/generic/backport-6.6/706-v6.6-06-net-phy-at803x-add-qca8081-fifo-reset-on-the-link-ch.patch
@@ -0,0 +1,55 @@
+From 723970affdd8766fa0d91cd34bf2ffc861538b5f Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Sun, 16 Jul 2023 16:49:24 +0800
+Subject: [PATCH 6/6] net: phy: at803x: add qca8081 fifo reset on the link
+ changed
+
+The qca8081 sgmii fifo needs to be reset on link down and
+released on the link up in case of any abnormal issue
+such as the packet blocked on the PHY.
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -276,6 +276,9 @@
+ #define QCA808X_PHY_MMD7_CHIP_TYPE 0x901d
+ #define QCA808X_PHY_CHIP_TYPE_1G BIT(0)
+
++#define QCA8081_PHY_SERDES_MMD1_FIFO_CTRL 0x9072
++#define QCA8081_PHY_FIFO_RSTN BIT(11)
++
+ MODULE_DESCRIPTION("Qualcomm Atheros AR803x and QCA808X PHY driver");
+ MODULE_AUTHOR("Matus Ujhelyi");
+ MODULE_LICENSE("GPL");
+@@ -2037,6 +2040,16 @@ static int qca808x_get_features(struct p
+ return 0;
+ }
+
++static void qca808x_link_change_notify(struct phy_device *phydev)
++{
++ /* Assert interface sgmii fifo on link down, deassert it on link up,
++ * the interface device address is always phy address added by 1.
++ */
++ mdiobus_c45_modify_changed(phydev->mdio.bus, phydev->mdio.addr + 1,
++ MDIO_MMD_PMAPMD, QCA8081_PHY_SERDES_MMD1_FIFO_CTRL,
++ QCA8081_PHY_FIFO_RSTN, phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
++}
++
+ static struct phy_driver at803x_driver[] = {
+ {
+ /* Qualcomm Atheros AR8035 */
+@@ -2213,6 +2226,7 @@ static struct phy_driver at803x_driver[]
+ .soft_reset = qca808x_soft_reset,
+ .cable_test_start = qca808x_cable_test_start,
+ .cable_test_get_status = qca808x_cable_test_get_status,
++ .link_change_notify = qca808x_link_change_notify,
+ }, };
+
+ module_phy_driver(at803x_driver);
diff --git a/target/linux/generic/backport-6.6/707-v6.8-02-net-phy-at803x-move-disable-WOL-to-specific-at8031-p.patch b/target/linux/generic/backport-6.6/707-v6.8-02-net-phy-at803x-move-disable-WOL-to-specific-at8031-p.patch
new file mode 100644
index 0000000000..eb9172b1cc
--- /dev/null
+++ b/target/linux/generic/backport-6.6/707-v6.8-02-net-phy-at803x-move-disable-WOL-to-specific-at8031-p.patch
@@ -0,0 +1,69 @@
+From 6a3b8c573b5a152a6aa7a0b54c5e18b84c6ba6f5 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Fri, 8 Dec 2023 15:51:49 +0100
+Subject: [PATCH 02/13] net: phy: at803x: move disable WOL to specific at8031
+ probe
+
+Move the WOL disable call to specific at8031 probe to make at803x_probe
+more generic and drop extra check for PHY ID.
+
+Keep the same previous behaviour by first calling at803x_probe and then
+disabling WOL.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 27 +++++++++++++++++----------
+ 1 file changed, 17 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -886,15 +886,6 @@ static int at803x_probe(struct phy_devic
+ priv->is_fiber = true;
+ break;
+ }
+-
+- /* Disable WoL in 1588 register which is enabled
+- * by default
+- */
+- ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
+- AT803X_PHY_MMD3_WOL_CTRL,
+- AT803X_WOL_EN, 0);
+- if (ret)
+- return ret;
+ }
+
+ return 0;
+@@ -1591,6 +1582,22 @@ static int at803x_cable_test_start(struc
+ return 0;
+ }
+
++static int at8031_probe(struct phy_device *phydev)
++{
++ int ret;
++
++ ret = at803x_probe(phydev);
++ if (ret)
++ return ret;
++
++ /* Disable WoL in 1588 register which is enabled
++ * by default
++ */
++ return phy_modify_mmd(phydev, MDIO_MMD_PCS,
++ AT803X_PHY_MMD3_WOL_CTRL,
++ AT803X_WOL_EN, 0);
++}
++
+ static int qca83xx_config_init(struct phy_device *phydev)
+ {
+ u8 switch_revision;
+@@ -2092,7 +2099,7 @@ static struct phy_driver at803x_driver[]
+ PHY_ID_MATCH_EXACT(ATH8031_PHY_ID),
+ .name = "Qualcomm Atheros AR8031/AR8033",
+ .flags = PHY_POLL_CABLE_TEST,
+- .probe = at803x_probe,
++ .probe = at8031_probe,
+ .config_init = at803x_config_init,
+ .config_aneg = at803x_config_aneg,
+ .soft_reset = genphy_soft_reset,
diff --git a/target/linux/generic/backport-6.6/707-v6.8-03-net-phy-at803x-raname-hw_stats-functions-to-qca83xx-.patch b/target/linux/generic/backport-6.6/707-v6.8-03-net-phy-at803x-raname-hw_stats-functions-to-qca83xx-.patch
new file mode 100644
index 0000000000..5a9d1764f1
--- /dev/null
+++ b/target/linux/generic/backport-6.6/707-v6.8-03-net-phy-at803x-raname-hw_stats-functions-to-qca83xx-.patch
@@ -0,0 +1,129 @@
+From 07b1ad83b9ed6db1735ba10baf67b7a565ac0cef Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Fri, 8 Dec 2023 15:51:50 +0100
+Subject: [PATCH 03/13] net: phy: at803x: raname hw_stats functions to qca83xx
+ specific name
+
+The function and the struct related to hw_stats were specific to qca83xx
+PHY but were called following the convention in the driver of calling
+everything with at803x prefix.
+
+To better organize the code, rename these function a more specific name
+to better describe that they are specific to 83xx PHY family.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 44 ++++++++++++++++++++--------------------
+ 1 file changed, 22 insertions(+), 22 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -295,7 +295,7 @@ struct at803x_hw_stat {
+ enum stat_access_type access_type;
+ };
+
+-static struct at803x_hw_stat at803x_hw_stats[] = {
++static struct at803x_hw_stat qca83xx_hw_stats[] = {
+ { "phy_idle_errors", 0xa, GENMASK(7, 0), PHY},
+ { "phy_receive_errors", 0x15, GENMASK(15, 0), PHY},
+ { "eee_wake_errors", 0x16, GENMASK(15, 0), MMD},
+@@ -311,7 +311,7 @@ struct at803x_priv {
+ bool is_1000basex;
+ struct regulator_dev *vddio_rdev;
+ struct regulator_dev *vddh_rdev;
+- u64 stats[ARRAY_SIZE(at803x_hw_stats)];
++ u64 stats[ARRAY_SIZE(qca83xx_hw_stats)];
+ };
+
+ struct at803x_context {
+@@ -529,24 +529,24 @@ static void at803x_get_wol(struct phy_de
+ wol->wolopts |= WAKE_MAGIC;
+ }
+
+-static int at803x_get_sset_count(struct phy_device *phydev)
++static int qca83xx_get_sset_count(struct phy_device *phydev)
+ {
+- return ARRAY_SIZE(at803x_hw_stats);
++ return ARRAY_SIZE(qca83xx_hw_stats);
+ }
+
+-static void at803x_get_strings(struct phy_device *phydev, u8 *data)
++static void qca83xx_get_strings(struct phy_device *phydev, u8 *data)
+ {
+ int i;
+
+- for (i = 0; i < ARRAY_SIZE(at803x_hw_stats); i++) {
++ for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++) {
+ strscpy(data + i * ETH_GSTRING_LEN,
+- at803x_hw_stats[i].string, ETH_GSTRING_LEN);
++ qca83xx_hw_stats[i].string, ETH_GSTRING_LEN);
+ }
+ }
+
+-static u64 at803x_get_stat(struct phy_device *phydev, int i)
++static u64 qca83xx_get_stat(struct phy_device *phydev, int i)
+ {
+- struct at803x_hw_stat stat = at803x_hw_stats[i];
++ struct at803x_hw_stat stat = qca83xx_hw_stats[i];
+ struct at803x_priv *priv = phydev->priv;
+ int val;
+ u64 ret;
+@@ -567,13 +567,13 @@ static u64 at803x_get_stat(struct phy_de
+ return ret;
+ }
+
+-static void at803x_get_stats(struct phy_device *phydev,
+- struct ethtool_stats *stats, u64 *data)
++static void qca83xx_get_stats(struct phy_device *phydev,
++ struct ethtool_stats *stats, u64 *data)
+ {
+ int i;
+
+- for (i = 0; i < ARRAY_SIZE(at803x_hw_stats); i++)
+- data[i] = at803x_get_stat(phydev, i);
++ for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++)
++ data[i] = qca83xx_get_stat(phydev, i);
+ }
+
+ static int at803x_suspend(struct phy_device *phydev)
+@@ -2175,9 +2175,9 @@ static struct phy_driver at803x_driver[]
+ .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,
++ .get_sset_count = qca83xx_get_sset_count,
++ .get_strings = qca83xx_get_strings,
++ .get_stats = qca83xx_get_stats,
+ .suspend = qca83xx_suspend,
+ .resume = qca83xx_resume,
+ }, {
+@@ -2191,9 +2191,9 @@ static struct phy_driver at803x_driver[]
+ .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,
++ .get_sset_count = qca83xx_get_sset_count,
++ .get_strings = qca83xx_get_strings,
++ .get_stats = qca83xx_get_stats,
+ .suspend = qca83xx_suspend,
+ .resume = qca83xx_resume,
+ }, {
+@@ -2207,9 +2207,9 @@ static struct phy_driver at803x_driver[]
+ .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,
++ .get_sset_count = qca83xx_get_sset_count,
++ .get_strings = qca83xx_get_strings,
++ .get_stats = qca83xx_get_stats,
+ .suspend = qca83xx_suspend,
+ .resume = qca83xx_resume,
+ }, {
diff --git a/target/linux/generic/backport-6.6/707-v6.8-04-net-phy-at803x-move-qca83xx-specific-check-in-dedica.patch b/target/linux/generic/backport-6.6/707-v6.8-04-net-phy-at803x-move-qca83xx-specific-check-in-dedica.patch
new file mode 100644
index 0000000000..f09142c5ed
--- /dev/null
+++ b/target/linux/generic/backport-6.6/707-v6.8-04-net-phy-at803x-move-qca83xx-specific-check-in-dedica.patch
@@ -0,0 +1,155 @@
+From d43cff3f82336c0bd965ea552232d9f4ddac71a6 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Fri, 8 Dec 2023 15:51:51 +0100
+Subject: [PATCH 04/13] net: phy: at803x: move qca83xx specific check in
+ dedicated functions
+
+Rework qca83xx specific check to dedicated function to tidy things up
+and drop useless phy_id check.
+
+Also drop an useless link_change_notify for QCA8337 as it did nothing an
+returned early.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 68 ++++++++++++++++++++++------------------
+ 1 file changed, 37 insertions(+), 31 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -1623,27 +1623,26 @@ static int qca83xx_config_init(struct ph
+ break;
+ }
+
++ /* Following original QCA sourcecode set port to prefer master */
++ phy_set_bits(phydev, MII_CTRL1000, CTL1000_PREFER_MASTER);
++
++ return 0;
++}
++
++static int qca8327_config_init(struct phy_device *phydev)
++{
+ /* 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_ANALOG_TEST_CTRL,
+- QCA8327_DEBUG_MANU_CTRL_EN, 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 */
+- phy_set_bits(phydev, MII_CTRL1000, CTL1000_PREFER_MASTER);
+-
+- return 0;
++ return qca83xx_config_init(phydev);
+ }
+
+ 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)
+@@ -1686,19 +1685,6 @@ static int qca83xx_resume(struct phy_dev
+
+ 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_GREEN,
+ AT803X_DEBUG_GATE_CLK_IN1000, 0);
+
+@@ -1709,6 +1695,27 @@ static int qca83xx_suspend(struct phy_de
+ return 0;
+ }
+
++static int qca8337_suspend(struct phy_device *phydev)
++{
++ /* Only QCA8337 support actual suspend. */
++ genphy_suspend(phydev);
++
++ return qca83xx_suspend(phydev);
++}
++
++static int qca8327_suspend(struct phy_device *phydev)
++{
++ u16 mask = 0;
++
++ /* QCA8327 cause port unreliability when phy suspend
++ * is set.
++ */
++ mask |= ~(BMCR_SPEED1000 | BMCR_FULLDPLX);
++ phy_modify(phydev, MII_BMCR, mask, 0);
++
++ return qca83xx_suspend(phydev);
++}
++
+ static int qca808x_phy_fast_retrain_config(struct phy_device *phydev)
+ {
+ int ret;
+@@ -2170,7 +2177,6 @@ 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,
+@@ -2178,7 +2184,7 @@ static struct phy_driver at803x_driver[]
+ .get_sset_count = qca83xx_get_sset_count,
+ .get_strings = qca83xx_get_strings,
+ .get_stats = qca83xx_get_stats,
+- .suspend = qca83xx_suspend,
++ .suspend = qca8337_suspend,
+ .resume = qca83xx_resume,
+ }, {
+ /* QCA8327-A from switch QCA8327-AL1A */
+@@ -2189,12 +2195,12 @@ static struct phy_driver at803x_driver[]
+ .link_change_notify = qca83xx_link_change_notify,
+ .probe = at803x_probe,
+ .flags = PHY_IS_INTERNAL,
+- .config_init = qca83xx_config_init,
++ .config_init = qca8327_config_init,
+ .soft_reset = genphy_soft_reset,
+ .get_sset_count = qca83xx_get_sset_count,
+ .get_strings = qca83xx_get_strings,
+ .get_stats = qca83xx_get_stats,
+- .suspend = qca83xx_suspend,
++ .suspend = qca8327_suspend,
+ .resume = qca83xx_resume,
+ }, {
+ /* QCA8327-B from switch QCA8327-BL1A */
+@@ -2205,12 +2211,12 @@ static struct phy_driver at803x_driver[]
+ .link_change_notify = qca83xx_link_change_notify,
+ .probe = at803x_probe,
+ .flags = PHY_IS_INTERNAL,
+- .config_init = qca83xx_config_init,
++ .config_init = qca8327_config_init,
+ .soft_reset = genphy_soft_reset,
+ .get_sset_count = qca83xx_get_sset_count,
+ .get_strings = qca83xx_get_strings,
+ .get_stats = qca83xx_get_stats,
+- .suspend = qca83xx_suspend,
++ .suspend = qca8327_suspend,
+ .resume = qca83xx_resume,
+ }, {
+ /* Qualcomm QCA8081 */
diff --git a/target/linux/generic/backport-6.6/707-v6.8-05-net-phy-at803x-move-specific-DT-option-for-at8031-to.patch b/target/linux/generic/backport-6.6/707-v6.8-05-net-phy-at803x-move-specific-DT-option-for-at8031-to.patch
new file mode 100644
index 0000000000..a5cc67a8c8
--- /dev/null
+++ b/target/linux/generic/backport-6.6/707-v6.8-05-net-phy-at803x-move-specific-DT-option-for-at8031-to.patch
@@ -0,0 +1,94 @@
+From 900eef75cc5018e149c52fe305c9c3fe424c52a7 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Fri, 8 Dec 2023 15:51:52 +0100
+Subject: [PATCH 05/13] net: phy: at803x: move specific DT option for at8031 to
+ specific probe
+
+Move specific DT options for at8031 to specific probe to tidy things up
+and make at803x_parse_dt more generic.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 55 ++++++++++++++++++++++------------------
+ 1 file changed, 31 insertions(+), 24 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -825,30 +825,6 @@ static int at803x_parse_dt(struct phy_de
+ }
+ }
+
+- /* Only supported on AR8031/AR8033, the AR8030/AR8035 use strapping
+- * options.
+- */
+- if (phydev->drv->phy_id == ATH8031_PHY_ID) {
+- if (of_property_read_bool(node, "qca,keep-pll-enabled"))
+- priv->flags |= AT803X_KEEP_PLL_ENABLED;
+-
+- ret = at8031_register_regulators(phydev);
+- if (ret < 0)
+- return ret;
+-
+- ret = devm_regulator_get_enable_optional(&phydev->mdio.dev,
+- "vddio");
+- if (ret) {
+- phydev_err(phydev, "failed to get VDDIO regulator\n");
+- return ret;
+- }
+-
+- /* Only AR8031/8033 support 1000Base-X for SFP modules */
+- ret = phy_sfp_probe(phydev, &at803x_sfp_ops);
+- if (ret < 0)
+- return ret;
+- }
+-
+ return 0;
+ }
+
+@@ -1582,6 +1558,30 @@ static int at803x_cable_test_start(struc
+ return 0;
+ }
+
++static int at8031_parse_dt(struct phy_device *phydev)
++{
++ struct device_node *node = phydev->mdio.dev.of_node;
++ struct at803x_priv *priv = phydev->priv;
++ int ret;
++
++ if (of_property_read_bool(node, "qca,keep-pll-enabled"))
++ priv->flags |= AT803X_KEEP_PLL_ENABLED;
++
++ ret = at8031_register_regulators(phydev);
++ if (ret < 0)
++ return ret;
++
++ ret = devm_regulator_get_enable_optional(&phydev->mdio.dev,
++ "vddio");
++ if (ret) {
++ phydev_err(phydev, "failed to get VDDIO regulator\n");
++ return ret;
++ }
++
++ /* Only AR8031/8033 support 1000Base-X for SFP modules */
++ return phy_sfp_probe(phydev, &at803x_sfp_ops);
++}
++
+ static int at8031_probe(struct phy_device *phydev)
+ {
+ int ret;
+@@ -1590,6 +1590,13 @@ static int at8031_probe(struct phy_devic
+ if (ret)
+ return ret;
+
++ /* Only supported on AR8031/AR8033, the AR8030/AR8035 use strapping
++ * options.
++ */
++ ret = at8031_parse_dt(phydev);
++ if (ret)
++ return ret;
++
+ /* Disable WoL in 1588 register which is enabled
+ * by default
+ */
diff --git a/target/linux/generic/backport-6.6/707-v6.8-06-net-phy-at803x-move-specific-at8031-probe-mode-check.patch b/target/linux/generic/backport-6.6/707-v6.8-06-net-phy-at803x-move-specific-at8031-probe-mode-check.patch
new file mode 100644
index 0000000000..9e10e000e5
--- /dev/null
+++ b/target/linux/generic/backport-6.6/707-v6.8-06-net-phy-at803x-move-specific-at8031-probe-mode-check.patch
@@ -0,0 +1,78 @@
+From 25d2ba94005fac18fe68878cddff59a67e115554 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Fri, 8 Dec 2023 15:51:53 +0100
+Subject: [PATCH 06/13] net: phy: at803x: move specific at8031 probe mode check
+ to dedicated probe
+
+Move specific at8031 probe mode check to dedicated probe to make
+at803x_probe more generic and keep code tidy.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 39 +++++++++++++++++++--------------------
+ 1 file changed, 19 insertions(+), 20 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -844,26 +844,6 @@ static int at803x_probe(struct phy_devic
+ if (ret)
+ return ret;
+
+- if (phydev->drv->phy_id == ATH8031_PHY_ID) {
+- int ccr = phy_read(phydev, AT803X_REG_CHIP_CONFIG);
+- int mode_cfg;
+-
+- if (ccr < 0)
+- return ccr;
+- mode_cfg = ccr & AT803X_MODE_CFG_MASK;
+-
+- switch (mode_cfg) {
+- case AT803X_MODE_CFG_BX1000_RGMII_50OHM:
+- case AT803X_MODE_CFG_BX1000_RGMII_75OHM:
+- priv->is_1000basex = true;
+- fallthrough;
+- case AT803X_MODE_CFG_FX100_RGMII_50OHM:
+- case AT803X_MODE_CFG_FX100_RGMII_75OHM:
+- priv->is_fiber = true;
+- break;
+- }
+- }
+-
+ return 0;
+ }
+
+@@ -1584,6 +1564,9 @@ static int at8031_parse_dt(struct phy_de
+
+ static int at8031_probe(struct phy_device *phydev)
+ {
++ struct at803x_priv *priv = phydev->priv;
++ int mode_cfg;
++ int ccr;
+ int ret;
+
+ ret = at803x_probe(phydev);
+@@ -1597,6 +1580,22 @@ static int at8031_probe(struct phy_devic
+ if (ret)
+ return ret;
+
++ ccr = phy_read(phydev, AT803X_REG_CHIP_CONFIG);
++ if (ccr < 0)
++ return ccr;
++ mode_cfg = ccr & AT803X_MODE_CFG_MASK;
++
++ switch (mode_cfg) {
++ case AT803X_MODE_CFG_BX1000_RGMII_50OHM:
++ case AT803X_MODE_CFG_BX1000_RGMII_75OHM:
++ priv->is_1000basex = true;
++ fallthrough;
++ case AT803X_MODE_CFG_FX100_RGMII_50OHM:
++ case AT803X_MODE_CFG_FX100_RGMII_75OHM:
++ priv->is_fiber = true;
++ break;
++ }
++
+ /* Disable WoL in 1588 register which is enabled
+ * by default
+ */
diff --git a/target/linux/generic/backport-6.6/707-v6.8-07-net-phy-at803x-move-specific-at8031-config_init-to-d.patch b/target/linux/generic/backport-6.6/707-v6.8-07-net-phy-at803x-move-specific-at8031-config_init-to-d.patch
new file mode 100644
index 0000000000..c9ef6df827
--- /dev/null
+++ b/target/linux/generic/backport-6.6/707-v6.8-07-net-phy-at803x-move-specific-at8031-config_init-to-d.patch
@@ -0,0 +1,86 @@
+From 3ae3bc426eaf57ca8f53d75777d9a5ef779bc7b7 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Fri, 8 Dec 2023 15:51:54 +0100
+Subject: [PATCH 07/13] net: phy: at803x: move specific at8031 config_init to
+ dedicated function
+
+Move specific at8031 config_init to dedicated function to make
+at803x_config_init more generic and tidy things up.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 45 ++++++++++++++++++++++------------------
+ 1 file changed, 25 insertions(+), 20 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -951,27 +951,8 @@ static int at803x_hibernation_mode_confi
+
+ static int at803x_config_init(struct phy_device *phydev)
+ {
+- struct at803x_priv *priv = phydev->priv;
+ int ret;
+
+- if (phydev->drv->phy_id == ATH8031_PHY_ID) {
+- /* Some bootloaders leave the fiber page selected.
+- * Switch to the appropriate page (fiber or copper), as otherwise we
+- * read the PHY capabilities from the wrong page.
+- */
+- phy_lock_mdio_bus(phydev);
+- ret = at803x_write_page(phydev,
+- priv->is_fiber ? AT803X_PAGE_FIBER :
+- AT803X_PAGE_COPPER);
+- phy_unlock_mdio_bus(phydev);
+- if (ret)
+- return ret;
+-
+- ret = at8031_pll_config(phydev);
+- if (ret < 0)
+- return ret;
+- }
+-
+ /* The RX and TX delay default is:
+ * after HW reset: RX delay enabled and TX delay disabled
+ * after SW reset: RX delay enabled, while TX delay retains the
+@@ -1604,6 +1585,30 @@ static int at8031_probe(struct phy_devic
+ AT803X_WOL_EN, 0);
+ }
+
++static int at8031_config_init(struct phy_device *phydev)
++{
++ struct at803x_priv *priv = phydev->priv;
++ int ret;
++
++ /* Some bootloaders leave the fiber page selected.
++ * Switch to the appropriate page (fiber or copper), as otherwise we
++ * read the PHY capabilities from the wrong page.
++ */
++ phy_lock_mdio_bus(phydev);
++ ret = at803x_write_page(phydev,
++ priv->is_fiber ? AT803X_PAGE_FIBER :
++ AT803X_PAGE_COPPER);
++ phy_unlock_mdio_bus(phydev);
++ if (ret)
++ return ret;
++
++ ret = at8031_pll_config(phydev);
++ if (ret < 0)
++ return ret;
++
++ return at803x_config_init(phydev);
++}
++
+ static int qca83xx_config_init(struct phy_device *phydev)
+ {
+ u8 switch_revision;
+@@ -2113,7 +2118,7 @@ static struct phy_driver at803x_driver[]
+ .name = "Qualcomm Atheros AR8031/AR8033",
+ .flags = PHY_POLL_CABLE_TEST,
+ .probe = at8031_probe,
+- .config_init = at803x_config_init,
++ .config_init = at8031_config_init,
+ .config_aneg = at803x_config_aneg,
+ .soft_reset = genphy_soft_reset,
+ .set_wol = at803x_set_wol,
diff --git a/target/linux/generic/backport-6.6/707-v6.8-08-net-phy-at803x-move-specific-at8031-WOL-bits-to-dedi.patch b/target/linux/generic/backport-6.6/707-v6.8-08-net-phy-at803x-move-specific-at8031-WOL-bits-to-dedi.patch
new file mode 100644
index 0000000000..6746a4d43c
--- /dev/null
+++ b/target/linux/generic/backport-6.6/707-v6.8-08-net-phy-at803x-move-specific-at8031-WOL-bits-to-dedi.patch
@@ -0,0 +1,92 @@
+From 27b89c9dc1b0393090d68d651b82f30ad2696baa Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Fri, 8 Dec 2023 15:51:55 +0100
+Subject: [PATCH 08/13] net: phy: at803x: move specific at8031 WOL bits to
+ dedicated function
+
+Move specific at8031 WOL enable/disable to dedicated function to make
+at803x_set_wol more generic.
+
+This is needed in preparation for PHY driver split as qca8081 share the
+same function to toggle WOL settings.
+
+In this new implementation WOL module in at8031 is enabled after the
+generic interrupt is setup. This should not cause any problem as the
+WOL_INT has a separate implementation and only relay on MAC bits.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 42 ++++++++++++++++++++++++----------------
+ 1 file changed, 25 insertions(+), 17 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -466,27 +466,11 @@ static int at803x_set_wol(struct phy_dev
+ phy_write_mmd(phydev, MDIO_MMD_PCS, offsets[i],
+ mac[(i * 2) + 1] | (mac[(i * 2)] << 8));
+
+- /* Enable WOL function for 1588 */
+- if (phydev->drv->phy_id == ATH8031_PHY_ID) {
+- ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
+- AT803X_PHY_MMD3_WOL_CTRL,
+- 0, AT803X_WOL_EN);
+- if (ret)
+- return ret;
+- }
+ /* Enable WOL interrupt */
+ ret = phy_modify(phydev, AT803X_INTR_ENABLE, 0, AT803X_INTR_ENABLE_WOL);
+ if (ret)
+ return ret;
+ } else {
+- /* Disable WoL function for 1588 */
+- if (phydev->drv->phy_id == ATH8031_PHY_ID) {
+- ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
+- AT803X_PHY_MMD3_WOL_CTRL,
+- AT803X_WOL_EN, 0);
+- if (ret)
+- return ret;
+- }
+ /* Disable WOL interrupt */
+ ret = phy_modify(phydev, AT803X_INTR_ENABLE, AT803X_INTR_ENABLE_WOL, 0);
+ if (ret)
+@@ -1609,6 +1593,30 @@ static int at8031_config_init(struct phy
+ return at803x_config_init(phydev);
+ }
+
++static int at8031_set_wol(struct phy_device *phydev,
++ struct ethtool_wolinfo *wol)
++{
++ int ret;
++
++ /* First setup MAC address and enable WOL interrupt */
++ ret = at803x_set_wol(phydev, wol);
++ if (ret)
++ return ret;
++
++ if (wol->wolopts & WAKE_MAGIC)
++ /* Enable WOL function for 1588 */
++ ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
++ AT803X_PHY_MMD3_WOL_CTRL,
++ 0, AT803X_WOL_EN);
++ else
++ /* Disable WoL function for 1588 */
++ ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
++ AT803X_PHY_MMD3_WOL_CTRL,
++ AT803X_WOL_EN, 0);
++
++ return ret;
++}
++
+ static int qca83xx_config_init(struct phy_device *phydev)
+ {
+ u8 switch_revision;
+@@ -2121,7 +2129,7 @@ static struct phy_driver at803x_driver[]
+ .config_init = at8031_config_init,
+ .config_aneg = at803x_config_aneg,
+ .soft_reset = genphy_soft_reset,
+- .set_wol = at803x_set_wol,
++ .set_wol = at8031_set_wol,
+ .get_wol = at803x_get_wol,
+ .suspend = at803x_suspend,
+ .resume = at803x_resume,
diff --git a/target/linux/generic/backport-6.6/707-v6.8-09-net-phy-at803x-move-specific-at8031-config_intr-to-d.patch b/target/linux/generic/backport-6.6/707-v6.8-09-net-phy-at803x-move-specific-at8031-config_intr-to-d.patch
new file mode 100644
index 0000000000..0d284c3d24
--- /dev/null
+++ b/target/linux/generic/backport-6.6/707-v6.8-09-net-phy-at803x-move-specific-at8031-config_intr-to-d.patch
@@ -0,0 +1,78 @@
+From 30dd62191d3dd97c08f7f9dc9ce77ffab457e4fb Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Fri, 8 Dec 2023 15:51:56 +0100
+Subject: [PATCH 09/13] net: phy: at803x: move specific at8031 config_intr to
+ dedicated function
+
+Move specific at8031 config_intr bits to dedicated function to make
+at803x_config_initr more generic.
+
+This is needed in preparation for PHY driver split as qca8081 share the
+same function to setup interrupts.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 30 ++++++++++++++++++++++++------
+ 1 file changed, 24 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -990,7 +990,6 @@ static int at803x_ack_interrupt(struct p
+
+ static int at803x_config_intr(struct phy_device *phydev)
+ {
+- struct at803x_priv *priv = phydev->priv;
+ int err;
+ int value;
+
+@@ -1007,10 +1006,6 @@ static int at803x_config_intr(struct phy
+ value |= AT803X_INTR_ENABLE_DUPLEX_CHANGED;
+ value |= AT803X_INTR_ENABLE_LINK_FAIL;
+ value |= AT803X_INTR_ENABLE_LINK_SUCCESS;
+- if (priv->is_fiber) {
+- value |= AT803X_INTR_ENABLE_LINK_FAIL_BX;
+- value |= AT803X_INTR_ENABLE_LINK_SUCCESS_BX;
+- }
+
+ err = phy_write(phydev, AT803X_INTR_ENABLE, value);
+ } else {
+@@ -1617,6 +1612,29 @@ static int at8031_set_wol(struct phy_dev
+ return ret;
+ }
+
++static int at8031_config_intr(struct phy_device *phydev)
++{
++ struct at803x_priv *priv = phydev->priv;
++ int err, value = 0;
++
++ if (phydev->interrupts == PHY_INTERRUPT_ENABLED &&
++ priv->is_fiber) {
++ /* Clear any pending interrupts */
++ err = at803x_ack_interrupt(phydev);
++ if (err)
++ return err;
++
++ value |= AT803X_INTR_ENABLE_LINK_FAIL_BX;
++ value |= AT803X_INTR_ENABLE_LINK_SUCCESS_BX;
++
++ err = phy_set_bits(phydev, AT803X_INTR_ENABLE, value);
++ if (err)
++ return err;
++ }
++
++ return at803x_config_intr(phydev);
++}
++
+ static int qca83xx_config_init(struct phy_device *phydev)
+ {
+ u8 switch_revision;
+@@ -2137,7 +2155,7 @@ static struct phy_driver at803x_driver[]
+ .write_page = at803x_write_page,
+ .get_features = at803x_get_features,
+ .read_status = at803x_read_status,
+- .config_intr = at803x_config_intr,
++ .config_intr = at8031_config_intr,
+ .handle_interrupt = at803x_handle_interrupt,
+ .get_tunable = at803x_get_tunable,
+ .set_tunable = at803x_set_tunable,
diff --git a/target/linux/generic/backport-6.6/707-v6.8-10-net-phy-at803x-make-at8031-related-DT-functions-name.patch b/target/linux/generic/backport-6.6/707-v6.8-10-net-phy-at803x-make-at8031-related-DT-functions-name.patch
new file mode 100644
index 0000000000..210949ea81
--- /dev/null
+++ b/target/linux/generic/backport-6.6/707-v6.8-10-net-phy-at803x-make-at8031-related-DT-functions-name.patch
@@ -0,0 +1,78 @@
+From a5ab9d8e7ae0da8328ac1637a9755311508dc8ab Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Fri, 8 Dec 2023 15:51:57 +0100
+Subject: [PATCH 10/13] net: phy: at803x: make at8031 related DT functions name
+ more specific
+
+Rename at8031 related DT function name to a more specific name
+referencing they are only related to at8031 and not to the generic
+at803x PHY family.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 16 ++++++++--------
+ 1 file changed, 8 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -583,7 +583,7 @@ static int at803x_resume(struct phy_devi
+ return phy_modify(phydev, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE, 0);
+ }
+
+-static int at803x_rgmii_reg_set_voltage_sel(struct regulator_dev *rdev,
++static int at8031_rgmii_reg_set_voltage_sel(struct regulator_dev *rdev,
+ unsigned int selector)
+ {
+ struct phy_device *phydev = rdev_get_drvdata(rdev);
+@@ -596,7 +596,7 @@ static int at803x_rgmii_reg_set_voltage_
+ AT803X_DEBUG_RGMII_1V8, 0);
+ }
+
+-static int at803x_rgmii_reg_get_voltage_sel(struct regulator_dev *rdev)
++static int at8031_rgmii_reg_get_voltage_sel(struct regulator_dev *rdev)
+ {
+ struct phy_device *phydev = rdev_get_drvdata(rdev);
+ int val;
+@@ -610,8 +610,8 @@ static int at803x_rgmii_reg_get_voltage_
+
+ static const struct regulator_ops vddio_regulator_ops = {
+ .list_voltage = regulator_list_voltage_table,
+- .set_voltage_sel = at803x_rgmii_reg_set_voltage_sel,
+- .get_voltage_sel = at803x_rgmii_reg_get_voltage_sel,
++ .set_voltage_sel = at8031_rgmii_reg_set_voltage_sel,
++ .get_voltage_sel = at8031_rgmii_reg_get_voltage_sel,
+ };
+
+ static const unsigned int vddio_voltage_table[] = {
+@@ -666,7 +666,7 @@ static int at8031_register_regulators(st
+ return 0;
+ }
+
+-static int at803x_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
++static int at8031_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
+ {
+ struct phy_device *phydev = upstream;
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(phy_support);
+@@ -710,10 +710,10 @@ static int at803x_sfp_insert(void *upstr
+ return 0;
+ }
+
+-static const struct sfp_upstream_ops at803x_sfp_ops = {
++static const struct sfp_upstream_ops at8031_sfp_ops = {
+ .attach = phy_sfp_attach,
+ .detach = phy_sfp_detach,
+- .module_insert = at803x_sfp_insert,
++ .module_insert = at8031_sfp_insert,
+ };
+
+ static int at803x_parse_dt(struct phy_device *phydev)
+@@ -1519,7 +1519,7 @@ static int at8031_parse_dt(struct phy_de
+ }
+
+ /* Only AR8031/8033 support 1000Base-X for SFP modules */
+- return phy_sfp_probe(phydev, &at803x_sfp_ops);
++ return phy_sfp_probe(phydev, &at8031_sfp_ops);
+ }
+
+ static int at8031_probe(struct phy_device *phydev)
diff --git a/target/linux/generic/backport-6.6/707-v6.8-11-net-phy-at803x-move-at8031-functions-in-dedicated-se.patch b/target/linux/generic/backport-6.6/707-v6.8-11-net-phy-at803x-move-at8031-functions-in-dedicated-se.patch
new file mode 100644
index 0000000000..5856b59751
--- /dev/null
+++ b/target/linux/generic/backport-6.6/707-v6.8-11-net-phy-at803x-move-at8031-functions-in-dedicated-se.patch
@@ -0,0 +1,297 @@
+From f932a6dc8bae0dae9645b5b1b4c65aed8a8acb2a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Fri, 8 Dec 2023 15:51:58 +0100
+Subject: [PATCH 11/13] net: phy: at803x: move at8031 functions in dedicated
+ section
+
+Move at8031 functions in dedicated section with dedicated at8031
+parse_dt and probe.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 266 +++++++++++++++++++--------------------
+ 1 file changed, 133 insertions(+), 133 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -583,139 +583,6 @@ static int at803x_resume(struct phy_devi
+ return phy_modify(phydev, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE, 0);
+ }
+
+-static int at8031_rgmii_reg_set_voltage_sel(struct regulator_dev *rdev,
+- unsigned int selector)
+-{
+- struct phy_device *phydev = rdev_get_drvdata(rdev);
+-
+- if (selector)
+- return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
+- 0, AT803X_DEBUG_RGMII_1V8);
+- else
+- return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
+- AT803X_DEBUG_RGMII_1V8, 0);
+-}
+-
+-static int at8031_rgmii_reg_get_voltage_sel(struct regulator_dev *rdev)
+-{
+- struct phy_device *phydev = rdev_get_drvdata(rdev);
+- int val;
+-
+- val = at803x_debug_reg_read(phydev, AT803X_DEBUG_REG_1F);
+- if (val < 0)
+- return val;
+-
+- return (val & AT803X_DEBUG_RGMII_1V8) ? 1 : 0;
+-}
+-
+-static const struct regulator_ops vddio_regulator_ops = {
+- .list_voltage = regulator_list_voltage_table,
+- .set_voltage_sel = at8031_rgmii_reg_set_voltage_sel,
+- .get_voltage_sel = at8031_rgmii_reg_get_voltage_sel,
+-};
+-
+-static const unsigned int vddio_voltage_table[] = {
+- 1500000,
+- 1800000,
+-};
+-
+-static const struct regulator_desc vddio_desc = {
+- .name = "vddio",
+- .of_match = of_match_ptr("vddio-regulator"),
+- .n_voltages = ARRAY_SIZE(vddio_voltage_table),
+- .volt_table = vddio_voltage_table,
+- .ops = &vddio_regulator_ops,
+- .type = REGULATOR_VOLTAGE,
+- .owner = THIS_MODULE,
+-};
+-
+-static const struct regulator_ops vddh_regulator_ops = {
+-};
+-
+-static const struct regulator_desc vddh_desc = {
+- .name = "vddh",
+- .of_match = of_match_ptr("vddh-regulator"),
+- .n_voltages = 1,
+- .fixed_uV = 2500000,
+- .ops = &vddh_regulator_ops,
+- .type = REGULATOR_VOLTAGE,
+- .owner = THIS_MODULE,
+-};
+-
+-static int at8031_register_regulators(struct phy_device *phydev)
+-{
+- struct at803x_priv *priv = phydev->priv;
+- struct device *dev = &phydev->mdio.dev;
+- struct regulator_config config = { };
+-
+- config.dev = dev;
+- config.driver_data = phydev;
+-
+- priv->vddio_rdev = devm_regulator_register(dev, &vddio_desc, &config);
+- if (IS_ERR(priv->vddio_rdev)) {
+- phydev_err(phydev, "failed to register VDDIO regulator\n");
+- return PTR_ERR(priv->vddio_rdev);
+- }
+-
+- priv->vddh_rdev = devm_regulator_register(dev, &vddh_desc, &config);
+- if (IS_ERR(priv->vddh_rdev)) {
+- phydev_err(phydev, "failed to register VDDH regulator\n");
+- return PTR_ERR(priv->vddh_rdev);
+- }
+-
+- return 0;
+-}
+-
+-static int at8031_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
+-{
+- struct phy_device *phydev = upstream;
+- __ETHTOOL_DECLARE_LINK_MODE_MASK(phy_support);
+- __ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
+- DECLARE_PHY_INTERFACE_MASK(interfaces);
+- phy_interface_t iface;
+-
+- linkmode_zero(phy_support);
+- phylink_set(phy_support, 1000baseX_Full);
+- phylink_set(phy_support, 1000baseT_Full);
+- phylink_set(phy_support, Autoneg);
+- phylink_set(phy_support, Pause);
+- phylink_set(phy_support, Asym_Pause);
+-
+- linkmode_zero(sfp_support);
+- sfp_parse_support(phydev->sfp_bus, id, sfp_support, interfaces);
+- /* Some modules support 10G modes as well as others we support.
+- * Mask out non-supported modes so the correct interface is picked.
+- */
+- linkmode_and(sfp_support, phy_support, sfp_support);
+-
+- if (linkmode_empty(sfp_support)) {
+- dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n");
+- return -EINVAL;
+- }
+-
+- iface = sfp_select_interface(phydev->sfp_bus, sfp_support);
+-
+- /* Only 1000Base-X is supported by AR8031/8033 as the downstream SerDes
+- * interface for use with SFP modules.
+- * However, some copper modules detected as having a preferred SGMII
+- * interface do default to and function in 1000Base-X mode, so just
+- * print a warning and allow such modules, as they may have some chance
+- * of working.
+- */
+- if (iface == PHY_INTERFACE_MODE_SGMII)
+- dev_warn(&phydev->mdio.dev, "module may not function if 1000Base-X not supported\n");
+- else if (iface != PHY_INTERFACE_MODE_1000BASEX)
+- return -EINVAL;
+-
+- return 0;
+-}
+-
+-static const struct sfp_upstream_ops at8031_sfp_ops = {
+- .attach = phy_sfp_attach,
+- .detach = phy_sfp_detach,
+- .module_insert = at8031_sfp_insert,
+-};
+-
+ static int at803x_parse_dt(struct phy_device *phydev)
+ {
+ struct device_node *node = phydev->mdio.dev.of_node;
+@@ -1498,6 +1365,139 @@ static int at803x_cable_test_start(struc
+ return 0;
+ }
+
++static int at8031_rgmii_reg_set_voltage_sel(struct regulator_dev *rdev,
++ unsigned int selector)
++{
++ struct phy_device *phydev = rdev_get_drvdata(rdev);
++
++ if (selector)
++ return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
++ 0, AT803X_DEBUG_RGMII_1V8);
++ else
++ return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
++ AT803X_DEBUG_RGMII_1V8, 0);
++}
++
++static int at8031_rgmii_reg_get_voltage_sel(struct regulator_dev *rdev)
++{
++ struct phy_device *phydev = rdev_get_drvdata(rdev);
++ int val;
++
++ val = at803x_debug_reg_read(phydev, AT803X_DEBUG_REG_1F);
++ if (val < 0)
++ return val;
++
++ return (val & AT803X_DEBUG_RGMII_1V8) ? 1 : 0;
++}
++
++static const struct regulator_ops vddio_regulator_ops = {
++ .list_voltage = regulator_list_voltage_table,
++ .set_voltage_sel = at8031_rgmii_reg_set_voltage_sel,
++ .get_voltage_sel = at8031_rgmii_reg_get_voltage_sel,
++};
++
++static const unsigned int vddio_voltage_table[] = {
++ 1500000,
++ 1800000,
++};
++
++static const struct regulator_desc vddio_desc = {
++ .name = "vddio",
++ .of_match = of_match_ptr("vddio-regulator"),
++ .n_voltages = ARRAY_SIZE(vddio_voltage_table),
++ .volt_table = vddio_voltage_table,
++ .ops = &vddio_regulator_ops,
++ .type = REGULATOR_VOLTAGE,
++ .owner = THIS_MODULE,
++};
++
++static const struct regulator_ops vddh_regulator_ops = {
++};
++
++static const struct regulator_desc vddh_desc = {
++ .name = "vddh",
++ .of_match = of_match_ptr("vddh-regulator"),
++ .n_voltages = 1,
++ .fixed_uV = 2500000,
++ .ops = &vddh_regulator_ops,
++ .type = REGULATOR_VOLTAGE,
++ .owner = THIS_MODULE,
++};
++
++static int at8031_register_regulators(struct phy_device *phydev)
++{
++ struct at803x_priv *priv = phydev->priv;
++ struct device *dev = &phydev->mdio.dev;
++ struct regulator_config config = { };
++
++ config.dev = dev;
++ config.driver_data = phydev;
++
++ priv->vddio_rdev = devm_regulator_register(dev, &vddio_desc, &config);
++ if (IS_ERR(priv->vddio_rdev)) {
++ phydev_err(phydev, "failed to register VDDIO regulator\n");
++ return PTR_ERR(priv->vddio_rdev);
++ }
++
++ priv->vddh_rdev = devm_regulator_register(dev, &vddh_desc, &config);
++ if (IS_ERR(priv->vddh_rdev)) {
++ phydev_err(phydev, "failed to register VDDH regulator\n");
++ return PTR_ERR(priv->vddh_rdev);
++ }
++
++ return 0;
++}
++
++static int at8031_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
++{
++ struct phy_device *phydev = upstream;
++ __ETHTOOL_DECLARE_LINK_MODE_MASK(phy_support);
++ __ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
++ DECLARE_PHY_INTERFACE_MASK(interfaces);
++ phy_interface_t iface;
++
++ linkmode_zero(phy_support);
++ phylink_set(phy_support, 1000baseX_Full);
++ phylink_set(phy_support, 1000baseT_Full);
++ phylink_set(phy_support, Autoneg);
++ phylink_set(phy_support, Pause);
++ phylink_set(phy_support, Asym_Pause);
++
++ linkmode_zero(sfp_support);
++ sfp_parse_support(phydev->sfp_bus, id, sfp_support, interfaces);
++ /* Some modules support 10G modes as well as others we support.
++ * Mask out non-supported modes so the correct interface is picked.
++ */
++ linkmode_and(sfp_support, phy_support, sfp_support);
++
++ if (linkmode_empty(sfp_support)) {
++ dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n");
++ return -EINVAL;
++ }
++
++ iface = sfp_select_interface(phydev->sfp_bus, sfp_support);
++
++ /* Only 1000Base-X is supported by AR8031/8033 as the downstream SerDes
++ * interface for use with SFP modules.
++ * However, some copper modules detected as having a preferred SGMII
++ * interface do default to and function in 1000Base-X mode, so just
++ * print a warning and allow such modules, as they may have some chance
++ * of working.
++ */
++ if (iface == PHY_INTERFACE_MODE_SGMII)
++ dev_warn(&phydev->mdio.dev, "module may not function if 1000Base-X not supported\n");
++ else if (iface != PHY_INTERFACE_MODE_1000BASEX)
++ return -EINVAL;
++
++ return 0;
++}
++
++static const struct sfp_upstream_ops at8031_sfp_ops = {
++ .attach = phy_sfp_attach,
++ .detach = phy_sfp_detach,
++ .module_insert = at8031_sfp_insert,
++};
++
+ static int at8031_parse_dt(struct phy_device *phydev)
+ {
+ struct device_node *node = phydev->mdio.dev.of_node;
diff --git a/target/linux/generic/backport-6.6/707-v6.8-12-net-phy-at803x-move-at8035-specific-DT-parse-to-dedi.patch b/target/linux/generic/backport-6.6/707-v6.8-12-net-phy-at803x-move-at8035-specific-DT-parse-to-dedi.patch
new file mode 100644
index 0000000000..0a822c6d37
--- /dev/null
+++ b/target/linux/generic/backport-6.6/707-v6.8-12-net-phy-at803x-move-at8035-specific-DT-parse-to-dedi.patch
@@ -0,0 +1,114 @@
+From 21a2802a8365cfa82cc02187c1f95136d85592ad Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Fri, 8 Dec 2023 15:51:59 +0100
+Subject: [PATCH 12/13] net: phy: at803x: move at8035 specific DT parse to
+ dedicated probe
+
+Move at8035 specific DT parse for clock out frequency to dedicated probe
+to make at803x probe function more generic.
+
+This is to tidy code and no behaviour change are intended.
+
+Detection logic is changed, we check if the clk 25m mask is set and if
+it's not zero, we assume the qca,clk-out-frequency property is set.
+
+The property is checked in the generic at803x_parse_dt called by
+at803x_probe.
+
+Signed-off-by: Christian Marangi <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, 41 insertions(+), 19 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -638,23 +638,6 @@ static int at803x_parse_dt(struct phy_de
+
+ priv->clk_25m_reg |= FIELD_PREP(AT803X_CLK_OUT_MASK, sel);
+ priv->clk_25m_mask |= AT803X_CLK_OUT_MASK;
+-
+- /* Fixup for the AR8030/AR8035. This chip has another mask and
+- * doesn't support the DSP reference. Eg. the lowest bit of the
+- * mask. The upper two bits select the same frequencies. Mask
+- * the lowest bit here.
+- *
+- * Warning:
+- * There was no datasheet for the AR8030 available so this is
+- * just a guess. But the AR8035 is listed as pin compatible
+- * to the AR8030 so there might be a good chance it works on
+- * the AR8030 too.
+- */
+- if (phydev->drv->phy_id == ATH8030_PHY_ID ||
+- phydev->drv->phy_id == ATH8035_PHY_ID) {
+- priv->clk_25m_reg &= AT8035_CLK_OUT_MASK;
+- priv->clk_25m_mask &= AT8035_CLK_OUT_MASK;
+- }
+ }
+
+ ret = of_property_read_u32(node, "qca,clk-out-strength", &strength);
+@@ -1635,6 +1618,45 @@ static int at8031_config_intr(struct phy
+ return at803x_config_intr(phydev);
+ }
+
++static int at8035_parse_dt(struct phy_device *phydev)
++{
++ struct at803x_priv *priv = phydev->priv;
++
++ /* Mask is set by the generic at803x_parse_dt
++ * if property is set. Assume property is set
++ * with the mask not zero.
++ */
++ if (priv->clk_25m_mask) {
++ /* Fixup for the AR8030/AR8035. This chip has another mask and
++ * doesn't support the DSP reference. Eg. the lowest bit of the
++ * mask. The upper two bits select the same frequencies. Mask
++ * the lowest bit here.
++ *
++ * Warning:
++ * There was no datasheet for the AR8030 available so this is
++ * just a guess. But the AR8035 is listed as pin compatible
++ * to the AR8030 so there might be a good chance it works on
++ * the AR8030 too.
++ */
++ priv->clk_25m_reg &= AT8035_CLK_OUT_MASK;
++ priv->clk_25m_mask &= AT8035_CLK_OUT_MASK;
++ }
++
++ return 0;
++}
++
++/* AR8030 and AR8035 shared the same special mask for clk_25m */
++static int at8035_probe(struct phy_device *phydev)
++{
++ int ret;
++
++ ret = at803x_probe(phydev);
++ if (ret)
++ return ret;
++
++ return at8035_parse_dt(phydev);
++}
++
+ static int qca83xx_config_init(struct phy_device *phydev)
+ {
+ u8 switch_revision;
+@@ -2107,7 +2129,7 @@ static struct phy_driver at803x_driver[]
+ PHY_ID_MATCH_EXACT(ATH8035_PHY_ID),
+ .name = "Qualcomm Atheros AR8035",
+ .flags = PHY_POLL_CABLE_TEST,
+- .probe = at803x_probe,
++ .probe = at8035_probe,
+ .config_aneg = at803x_config_aneg,
+ .config_init = at803x_config_init,
+ .soft_reset = genphy_soft_reset,
+@@ -2128,7 +2150,7 @@ static struct phy_driver at803x_driver[]
+ .phy_id = ATH8030_PHY_ID,
+ .name = "Qualcomm Atheros AR8030",
+ .phy_id_mask = AT8030_PHY_ID_MASK,
+- .probe = at803x_probe,
++ .probe = at8035_probe,
+ .config_init = at803x_config_init,
+ .link_change_notify = at803x_link_change_notify,
+ .set_wol = at803x_set_wol,
diff --git a/target/linux/generic/backport-6.6/707-v6.8-13-net-phy-at803x-drop-specific-PHY-ID-check-from-cable.patch b/target/linux/generic/backport-6.6/707-v6.8-13-net-phy-at803x-drop-specific-PHY-ID-check-from-cable.patch
new file mode 100644
index 0000000000..e1ba6ec2d0
--- /dev/null
+++ b/target/linux/generic/backport-6.6/707-v6.8-13-net-phy-at803x-drop-specific-PHY-ID-check-from-cable.patch
@@ -0,0 +1,219 @@
+From ef9df47b449e32e06501a11272809be49019bdb6 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Fri, 8 Dec 2023 15:52:00 +0100
+Subject: [PATCH 13/13] net: phy: at803x: drop specific PHY ID check from cable
+ test functions
+
+Drop specific PHY ID check for cable test functions for at803x. This is
+done to make functions more generic. While at it better describe what
+the functions does by using more symbolic function names.
+
+PHYs that requires to set additional reg are moved to specific function
+calling the more generic one.
+
+cdt_start and cdt_wait_for_completion are changed to take an additional
+arg to pass specific values specific to the PHY.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 95 +++++++++++++++++++++-------------------
+ 1 file changed, 50 insertions(+), 45 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -1222,31 +1222,16 @@ static int at803x_cdt_fault_length(u16 s
+ return (dt * 824) / 10;
+ }
+
+-static int at803x_cdt_start(struct phy_device *phydev, int pair)
++static int at803x_cdt_start(struct phy_device *phydev,
++ u32 cdt_start)
+ {
+- u16 cdt;
+-
+- /* qca8081 takes the different bit 15 to enable CDT test */
+- if (phydev->drv->phy_id == QCA8081_PHY_ID)
+- cdt = QCA808X_CDT_ENABLE_TEST |
+- QCA808X_CDT_LENGTH_UNIT |
+- QCA808X_CDT_INTER_CHECK_DIS;
+- else
+- cdt = FIELD_PREP(AT803X_CDT_MDI_PAIR_MASK, pair) |
+- AT803X_CDT_ENABLE_TEST;
+-
+- return phy_write(phydev, AT803X_CDT, cdt);
++ return phy_write(phydev, AT803X_CDT, cdt_start);
+ }
+
+-static int at803x_cdt_wait_for_completion(struct phy_device *phydev)
++static int at803x_cdt_wait_for_completion(struct phy_device *phydev,
++ u32 cdt_en)
+ {
+ int val, ret;
+- u16 cdt_en;
+-
+- if (phydev->drv->phy_id == QCA8081_PHY_ID)
+- cdt_en = QCA808X_CDT_ENABLE_TEST;
+- else
+- cdt_en = AT803X_CDT_ENABLE_TEST;
+
+ /* One test run takes about 25ms */
+ ret = phy_read_poll_timeout(phydev, AT803X_CDT, val,
+@@ -1266,11 +1251,13 @@ static int at803x_cable_test_one_pair(st
+ };
+ int ret, val;
+
+- ret = at803x_cdt_start(phydev, pair);
++ val = FIELD_PREP(AT803X_CDT_MDI_PAIR_MASK, pair) |
++ AT803X_CDT_ENABLE_TEST;
++ ret = at803x_cdt_start(phydev, val);
+ if (ret)
+ return ret;
+
+- ret = at803x_cdt_wait_for_completion(phydev);
++ ret = at803x_cdt_wait_for_completion(phydev, AT803X_CDT_ENABLE_TEST);
+ if (ret)
+ return ret;
+
+@@ -1292,19 +1279,11 @@ static int at803x_cable_test_one_pair(st
+ }
+
+ static int at803x_cable_test_get_status(struct phy_device *phydev,
+- bool *finished)
++ bool *finished, unsigned long pair_mask)
+ {
+- unsigned long pair_mask;
+ int retries = 20;
+ int pair, ret;
+
+- if (phydev->phy_id == ATH9331_PHY_ID ||
+- phydev->phy_id == ATH8032_PHY_ID ||
+- phydev->phy_id == QCA9561_PHY_ID)
+- pair_mask = 0x3;
+- else
+- pair_mask = 0xf;
+-
+ *finished = false;
+
+ /* According to the datasheet the CDT can be performed when
+@@ -1331,7 +1310,7 @@ static int at803x_cable_test_get_status(
+ return 0;
+ }
+
+-static int at803x_cable_test_start(struct phy_device *phydev)
++static void at803x_cable_test_autoneg(struct phy_device *phydev)
+ {
+ /* Enable auto-negotiation, but advertise no capabilities, no link
+ * will be established. A restart of the auto-negotiation is not
+@@ -1339,11 +1318,11 @@ static int at803x_cable_test_start(struc
+ */
+ phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
+ phy_write(phydev, MII_ADVERTISE, ADVERTISE_CSMA);
+- if (phydev->phy_id != ATH9331_PHY_ID &&
+- phydev->phy_id != ATH8032_PHY_ID &&
+- phydev->phy_id != QCA9561_PHY_ID)
+- phy_write(phydev, MII_CTRL1000, 0);
++}
+
++static int at803x_cable_test_start(struct phy_device *phydev)
++{
++ at803x_cable_test_autoneg(phydev);
+ /* we do all the (time consuming) work later */
+ return 0;
+ }
+@@ -1618,6 +1597,29 @@ static int at8031_config_intr(struct phy
+ return at803x_config_intr(phydev);
+ }
+
++/* AR8031 and AR8035 share the same cable test get status reg */
++static int at8031_cable_test_get_status(struct phy_device *phydev,
++ bool *finished)
++{
++ return at803x_cable_test_get_status(phydev, finished, 0xf);
++}
++
++/* AR8031 and AR8035 share the same cable test start logic */
++static int at8031_cable_test_start(struct phy_device *phydev)
++{
++ at803x_cable_test_autoneg(phydev);
++ phy_write(phydev, MII_CTRL1000, 0);
++ /* we do all the (time consuming) work later */
++ return 0;
++}
++
++/* AR8032, AR9331 and QCA9561 share the same cable test get status reg */
++static int at8032_cable_test_get_status(struct phy_device *phydev,
++ bool *finished)
++{
++ return at803x_cable_test_get_status(phydev, finished, 0x3);
++}
++
+ static int at8035_parse_dt(struct phy_device *phydev)
+ {
+ struct at803x_priv *priv = phydev->priv;
+@@ -2041,11 +2043,14 @@ static int qca808x_cable_test_get_status
+
+ *finished = false;
+
+- ret = at803x_cdt_start(phydev, 0);
++ val = QCA808X_CDT_ENABLE_TEST |
++ QCA808X_CDT_LENGTH_UNIT |
++ QCA808X_CDT_INTER_CHECK_DIS;
++ ret = at803x_cdt_start(phydev, val);
+ if (ret)
+ return ret;
+
+- ret = at803x_cdt_wait_for_completion(phydev);
++ ret = at803x_cdt_wait_for_completion(phydev, QCA808X_CDT_ENABLE_TEST);
+ if (ret)
+ return ret;
+
+@@ -2143,8 +2148,8 @@ static struct phy_driver at803x_driver[]
+ .handle_interrupt = at803x_handle_interrupt,
+ .get_tunable = at803x_get_tunable,
+ .set_tunable = at803x_set_tunable,
+- .cable_test_start = at803x_cable_test_start,
+- .cable_test_get_status = at803x_cable_test_get_status,
++ .cable_test_start = at8031_cable_test_start,
++ .cable_test_get_status = at8031_cable_test_get_status,
+ }, {
+ /* Qualcomm Atheros AR8030 */
+ .phy_id = ATH8030_PHY_ID,
+@@ -2181,8 +2186,8 @@ static struct phy_driver at803x_driver[]
+ .handle_interrupt = at803x_handle_interrupt,
+ .get_tunable = at803x_get_tunable,
+ .set_tunable = at803x_set_tunable,
+- .cable_test_start = at803x_cable_test_start,
+- .cable_test_get_status = at803x_cable_test_get_status,
++ .cable_test_start = at8031_cable_test_start,
++ .cable_test_get_status = at8031_cable_test_get_status,
+ }, {
+ /* Qualcomm Atheros AR8032 */
+ PHY_ID_MATCH_EXACT(ATH8032_PHY_ID),
+@@ -2197,7 +2202,7 @@ static struct phy_driver at803x_driver[]
+ .config_intr = at803x_config_intr,
+ .handle_interrupt = at803x_handle_interrupt,
+ .cable_test_start = at803x_cable_test_start,
+- .cable_test_get_status = at803x_cable_test_get_status,
++ .cable_test_get_status = at8032_cable_test_get_status,
+ }, {
+ /* ATHEROS AR9331 */
+ PHY_ID_MATCH_EXACT(ATH9331_PHY_ID),
+@@ -2210,7 +2215,7 @@ static struct phy_driver at803x_driver[]
+ .config_intr = at803x_config_intr,
+ .handle_interrupt = at803x_handle_interrupt,
+ .cable_test_start = at803x_cable_test_start,
+- .cable_test_get_status = at803x_cable_test_get_status,
++ .cable_test_get_status = at8032_cable_test_get_status,
+ .read_status = at803x_read_status,
+ .soft_reset = genphy_soft_reset,
+ .config_aneg = at803x_config_aneg,
+@@ -2226,7 +2231,7 @@ static struct phy_driver at803x_driver[]
+ .config_intr = at803x_config_intr,
+ .handle_interrupt = at803x_handle_interrupt,
+ .cable_test_start = at803x_cable_test_start,
+- .cable_test_get_status = at803x_cable_test_get_status,
++ .cable_test_get_status = at8032_cable_test_get_status,
+ .read_status = at803x_read_status,
+ .soft_reset = genphy_soft_reset,
+ .config_aneg = at803x_config_aneg,
diff --git a/target/linux/generic/backport-6.6/708-v6.8-01-net-phy-at803x-move-specific-qca808x-config_aneg-to-.patch b/target/linux/generic/backport-6.6/708-v6.8-01-net-phy-at803x-move-specific-qca808x-config_aneg-to-.patch
new file mode 100644
index 0000000000..0bb3673c02
--- /dev/null
+++ b/target/linux/generic/backport-6.6/708-v6.8-01-net-phy-at803x-move-specific-qca808x-config_aneg-to-.patch
@@ -0,0 +1,116 @@
+From 8e732f1c6f2dc5e18f766d0f1b11df9db2dd044a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 14 Dec 2023 01:44:31 +0100
+Subject: [PATCH 1/2] net: phy: at803x: move specific qca808x config_aneg to
+ dedicated function
+
+Move specific qca808x config_aneg to dedicated function to permit easier
+split of qca808x portion from at803x driver.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 66 ++++++++++++++++++++++++----------------
+ 1 file changed, 40 insertions(+), 26 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -1045,9 +1045,8 @@ static int at803x_config_mdix(struct phy
+ FIELD_PREP(AT803X_SFC_MDI_CROSSOVER_MODE_M, val));
+ }
+
+-static int at803x_config_aneg(struct phy_device *phydev)
++static int at803x_prepare_config_aneg(struct phy_device *phydev)
+ {
+- struct at803x_priv *priv = phydev->priv;
+ int ret;
+
+ ret = at803x_config_mdix(phydev, phydev->mdix_ctrl);
+@@ -1064,33 +1063,22 @@ static int at803x_config_aneg(struct phy
+ return ret;
+ }
+
+- if (priv->is_1000basex)
+- return genphy_c37_config_aneg(phydev);
+-
+- /* Do not restart auto-negotiation by setting ret to 0 defautly,
+- * when calling __genphy_config_aneg later.
+- */
+- ret = 0;
+-
+- if (phydev->drv->phy_id == QCA8081_PHY_ID) {
+- int phy_ctrl = 0;
++ return 0;
++}
+
+- /* The reg MII_BMCR also needs to be configured for force mode, the
+- * genphy_config_aneg is also needed.
+- */
+- if (phydev->autoneg == AUTONEG_DISABLE)
+- genphy_c45_pma_setup_forced(phydev);
++static int at803x_config_aneg(struct phy_device *phydev)
++{
++ struct at803x_priv *priv = phydev->priv;
++ int ret;
+
+- if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->advertising))
+- phy_ctrl = MDIO_AN_10GBT_CTRL_ADV2_5G;
++ ret = at803x_prepare_config_aneg(phydev);
++ if (ret)
++ return ret;
+
+- ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
+- MDIO_AN_10GBT_CTRL_ADV2_5G, phy_ctrl);
+- if (ret < 0)
+- return ret;
+- }
++ if (priv->is_1000basex)
++ return genphy_c37_config_aneg(phydev);
+
+- return __genphy_config_aneg(phydev, ret);
++ return genphy_config_aneg(phydev);
+ }
+
+ static int at803x_get_downshift(struct phy_device *phydev, u8 *d)
+@@ -2118,6 +2106,32 @@ static int qca808x_get_features(struct p
+ return 0;
+ }
+
++static int qca808x_config_aneg(struct phy_device *phydev)
++{
++ int phy_ctrl = 0;
++ int ret;
++
++ ret = at803x_prepare_config_aneg(phydev);
++ if (ret)
++ return ret;
++
++ /* The reg MII_BMCR also needs to be configured for force mode, the
++ * genphy_config_aneg is also needed.
++ */
++ if (phydev->autoneg == AUTONEG_DISABLE)
++ genphy_c45_pma_setup_forced(phydev);
++
++ if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->advertising))
++ phy_ctrl = MDIO_AN_10GBT_CTRL_ADV2_5G;
++
++ ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
++ MDIO_AN_10GBT_CTRL_ADV2_5G, phy_ctrl);
++ if (ret < 0)
++ return ret;
++
++ return __genphy_config_aneg(phydev, ret);
++}
++
+ static void qca808x_link_change_notify(struct phy_device *phydev)
+ {
+ /* Assert interface sgmii fifo on link down, deassert it on link up,
+@@ -2295,7 +2309,7 @@ static struct phy_driver at803x_driver[]
+ .set_wol = at803x_set_wol,
+ .get_wol = at803x_get_wol,
+ .get_features = qca808x_get_features,
+- .config_aneg = at803x_config_aneg,
++ .config_aneg = qca808x_config_aneg,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .read_status = qca808x_read_status,
diff --git a/target/linux/generic/backport-6.6/708-v6.8-02-net-phy-at803x-make-read-specific-status-function-mo.patch b/target/linux/generic/backport-6.6/708-v6.8-02-net-phy-at803x-make-read-specific-status-function-mo.patch
new file mode 100644
index 0000000000..54ca3bdfc8
--- /dev/null
+++ b/target/linux/generic/backport-6.6/708-v6.8-02-net-phy-at803x-make-read-specific-status-function-mo.patch
@@ -0,0 +1,97 @@
+From 38eb804e8458ba181a03a0498ce4bf84eebd1931 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 14 Dec 2023 01:44:32 +0100
+Subject: [PATCH 2/2] net: phy: at803x: make read specific status function more
+ generic
+
+Rework read specific status function to be more generic. The function
+apply different speed mask based on the PHY ID. Make it more generic by
+adding an additional arg to pass the specific speed (ss) mask and use
+the provided mask to parse the speed value.
+
+This is needed to permit an easier deatch of qca808x code from the
+at803x driver.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 26 ++++++++++++++++++--------
+ 1 file changed, 18 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -301,6 +301,11 @@ static struct at803x_hw_stat qca83xx_hw_
+ { "eee_wake_errors", 0x16, GENMASK(15, 0), MMD},
+ };
+
++struct at803x_ss_mask {
++ u16 speed_mask;
++ u8 speed_shift;
++};
++
+ struct at803x_priv {
+ int flags;
+ u16 clk_25m_reg;
+@@ -921,7 +926,8 @@ static void at803x_link_change_notify(st
+ }
+ }
+
+-static int at803x_read_specific_status(struct phy_device *phydev)
++static int at803x_read_specific_status(struct phy_device *phydev,
++ struct at803x_ss_mask ss_mask)
+ {
+ int ss;
+
+@@ -940,11 +946,8 @@ static int at803x_read_specific_status(s
+ if (sfc < 0)
+ return sfc;
+
+- /* qca8081 takes the different bits for speed value from at803x */
+- if (phydev->drv->phy_id == QCA8081_PHY_ID)
+- speed = FIELD_GET(QCA808X_SS_SPEED_MASK, ss);
+- else
+- speed = FIELD_GET(AT803X_SS_SPEED_MASK, ss);
++ speed = ss & ss_mask.speed_mask;
++ speed >>= ss_mask.speed_shift;
+
+ switch (speed) {
+ case AT803X_SS_SPEED_10:
+@@ -989,6 +992,7 @@ static int at803x_read_specific_status(s
+ static int at803x_read_status(struct phy_device *phydev)
+ {
+ struct at803x_priv *priv = phydev->priv;
++ struct at803x_ss_mask ss_mask = { 0 };
+ int err, old_link = phydev->link;
+
+ if (priv->is_1000basex)
+@@ -1012,7 +1016,9 @@ static int at803x_read_status(struct phy
+ if (err < 0)
+ return err;
+
+- err = at803x_read_specific_status(phydev);
++ ss_mask.speed_mask = AT803X_SS_SPEED_MASK;
++ ss_mask.speed_shift = __bf_shf(AT803X_SS_SPEED_MASK);
++ err = at803x_read_specific_status(phydev, ss_mask);
+ if (err < 0)
+ return err;
+
+@@ -1869,6 +1875,7 @@ static int qca808x_config_init(struct ph
+
+ static int qca808x_read_status(struct phy_device *phydev)
+ {
++ struct at803x_ss_mask ss_mask = { 0 };
+ int ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
+@@ -1882,7 +1889,10 @@ static int qca808x_read_status(struct ph
+ if (ret)
+ return ret;
+
+- ret = at803x_read_specific_status(phydev);
++ /* qca8081 takes the different bits for speed value from at803x */
++ ss_mask.speed_mask = QCA808X_SS_SPEED_MASK;
++ ss_mask.speed_shift = __bf_shf(QCA808X_SS_SPEED_MASK);
++ ret = at803x_read_specific_status(phydev, ss_mask);
+ if (ret < 0)
+ return ret;
+
diff --git a/target/linux/generic/backport-6.6/709-v6.8-01-net-phy-at803x-remove-extra-space-after-cast.patch b/target/linux/generic/backport-6.6/709-v6.8-01-net-phy-at803x-remove-extra-space-after-cast.patch
new file mode 100644
index 0000000000..8097a33e49
--- /dev/null
+++ b/target/linux/generic/backport-6.6/709-v6.8-01-net-phy-at803x-remove-extra-space-after-cast.patch
@@ -0,0 +1,27 @@
+From fc9d7264ddc32eaa647d6bfcdc25cdf9f786fde0 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 18 Dec 2023 00:27:39 +0100
+Subject: [PATCH 1/2] net: phy: at803x: remove extra space after cast
+
+Remove extra space after cast as reported by checkpatch to keep code
+clean.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/20231217232739.27065-1-ansuelsmth@gmail.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/at803x.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -462,7 +462,7 @@ static int at803x_set_wol(struct phy_dev
+ if (!ndev)
+ return -ENODEV;
+
+- mac = (const u8 *) ndev->dev_addr;
++ mac = (const u8 *)ndev->dev_addr;
+
+ if (!is_valid_ether_addr(mac))
+ return -EINVAL;
diff --git a/target/linux/generic/backport-6.6/709-v6.8-02-net-phy-at803x-replace-msleep-1-with-usleep_range.patch b/target/linux/generic/backport-6.6/709-v6.8-02-net-phy-at803x-replace-msleep-1-with-usleep_range.patch
new file mode 100644
index 0000000000..b8d4591087
--- /dev/null
+++ b/target/linux/generic/backport-6.6/709-v6.8-02-net-phy-at803x-replace-msleep-1-with-usleep_range.patch
@@ -0,0 +1,38 @@
+From 3ab5720881a924fb6405d9e6a3b09f1026467c47 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 18 Dec 2023 00:25:08 +0100
+Subject: [PATCH 2/2] net: phy: at803x: replace msleep(1) with usleep_range
+
+Replace msleep(1) with usleep_range as suggested by timers-howto guide.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/20231217232508.26470-1-ansuelsmth@gmail.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/at803x.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -916,9 +916,9 @@ static void at803x_link_change_notify(st
+ at803x_context_save(phydev, &context);
+
+ phy_device_reset(phydev, 1);
+- msleep(1);
++ usleep_range(1000, 2000);
+ phy_device_reset(phydev, 0);
+- msleep(1);
++ usleep_range(1000, 2000);
+
+ at803x_context_restore(phydev, &context);
+
+@@ -1733,7 +1733,7 @@ static int qca83xx_resume(struct phy_dev
+ if (ret)
+ return ret;
+
+- msleep(1);
++ usleep_range(1000, 2000);
+
+ return 0;
+ }
diff --git a/target/linux/generic/backport-6.6/710-v6.8-net-phy-at803x-better-align-function-varibles-to-ope.patch b/target/linux/generic/backport-6.6/710-v6.8-net-phy-at803x-better-align-function-varibles-to-ope.patch
new file mode 100644
index 0000000000..776b7bb43f
--- /dev/null
+++ b/target/linux/generic/backport-6.6/710-v6.8-net-phy-at803x-better-align-function-varibles-to-ope.patch
@@ -0,0 +1,152 @@
+From 7961ef1fa10ec35ad6923fb5751877116e4b035b Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 19 Dec 2023 21:21:24 +0100
+Subject: [PATCH] net: phy: at803x: better align function varibles to open
+ parenthesis
+
+Better align function variables to open parenthesis as suggested by
+checkpatch script for qca808x function to make code cleaner.
+
+For cable_test_get_status function some additional rework was needed to
+handle too long functions.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 67 ++++++++++++++++++++++------------------
+ 1 file changed, 37 insertions(+), 30 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -1781,27 +1781,27 @@ static int qca808x_phy_fast_retrain_conf
+ return ret;
+
+ phy_write_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_TOP_OPTION1,
+- QCA808X_TOP_OPTION1_DATA);
++ QCA808X_TOP_OPTION1_DATA);
+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB,
+- QCA808X_MSE_THRESHOLD_20DB_VALUE);
++ QCA808X_MSE_THRESHOLD_20DB_VALUE);
+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB,
+- QCA808X_MSE_THRESHOLD_17DB_VALUE);
++ QCA808X_MSE_THRESHOLD_17DB_VALUE);
+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB,
+- QCA808X_MSE_THRESHOLD_27DB_VALUE);
++ QCA808X_MSE_THRESHOLD_27DB_VALUE);
+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB,
+- QCA808X_MSE_THRESHOLD_28DB_VALUE);
++ QCA808X_MSE_THRESHOLD_28DB_VALUE);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_1,
+- QCA808X_MMD3_DEBUG_1_VALUE);
++ QCA808X_MMD3_DEBUG_1_VALUE);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_4,
+- QCA808X_MMD3_DEBUG_4_VALUE);
++ QCA808X_MMD3_DEBUG_4_VALUE);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_5,
+- QCA808X_MMD3_DEBUG_5_VALUE);
++ QCA808X_MMD3_DEBUG_5_VALUE);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_3,
+- QCA808X_MMD3_DEBUG_3_VALUE);
++ QCA808X_MMD3_DEBUG_3_VALUE);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_6,
+- QCA808X_MMD3_DEBUG_6_VALUE);
++ QCA808X_MMD3_DEBUG_6_VALUE);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_2,
+- QCA808X_MMD3_DEBUG_2_VALUE);
++ QCA808X_MMD3_DEBUG_2_VALUE);
+
+ return 0;
+ }
+@@ -1838,13 +1838,14 @@ static int qca808x_config_init(struct ph
+
+ /* Active adc&vga on 802.3az for the link 1000M and 100M */
+ ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_ADDR_CLD_CTRL7,
+- QCA808X_8023AZ_AFE_CTRL_MASK, QCA808X_8023AZ_AFE_EN);
++ QCA808X_8023AZ_AFE_CTRL_MASK, QCA808X_8023AZ_AFE_EN);
+ if (ret)
+ return ret;
+
+ /* Adjust the threshold on 802.3az for the link 1000M */
+ ret = phy_write_mmd(phydev, MDIO_MMD_PCS,
+- QCA808X_PHY_MMD3_AZ_TRAINING_CTRL, QCA808X_MMD3_AZ_TRAINING_VAL);
++ QCA808X_PHY_MMD3_AZ_TRAINING_CTRL,
++ QCA808X_MMD3_AZ_TRAINING_VAL);
+ if (ret)
+ return ret;
+
+@@ -1870,7 +1871,8 @@ static int qca808x_config_init(struct ph
+
+ /* Configure adc threshold as 100mv for the link 10M */
+ return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_ADC_THRESHOLD,
+- QCA808X_ADC_THRESHOLD_MASK, QCA808X_ADC_THRESHOLD_100MV);
++ QCA808X_ADC_THRESHOLD_MASK,
++ QCA808X_ADC_THRESHOLD_100MV);
+ }
+
+ static int qca808x_read_status(struct phy_device *phydev)
+@@ -1883,7 +1885,7 @@ static int qca808x_read_status(struct ph
+ return ret;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->lp_advertising,
+- ret & MDIO_AN_10GBT_STAT_LP2_5G);
++ ret & MDIO_AN_10GBT_STAT_LP2_5G);
+
+ ret = genphy_read_status(phydev);
+ if (ret)
+@@ -1913,7 +1915,7 @@ static int qca808x_read_status(struct ph
+ */
+ if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
+ if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR ||
+- qca808x_is_prefer_master(phydev)) {
++ qca808x_is_prefer_master(phydev)) {
+ qca808x_phy_ms_seed_enable(phydev, false);
+ } else {
+ qca808x_phy_ms_seed_enable(phydev, true);
+@@ -2070,18 +2072,22 @@ static int qca808x_cable_test_get_status
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_D,
+ qca808x_cable_test_result_trans(pair_d));
+
+- if (qca808x_cdt_fault_length_valid(pair_a))
+- ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A,
+- qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A));
+- if (qca808x_cdt_fault_length_valid(pair_b))
+- ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_B,
+- qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_B));
+- if (qca808x_cdt_fault_length_valid(pair_c))
+- ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_C,
+- qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_C));
+- if (qca808x_cdt_fault_length_valid(pair_d))
+- ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_D,
+- qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_D));
++ if (qca808x_cdt_fault_length_valid(pair_a)) {
++ val = qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A);
++ ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A, val);
++ }
++ if (qca808x_cdt_fault_length_valid(pair_b)) {
++ val = qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_B);
++ ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_B, val);
++ }
++ if (qca808x_cdt_fault_length_valid(pair_c)) {
++ val = qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_C);
++ ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_C, val);
++ }
++ if (qca808x_cdt_fault_length_valid(pair_d)) {
++ val = qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_D);
++ ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_D, val);
++ }
+
+ *finished = true;
+
+@@ -2148,8 +2154,9 @@ static void qca808x_link_change_notify(s
+ * the interface device address is always phy address added by 1.
+ */
+ mdiobus_c45_modify_changed(phydev->mdio.bus, phydev->mdio.addr + 1,
+- MDIO_MMD_PMAPMD, QCA8081_PHY_SERDES_MMD1_FIFO_CTRL,
+- QCA8081_PHY_FIFO_RSTN, phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
++ MDIO_MMD_PMAPMD, QCA8081_PHY_SERDES_MMD1_FIFO_CTRL,
++ QCA8081_PHY_FIFO_RSTN,
++ phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
+ }
+
+ static struct phy_driver at803x_driver[] = {
diff --git a/target/linux/generic/backport-6.6/711-v6.8-01-net-phy-at803x-generalize-cdt-fault-length-function.patch b/target/linux/generic/backport-6.6/711-v6.8-01-net-phy-at803x-generalize-cdt-fault-length-function.patch
new file mode 100644
index 0000000000..d7196da46e
--- /dev/null
+++ b/target/linux/generic/backport-6.6/711-v6.8-01-net-phy-at803x-generalize-cdt-fault-length-function.patch
@@ -0,0 +1,62 @@
+From 22eb276098da820d9440fad22901f9b74ed4d659 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 4 Jan 2024 22:30:38 +0100
+Subject: [PATCH 1/4] net: phy: at803x: generalize cdt fault length function
+
+Generalize cable test fault length function since they all base on the
+same magic values (already reverse engineered to understand the meaning
+of it) to have consistenct values on every PHY.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 13 ++++++-------
+ 1 file changed, 6 insertions(+), 7 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -1192,10 +1192,8 @@ static bool at803x_cdt_fault_length_vali
+ return false;
+ }
+
+-static int at803x_cdt_fault_length(u16 status)
++static int at803x_cdt_fault_length(int dt)
+ {
+- int dt;
+-
+ /* According to the datasheet the distance to the fault is
+ * DELTA_TIME * 0.824 meters.
+ *
+@@ -1211,8 +1209,6 @@ static int at803x_cdt_fault_length(u16 s
+ * With a VF of 0.69 we get the factor 0.824 mentioned in the
+ * datasheet.
+ */
+- dt = FIELD_GET(AT803X_CDT_STATUS_DELTA_TIME_MASK, status);
+-
+ return (dt * 824) / 10;
+ }
+
+@@ -1265,9 +1261,11 @@ static int at803x_cable_test_one_pair(st
+ ethnl_cable_test_result(phydev, ethtool_pair[pair],
+ at803x_cable_test_result_trans(val));
+
+- if (at803x_cdt_fault_length_valid(val))
++ if (at803x_cdt_fault_length_valid(val)) {
++ val = FIELD_GET(AT803X_CDT_STATUS_DELTA_TIME_MASK, val);
+ ethnl_cable_test_fault_length(phydev, ethtool_pair[pair],
+ at803x_cdt_fault_length(val));
++ }
+
+ return 1;
+ }
+@@ -1992,7 +1990,8 @@ static int qca808x_cdt_fault_length(stru
+ if (val < 0)
+ return val;
+
+- return (FIELD_GET(QCA808X_CDT_DIAG_LENGTH, val) * 824) / 10;
++ val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH, val);
++ return at803x_cdt_fault_length(val);
+ }
+
+ static int qca808x_cable_test_start(struct phy_device *phydev)
diff --git a/target/linux/generic/backport-6.6/711-v6.8-02-net-phy-at803x-refactor-qca808x-cable-test-get-statu.patch b/target/linux/generic/backport-6.6/711-v6.8-02-net-phy-at803x-refactor-qca808x-cable-test-get-statu.patch
new file mode 100644
index 0000000000..5d38fe7e91
--- /dev/null
+++ b/target/linux/generic/backport-6.6/711-v6.8-02-net-phy-at803x-refactor-qca808x-cable-test-get-statu.patch
@@ -0,0 +1,118 @@
+From e0e9ada1df6133513249861c1d91c1dbefd9383b Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 4 Jan 2024 22:30:39 +0100
+Subject: [PATCH 2/4] net: phy: at803x: refactor qca808x cable test get status
+ function
+
+Refactor qca808x cable test get status function to remove code
+duplication and clean things up.
+
+The same logic is applied to each pair hence it can be generalized and
+moved to a common function.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 80 ++++++++++++++++++++++++----------------
+ 1 file changed, 49 insertions(+), 31 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -2035,10 +2035,43 @@ static int qca808x_cable_test_start(stru
+ return 0;
+ }
+
++static int qca808x_cable_test_get_pair_status(struct phy_device *phydev, u8 pair,
++ u16 status)
++{
++ u16 pair_code;
++ int length;
++
++ switch (pair) {
++ case ETHTOOL_A_CABLE_PAIR_A:
++ pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, status);
++ break;
++ case ETHTOOL_A_CABLE_PAIR_B:
++ pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, status);
++ break;
++ case ETHTOOL_A_CABLE_PAIR_C:
++ pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, status);
++ break;
++ case ETHTOOL_A_CABLE_PAIR_D:
++ pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, status);
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ ethnl_cable_test_result(phydev, pair,
++ qca808x_cable_test_result_trans(pair_code));
++
++ if (qca808x_cdt_fault_length_valid(pair_code)) {
++ length = qca808x_cdt_fault_length(phydev, pair);
++ ethnl_cable_test_fault_length(phydev, pair, length);
++ }
++
++ return 0;
++}
++
+ static int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished)
+ {
+ int ret, val;
+- int pair_a, pair_b, pair_c, pair_d;
+
+ *finished = false;
+
+@@ -2057,36 +2090,21 @@ static int qca808x_cable_test_get_status
+ if (val < 0)
+ return val;
+
+- pair_a = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, val);
+- pair_b = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, val);
+- pair_c = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, val);
+- pair_d = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, val);
+-
+- ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
+- qca808x_cable_test_result_trans(pair_a));
+- ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_B,
+- qca808x_cable_test_result_trans(pair_b));
+- ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_C,
+- qca808x_cable_test_result_trans(pair_c));
+- ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_D,
+- qca808x_cable_test_result_trans(pair_d));
+-
+- if (qca808x_cdt_fault_length_valid(pair_a)) {
+- val = qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A);
+- ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A, val);
+- }
+- if (qca808x_cdt_fault_length_valid(pair_b)) {
+- val = qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_B);
+- ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_B, val);
+- }
+- if (qca808x_cdt_fault_length_valid(pair_c)) {
+- val = qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_C);
+- ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_C, val);
+- }
+- if (qca808x_cdt_fault_length_valid(pair_d)) {
+- val = qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_D);
+- ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_D, val);
+- }
++ ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_A, val);
++ if (ret)
++ return ret;
++
++ ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_B, val);
++ if (ret)
++ return ret;
++
++ ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_C, val);
++ if (ret)
++ return ret;
++
++ ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_D, val);
++ if (ret)
++ return ret;
+
+ *finished = true;
+
diff --git a/target/linux/generic/backport-6.6/711-v6.8-03-net-phy-at803x-add-support-for-cdt-cross-short-test-.patch b/target/linux/generic/backport-6.6/711-v6.8-03-net-phy-at803x-add-support-for-cdt-cross-short-test-.patch
new file mode 100644
index 0000000000..d5793860a9
--- /dev/null
+++ b/target/linux/generic/backport-6.6/711-v6.8-03-net-phy-at803x-add-support-for-cdt-cross-short-test-.patch
@@ -0,0 +1,182 @@
+From ea73e5ea442ee2aade67b1fb1233ccb3cbea2ceb Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 4 Jan 2024 22:30:40 +0100
+Subject: [PATCH 3/4] net: phy: at803x: add support for cdt cross short test
+ for qca808x
+
+QCA808x PHY Family supports Cable Diagnostic Test also for Cross Pair
+Short.
+
+Add all the define to make enable and support these additional tests.
+
+Cross Short test was previously disabled by default, this is now changed
+and enabled by default. In this mode, the mask changed a bit and length
+is shifted based on the fault condition.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 86 ++++++++++++++++++++++++++++++++--------
+ 1 file changed, 69 insertions(+), 17 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -254,6 +254,7 @@
+
+ #define QCA808X_CDT_ENABLE_TEST BIT(15)
+ #define QCA808X_CDT_INTER_CHECK_DIS BIT(13)
++#define QCA808X_CDT_STATUS BIT(11)
+ #define QCA808X_CDT_LENGTH_UNIT BIT(10)
+
+ #define QCA808X_MMD3_CDT_STATUS 0x8064
+@@ -261,16 +262,44 @@
+ #define QCA808X_MMD3_CDT_DIAG_PAIR_B 0x8066
+ #define QCA808X_MMD3_CDT_DIAG_PAIR_C 0x8067
+ #define QCA808X_MMD3_CDT_DIAG_PAIR_D 0x8068
+-#define QCA808X_CDT_DIAG_LENGTH GENMASK(7, 0)
++#define QCA808X_CDT_DIAG_LENGTH_SAME_SHORT GENMASK(15, 8)
++#define QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT GENMASK(7, 0)
+
+ #define QCA808X_CDT_CODE_PAIR_A GENMASK(15, 12)
+ #define QCA808X_CDT_CODE_PAIR_B GENMASK(11, 8)
+ #define QCA808X_CDT_CODE_PAIR_C GENMASK(7, 4)
+ #define QCA808X_CDT_CODE_PAIR_D GENMASK(3, 0)
+-#define QCA808X_CDT_STATUS_STAT_FAIL 0
+-#define QCA808X_CDT_STATUS_STAT_NORMAL 1
+-#define QCA808X_CDT_STATUS_STAT_OPEN 2
+-#define QCA808X_CDT_STATUS_STAT_SHORT 3
++
++#define QCA808X_CDT_STATUS_STAT_TYPE GENMASK(1, 0)
++#define QCA808X_CDT_STATUS_STAT_FAIL FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 0)
++#define QCA808X_CDT_STATUS_STAT_NORMAL FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 1)
++#define QCA808X_CDT_STATUS_STAT_SAME_OPEN FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 2)
++#define QCA808X_CDT_STATUS_STAT_SAME_SHORT FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 3)
++
++#define QCA808X_CDT_STATUS_STAT_MDI GENMASK(3, 2)
++#define QCA808X_CDT_STATUS_STAT_MDI1 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 1)
++#define QCA808X_CDT_STATUS_STAT_MDI2 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 2)
++#define QCA808X_CDT_STATUS_STAT_MDI3 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 3)
++
++/* NORMAL are MDI with type set to 0 */
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI1
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++ QCA808X_CDT_STATUS_STAT_MDI1)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++ QCA808X_CDT_STATUS_STAT_MDI1)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI2
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++ QCA808X_CDT_STATUS_STAT_MDI2)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++ QCA808X_CDT_STATUS_STAT_MDI2)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI3
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++ QCA808X_CDT_STATUS_STAT_MDI3)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++ QCA808X_CDT_STATUS_STAT_MDI3)
++
++/* Added for reference of existence but should be handled by wait_for_completion already */
++#define QCA808X_CDT_STATUS_STAT_BUSY (BIT(1) | BIT(3))
+
+ /* QCA808X 1G chip type */
+ #define QCA808X_PHY_MMD7_CHIP_TYPE 0x901d
+@@ -1941,8 +1970,17 @@ static int qca808x_soft_reset(struct phy
+ static bool qca808x_cdt_fault_length_valid(int cdt_code)
+ {
+ switch (cdt_code) {
+- case QCA808X_CDT_STATUS_STAT_SHORT:
+- case QCA808X_CDT_STATUS_STAT_OPEN:
++ case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
+ return true;
+ default:
+ return false;
+@@ -1954,17 +1992,28 @@ static int qca808x_cable_test_result_tra
+ switch (cdt_code) {
+ case QCA808X_CDT_STATUS_STAT_NORMAL:
+ return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+- case QCA808X_CDT_STATUS_STAT_SHORT:
++ case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
+ return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+- case QCA808X_CDT_STATUS_STAT_OPEN:
++ case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
+ return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
++ return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
+ case QCA808X_CDT_STATUS_STAT_FAIL:
+ default:
+ return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+ }
+ }
+
+-static int qca808x_cdt_fault_length(struct phy_device *phydev, int pair)
++static int qca808x_cdt_fault_length(struct phy_device *phydev, int pair,
++ int result)
+ {
+ int val;
+ u32 cdt_length_reg = 0;
+@@ -1990,7 +2039,11 @@ static int qca808x_cdt_fault_length(stru
+ if (val < 0)
+ return val;
+
+- val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH, val);
++ if (result == ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT)
++ val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_SAME_SHORT, val);
++ else
++ val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT, val);
++
+ return at803x_cdt_fault_length(val);
+ }
+
+@@ -2038,8 +2091,8 @@ static int qca808x_cable_test_start(stru
+ static int qca808x_cable_test_get_pair_status(struct phy_device *phydev, u8 pair,
+ u16 status)
+ {
++ int length, result;
+ u16 pair_code;
+- int length;
+
+ switch (pair) {
+ case ETHTOOL_A_CABLE_PAIR_A:
+@@ -2058,11 +2111,11 @@ static int qca808x_cable_test_get_pair_s
+ return -EINVAL;
+ }
+
+- ethnl_cable_test_result(phydev, pair,
+- qca808x_cable_test_result_trans(pair_code));
++ result = qca808x_cable_test_result_trans(pair_code);
++ ethnl_cable_test_result(phydev, pair, result);
+
+ if (qca808x_cdt_fault_length_valid(pair_code)) {
+- length = qca808x_cdt_fault_length(phydev, pair);
++ length = qca808x_cdt_fault_length(phydev, pair, result);
+ ethnl_cable_test_fault_length(phydev, pair, length);
+ }
+
+@@ -2076,8 +2129,7 @@ static int qca808x_cable_test_get_status
+ *finished = false;
+
+ val = QCA808X_CDT_ENABLE_TEST |
+- QCA808X_CDT_LENGTH_UNIT |
+- QCA808X_CDT_INTER_CHECK_DIS;
++ QCA808X_CDT_LENGTH_UNIT;
+ ret = at803x_cdt_start(phydev, val);
+ if (ret)
+ return ret;
diff --git a/target/linux/generic/backport-6.6/711-v6.8-04-net-phy-at803x-make-read_status-more-generic.patch b/target/linux/generic/backport-6.6/711-v6.8-04-net-phy-at803x-make-read_status-more-generic.patch
new file mode 100644
index 0000000000..c146e502fe
--- /dev/null
+++ b/target/linux/generic/backport-6.6/711-v6.8-04-net-phy-at803x-make-read_status-more-generic.patch
@@ -0,0 +1,62 @@
+From c34d9452d4e5d98a655d7b625e85466320885416 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 4 Jan 2024 22:30:41 +0100
+Subject: [PATCH 4/4] net: phy: at803x: make read_status more generic
+
+Make read_status more generic in preparation on moving it to shared
+library as other PHY Family Driver will have the exact same
+implementation.
+
+The only specific part was a check for AR8031/33 if 1000basex was used.
+The check is moved to a dedicated function specific for those PHYs.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/at803x.c | 17 ++++++++++++-----
+ 1 file changed, 12 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -1020,13 +1020,9 @@ static int at803x_read_specific_status(s
+
+ static int at803x_read_status(struct phy_device *phydev)
+ {
+- struct at803x_priv *priv = phydev->priv;
+ struct at803x_ss_mask ss_mask = { 0 };
+ int err, old_link = phydev->link;
+
+- if (priv->is_1000basex)
+- return genphy_c37_read_status(phydev);
+-
+ /* Update the link, but return if there was an error */
+ err = genphy_update_link(phydev);
+ if (err)
+@@ -1618,6 +1614,17 @@ static int at8031_config_intr(struct phy
+ return at803x_config_intr(phydev);
+ }
+
++/* AR8031 and AR8033 share the same read status logic */
++static int at8031_read_status(struct phy_device *phydev)
++{
++ struct at803x_priv *priv = phydev->priv;
++
++ if (priv->is_1000basex)
++ return genphy_c37_read_status(phydev);
++
++ return at803x_read_status(phydev);
++}
++
+ /* AR8031 and AR8035 share the same cable test get status reg */
+ static int at8031_cable_test_get_status(struct phy_device *phydev,
+ bool *finished)
+@@ -2281,7 +2288,7 @@ static struct phy_driver at803x_driver[]
+ .read_page = at803x_read_page,
+ .write_page = at803x_write_page,
+ .get_features = at803x_get_features,
+- .read_status = at803x_read_status,
++ .read_status = at8031_read_status,
+ .config_intr = at8031_config_intr,
+ .handle_interrupt = at803x_handle_interrupt,
+ .get_tunable = at803x_get_tunable,
diff --git a/target/linux/generic/backport-6.6/712-v6.9-net-phy-at803x-add-LED-support-for-qca808x.patch b/target/linux/generic/backport-6.6/712-v6.9-net-phy-at803x-add-LED-support-for-qca808x.patch
new file mode 100644
index 0000000000..36675e7588
--- /dev/null
+++ b/target/linux/generic/backport-6.6/712-v6.9-net-phy-at803x-add-LED-support-for-qca808x.patch
@@ -0,0 +1,408 @@
+From 7196062b64ee470b91015f3d2e82d225948258ea Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 25 Jan 2024 21:37:01 +0100
+Subject: [PATCH 5/5] net: phy: at803x: add LED support for qca808x
+
+Add LED support for QCA8081 PHY.
+
+Documentation for this LEDs PHY is very scarce even with NDA access
+to Documentation for OEMs. Only the blink pattern are documented and are
+very confusing most of the time. No documentation is present about
+forcing the LED on/off or to always blink.
+
+Those settings were reversed by poking the regs and trying to find the
+correct bits to trigger these modes. Some bits mode are not clear and
+maybe the documentation option are not 100% correct. For the sake of LED
+support the reversed option are enough to add support for current LED
+APIs.
+
+Supported HW control modes are:
+- tx
+- rx
+- link_10
+- link_100
+- link_1000
+- link_2500
+- half_duplex
+- full_duplex
+
+Also add support for LED polarity set to set LED polarity to active
+high or low. QSDK sets this value to high by default but PHY reset value
+doesn't have this enabled by default.
+
+QSDK also sets 2 additional bits but their usage is not clear, info about
+this is added in the header. It was verified that for correct function
+of the LED if active high is needed, only BIT 6 is needed.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/20240125203702.4552-6-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/at803x.c | 327 +++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 327 insertions(+)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -301,6 +301,87 @@
+ /* Added for reference of existence but should be handled by wait_for_completion already */
+ #define QCA808X_CDT_STATUS_STAT_BUSY (BIT(1) | BIT(3))
+
++#define QCA808X_MMD7_LED_GLOBAL 0x8073
++#define QCA808X_LED_BLINK_1 GENMASK(11, 6)
++#define QCA808X_LED_BLINK_2 GENMASK(5, 0)
++/* Values are the same for both BLINK_1 and BLINK_2 */
++#define QCA808X_LED_BLINK_FREQ_MASK GENMASK(5, 3)
++#define QCA808X_LED_BLINK_FREQ_2HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x0)
++#define QCA808X_LED_BLINK_FREQ_4HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x1)
++#define QCA808X_LED_BLINK_FREQ_8HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x2)
++#define QCA808X_LED_BLINK_FREQ_16HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x3)
++#define QCA808X_LED_BLINK_FREQ_32HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x4)
++#define QCA808X_LED_BLINK_FREQ_64HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x5)
++#define QCA808X_LED_BLINK_FREQ_128HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x6)
++#define QCA808X_LED_BLINK_FREQ_256HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x7)
++#define QCA808X_LED_BLINK_DUTY_MASK GENMASK(2, 0)
++#define QCA808X_LED_BLINK_DUTY_50_50 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x0)
++#define QCA808X_LED_BLINK_DUTY_75_25 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x1)
++#define QCA808X_LED_BLINK_DUTY_25_75 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x2)
++#define QCA808X_LED_BLINK_DUTY_33_67 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x3)
++#define QCA808X_LED_BLINK_DUTY_67_33 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x4)
++#define QCA808X_LED_BLINK_DUTY_17_83 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x5)
++#define QCA808X_LED_BLINK_DUTY_83_17 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x6)
++#define QCA808X_LED_BLINK_DUTY_8_92 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x7)
++
++#define QCA808X_MMD7_LED2_CTRL 0x8074
++#define QCA808X_MMD7_LED2_FORCE_CTRL 0x8075
++#define QCA808X_MMD7_LED1_CTRL 0x8076
++#define QCA808X_MMD7_LED1_FORCE_CTRL 0x8077
++#define QCA808X_MMD7_LED0_CTRL 0x8078
++#define QCA808X_MMD7_LED_CTRL(x) (0x8078 - ((x) * 2))
++
++/* LED hw control pattern is the same for every LED */
++#define QCA808X_LED_PATTERN_MASK GENMASK(15, 0)
++#define QCA808X_LED_SPEED2500_ON BIT(15)
++#define QCA808X_LED_SPEED2500_BLINK BIT(14)
++/* Follow blink trigger even if duplex or speed condition doesn't match */
++#define QCA808X_LED_BLINK_CHECK_BYPASS BIT(13)
++#define QCA808X_LED_FULL_DUPLEX_ON BIT(12)
++#define QCA808X_LED_HALF_DUPLEX_ON BIT(11)
++#define QCA808X_LED_TX_BLINK BIT(10)
++#define QCA808X_LED_RX_BLINK BIT(9)
++#define QCA808X_LED_TX_ON_10MS BIT(8)
++#define QCA808X_LED_RX_ON_10MS BIT(7)
++#define QCA808X_LED_SPEED1000_ON BIT(6)
++#define QCA808X_LED_SPEED100_ON BIT(5)
++#define QCA808X_LED_SPEED10_ON BIT(4)
++#define QCA808X_LED_COLLISION_BLINK BIT(3)
++#define QCA808X_LED_SPEED1000_BLINK BIT(2)
++#define QCA808X_LED_SPEED100_BLINK BIT(1)
++#define QCA808X_LED_SPEED10_BLINK BIT(0)
++
++#define QCA808X_MMD7_LED0_FORCE_CTRL 0x8079
++#define QCA808X_MMD7_LED_FORCE_CTRL(x) (0x8079 - ((x) * 2))
++
++/* LED force ctrl is the same for every LED
++ * No documentation exist for this, not even internal one
++ * with NDA as QCOM gives only info about configuring
++ * hw control pattern rules and doesn't indicate any way
++ * to force the LED to specific mode.
++ * These define comes from reverse and testing and maybe
++ * lack of some info or some info are not entirely correct.
++ * For the basic LED control and hw control these finding
++ * are enough to support LED control in all the required APIs.
++ *
++ * On doing some comparison with implementation with qca807x,
++ * it was found that it's 1:1 equal to it and confirms all the
++ * reverse done. It was also found further specification with the
++ * force mode and the blink modes.
++ */
++#define QCA808X_LED_FORCE_EN BIT(15)
++#define QCA808X_LED_FORCE_MODE_MASK GENMASK(14, 13)
++#define QCA808X_LED_FORCE_BLINK_1 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x3)
++#define QCA808X_LED_FORCE_BLINK_2 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x2)
++#define QCA808X_LED_FORCE_ON FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x1)
++#define QCA808X_LED_FORCE_OFF FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x0)
++
++#define QCA808X_MMD7_LED_POLARITY_CTRL 0x901a
++/* QSDK sets by default 0x46 to this reg that sets BIT 6 for
++ * LED to active high. It's not clear what BIT 3 and BIT 4 does.
++ */
++#define QCA808X_LED_ACTIVE_HIGH BIT(6)
++
+ /* QCA808X 1G chip type */
+ #define QCA808X_PHY_MMD7_CHIP_TYPE 0x901d
+ #define QCA808X_PHY_CHIP_TYPE_1G BIT(0)
+@@ -346,6 +427,7 @@ struct at803x_priv {
+ struct regulator_dev *vddio_rdev;
+ struct regulator_dev *vddh_rdev;
+ u64 stats[ARRAY_SIZE(qca83xx_hw_stats)];
++ int led_polarity_mode;
+ };
+
+ struct at803x_context {
+@@ -706,6 +788,9 @@ static int at803x_probe(struct phy_devic
+ if (!priv)
+ return -ENOMEM;
+
++ /* Init LED polarity mode to -1 */
++ priv->led_polarity_mode = -1;
++
+ phydev->priv = priv;
+
+ ret = at803x_parse_dt(phydev);
+@@ -2235,6 +2320,242 @@ static void qca808x_link_change_notify(s
+ phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
+ }
+
++static int qca808x_led_parse_netdev(struct phy_device *phydev, unsigned long rules,
++ u16 *offload_trigger)
++{
++ /* Parsing specific to netdev trigger */
++ if (test_bit(TRIGGER_NETDEV_TX, &rules))
++ *offload_trigger |= QCA808X_LED_TX_BLINK;
++ if (test_bit(TRIGGER_NETDEV_RX, &rules))
++ *offload_trigger |= QCA808X_LED_RX_BLINK;
++ if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
++ *offload_trigger |= QCA808X_LED_SPEED10_ON;
++ if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
++ *offload_trigger |= QCA808X_LED_SPEED100_ON;
++ if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
++ *offload_trigger |= QCA808X_LED_SPEED1000_ON;
++ if (test_bit(TRIGGER_NETDEV_LINK_2500, &rules))
++ *offload_trigger |= QCA808X_LED_SPEED2500_ON;
++ if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
++ *offload_trigger |= QCA808X_LED_HALF_DUPLEX_ON;
++ if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
++ *offload_trigger |= QCA808X_LED_FULL_DUPLEX_ON;
++
++ if (rules && !*offload_trigger)
++ return -EOPNOTSUPP;
++
++ /* Enable BLINK_CHECK_BYPASS by default to make the LED
++ * blink even with duplex or speed mode not enabled.
++ */
++ *offload_trigger |= QCA808X_LED_BLINK_CHECK_BYPASS;
++
++ return 0;
++}
++
++static int qca808x_led_hw_control_enable(struct phy_device *phydev, u8 index)
++{
++ u16 reg;
++
++ if (index > 2)
++ return -EINVAL;
++
++ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++ return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
++ QCA808X_LED_FORCE_EN);
++}
++
++static int qca808x_led_hw_is_supported(struct phy_device *phydev, u8 index,
++ unsigned long rules)
++{
++ u16 offload_trigger = 0;
++
++ if (index > 2)
++ return -EINVAL;
++
++ return qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
++}
++
++static int qca808x_led_hw_control_set(struct phy_device *phydev, u8 index,
++ unsigned long rules)
++{
++ u16 reg, offload_trigger = 0;
++ int ret;
++
++ if (index > 2)
++ return -EINVAL;
++
++ reg = QCA808X_MMD7_LED_CTRL(index);
++
++ ret = qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
++ if (ret)
++ return ret;
++
++ ret = qca808x_led_hw_control_enable(phydev, index);
++ if (ret)
++ return ret;
++
++ return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++ QCA808X_LED_PATTERN_MASK,
++ offload_trigger);
++}
++
++static bool qca808x_led_hw_control_status(struct phy_device *phydev, u8 index)
++{
++ u16 reg;
++ int val;
++
++ if (index > 2)
++ return false;
++
++ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++ val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
++
++ return !(val & QCA808X_LED_FORCE_EN);
++}
++
++static int qca808x_led_hw_control_get(struct phy_device *phydev, u8 index,
++ unsigned long *rules)
++{
++ u16 reg;
++ int val;
++
++ if (index > 2)
++ return -EINVAL;
++
++ /* Check if we have hw control enabled */
++ if (qca808x_led_hw_control_status(phydev, index))
++ return -EINVAL;
++
++ reg = QCA808X_MMD7_LED_CTRL(index);
++
++ val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
++ if (val & QCA808X_LED_TX_BLINK)
++ set_bit(TRIGGER_NETDEV_TX, rules);
++ if (val & QCA808X_LED_RX_BLINK)
++ set_bit(TRIGGER_NETDEV_RX, rules);
++ if (val & QCA808X_LED_SPEED10_ON)
++ set_bit(TRIGGER_NETDEV_LINK_10, rules);
++ if (val & QCA808X_LED_SPEED100_ON)
++ set_bit(TRIGGER_NETDEV_LINK_100, rules);
++ if (val & QCA808X_LED_SPEED1000_ON)
++ set_bit(TRIGGER_NETDEV_LINK_1000, rules);
++ if (val & QCA808X_LED_SPEED2500_ON)
++ set_bit(TRIGGER_NETDEV_LINK_2500, rules);
++ if (val & QCA808X_LED_HALF_DUPLEX_ON)
++ set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
++ if (val & QCA808X_LED_FULL_DUPLEX_ON)
++ set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
++
++ return 0;
++}
++
++static int qca808x_led_hw_control_reset(struct phy_device *phydev, u8 index)
++{
++ u16 reg;
++
++ if (index > 2)
++ return -EINVAL;
++
++ reg = QCA808X_MMD7_LED_CTRL(index);
++
++ return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
++ QCA808X_LED_PATTERN_MASK);
++}
++
++static int qca808x_led_brightness_set(struct phy_device *phydev,
++ u8 index, enum led_brightness value)
++{
++ u16 reg;
++ int ret;
++
++ if (index > 2)
++ return -EINVAL;
++
++ if (!value) {
++ ret = qca808x_led_hw_control_reset(phydev, index);
++ if (ret)
++ return ret;
++ }
++
++ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++ return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++ QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
++ QCA808X_LED_FORCE_EN | value ? QCA808X_LED_FORCE_ON :
++ QCA808X_LED_FORCE_OFF);
++}
++
++static int qca808x_led_blink_set(struct phy_device *phydev, u8 index,
++ unsigned long *delay_on,
++ unsigned long *delay_off)
++{
++ int ret;
++ u16 reg;
++
++ if (index > 2)
++ return -EINVAL;
++
++ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++ /* Set blink to 50% off, 50% on at 4Hz by default */
++ ret = phy_modify_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_LED_GLOBAL,
++ QCA808X_LED_BLINK_FREQ_MASK | QCA808X_LED_BLINK_DUTY_MASK,
++ QCA808X_LED_BLINK_FREQ_4HZ | QCA808X_LED_BLINK_DUTY_50_50);
++ if (ret)
++ return ret;
++
++ /* We use BLINK_1 for normal blinking */
++ ret = phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++ QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
++ QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_BLINK_1);
++ if (ret)
++ return ret;
++
++ /* We set blink to 4Hz, aka 250ms */
++ *delay_on = 250 / 2;
++ *delay_off = 250 / 2;
++
++ return 0;
++}
++
++static int qca808x_led_polarity_set(struct phy_device *phydev, int index,
++ unsigned long modes)
++{
++ struct at803x_priv *priv = phydev->priv;
++ bool active_low = false;
++ u32 mode;
++
++ for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
++ switch (mode) {
++ case PHY_LED_ACTIVE_LOW:
++ active_low = true;
++ break;
++ default:
++ return -EINVAL;
++ }
++ }
++
++ /* PHY polarity is global and can't be set per LED.
++ * To detect this, check if last requested polarity mode
++ * match the new one.
++ */
++ if (priv->led_polarity_mode >= 0 &&
++ priv->led_polarity_mode != active_low) {
++ phydev_err(phydev, "PHY polarity is global. Mismatched polarity on different LED\n");
++ return -EINVAL;
++ }
++
++ /* Save the last PHY polarity mode */
++ priv->led_polarity_mode = active_low;
++
++ return phy_modify_mmd(phydev, MDIO_MMD_AN,
++ QCA808X_MMD7_LED_POLARITY_CTRL,
++ QCA808X_LED_ACTIVE_HIGH,
++ active_low ? 0 : QCA808X_LED_ACTIVE_HIGH);
++}
++
+ static struct phy_driver at803x_driver[] = {
+ {
+ /* Qualcomm Atheros AR8035 */
+@@ -2411,6 +2732,12 @@ static struct phy_driver at803x_driver[]
+ .cable_test_start = qca808x_cable_test_start,
+ .cable_test_get_status = qca808x_cable_test_get_status,
+ .link_change_notify = qca808x_link_change_notify,
++ .led_brightness_set = qca808x_led_brightness_set,
++ .led_blink_set = qca808x_led_blink_set,
++ .led_hw_is_supported = qca808x_led_hw_is_supported,
++ .led_hw_control_set = qca808x_led_hw_control_set,
++ .led_hw_control_get = qca808x_led_hw_control_get,
++ .led_polarity_set = qca808x_led_polarity_set,
+ }, };
+
+ module_phy_driver(at803x_driver);
diff --git a/target/linux/generic/backport-6.6/713-v6.9-01-net-phy-move-at803x-PHY-driver-to-dedicated-director.patch b/target/linux/generic/backport-6.6/713-v6.9-01-net-phy-move-at803x-PHY-driver-to-dedicated-director.patch
new file mode 100644
index 0000000000..8c9babea7b
--- /dev/null
+++ b/target/linux/generic/backport-6.6/713-v6.9-01-net-phy-move-at803x-PHY-driver-to-dedicated-director.patch
@@ -0,0 +1,5598 @@
+From 9e56ff53b4115875667760445b028357848b4748 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 Jan 2024 15:15:19 +0100
+Subject: [PATCH 1/5] net: phy: move at803x PHY driver to dedicated directory
+
+In preparation for addition of other Qcom PHY and to tidy things up,
+move the at803x PHY driver to dedicated directory.
+
+The same order in the Kconfig selection is saved.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/20240129141600.2592-2-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/Kconfig | 7 +------
+ drivers/net/phy/Makefile | 2 +-
+ drivers/net/phy/qcom/Kconfig | 7 +++++++
+ drivers/net/phy/qcom/Makefile | 2 ++
+ drivers/net/phy/{ => qcom}/at803x.c | 0
+ 5 files changed, 11 insertions(+), 7 deletions(-)
+ create mode 100644 drivers/net/phy/qcom/Kconfig
+ create mode 100644 drivers/net/phy/qcom/Makefile
+ rename drivers/net/phy/{ => qcom}/at803x.c (100%)
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -277,12 +277,7 @@ config NXP_TJA11XX_PHY
+ help
+ Currently supports the NXP TJA1100 and TJA1101 PHY.
+
+-config AT803X_PHY
+- tristate "Qualcomm Atheros AR803X PHYs and QCA833x PHYs"
+- depends on REGULATOR
+- help
+- Currently supports the AR8030, AR8031, AR8033, AR8035 and internal
+- QCA8337(Internal qca8k PHY) model
++source "drivers/net/phy/qcom/Kconfig"
+
+ config QSEMI_PHY
+ tristate "Quality Semiconductor PHYs"
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -34,7 +34,6 @@ obj-$(CONFIG_ADIN_PHY) += adin.o
+ obj-$(CONFIG_ADIN1100_PHY) += adin1100.o
+ obj-$(CONFIG_AMD_PHY) += amd.o
+ obj-$(CONFIG_AQUANTIA_PHY) += aquantia/
+-obj-$(CONFIG_AT803X_PHY) += at803x.o
+ obj-$(CONFIG_AX88796B_PHY) += ax88796b.o
+ obj-$(CONFIG_BCM54140_PHY) += bcm54140.o
+ obj-$(CONFIG_BCM63XX_PHY) += bcm63xx.o
+@@ -75,6 +74,7 @@ obj-$(CONFIG_MOTORCOMM_PHY) += motorcomm
+ obj-$(CONFIG_NATIONAL_PHY) += national.o
+ obj-$(CONFIG_NXP_C45_TJA11XX_PHY) += nxp-c45-tja11xx.o
+ obj-$(CONFIG_NXP_TJA11XX_PHY) += nxp-tja11xx.o
++obj-y += qcom/
+ obj-$(CONFIG_QSEMI_PHY) += qsemi.o
+ obj-$(CONFIG_REALTEK_PHY) += realtek.o
+ obj-$(CONFIG_RENESAS_PHY) += uPD60620.o
+--- /dev/null
++++ b/drivers/net/phy/qcom/Kconfig
+@@ -0,0 +1,7 @@
++# SPDX-License-Identifier: GPL-2.0-only
++config AT803X_PHY
++ tristate "Qualcomm Atheros AR803X PHYs and QCA833x PHYs"
++ depends on REGULATOR
++ help
++ Currently supports the AR8030, AR8031, AR8033, AR8035 and internal
++ QCA8337(Internal qca8k PHY) model
+--- /dev/null
++++ b/drivers/net/phy/qcom/Makefile
+@@ -0,0 +1,2 @@
++# SPDX-License-Identifier: GPL-2.0
++obj-$(CONFIG_AT803X_PHY) += at803x.o
+--- a/drivers/net/phy/at803x.c
++++ /dev/null
+@@ -1,2759 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0+
+-/*
+- * drivers/net/phy/at803x.c
+- *
+- * Driver for Qualcomm Atheros AR803x PHY
+- *
+- * Author: Matus Ujhelyi <ujhelyi.m@gmail.com>
+- */
+-
+-#include <linux/phy.h>
+-#include <linux/module.h>
+-#include <linux/string.h>
+-#include <linux/netdevice.h>
+-#include <linux/etherdevice.h>
+-#include <linux/ethtool_netlink.h>
+-#include <linux/bitfield.h>
+-#include <linux/regulator/of_regulator.h>
+-#include <linux/regulator/driver.h>
+-#include <linux/regulator/consumer.h>
+-#include <linux/of.h>
+-#include <linux/phylink.h>
+-#include <linux/sfp.h>
+-#include <dt-bindings/net/qca-ar803x.h>
+-
+-#define AT803X_SPECIFIC_FUNCTION_CONTROL 0x10
+-#define AT803X_SFC_ASSERT_CRS BIT(11)
+-#define AT803X_SFC_FORCE_LINK BIT(10)
+-#define AT803X_SFC_MDI_CROSSOVER_MODE_M GENMASK(6, 5)
+-#define AT803X_SFC_AUTOMATIC_CROSSOVER 0x3
+-#define AT803X_SFC_MANUAL_MDIX 0x1
+-#define AT803X_SFC_MANUAL_MDI 0x0
+-#define AT803X_SFC_SQE_TEST BIT(2)
+-#define AT803X_SFC_POLARITY_REVERSAL BIT(1)
+-#define AT803X_SFC_DISABLE_JABBER BIT(0)
+-
+-#define AT803X_SPECIFIC_STATUS 0x11
+-#define AT803X_SS_SPEED_MASK GENMASK(15, 14)
+-#define AT803X_SS_SPEED_1000 2
+-#define AT803X_SS_SPEED_100 1
+-#define AT803X_SS_SPEED_10 0
+-#define AT803X_SS_DUPLEX BIT(13)
+-#define AT803X_SS_SPEED_DUPLEX_RESOLVED BIT(11)
+-#define AT803X_SS_MDIX BIT(6)
+-
+-#define QCA808X_SS_SPEED_MASK GENMASK(9, 7)
+-#define QCA808X_SS_SPEED_2500 4
+-
+-#define AT803X_INTR_ENABLE 0x12
+-#define AT803X_INTR_ENABLE_AUTONEG_ERR BIT(15)
+-#define AT803X_INTR_ENABLE_SPEED_CHANGED BIT(14)
+-#define AT803X_INTR_ENABLE_DUPLEX_CHANGED BIT(13)
+-#define AT803X_INTR_ENABLE_PAGE_RECEIVED BIT(12)
+-#define AT803X_INTR_ENABLE_LINK_FAIL BIT(11)
+-#define AT803X_INTR_ENABLE_LINK_SUCCESS BIT(10)
+-#define AT803X_INTR_ENABLE_LINK_FAIL_BX BIT(8)
+-#define AT803X_INTR_ENABLE_LINK_SUCCESS_BX BIT(7)
+-#define AT803X_INTR_ENABLE_WIRESPEED_DOWNGRADE BIT(5)
+-#define AT803X_INTR_ENABLE_POLARITY_CHANGED BIT(1)
+-#define AT803X_INTR_ENABLE_WOL BIT(0)
+-
+-#define AT803X_INTR_STATUS 0x13
+-
+-#define AT803X_SMART_SPEED 0x14
+-#define AT803X_SMART_SPEED_ENABLE BIT(5)
+-#define AT803X_SMART_SPEED_RETRY_LIMIT_MASK GENMASK(4, 2)
+-#define AT803X_SMART_SPEED_BYPASS_TIMER BIT(1)
+-#define AT803X_CDT 0x16
+-#define AT803X_CDT_MDI_PAIR_MASK GENMASK(9, 8)
+-#define AT803X_CDT_ENABLE_TEST BIT(0)
+-#define AT803X_CDT_STATUS 0x1c
+-#define AT803X_CDT_STATUS_STAT_NORMAL 0
+-#define AT803X_CDT_STATUS_STAT_SHORT 1
+-#define AT803X_CDT_STATUS_STAT_OPEN 2
+-#define AT803X_CDT_STATUS_STAT_FAIL 3
+-#define AT803X_CDT_STATUS_STAT_MASK GENMASK(9, 8)
+-#define AT803X_CDT_STATUS_DELTA_TIME_MASK GENMASK(7, 0)
+-#define AT803X_LED_CONTROL 0x18
+-
+-#define AT803X_PHY_MMD3_WOL_CTRL 0x8012
+-#define AT803X_WOL_EN BIT(5)
+-#define AT803X_LOC_MAC_ADDR_0_15_OFFSET 0x804C
+-#define AT803X_LOC_MAC_ADDR_16_31_OFFSET 0x804B
+-#define AT803X_LOC_MAC_ADDR_32_47_OFFSET 0x804A
+-#define AT803X_REG_CHIP_CONFIG 0x1f
+-#define AT803X_BT_BX_REG_SEL 0x8000
+-
+-#define AT803X_DEBUG_ADDR 0x1D
+-#define AT803X_DEBUG_DATA 0x1E
+-
+-#define AT803X_MODE_CFG_MASK 0x0F
+-#define AT803X_MODE_CFG_BASET_RGMII 0x00
+-#define AT803X_MODE_CFG_BASET_SGMII 0x01
+-#define AT803X_MODE_CFG_BX1000_RGMII_50OHM 0x02
+-#define AT803X_MODE_CFG_BX1000_RGMII_75OHM 0x03
+-#define AT803X_MODE_CFG_BX1000_CONV_50OHM 0x04
+-#define AT803X_MODE_CFG_BX1000_CONV_75OHM 0x05
+-#define AT803X_MODE_CFG_FX100_RGMII_50OHM 0x06
+-#define AT803X_MODE_CFG_FX100_CONV_50OHM 0x07
+-#define AT803X_MODE_CFG_RGMII_AUTO_MDET 0x0B
+-#define AT803X_MODE_CFG_FX100_RGMII_75OHM 0x0E
+-#define AT803X_MODE_CFG_FX100_CONV_75OHM 0x0F
+-
+-#define AT803X_PSSR 0x11 /*PHY-Specific Status Register*/
+-#define AT803X_PSSR_MR_AN_COMPLETE 0x0200
+-
+-#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_SYSTEM_CTRL_MODE 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_HIB_CTRL_PS_HIB_EN BIT(15)
+-
+-#define AT803X_DEBUG_REG_3C 0x3C
+-
+-#define AT803X_DEBUG_REG_GREEN 0x3D
+-#define AT803X_DEBUG_GATE_CLK_IN1000 BIT(6)
+-
+-#define AT803X_DEBUG_REG_1F 0x1F
+-#define AT803X_DEBUG_PLL_ON BIT(2)
+-#define AT803X_DEBUG_RGMII_1V8 BIT(3)
+-
+-#define MDIO_AZ_DEBUG 0x800D
+-
+-/* AT803x supports either the XTAL input pad, an internal PLL or the
+- * DSP as clock reference for the clock output pad. The XTAL reference
+- * is only used for 25 MHz output, all other frequencies need the PLL.
+- * The DSP as a clock reference is used in synchronous ethernet
+- * applications.
+- *
+- * By default the PLL is only enabled if there is a link. Otherwise
+- * the PHY will go into low power state and disabled the PLL. You can
+- * set the PLL_ON bit (see debug register 0x1f) to keep the PLL always
+- * enabled.
+- */
+-#define AT803X_MMD7_CLK25M 0x8016
+-#define AT803X_CLK_OUT_MASK GENMASK(4, 2)
+-#define AT803X_CLK_OUT_25MHZ_XTAL 0
+-#define AT803X_CLK_OUT_25MHZ_DSP 1
+-#define AT803X_CLK_OUT_50MHZ_PLL 2
+-#define AT803X_CLK_OUT_50MHZ_DSP 3
+-#define AT803X_CLK_OUT_62_5MHZ_PLL 4
+-#define AT803X_CLK_OUT_62_5MHZ_DSP 5
+-#define AT803X_CLK_OUT_125MHZ_PLL 6
+-#define AT803X_CLK_OUT_125MHZ_DSP 7
+-
+-/* The AR8035 has another mask which is compatible with the AR8031/AR8033 mask
+- * but doesn't support choosing between XTAL/PLL and DSP.
+- */
+-#define AT8035_CLK_OUT_MASK GENMASK(4, 3)
+-
+-#define AT803X_CLK_OUT_STRENGTH_MASK GENMASK(8, 7)
+-#define AT803X_CLK_OUT_STRENGTH_FULL 0
+-#define AT803X_CLK_OUT_STRENGTH_HALF 1
+-#define AT803X_CLK_OUT_STRENGTH_QUARTER 2
+-
+-#define AT803X_DEFAULT_DOWNSHIFT 5
+-#define AT803X_MIN_DOWNSHIFT 2
+-#define AT803X_MAX_DOWNSHIFT 9
+-
+-#define AT803X_MMD3_SMARTEEE_CTL1 0x805b
+-#define AT803X_MMD3_SMARTEEE_CTL2 0x805c
+-#define AT803X_MMD3_SMARTEEE_CTL3 0x805d
+-#define AT803X_MMD3_SMARTEEE_CTL3_LPI_EN BIT(8)
+-
+-#define ATH9331_PHY_ID 0x004dd041
+-#define ATH8030_PHY_ID 0x004dd076
+-#define ATH8031_PHY_ID 0x004dd074
+-#define ATH8032_PHY_ID 0x004dd023
+-#define ATH8035_PHY_ID 0x004dd072
+-#define AT8030_PHY_ID_MASK 0xffffffef
+-
+-#define QCA8081_PHY_ID 0x004dd101
+-
+-#define QCA8327_A_PHY_ID 0x004dd033
+-#define QCA8327_B_PHY_ID 0x004dd034
+-#define QCA8337_PHY_ID 0x004dd036
+-#define QCA9561_PHY_ID 0x004dd042
+-#define QCA8K_PHY_ID_MASK 0xffffffff
+-
+-#define QCA8K_DEVFLAGS_REVISION_MASK GENMASK(2, 0)
+-
+-#define AT803X_PAGE_FIBER 0
+-#define AT803X_PAGE_COPPER 1
+-
+-/* don't turn off internal PLL */
+-#define AT803X_KEEP_PLL_ENABLED BIT(0)
+-#define AT803X_DISABLE_SMARTEEE BIT(1)
+-
+-/* disable hibernation mode */
+-#define AT803X_DISABLE_HIBERNATION_MODE BIT(2)
+-
+-/* ADC threshold */
+-#define QCA808X_PHY_DEBUG_ADC_THRESHOLD 0x2c80
+-#define QCA808X_ADC_THRESHOLD_MASK GENMASK(7, 0)
+-#define QCA808X_ADC_THRESHOLD_80MV 0
+-#define QCA808X_ADC_THRESHOLD_100MV 0xf0
+-#define QCA808X_ADC_THRESHOLD_200MV 0x0f
+-#define QCA808X_ADC_THRESHOLD_300MV 0xff
+-
+-/* CLD control */
+-#define QCA808X_PHY_MMD3_ADDR_CLD_CTRL7 0x8007
+-#define QCA808X_8023AZ_AFE_CTRL_MASK GENMASK(8, 4)
+-#define QCA808X_8023AZ_AFE_EN 0x90
+-
+-/* AZ control */
+-#define QCA808X_PHY_MMD3_AZ_TRAINING_CTRL 0x8008
+-#define QCA808X_MMD3_AZ_TRAINING_VAL 0x1c32
+-
+-#define QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB 0x8014
+-#define QCA808X_MSE_THRESHOLD_20DB_VALUE 0x529
+-
+-#define QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB 0x800E
+-#define QCA808X_MSE_THRESHOLD_17DB_VALUE 0x341
+-
+-#define QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB 0x801E
+-#define QCA808X_MSE_THRESHOLD_27DB_VALUE 0x419
+-
+-#define QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB 0x8020
+-#define QCA808X_MSE_THRESHOLD_28DB_VALUE 0x341
+-
+-#define QCA808X_PHY_MMD7_TOP_OPTION1 0x901c
+-#define QCA808X_TOP_OPTION1_DATA 0x0
+-
+-#define QCA808X_PHY_MMD3_DEBUG_1 0xa100
+-#define QCA808X_MMD3_DEBUG_1_VALUE 0x9203
+-#define QCA808X_PHY_MMD3_DEBUG_2 0xa101
+-#define QCA808X_MMD3_DEBUG_2_VALUE 0x48ad
+-#define QCA808X_PHY_MMD3_DEBUG_3 0xa103
+-#define QCA808X_MMD3_DEBUG_3_VALUE 0x1698
+-#define QCA808X_PHY_MMD3_DEBUG_4 0xa105
+-#define QCA808X_MMD3_DEBUG_4_VALUE 0x8001
+-#define QCA808X_PHY_MMD3_DEBUG_5 0xa106
+-#define QCA808X_MMD3_DEBUG_5_VALUE 0x1111
+-#define QCA808X_PHY_MMD3_DEBUG_6 0xa011
+-#define QCA808X_MMD3_DEBUG_6_VALUE 0x5f85
+-
+-/* master/slave seed config */
+-#define QCA808X_PHY_DEBUG_LOCAL_SEED 9
+-#define QCA808X_MASTER_SLAVE_SEED_ENABLE BIT(1)
+-#define QCA808X_MASTER_SLAVE_SEED_CFG GENMASK(12, 2)
+-#define QCA808X_MASTER_SLAVE_SEED_RANGE 0x32
+-
+-/* Hibernation yields lower power consumpiton in contrast with normal operation mode.
+- * when the copper cable is unplugged, the PHY enters into hibernation mode in about 10s.
+- */
+-#define QCA808X_DBG_AN_TEST 0xb
+-#define QCA808X_HIBERNATION_EN BIT(15)
+-
+-#define QCA808X_CDT_ENABLE_TEST BIT(15)
+-#define QCA808X_CDT_INTER_CHECK_DIS BIT(13)
+-#define QCA808X_CDT_STATUS BIT(11)
+-#define QCA808X_CDT_LENGTH_UNIT BIT(10)
+-
+-#define QCA808X_MMD3_CDT_STATUS 0x8064
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_A 0x8065
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_B 0x8066
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_C 0x8067
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_D 0x8068
+-#define QCA808X_CDT_DIAG_LENGTH_SAME_SHORT GENMASK(15, 8)
+-#define QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT GENMASK(7, 0)
+-
+-#define QCA808X_CDT_CODE_PAIR_A GENMASK(15, 12)
+-#define QCA808X_CDT_CODE_PAIR_B GENMASK(11, 8)
+-#define QCA808X_CDT_CODE_PAIR_C GENMASK(7, 4)
+-#define QCA808X_CDT_CODE_PAIR_D GENMASK(3, 0)
+-
+-#define QCA808X_CDT_STATUS_STAT_TYPE GENMASK(1, 0)
+-#define QCA808X_CDT_STATUS_STAT_FAIL FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 0)
+-#define QCA808X_CDT_STATUS_STAT_NORMAL FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 1)
+-#define QCA808X_CDT_STATUS_STAT_SAME_OPEN FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 2)
+-#define QCA808X_CDT_STATUS_STAT_SAME_SHORT FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 3)
+-
+-#define QCA808X_CDT_STATUS_STAT_MDI GENMASK(3, 2)
+-#define QCA808X_CDT_STATUS_STAT_MDI1 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 1)
+-#define QCA808X_CDT_STATUS_STAT_MDI2 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 2)
+-#define QCA808X_CDT_STATUS_STAT_MDI3 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 3)
+-
+-/* NORMAL are MDI with type set to 0 */
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI1
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
+- QCA808X_CDT_STATUS_STAT_MDI1)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
+- QCA808X_CDT_STATUS_STAT_MDI1)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI2
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
+- QCA808X_CDT_STATUS_STAT_MDI2)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
+- QCA808X_CDT_STATUS_STAT_MDI2)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI3
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
+- QCA808X_CDT_STATUS_STAT_MDI3)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
+- QCA808X_CDT_STATUS_STAT_MDI3)
+-
+-/* Added for reference of existence but should be handled by wait_for_completion already */
+-#define QCA808X_CDT_STATUS_STAT_BUSY (BIT(1) | BIT(3))
+-
+-#define QCA808X_MMD7_LED_GLOBAL 0x8073
+-#define QCA808X_LED_BLINK_1 GENMASK(11, 6)
+-#define QCA808X_LED_BLINK_2 GENMASK(5, 0)
+-/* Values are the same for both BLINK_1 and BLINK_2 */
+-#define QCA808X_LED_BLINK_FREQ_MASK GENMASK(5, 3)
+-#define QCA808X_LED_BLINK_FREQ_2HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x0)
+-#define QCA808X_LED_BLINK_FREQ_4HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x1)
+-#define QCA808X_LED_BLINK_FREQ_8HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x2)
+-#define QCA808X_LED_BLINK_FREQ_16HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x3)
+-#define QCA808X_LED_BLINK_FREQ_32HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x4)
+-#define QCA808X_LED_BLINK_FREQ_64HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x5)
+-#define QCA808X_LED_BLINK_FREQ_128HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x6)
+-#define QCA808X_LED_BLINK_FREQ_256HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x7)
+-#define QCA808X_LED_BLINK_DUTY_MASK GENMASK(2, 0)
+-#define QCA808X_LED_BLINK_DUTY_50_50 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x0)
+-#define QCA808X_LED_BLINK_DUTY_75_25 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x1)
+-#define QCA808X_LED_BLINK_DUTY_25_75 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x2)
+-#define QCA808X_LED_BLINK_DUTY_33_67 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x3)
+-#define QCA808X_LED_BLINK_DUTY_67_33 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x4)
+-#define QCA808X_LED_BLINK_DUTY_17_83 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x5)
+-#define QCA808X_LED_BLINK_DUTY_83_17 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x6)
+-#define QCA808X_LED_BLINK_DUTY_8_92 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x7)
+-
+-#define QCA808X_MMD7_LED2_CTRL 0x8074
+-#define QCA808X_MMD7_LED2_FORCE_CTRL 0x8075
+-#define QCA808X_MMD7_LED1_CTRL 0x8076
+-#define QCA808X_MMD7_LED1_FORCE_CTRL 0x8077
+-#define QCA808X_MMD7_LED0_CTRL 0x8078
+-#define QCA808X_MMD7_LED_CTRL(x) (0x8078 - ((x) * 2))
+-
+-/* LED hw control pattern is the same for every LED */
+-#define QCA808X_LED_PATTERN_MASK GENMASK(15, 0)
+-#define QCA808X_LED_SPEED2500_ON BIT(15)
+-#define QCA808X_LED_SPEED2500_BLINK BIT(14)
+-/* Follow blink trigger even if duplex or speed condition doesn't match */
+-#define QCA808X_LED_BLINK_CHECK_BYPASS BIT(13)
+-#define QCA808X_LED_FULL_DUPLEX_ON BIT(12)
+-#define QCA808X_LED_HALF_DUPLEX_ON BIT(11)
+-#define QCA808X_LED_TX_BLINK BIT(10)
+-#define QCA808X_LED_RX_BLINK BIT(9)
+-#define QCA808X_LED_TX_ON_10MS BIT(8)
+-#define QCA808X_LED_RX_ON_10MS BIT(7)
+-#define QCA808X_LED_SPEED1000_ON BIT(6)
+-#define QCA808X_LED_SPEED100_ON BIT(5)
+-#define QCA808X_LED_SPEED10_ON BIT(4)
+-#define QCA808X_LED_COLLISION_BLINK BIT(3)
+-#define QCA808X_LED_SPEED1000_BLINK BIT(2)
+-#define QCA808X_LED_SPEED100_BLINK BIT(1)
+-#define QCA808X_LED_SPEED10_BLINK BIT(0)
+-
+-#define QCA808X_MMD7_LED0_FORCE_CTRL 0x8079
+-#define QCA808X_MMD7_LED_FORCE_CTRL(x) (0x8079 - ((x) * 2))
+-
+-/* LED force ctrl is the same for every LED
+- * No documentation exist for this, not even internal one
+- * with NDA as QCOM gives only info about configuring
+- * hw control pattern rules and doesn't indicate any way
+- * to force the LED to specific mode.
+- * These define comes from reverse and testing and maybe
+- * lack of some info or some info are not entirely correct.
+- * For the basic LED control and hw control these finding
+- * are enough to support LED control in all the required APIs.
+- *
+- * On doing some comparison with implementation with qca807x,
+- * it was found that it's 1:1 equal to it and confirms all the
+- * reverse done. It was also found further specification with the
+- * force mode and the blink modes.
+- */
+-#define QCA808X_LED_FORCE_EN BIT(15)
+-#define QCA808X_LED_FORCE_MODE_MASK GENMASK(14, 13)
+-#define QCA808X_LED_FORCE_BLINK_1 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x3)
+-#define QCA808X_LED_FORCE_BLINK_2 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x2)
+-#define QCA808X_LED_FORCE_ON FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x1)
+-#define QCA808X_LED_FORCE_OFF FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x0)
+-
+-#define QCA808X_MMD7_LED_POLARITY_CTRL 0x901a
+-/* QSDK sets by default 0x46 to this reg that sets BIT 6 for
+- * LED to active high. It's not clear what BIT 3 and BIT 4 does.
+- */
+-#define QCA808X_LED_ACTIVE_HIGH BIT(6)
+-
+-/* QCA808X 1G chip type */
+-#define QCA808X_PHY_MMD7_CHIP_TYPE 0x901d
+-#define QCA808X_PHY_CHIP_TYPE_1G BIT(0)
+-
+-#define QCA8081_PHY_SERDES_MMD1_FIFO_CTRL 0x9072
+-#define QCA8081_PHY_FIFO_RSTN BIT(11)
+-
+-MODULE_DESCRIPTION("Qualcomm Atheros AR803x and QCA808X PHY driver");
+-MODULE_AUTHOR("Matus Ujhelyi");
+-MODULE_LICENSE("GPL");
+-
+-enum stat_access_type {
+- PHY,
+- MMD
+-};
+-
+-struct at803x_hw_stat {
+- const char *string;
+- u8 reg;
+- u32 mask;
+- enum stat_access_type access_type;
+-};
+-
+-static struct at803x_hw_stat qca83xx_hw_stats[] = {
+- { "phy_idle_errors", 0xa, GENMASK(7, 0), PHY},
+- { "phy_receive_errors", 0x15, GENMASK(15, 0), PHY},
+- { "eee_wake_errors", 0x16, GENMASK(15, 0), MMD},
+-};
+-
+-struct at803x_ss_mask {
+- u16 speed_mask;
+- u8 speed_shift;
+-};
+-
+-struct at803x_priv {
+- int flags;
+- u16 clk_25m_reg;
+- u16 clk_25m_mask;
+- u8 smarteee_lpi_tw_1g;
+- u8 smarteee_lpi_tw_100m;
+- bool is_fiber;
+- bool is_1000basex;
+- struct regulator_dev *vddio_rdev;
+- struct regulator_dev *vddh_rdev;
+- u64 stats[ARRAY_SIZE(qca83xx_hw_stats)];
+- int led_polarity_mode;
+-};
+-
+-struct at803x_context {
+- u16 bmcr;
+- u16 advertise;
+- u16 control1000;
+- u16 int_enable;
+- u16 smart_speed;
+- u16 led_control;
+-};
+-
+-static int at803x_debug_reg_write(struct phy_device *phydev, u16 reg, u16 data)
+-{
+- int ret;
+-
+- ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
+- if (ret < 0)
+- return ret;
+-
+- return phy_write(phydev, AT803X_DEBUG_DATA, data);
+-}
+-
+-static int at803x_debug_reg_read(struct phy_device *phydev, u16 reg)
+-{
+- int ret;
+-
+- ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
+- if (ret < 0)
+- return ret;
+-
+- return phy_read(phydev, AT803X_DEBUG_DATA);
+-}
+-
+-static int at803x_debug_reg_mask(struct phy_device *phydev, u16 reg,
+- u16 clear, u16 set)
+-{
+- u16 val;
+- int ret;
+-
+- ret = at803x_debug_reg_read(phydev, reg);
+- if (ret < 0)
+- return ret;
+-
+- val = ret & 0xffff;
+- val &= ~clear;
+- val |= set;
+-
+- return phy_write(phydev, AT803X_DEBUG_DATA, val);
+-}
+-
+-static int at803x_write_page(struct phy_device *phydev, int page)
+-{
+- int mask;
+- int set;
+-
+- if (page == AT803X_PAGE_COPPER) {
+- set = AT803X_BT_BX_REG_SEL;
+- mask = 0;
+- } else {
+- set = 0;
+- mask = AT803X_BT_BX_REG_SEL;
+- }
+-
+- return __phy_modify(phydev, AT803X_REG_CHIP_CONFIG, mask, set);
+-}
+-
+-static int at803x_read_page(struct phy_device *phydev)
+-{
+- int ccr = __phy_read(phydev, AT803X_REG_CHIP_CONFIG);
+-
+- if (ccr < 0)
+- return ccr;
+-
+- if (ccr & AT803X_BT_BX_REG_SEL)
+- return AT803X_PAGE_COPPER;
+-
+- return AT803X_PAGE_FIBER;
+-}
+-
+-static int at803x_enable_rx_delay(struct phy_device *phydev)
+-{
+- 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_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_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_SYSTEM_CTRL_MODE,
+- AT803X_DEBUG_TX_CLK_DLY_EN, 0);
+-}
+-
+-/* save relevant PHY registers to private copy */
+-static void at803x_context_save(struct phy_device *phydev,
+- struct at803x_context *context)
+-{
+- context->bmcr = phy_read(phydev, MII_BMCR);
+- context->advertise = phy_read(phydev, MII_ADVERTISE);
+- context->control1000 = phy_read(phydev, MII_CTRL1000);
+- context->int_enable = phy_read(phydev, AT803X_INTR_ENABLE);
+- context->smart_speed = phy_read(phydev, AT803X_SMART_SPEED);
+- context->led_control = phy_read(phydev, AT803X_LED_CONTROL);
+-}
+-
+-/* restore relevant PHY registers from private copy */
+-static void at803x_context_restore(struct phy_device *phydev,
+- const struct at803x_context *context)
+-{
+- phy_write(phydev, MII_BMCR, context->bmcr);
+- phy_write(phydev, MII_ADVERTISE, context->advertise);
+- phy_write(phydev, MII_CTRL1000, context->control1000);
+- phy_write(phydev, AT803X_INTR_ENABLE, context->int_enable);
+- phy_write(phydev, AT803X_SMART_SPEED, context->smart_speed);
+- phy_write(phydev, AT803X_LED_CONTROL, context->led_control);
+-}
+-
+-static int at803x_set_wol(struct phy_device *phydev,
+- struct ethtool_wolinfo *wol)
+-{
+- int ret, irq_enabled;
+-
+- if (wol->wolopts & WAKE_MAGIC) {
+- struct net_device *ndev = phydev->attached_dev;
+- const u8 *mac;
+- unsigned int i;
+- static const unsigned int offsets[] = {
+- AT803X_LOC_MAC_ADDR_32_47_OFFSET,
+- AT803X_LOC_MAC_ADDR_16_31_OFFSET,
+- AT803X_LOC_MAC_ADDR_0_15_OFFSET,
+- };
+-
+- if (!ndev)
+- return -ENODEV;
+-
+- mac = (const u8 *)ndev->dev_addr;
+-
+- if (!is_valid_ether_addr(mac))
+- return -EINVAL;
+-
+- for (i = 0; i < 3; i++)
+- phy_write_mmd(phydev, MDIO_MMD_PCS, offsets[i],
+- mac[(i * 2) + 1] | (mac[(i * 2)] << 8));
+-
+- /* Enable WOL interrupt */
+- ret = phy_modify(phydev, AT803X_INTR_ENABLE, 0, AT803X_INTR_ENABLE_WOL);
+- if (ret)
+- return ret;
+- } else {
+- /* Disable WOL interrupt */
+- ret = phy_modify(phydev, AT803X_INTR_ENABLE, AT803X_INTR_ENABLE_WOL, 0);
+- if (ret)
+- return ret;
+- }
+-
+- /* Clear WOL status */
+- ret = phy_read(phydev, AT803X_INTR_STATUS);
+- if (ret < 0)
+- return ret;
+-
+- /* Check if there are other interrupts except for WOL triggered when PHY is
+- * in interrupt mode, only the interrupts enabled by AT803X_INTR_ENABLE can
+- * be passed up to the interrupt PIN.
+- */
+- irq_enabled = phy_read(phydev, AT803X_INTR_ENABLE);
+- if (irq_enabled < 0)
+- return irq_enabled;
+-
+- irq_enabled &= ~AT803X_INTR_ENABLE_WOL;
+- if (ret & irq_enabled && !phy_polling_mode(phydev))
+- phy_trigger_machine(phydev);
+-
+- return 0;
+-}
+-
+-static void at803x_get_wol(struct phy_device *phydev,
+- struct ethtool_wolinfo *wol)
+-{
+- int value;
+-
+- wol->supported = WAKE_MAGIC;
+- wol->wolopts = 0;
+-
+- value = phy_read(phydev, AT803X_INTR_ENABLE);
+- if (value < 0)
+- return;
+-
+- if (value & AT803X_INTR_ENABLE_WOL)
+- wol->wolopts |= WAKE_MAGIC;
+-}
+-
+-static int qca83xx_get_sset_count(struct phy_device *phydev)
+-{
+- return ARRAY_SIZE(qca83xx_hw_stats);
+-}
+-
+-static void qca83xx_get_strings(struct phy_device *phydev, u8 *data)
+-{
+- int i;
+-
+- for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++) {
+- strscpy(data + i * ETH_GSTRING_LEN,
+- qca83xx_hw_stats[i].string, ETH_GSTRING_LEN);
+- }
+-}
+-
+-static u64 qca83xx_get_stat(struct phy_device *phydev, int i)
+-{
+- struct at803x_hw_stat stat = qca83xx_hw_stats[i];
+- struct at803x_priv *priv = phydev->priv;
+- int val;
+- u64 ret;
+-
+- if (stat.access_type == MMD)
+- val = phy_read_mmd(phydev, MDIO_MMD_PCS, stat.reg);
+- else
+- val = phy_read(phydev, stat.reg);
+-
+- if (val < 0) {
+- ret = U64_MAX;
+- } else {
+- val = val & stat.mask;
+- priv->stats[i] += val;
+- ret = priv->stats[i];
+- }
+-
+- return ret;
+-}
+-
+-static void qca83xx_get_stats(struct phy_device *phydev,
+- struct ethtool_stats *stats, u64 *data)
+-{
+- int i;
+-
+- for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++)
+- data[i] = qca83xx_get_stat(phydev, i);
+-}
+-
+-static int at803x_suspend(struct phy_device *phydev)
+-{
+- int value;
+- int wol_enabled;
+-
+- value = phy_read(phydev, AT803X_INTR_ENABLE);
+- wol_enabled = value & AT803X_INTR_ENABLE_WOL;
+-
+- if (wol_enabled)
+- value = BMCR_ISOLATE;
+- else
+- value = BMCR_PDOWN;
+-
+- phy_modify(phydev, MII_BMCR, 0, value);
+-
+- return 0;
+-}
+-
+-static int at803x_resume(struct phy_device *phydev)
+-{
+- return phy_modify(phydev, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE, 0);
+-}
+-
+-static int at803x_parse_dt(struct phy_device *phydev)
+-{
+- struct device_node *node = phydev->mdio.dev.of_node;
+- struct at803x_priv *priv = phydev->priv;
+- u32 freq, strength, tw;
+- unsigned int sel;
+- int ret;
+-
+- if (!IS_ENABLED(CONFIG_OF_MDIO))
+- return 0;
+-
+- if (of_property_read_bool(node, "qca,disable-smarteee"))
+- priv->flags |= AT803X_DISABLE_SMARTEEE;
+-
+- if (of_property_read_bool(node, "qca,disable-hibernation-mode"))
+- priv->flags |= AT803X_DISABLE_HIBERNATION_MODE;
+-
+- if (!of_property_read_u32(node, "qca,smarteee-tw-us-1g", &tw)) {
+- if (!tw || tw > 255) {
+- phydev_err(phydev, "invalid qca,smarteee-tw-us-1g\n");
+- return -EINVAL;
+- }
+- priv->smarteee_lpi_tw_1g = tw;
+- }
+-
+- if (!of_property_read_u32(node, "qca,smarteee-tw-us-100m", &tw)) {
+- if (!tw || tw > 255) {
+- phydev_err(phydev, "invalid qca,smarteee-tw-us-100m\n");
+- return -EINVAL;
+- }
+- priv->smarteee_lpi_tw_100m = tw;
+- }
+-
+- ret = of_property_read_u32(node, "qca,clk-out-frequency", &freq);
+- if (!ret) {
+- switch (freq) {
+- case 25000000:
+- sel = AT803X_CLK_OUT_25MHZ_XTAL;
+- break;
+- case 50000000:
+- sel = AT803X_CLK_OUT_50MHZ_PLL;
+- break;
+- case 62500000:
+- sel = AT803X_CLK_OUT_62_5MHZ_PLL;
+- break;
+- case 125000000:
+- sel = AT803X_CLK_OUT_125MHZ_PLL;
+- break;
+- default:
+- phydev_err(phydev, "invalid qca,clk-out-frequency\n");
+- return -EINVAL;
+- }
+-
+- priv->clk_25m_reg |= FIELD_PREP(AT803X_CLK_OUT_MASK, sel);
+- priv->clk_25m_mask |= AT803X_CLK_OUT_MASK;
+- }
+-
+- ret = of_property_read_u32(node, "qca,clk-out-strength", &strength);
+- if (!ret) {
+- priv->clk_25m_mask |= AT803X_CLK_OUT_STRENGTH_MASK;
+- switch (strength) {
+- case AR803X_STRENGTH_FULL:
+- priv->clk_25m_reg |= AT803X_CLK_OUT_STRENGTH_FULL;
+- break;
+- case AR803X_STRENGTH_HALF:
+- priv->clk_25m_reg |= AT803X_CLK_OUT_STRENGTH_HALF;
+- break;
+- case AR803X_STRENGTH_QUARTER:
+- priv->clk_25m_reg |= AT803X_CLK_OUT_STRENGTH_QUARTER;
+- break;
+- default:
+- phydev_err(phydev, "invalid qca,clk-out-strength\n");
+- return -EINVAL;
+- }
+- }
+-
+- return 0;
+-}
+-
+-static int at803x_probe(struct phy_device *phydev)
+-{
+- struct device *dev = &phydev->mdio.dev;
+- struct at803x_priv *priv;
+- int ret;
+-
+- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+- if (!priv)
+- return -ENOMEM;
+-
+- /* Init LED polarity mode to -1 */
+- priv->led_polarity_mode = -1;
+-
+- phydev->priv = priv;
+-
+- ret = at803x_parse_dt(phydev);
+- if (ret)
+- return ret;
+-
+- return 0;
+-}
+-
+-static int at803x_get_features(struct phy_device *phydev)
+-{
+- struct at803x_priv *priv = phydev->priv;
+- int err;
+-
+- err = genphy_read_abilities(phydev);
+- if (err)
+- return err;
+-
+- if (phydev->drv->phy_id != ATH8031_PHY_ID)
+- return 0;
+-
+- /* AR8031/AR8033 have different status registers
+- * for copper and fiber operation. However, the
+- * extended status register is the same for both
+- * operation modes.
+- *
+- * As a result of that, ESTATUS_1000_XFULL is set
+- * to 1 even when operating in copper TP mode.
+- *
+- * Remove this mode from the supported link modes
+- * when not operating in 1000BaseX mode.
+- */
+- if (!priv->is_1000basex)
+- linkmode_clear_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
+- phydev->supported);
+-
+- return 0;
+-}
+-
+-static int at803x_smarteee_config(struct phy_device *phydev)
+-{
+- struct at803x_priv *priv = phydev->priv;
+- u16 mask = 0, val = 0;
+- int ret;
+-
+- if (priv->flags & AT803X_DISABLE_SMARTEEE)
+- return phy_modify_mmd(phydev, MDIO_MMD_PCS,
+- AT803X_MMD3_SMARTEEE_CTL3,
+- AT803X_MMD3_SMARTEEE_CTL3_LPI_EN, 0);
+-
+- if (priv->smarteee_lpi_tw_1g) {
+- mask |= 0xff00;
+- val |= priv->smarteee_lpi_tw_1g << 8;
+- }
+- if (priv->smarteee_lpi_tw_100m) {
+- mask |= 0x00ff;
+- val |= priv->smarteee_lpi_tw_100m;
+- }
+- if (!mask)
+- return 0;
+-
+- ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, AT803X_MMD3_SMARTEEE_CTL1,
+- mask, val);
+- if (ret)
+- return ret;
+-
+- return phy_modify_mmd(phydev, MDIO_MMD_PCS, AT803X_MMD3_SMARTEEE_CTL3,
+- AT803X_MMD3_SMARTEEE_CTL3_LPI_EN,
+- AT803X_MMD3_SMARTEEE_CTL3_LPI_EN);
+-}
+-
+-static int at803x_clk_out_config(struct phy_device *phydev)
+-{
+- struct at803x_priv *priv = phydev->priv;
+-
+- if (!priv->clk_25m_mask)
+- return 0;
+-
+- return phy_modify_mmd(phydev, MDIO_MMD_AN, AT803X_MMD7_CLK25M,
+- priv->clk_25m_mask, priv->clk_25m_reg);
+-}
+-
+-static int at8031_pll_config(struct phy_device *phydev)
+-{
+- struct at803x_priv *priv = phydev->priv;
+-
+- /* The default after hardware reset is PLL OFF. After a soft reset, the
+- * values are retained.
+- */
+- if (priv->flags & AT803X_KEEP_PLL_ENABLED)
+- return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
+- 0, AT803X_DEBUG_PLL_ON);
+- else
+- return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
+- AT803X_DEBUG_PLL_ON, 0);
+-}
+-
+-static int at803x_hibernation_mode_config(struct phy_device *phydev)
+-{
+- struct at803x_priv *priv = phydev->priv;
+-
+- /* The default after hardware reset is hibernation mode enabled. After
+- * software reset, the value is retained.
+- */
+- if (!(priv->flags & AT803X_DISABLE_HIBERNATION_MODE))
+- return 0;
+-
+- return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_HIB_CTRL,
+- AT803X_DEBUG_HIB_CTRL_PS_HIB_EN, 0);
+-}
+-
+-static int at803x_config_init(struct phy_device *phydev)
+-{
+- int ret;
+-
+- /* The RX and TX delay default is:
+- * after HW reset: RX delay enabled and TX delay disabled
+- * after SW reset: RX delay enabled, while TX delay retains the
+- * value before reset.
+- */
+- if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+- phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID)
+- ret = at803x_enable_rx_delay(phydev);
+- else
+- ret = at803x_disable_rx_delay(phydev);
+- if (ret < 0)
+- return ret;
+-
+- if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+- phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
+- ret = at803x_enable_tx_delay(phydev);
+- else
+- ret = at803x_disable_tx_delay(phydev);
+- if (ret < 0)
+- return ret;
+-
+- ret = at803x_smarteee_config(phydev);
+- if (ret < 0)
+- return ret;
+-
+- ret = at803x_clk_out_config(phydev);
+- if (ret < 0)
+- return ret;
+-
+- ret = at803x_hibernation_mode_config(phydev);
+- if (ret < 0)
+- return ret;
+-
+- /* Ar803x extended next page bit is enabled by default. Cisco
+- * multigig switches read this bit and attempt to negotiate 10Gbps
+- * rates even if the next page bit is disabled. This is incorrect
+- * behaviour but we still need to accommodate it. XNP is only needed
+- * for 10Gbps support, so disable XNP.
+- */
+- return phy_modify(phydev, MII_ADVERTISE, MDIO_AN_CTRL1_XNP, 0);
+-}
+-
+-static int at803x_ack_interrupt(struct phy_device *phydev)
+-{
+- int err;
+-
+- err = phy_read(phydev, AT803X_INTR_STATUS);
+-
+- return (err < 0) ? err : 0;
+-}
+-
+-static int at803x_config_intr(struct phy_device *phydev)
+-{
+- int err;
+- int value;
+-
+- value = phy_read(phydev, AT803X_INTR_ENABLE);
+-
+- if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+- /* Clear any pending interrupts */
+- err = at803x_ack_interrupt(phydev);
+- if (err)
+- return err;
+-
+- value |= AT803X_INTR_ENABLE_AUTONEG_ERR;
+- value |= AT803X_INTR_ENABLE_SPEED_CHANGED;
+- value |= AT803X_INTR_ENABLE_DUPLEX_CHANGED;
+- value |= AT803X_INTR_ENABLE_LINK_FAIL;
+- value |= AT803X_INTR_ENABLE_LINK_SUCCESS;
+-
+- err = phy_write(phydev, AT803X_INTR_ENABLE, value);
+- } else {
+- err = phy_write(phydev, AT803X_INTR_ENABLE, 0);
+- if (err)
+- return err;
+-
+- /* Clear any pending interrupts */
+- err = at803x_ack_interrupt(phydev);
+- }
+-
+- return err;
+-}
+-
+-static irqreturn_t at803x_handle_interrupt(struct phy_device *phydev)
+-{
+- int irq_status, int_enabled;
+-
+- irq_status = phy_read(phydev, AT803X_INTR_STATUS);
+- if (irq_status < 0) {
+- phy_error(phydev);
+- return IRQ_NONE;
+- }
+-
+- /* Read the current enabled interrupts */
+- int_enabled = phy_read(phydev, AT803X_INTR_ENABLE);
+- if (int_enabled < 0) {
+- phy_error(phydev);
+- return IRQ_NONE;
+- }
+-
+- /* See if this was one of our enabled interrupts */
+- if (!(irq_status & int_enabled))
+- return IRQ_NONE;
+-
+- phy_trigger_machine(phydev);
+-
+- return IRQ_HANDLED;
+-}
+-
+-static void at803x_link_change_notify(struct phy_device *phydev)
+-{
+- /*
+- * Conduct a hardware reset for AT8030 every time a link loss is
+- * signalled. This is necessary to circumvent a hardware bug that
+- * occurs when the cable is unplugged while TX packets are pending
+- * in the FIFO. In such cases, the FIFO enters an error mode it
+- * cannot recover from by software.
+- */
+- if (phydev->state == PHY_NOLINK && phydev->mdio.reset_gpio) {
+- struct at803x_context context;
+-
+- at803x_context_save(phydev, &context);
+-
+- phy_device_reset(phydev, 1);
+- usleep_range(1000, 2000);
+- phy_device_reset(phydev, 0);
+- usleep_range(1000, 2000);
+-
+- at803x_context_restore(phydev, &context);
+-
+- phydev_dbg(phydev, "%s(): phy was reset\n", __func__);
+- }
+-}
+-
+-static int at803x_read_specific_status(struct phy_device *phydev,
+- struct at803x_ss_mask ss_mask)
+-{
+- int ss;
+-
+- /* Read the AT8035 PHY-Specific Status register, which indicates the
+- * speed and duplex that the PHY is actually using, irrespective of
+- * whether we are in autoneg mode or not.
+- */
+- ss = phy_read(phydev, AT803X_SPECIFIC_STATUS);
+- if (ss < 0)
+- return ss;
+-
+- if (ss & AT803X_SS_SPEED_DUPLEX_RESOLVED) {
+- int sfc, speed;
+-
+- sfc = phy_read(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL);
+- if (sfc < 0)
+- return sfc;
+-
+- speed = ss & ss_mask.speed_mask;
+- speed >>= ss_mask.speed_shift;
+-
+- switch (speed) {
+- case AT803X_SS_SPEED_10:
+- phydev->speed = SPEED_10;
+- break;
+- case AT803X_SS_SPEED_100:
+- phydev->speed = SPEED_100;
+- break;
+- case AT803X_SS_SPEED_1000:
+- phydev->speed = SPEED_1000;
+- break;
+- case QCA808X_SS_SPEED_2500:
+- phydev->speed = SPEED_2500;
+- break;
+- }
+- if (ss & AT803X_SS_DUPLEX)
+- phydev->duplex = DUPLEX_FULL;
+- else
+- phydev->duplex = DUPLEX_HALF;
+-
+- if (ss & AT803X_SS_MDIX)
+- phydev->mdix = ETH_TP_MDI_X;
+- else
+- phydev->mdix = ETH_TP_MDI;
+-
+- switch (FIELD_GET(AT803X_SFC_MDI_CROSSOVER_MODE_M, sfc)) {
+- case AT803X_SFC_MANUAL_MDI:
+- phydev->mdix_ctrl = ETH_TP_MDI;
+- break;
+- case AT803X_SFC_MANUAL_MDIX:
+- phydev->mdix_ctrl = ETH_TP_MDI_X;
+- break;
+- case AT803X_SFC_AUTOMATIC_CROSSOVER:
+- phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+- break;
+- }
+- }
+-
+- return 0;
+-}
+-
+-static int at803x_read_status(struct phy_device *phydev)
+-{
+- struct at803x_ss_mask ss_mask = { 0 };
+- int err, old_link = phydev->link;
+-
+- /* Update the link, but return if there was an error */
+- err = genphy_update_link(phydev);
+- if (err)
+- return err;
+-
+- /* why bother the PHY if nothing can have changed */
+- if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
+- return 0;
+-
+- phydev->speed = SPEED_UNKNOWN;
+- phydev->duplex = DUPLEX_UNKNOWN;
+- phydev->pause = 0;
+- phydev->asym_pause = 0;
+-
+- err = genphy_read_lpa(phydev);
+- if (err < 0)
+- return err;
+-
+- ss_mask.speed_mask = AT803X_SS_SPEED_MASK;
+- ss_mask.speed_shift = __bf_shf(AT803X_SS_SPEED_MASK);
+- err = at803x_read_specific_status(phydev, ss_mask);
+- if (err < 0)
+- return err;
+-
+- if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete)
+- phy_resolve_aneg_pause(phydev);
+-
+- return 0;
+-}
+-
+-static int at803x_config_mdix(struct phy_device *phydev, u8 ctrl)
+-{
+- u16 val;
+-
+- switch (ctrl) {
+- case ETH_TP_MDI:
+- val = AT803X_SFC_MANUAL_MDI;
+- break;
+- case ETH_TP_MDI_X:
+- val = AT803X_SFC_MANUAL_MDIX;
+- break;
+- case ETH_TP_MDI_AUTO:
+- val = AT803X_SFC_AUTOMATIC_CROSSOVER;
+- break;
+- default:
+- return 0;
+- }
+-
+- return phy_modify_changed(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL,
+- AT803X_SFC_MDI_CROSSOVER_MODE_M,
+- FIELD_PREP(AT803X_SFC_MDI_CROSSOVER_MODE_M, val));
+-}
+-
+-static int at803x_prepare_config_aneg(struct phy_device *phydev)
+-{
+- int ret;
+-
+- ret = at803x_config_mdix(phydev, phydev->mdix_ctrl);
+- if (ret < 0)
+- return ret;
+-
+- /* Changes of the midx bits are disruptive to the normal operation;
+- * therefore any changes to these registers must be followed by a
+- * software reset to take effect.
+- */
+- if (ret == 1) {
+- ret = genphy_soft_reset(phydev);
+- if (ret < 0)
+- return ret;
+- }
+-
+- return 0;
+-}
+-
+-static int at803x_config_aneg(struct phy_device *phydev)
+-{
+- struct at803x_priv *priv = phydev->priv;
+- int ret;
+-
+- ret = at803x_prepare_config_aneg(phydev);
+- if (ret)
+- return ret;
+-
+- if (priv->is_1000basex)
+- return genphy_c37_config_aneg(phydev);
+-
+- return genphy_config_aneg(phydev);
+-}
+-
+-static int at803x_get_downshift(struct phy_device *phydev, u8 *d)
+-{
+- int val;
+-
+- val = phy_read(phydev, AT803X_SMART_SPEED);
+- if (val < 0)
+- return val;
+-
+- if (val & AT803X_SMART_SPEED_ENABLE)
+- *d = FIELD_GET(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, val) + 2;
+- else
+- *d = DOWNSHIFT_DEV_DISABLE;
+-
+- return 0;
+-}
+-
+-static int at803x_set_downshift(struct phy_device *phydev, u8 cnt)
+-{
+- u16 mask, set;
+- int ret;
+-
+- switch (cnt) {
+- case DOWNSHIFT_DEV_DEFAULT_COUNT:
+- cnt = AT803X_DEFAULT_DOWNSHIFT;
+- fallthrough;
+- case AT803X_MIN_DOWNSHIFT ... AT803X_MAX_DOWNSHIFT:
+- set = AT803X_SMART_SPEED_ENABLE |
+- AT803X_SMART_SPEED_BYPASS_TIMER |
+- FIELD_PREP(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, cnt - 2);
+- mask = AT803X_SMART_SPEED_RETRY_LIMIT_MASK;
+- break;
+- case DOWNSHIFT_DEV_DISABLE:
+- set = 0;
+- mask = AT803X_SMART_SPEED_ENABLE |
+- AT803X_SMART_SPEED_BYPASS_TIMER;
+- break;
+- default:
+- return -EINVAL;
+- }
+-
+- ret = phy_modify_changed(phydev, AT803X_SMART_SPEED, mask, set);
+-
+- /* After changing the smart speed settings, we need to perform a
+- * software reset, use phy_init_hw() to make sure we set the
+- * reapply any values which might got lost during software reset.
+- */
+- if (ret == 1)
+- ret = phy_init_hw(phydev);
+-
+- return ret;
+-}
+-
+-static int at803x_get_tunable(struct phy_device *phydev,
+- struct ethtool_tunable *tuna, void *data)
+-{
+- switch (tuna->id) {
+- case ETHTOOL_PHY_DOWNSHIFT:
+- return at803x_get_downshift(phydev, data);
+- default:
+- return -EOPNOTSUPP;
+- }
+-}
+-
+-static int at803x_set_tunable(struct phy_device *phydev,
+- struct ethtool_tunable *tuna, const void *data)
+-{
+- switch (tuna->id) {
+- case ETHTOOL_PHY_DOWNSHIFT:
+- return at803x_set_downshift(phydev, *(const u8 *)data);
+- default:
+- return -EOPNOTSUPP;
+- }
+-}
+-
+-static int at803x_cable_test_result_trans(u16 status)
+-{
+- switch (FIELD_GET(AT803X_CDT_STATUS_STAT_MASK, status)) {
+- case AT803X_CDT_STATUS_STAT_NORMAL:
+- return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+- case AT803X_CDT_STATUS_STAT_SHORT:
+- return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+- case AT803X_CDT_STATUS_STAT_OPEN:
+- return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+- case AT803X_CDT_STATUS_STAT_FAIL:
+- default:
+- return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+- }
+-}
+-
+-static bool at803x_cdt_test_failed(u16 status)
+-{
+- return FIELD_GET(AT803X_CDT_STATUS_STAT_MASK, status) ==
+- AT803X_CDT_STATUS_STAT_FAIL;
+-}
+-
+-static bool at803x_cdt_fault_length_valid(u16 status)
+-{
+- switch (FIELD_GET(AT803X_CDT_STATUS_STAT_MASK, status)) {
+- case AT803X_CDT_STATUS_STAT_OPEN:
+- case AT803X_CDT_STATUS_STAT_SHORT:
+- return true;
+- }
+- return false;
+-}
+-
+-static int at803x_cdt_fault_length(int dt)
+-{
+- /* According to the datasheet the distance to the fault is
+- * DELTA_TIME * 0.824 meters.
+- *
+- * The author suspect the correct formula is:
+- *
+- * fault_distance = DELTA_TIME * (c * VF) / 125MHz / 2
+- *
+- * where c is the speed of light, VF is the velocity factor of
+- * the twisted pair cable, 125MHz the counter frequency and
+- * we need to divide by 2 because the hardware will measure the
+- * round trip time to the fault and back to the PHY.
+- *
+- * With a VF of 0.69 we get the factor 0.824 mentioned in the
+- * datasheet.
+- */
+- return (dt * 824) / 10;
+-}
+-
+-static int at803x_cdt_start(struct phy_device *phydev,
+- u32 cdt_start)
+-{
+- return phy_write(phydev, AT803X_CDT, cdt_start);
+-}
+-
+-static int at803x_cdt_wait_for_completion(struct phy_device *phydev,
+- u32 cdt_en)
+-{
+- int val, ret;
+-
+- /* One test run takes about 25ms */
+- ret = phy_read_poll_timeout(phydev, AT803X_CDT, val,
+- !(val & cdt_en),
+- 30000, 100000, true);
+-
+- return ret < 0 ? ret : 0;
+-}
+-
+-static int at803x_cable_test_one_pair(struct phy_device *phydev, int pair)
+-{
+- static const int ethtool_pair[] = {
+- ETHTOOL_A_CABLE_PAIR_A,
+- ETHTOOL_A_CABLE_PAIR_B,
+- ETHTOOL_A_CABLE_PAIR_C,
+- ETHTOOL_A_CABLE_PAIR_D,
+- };
+- int ret, val;
+-
+- val = FIELD_PREP(AT803X_CDT_MDI_PAIR_MASK, pair) |
+- AT803X_CDT_ENABLE_TEST;
+- ret = at803x_cdt_start(phydev, val);
+- if (ret)
+- return ret;
+-
+- ret = at803x_cdt_wait_for_completion(phydev, AT803X_CDT_ENABLE_TEST);
+- if (ret)
+- return ret;
+-
+- val = phy_read(phydev, AT803X_CDT_STATUS);
+- if (val < 0)
+- return val;
+-
+- if (at803x_cdt_test_failed(val))
+- return 0;
+-
+- ethnl_cable_test_result(phydev, ethtool_pair[pair],
+- at803x_cable_test_result_trans(val));
+-
+- if (at803x_cdt_fault_length_valid(val)) {
+- val = FIELD_GET(AT803X_CDT_STATUS_DELTA_TIME_MASK, val);
+- ethnl_cable_test_fault_length(phydev, ethtool_pair[pair],
+- at803x_cdt_fault_length(val));
+- }
+-
+- return 1;
+-}
+-
+-static int at803x_cable_test_get_status(struct phy_device *phydev,
+- bool *finished, unsigned long pair_mask)
+-{
+- int retries = 20;
+- int pair, ret;
+-
+- *finished = false;
+-
+- /* According to the datasheet the CDT can be performed when
+- * there is no link partner or when the link partner is
+- * auto-negotiating. Starting the test will restart the AN
+- * automatically. It seems that doing this repeatedly we will
+- * get a slot where our link partner won't disturb our
+- * measurement.
+- */
+- while (pair_mask && retries--) {
+- for_each_set_bit(pair, &pair_mask, 4) {
+- ret = at803x_cable_test_one_pair(phydev, pair);
+- if (ret < 0)
+- return ret;
+- if (ret)
+- clear_bit(pair, &pair_mask);
+- }
+- if (pair_mask)
+- msleep(250);
+- }
+-
+- *finished = true;
+-
+- return 0;
+-}
+-
+-static void at803x_cable_test_autoneg(struct phy_device *phydev)
+-{
+- /* Enable auto-negotiation, but advertise no capabilities, no link
+- * will be established. A restart of the auto-negotiation is not
+- * required, because the cable test will automatically break the link.
+- */
+- phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
+- phy_write(phydev, MII_ADVERTISE, ADVERTISE_CSMA);
+-}
+-
+-static int at803x_cable_test_start(struct phy_device *phydev)
+-{
+- at803x_cable_test_autoneg(phydev);
+- /* we do all the (time consuming) work later */
+- return 0;
+-}
+-
+-static int at8031_rgmii_reg_set_voltage_sel(struct regulator_dev *rdev,
+- unsigned int selector)
+-{
+- struct phy_device *phydev = rdev_get_drvdata(rdev);
+-
+- if (selector)
+- return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
+- 0, AT803X_DEBUG_RGMII_1V8);
+- else
+- return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
+- AT803X_DEBUG_RGMII_1V8, 0);
+-}
+-
+-static int at8031_rgmii_reg_get_voltage_sel(struct regulator_dev *rdev)
+-{
+- struct phy_device *phydev = rdev_get_drvdata(rdev);
+- int val;
+-
+- val = at803x_debug_reg_read(phydev, AT803X_DEBUG_REG_1F);
+- if (val < 0)
+- return val;
+-
+- return (val & AT803X_DEBUG_RGMII_1V8) ? 1 : 0;
+-}
+-
+-static const struct regulator_ops vddio_regulator_ops = {
+- .list_voltage = regulator_list_voltage_table,
+- .set_voltage_sel = at8031_rgmii_reg_set_voltage_sel,
+- .get_voltage_sel = at8031_rgmii_reg_get_voltage_sel,
+-};
+-
+-static const unsigned int vddio_voltage_table[] = {
+- 1500000,
+- 1800000,
+-};
+-
+-static const struct regulator_desc vddio_desc = {
+- .name = "vddio",
+- .of_match = of_match_ptr("vddio-regulator"),
+- .n_voltages = ARRAY_SIZE(vddio_voltage_table),
+- .volt_table = vddio_voltage_table,
+- .ops = &vddio_regulator_ops,
+- .type = REGULATOR_VOLTAGE,
+- .owner = THIS_MODULE,
+-};
+-
+-static const struct regulator_ops vddh_regulator_ops = {
+-};
+-
+-static const struct regulator_desc vddh_desc = {
+- .name = "vddh",
+- .of_match = of_match_ptr("vddh-regulator"),
+- .n_voltages = 1,
+- .fixed_uV = 2500000,
+- .ops = &vddh_regulator_ops,
+- .type = REGULATOR_VOLTAGE,
+- .owner = THIS_MODULE,
+-};
+-
+-static int at8031_register_regulators(struct phy_device *phydev)
+-{
+- struct at803x_priv *priv = phydev->priv;
+- struct device *dev = &phydev->mdio.dev;
+- struct regulator_config config = { };
+-
+- config.dev = dev;
+- config.driver_data = phydev;
+-
+- priv->vddio_rdev = devm_regulator_register(dev, &vddio_desc, &config);
+- if (IS_ERR(priv->vddio_rdev)) {
+- phydev_err(phydev, "failed to register VDDIO regulator\n");
+- return PTR_ERR(priv->vddio_rdev);
+- }
+-
+- priv->vddh_rdev = devm_regulator_register(dev, &vddh_desc, &config);
+- if (IS_ERR(priv->vddh_rdev)) {
+- phydev_err(phydev, "failed to register VDDH regulator\n");
+- return PTR_ERR(priv->vddh_rdev);
+- }
+-
+- return 0;
+-}
+-
+-static int at8031_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
+-{
+- struct phy_device *phydev = upstream;
+- __ETHTOOL_DECLARE_LINK_MODE_MASK(phy_support);
+- __ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
+- DECLARE_PHY_INTERFACE_MASK(interfaces);
+- phy_interface_t iface;
+-
+- linkmode_zero(phy_support);
+- phylink_set(phy_support, 1000baseX_Full);
+- phylink_set(phy_support, 1000baseT_Full);
+- phylink_set(phy_support, Autoneg);
+- phylink_set(phy_support, Pause);
+- phylink_set(phy_support, Asym_Pause);
+-
+- linkmode_zero(sfp_support);
+- sfp_parse_support(phydev->sfp_bus, id, sfp_support, interfaces);
+- /* Some modules support 10G modes as well as others we support.
+- * Mask out non-supported modes so the correct interface is picked.
+- */
+- linkmode_and(sfp_support, phy_support, sfp_support);
+-
+- if (linkmode_empty(sfp_support)) {
+- dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n");
+- return -EINVAL;
+- }
+-
+- iface = sfp_select_interface(phydev->sfp_bus, sfp_support);
+-
+- /* Only 1000Base-X is supported by AR8031/8033 as the downstream SerDes
+- * interface for use with SFP modules.
+- * However, some copper modules detected as having a preferred SGMII
+- * interface do default to and function in 1000Base-X mode, so just
+- * print a warning and allow such modules, as they may have some chance
+- * of working.
+- */
+- if (iface == PHY_INTERFACE_MODE_SGMII)
+- dev_warn(&phydev->mdio.dev, "module may not function if 1000Base-X not supported\n");
+- else if (iface != PHY_INTERFACE_MODE_1000BASEX)
+- return -EINVAL;
+-
+- return 0;
+-}
+-
+-static const struct sfp_upstream_ops at8031_sfp_ops = {
+- .attach = phy_sfp_attach,
+- .detach = phy_sfp_detach,
+- .module_insert = at8031_sfp_insert,
+-};
+-
+-static int at8031_parse_dt(struct phy_device *phydev)
+-{
+- struct device_node *node = phydev->mdio.dev.of_node;
+- struct at803x_priv *priv = phydev->priv;
+- int ret;
+-
+- if (of_property_read_bool(node, "qca,keep-pll-enabled"))
+- priv->flags |= AT803X_KEEP_PLL_ENABLED;
+-
+- ret = at8031_register_regulators(phydev);
+- if (ret < 0)
+- return ret;
+-
+- ret = devm_regulator_get_enable_optional(&phydev->mdio.dev,
+- "vddio");
+- if (ret) {
+- phydev_err(phydev, "failed to get VDDIO regulator\n");
+- return ret;
+- }
+-
+- /* Only AR8031/8033 support 1000Base-X for SFP modules */
+- return phy_sfp_probe(phydev, &at8031_sfp_ops);
+-}
+-
+-static int at8031_probe(struct phy_device *phydev)
+-{
+- struct at803x_priv *priv = phydev->priv;
+- int mode_cfg;
+- int ccr;
+- int ret;
+-
+- ret = at803x_probe(phydev);
+- if (ret)
+- return ret;
+-
+- /* Only supported on AR8031/AR8033, the AR8030/AR8035 use strapping
+- * options.
+- */
+- ret = at8031_parse_dt(phydev);
+- if (ret)
+- return ret;
+-
+- ccr = phy_read(phydev, AT803X_REG_CHIP_CONFIG);
+- if (ccr < 0)
+- return ccr;
+- mode_cfg = ccr & AT803X_MODE_CFG_MASK;
+-
+- switch (mode_cfg) {
+- case AT803X_MODE_CFG_BX1000_RGMII_50OHM:
+- case AT803X_MODE_CFG_BX1000_RGMII_75OHM:
+- priv->is_1000basex = true;
+- fallthrough;
+- case AT803X_MODE_CFG_FX100_RGMII_50OHM:
+- case AT803X_MODE_CFG_FX100_RGMII_75OHM:
+- priv->is_fiber = true;
+- break;
+- }
+-
+- /* Disable WoL in 1588 register which is enabled
+- * by default
+- */
+- return phy_modify_mmd(phydev, MDIO_MMD_PCS,
+- AT803X_PHY_MMD3_WOL_CTRL,
+- AT803X_WOL_EN, 0);
+-}
+-
+-static int at8031_config_init(struct phy_device *phydev)
+-{
+- struct at803x_priv *priv = phydev->priv;
+- int ret;
+-
+- /* Some bootloaders leave the fiber page selected.
+- * Switch to the appropriate page (fiber or copper), as otherwise we
+- * read the PHY capabilities from the wrong page.
+- */
+- phy_lock_mdio_bus(phydev);
+- ret = at803x_write_page(phydev,
+- priv->is_fiber ? AT803X_PAGE_FIBER :
+- AT803X_PAGE_COPPER);
+- phy_unlock_mdio_bus(phydev);
+- if (ret)
+- return ret;
+-
+- ret = at8031_pll_config(phydev);
+- if (ret < 0)
+- return ret;
+-
+- return at803x_config_init(phydev);
+-}
+-
+-static int at8031_set_wol(struct phy_device *phydev,
+- struct ethtool_wolinfo *wol)
+-{
+- int ret;
+-
+- /* First setup MAC address and enable WOL interrupt */
+- ret = at803x_set_wol(phydev, wol);
+- if (ret)
+- return ret;
+-
+- if (wol->wolopts & WAKE_MAGIC)
+- /* Enable WOL function for 1588 */
+- ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
+- AT803X_PHY_MMD3_WOL_CTRL,
+- 0, AT803X_WOL_EN);
+- else
+- /* Disable WoL function for 1588 */
+- ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
+- AT803X_PHY_MMD3_WOL_CTRL,
+- AT803X_WOL_EN, 0);
+-
+- return ret;
+-}
+-
+-static int at8031_config_intr(struct phy_device *phydev)
+-{
+- struct at803x_priv *priv = phydev->priv;
+- int err, value = 0;
+-
+- if (phydev->interrupts == PHY_INTERRUPT_ENABLED &&
+- priv->is_fiber) {
+- /* Clear any pending interrupts */
+- err = at803x_ack_interrupt(phydev);
+- if (err)
+- return err;
+-
+- value |= AT803X_INTR_ENABLE_LINK_FAIL_BX;
+- value |= AT803X_INTR_ENABLE_LINK_SUCCESS_BX;
+-
+- err = phy_set_bits(phydev, AT803X_INTR_ENABLE, value);
+- if (err)
+- return err;
+- }
+-
+- return at803x_config_intr(phydev);
+-}
+-
+-/* AR8031 and AR8033 share the same read status logic */
+-static int at8031_read_status(struct phy_device *phydev)
+-{
+- struct at803x_priv *priv = phydev->priv;
+-
+- if (priv->is_1000basex)
+- return genphy_c37_read_status(phydev);
+-
+- return at803x_read_status(phydev);
+-}
+-
+-/* AR8031 and AR8035 share the same cable test get status reg */
+-static int at8031_cable_test_get_status(struct phy_device *phydev,
+- bool *finished)
+-{
+- return at803x_cable_test_get_status(phydev, finished, 0xf);
+-}
+-
+-/* AR8031 and AR8035 share the same cable test start logic */
+-static int at8031_cable_test_start(struct phy_device *phydev)
+-{
+- at803x_cable_test_autoneg(phydev);
+- phy_write(phydev, MII_CTRL1000, 0);
+- /* we do all the (time consuming) work later */
+- return 0;
+-}
+-
+-/* AR8032, AR9331 and QCA9561 share the same cable test get status reg */
+-static int at8032_cable_test_get_status(struct phy_device *phydev,
+- bool *finished)
+-{
+- return at803x_cable_test_get_status(phydev, finished, 0x3);
+-}
+-
+-static int at8035_parse_dt(struct phy_device *phydev)
+-{
+- struct at803x_priv *priv = phydev->priv;
+-
+- /* Mask is set by the generic at803x_parse_dt
+- * if property is set. Assume property is set
+- * with the mask not zero.
+- */
+- if (priv->clk_25m_mask) {
+- /* Fixup for the AR8030/AR8035. This chip has another mask and
+- * doesn't support the DSP reference. Eg. the lowest bit of the
+- * mask. The upper two bits select the same frequencies. Mask
+- * the lowest bit here.
+- *
+- * Warning:
+- * There was no datasheet for the AR8030 available so this is
+- * just a guess. But the AR8035 is listed as pin compatible
+- * to the AR8030 so there might be a good chance it works on
+- * the AR8030 too.
+- */
+- priv->clk_25m_reg &= AT8035_CLK_OUT_MASK;
+- priv->clk_25m_mask &= AT8035_CLK_OUT_MASK;
+- }
+-
+- return 0;
+-}
+-
+-/* AR8030 and AR8035 shared the same special mask for clk_25m */
+-static int at8035_probe(struct phy_device *phydev)
+-{
+- int ret;
+-
+- ret = at803x_probe(phydev);
+- if (ret)
+- return ret;
+-
+- return at8035_parse_dt(phydev);
+-}
+-
+-static int qca83xx_config_init(struct phy_device *phydev)
+-{
+- u8 switch_revision;
+-
+- switch_revision = phydev->dev_flags & QCA8K_DEVFLAGS_REVISION_MASK;
+-
+- switch (switch_revision) {
+- case 1:
+- /* For 100M waveform */
+- at803x_debug_reg_write(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, 0x02ea);
+- /* Turn on Gigabit clock */
+- at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x68a0);
+- break;
+-
+- case 2:
+- phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0);
+- fallthrough;
+- case 4:
+- phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_AZ_DEBUG, 0x803f);
+- 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;
+- }
+-
+- /* Following original QCA sourcecode set port to prefer master */
+- phy_set_bits(phydev, MII_CTRL1000, CTL1000_PREFER_MASTER);
+-
+- return 0;
+-}
+-
+-static int qca8327_config_init(struct phy_device *phydev)
+-{
+- /* QCA8327 require DAC amplitude adjustment for 100m set to +6%.
+- * Disable on init and enable only with 100m speed following
+- * qca original source code.
+- */
+- at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
+- QCA8327_DEBUG_MANU_CTRL_EN, 0);
+-
+- return qca83xx_config_init(phydev);
+-}
+-
+-static void qca83xx_link_change_notify(struct phy_device *phydev)
+-{
+- /* 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_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_ANALOG_TEST_CTRL,
+- QCA8327_DEBUG_MANU_CTRL_EN, 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;
+-
+- usleep_range(1000, 2000);
+-
+- return 0;
+-}
+-
+-static int qca83xx_suspend(struct phy_device *phydev)
+-{
+- 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,
+- AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE |
+- AT803X_DEBUG_HIB_CTRL_SEL_RST_80U, 0);
+-
+- return 0;
+-}
+-
+-static int qca8337_suspend(struct phy_device *phydev)
+-{
+- /* Only QCA8337 support actual suspend. */
+- genphy_suspend(phydev);
+-
+- return qca83xx_suspend(phydev);
+-}
+-
+-static int qca8327_suspend(struct phy_device *phydev)
+-{
+- u16 mask = 0;
+-
+- /* QCA8327 cause port unreliability when phy suspend
+- * is set.
+- */
+- mask |= ~(BMCR_SPEED1000 | BMCR_FULLDPLX);
+- phy_modify(phydev, MII_BMCR, mask, 0);
+-
+- return qca83xx_suspend(phydev);
+-}
+-
+-static int qca808x_phy_fast_retrain_config(struct phy_device *phydev)
+-{
+- int ret;
+-
+- /* Enable fast retrain */
+- ret = genphy_c45_fast_retrain(phydev, true);
+- if (ret)
+- return ret;
+-
+- phy_write_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_TOP_OPTION1,
+- QCA808X_TOP_OPTION1_DATA);
+- phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB,
+- QCA808X_MSE_THRESHOLD_20DB_VALUE);
+- phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB,
+- QCA808X_MSE_THRESHOLD_17DB_VALUE);
+- phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB,
+- QCA808X_MSE_THRESHOLD_27DB_VALUE);
+- phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB,
+- QCA808X_MSE_THRESHOLD_28DB_VALUE);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_1,
+- QCA808X_MMD3_DEBUG_1_VALUE);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_4,
+- QCA808X_MMD3_DEBUG_4_VALUE);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_5,
+- QCA808X_MMD3_DEBUG_5_VALUE);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_3,
+- QCA808X_MMD3_DEBUG_3_VALUE);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_6,
+- QCA808X_MMD3_DEBUG_6_VALUE);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_2,
+- QCA808X_MMD3_DEBUG_2_VALUE);
+-
+- return 0;
+-}
+-
+-static int qca808x_phy_ms_seed_enable(struct phy_device *phydev, bool enable)
+-{
+- u16 seed_value;
+-
+- if (!enable)
+- return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
+- QCA808X_MASTER_SLAVE_SEED_ENABLE, 0);
+-
+- seed_value = prandom_u32_max(QCA808X_MASTER_SLAVE_SEED_RANGE);
+- return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
+- QCA808X_MASTER_SLAVE_SEED_CFG | QCA808X_MASTER_SLAVE_SEED_ENABLE,
+- FIELD_PREP(QCA808X_MASTER_SLAVE_SEED_CFG, seed_value) |
+- QCA808X_MASTER_SLAVE_SEED_ENABLE);
+-}
+-
+-static bool qca808x_is_prefer_master(struct phy_device *phydev)
+-{
+- return (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_FORCE) ||
+- (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_PREFERRED);
+-}
+-
+-static bool qca808x_has_fast_retrain_or_slave_seed(struct phy_device *phydev)
+-{
+- return linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
+-}
+-
+-static int qca808x_config_init(struct phy_device *phydev)
+-{
+- int ret;
+-
+- /* Active adc&vga on 802.3az for the link 1000M and 100M */
+- ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_ADDR_CLD_CTRL7,
+- QCA808X_8023AZ_AFE_CTRL_MASK, QCA808X_8023AZ_AFE_EN);
+- if (ret)
+- return ret;
+-
+- /* Adjust the threshold on 802.3az for the link 1000M */
+- ret = phy_write_mmd(phydev, MDIO_MMD_PCS,
+- QCA808X_PHY_MMD3_AZ_TRAINING_CTRL,
+- QCA808X_MMD3_AZ_TRAINING_VAL);
+- if (ret)
+- return ret;
+-
+- if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
+- /* Config the fast retrain for the link 2500M */
+- ret = qca808x_phy_fast_retrain_config(phydev);
+- if (ret)
+- return ret;
+-
+- ret = genphy_read_master_slave(phydev);
+- if (ret < 0)
+- return ret;
+-
+- if (!qca808x_is_prefer_master(phydev)) {
+- /* Enable seed and configure lower ramdom seed to make phy
+- * linked as slave mode.
+- */
+- ret = qca808x_phy_ms_seed_enable(phydev, true);
+- if (ret)
+- return ret;
+- }
+- }
+-
+- /* Configure adc threshold as 100mv for the link 10M */
+- return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_ADC_THRESHOLD,
+- QCA808X_ADC_THRESHOLD_MASK,
+- QCA808X_ADC_THRESHOLD_100MV);
+-}
+-
+-static int qca808x_read_status(struct phy_device *phydev)
+-{
+- struct at803x_ss_mask ss_mask = { 0 };
+- int ret;
+-
+- ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
+- if (ret < 0)
+- return ret;
+-
+- linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->lp_advertising,
+- ret & MDIO_AN_10GBT_STAT_LP2_5G);
+-
+- ret = genphy_read_status(phydev);
+- if (ret)
+- return ret;
+-
+- /* qca8081 takes the different bits for speed value from at803x */
+- ss_mask.speed_mask = QCA808X_SS_SPEED_MASK;
+- ss_mask.speed_shift = __bf_shf(QCA808X_SS_SPEED_MASK);
+- ret = at803x_read_specific_status(phydev, ss_mask);
+- if (ret < 0)
+- return ret;
+-
+- if (phydev->link) {
+- if (phydev->speed == SPEED_2500)
+- phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+- else
+- phydev->interface = PHY_INTERFACE_MODE_SGMII;
+- } else {
+- /* generate seed as a lower random value to make PHY linked as SLAVE easily,
+- * except for master/slave configuration fault detected or the master mode
+- * preferred.
+- *
+- * the reason for not putting this code into the function link_change_notify is
+- * the corner case where the link partner is also the qca8081 PHY and the seed
+- * value is configured as the same value, the link can't be up and no link change
+- * occurs.
+- */
+- if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
+- if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR ||
+- qca808x_is_prefer_master(phydev)) {
+- qca808x_phy_ms_seed_enable(phydev, false);
+- } else {
+- qca808x_phy_ms_seed_enable(phydev, true);
+- }
+- }
+- }
+-
+- return 0;
+-}
+-
+-static int qca808x_soft_reset(struct phy_device *phydev)
+-{
+- int ret;
+-
+- ret = genphy_soft_reset(phydev);
+- if (ret < 0)
+- return ret;
+-
+- if (qca808x_has_fast_retrain_or_slave_seed(phydev))
+- ret = qca808x_phy_ms_seed_enable(phydev, true);
+-
+- return ret;
+-}
+-
+-static bool qca808x_cdt_fault_length_valid(int cdt_code)
+-{
+- switch (cdt_code) {
+- case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
+- case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
+- return true;
+- default:
+- return false;
+- }
+-}
+-
+-static int qca808x_cable_test_result_trans(int cdt_code)
+-{
+- switch (cdt_code) {
+- case QCA808X_CDT_STATUS_STAT_NORMAL:
+- return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+- case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
+- return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+- case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
+- return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
+- return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
+- case QCA808X_CDT_STATUS_STAT_FAIL:
+- default:
+- return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+- }
+-}
+-
+-static int qca808x_cdt_fault_length(struct phy_device *phydev, int pair,
+- int result)
+-{
+- int val;
+- u32 cdt_length_reg = 0;
+-
+- switch (pair) {
+- case ETHTOOL_A_CABLE_PAIR_A:
+- cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_A;
+- break;
+- case ETHTOOL_A_CABLE_PAIR_B:
+- cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_B;
+- break;
+- case ETHTOOL_A_CABLE_PAIR_C:
+- cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_C;
+- break;
+- case ETHTOOL_A_CABLE_PAIR_D:
+- cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_D;
+- break;
+- default:
+- return -EINVAL;
+- }
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_PCS, cdt_length_reg);
+- if (val < 0)
+- return val;
+-
+- if (result == ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT)
+- val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_SAME_SHORT, val);
+- else
+- val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT, val);
+-
+- return at803x_cdt_fault_length(val);
+-}
+-
+-static int qca808x_cable_test_start(struct phy_device *phydev)
+-{
+- int ret;
+-
+- /* perform CDT with the following configs:
+- * 1. disable hibernation.
+- * 2. force PHY working in MDI mode.
+- * 3. for PHY working in 1000BaseT.
+- * 4. configure the threshold.
+- */
+-
+- ret = at803x_debug_reg_mask(phydev, QCA808X_DBG_AN_TEST, QCA808X_HIBERNATION_EN, 0);
+- if (ret < 0)
+- return ret;
+-
+- ret = at803x_config_mdix(phydev, ETH_TP_MDI);
+- if (ret < 0)
+- return ret;
+-
+- /* Force 1000base-T needs to configure PMA/PMD and MII_BMCR */
+- phydev->duplex = DUPLEX_FULL;
+- phydev->speed = SPEED_1000;
+- ret = genphy_c45_pma_setup_forced(phydev);
+- if (ret < 0)
+- return ret;
+-
+- ret = genphy_setup_forced(phydev);
+- if (ret < 0)
+- return ret;
+-
+- /* configure the thresholds for open, short, pair ok test */
+- phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8074, 0xc040);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8076, 0xc040);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8077, 0xa060);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8078, 0xc050);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807a, 0xc060);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807e, 0xb060);
+-
+- return 0;
+-}
+-
+-static int qca808x_cable_test_get_pair_status(struct phy_device *phydev, u8 pair,
+- u16 status)
+-{
+- int length, result;
+- u16 pair_code;
+-
+- switch (pair) {
+- case ETHTOOL_A_CABLE_PAIR_A:
+- pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, status);
+- break;
+- case ETHTOOL_A_CABLE_PAIR_B:
+- pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, status);
+- break;
+- case ETHTOOL_A_CABLE_PAIR_C:
+- pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, status);
+- break;
+- case ETHTOOL_A_CABLE_PAIR_D:
+- pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, status);
+- break;
+- default:
+- return -EINVAL;
+- }
+-
+- result = qca808x_cable_test_result_trans(pair_code);
+- ethnl_cable_test_result(phydev, pair, result);
+-
+- if (qca808x_cdt_fault_length_valid(pair_code)) {
+- length = qca808x_cdt_fault_length(phydev, pair, result);
+- ethnl_cable_test_fault_length(phydev, pair, length);
+- }
+-
+- return 0;
+-}
+-
+-static int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished)
+-{
+- int ret, val;
+-
+- *finished = false;
+-
+- val = QCA808X_CDT_ENABLE_TEST |
+- QCA808X_CDT_LENGTH_UNIT;
+- ret = at803x_cdt_start(phydev, val);
+- if (ret)
+- return ret;
+-
+- ret = at803x_cdt_wait_for_completion(phydev, QCA808X_CDT_ENABLE_TEST);
+- if (ret)
+- return ret;
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_PCS, QCA808X_MMD3_CDT_STATUS);
+- if (val < 0)
+- return val;
+-
+- ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_A, val);
+- if (ret)
+- return ret;
+-
+- ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_B, val);
+- if (ret)
+- return ret;
+-
+- ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_C, val);
+- if (ret)
+- return ret;
+-
+- ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_D, val);
+- if (ret)
+- return ret;
+-
+- *finished = true;
+-
+- return 0;
+-}
+-
+-static int qca808x_get_features(struct phy_device *phydev)
+-{
+- int ret;
+-
+- ret = genphy_c45_pma_read_abilities(phydev);
+- if (ret)
+- return ret;
+-
+- /* The autoneg ability is not existed in bit3 of MMD7.1,
+- * but it is supported by qca808x PHY, so we add it here
+- * manually.
+- */
+- linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
+-
+- /* As for the qca8081 1G version chip, the 2500baseT ability is also
+- * existed in the bit0 of MMD1.21, we need to remove it manually if
+- * it is the qca8081 1G chip according to the bit0 of MMD7.0x901d.
+- */
+- ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_CHIP_TYPE);
+- if (ret < 0)
+- return ret;
+-
+- if (QCA808X_PHY_CHIP_TYPE_1G & ret)
+- linkmode_clear_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
+-
+- return 0;
+-}
+-
+-static int qca808x_config_aneg(struct phy_device *phydev)
+-{
+- int phy_ctrl = 0;
+- int ret;
+-
+- ret = at803x_prepare_config_aneg(phydev);
+- if (ret)
+- return ret;
+-
+- /* The reg MII_BMCR also needs to be configured for force mode, the
+- * genphy_config_aneg is also needed.
+- */
+- if (phydev->autoneg == AUTONEG_DISABLE)
+- genphy_c45_pma_setup_forced(phydev);
+-
+- if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->advertising))
+- phy_ctrl = MDIO_AN_10GBT_CTRL_ADV2_5G;
+-
+- ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
+- MDIO_AN_10GBT_CTRL_ADV2_5G, phy_ctrl);
+- if (ret < 0)
+- return ret;
+-
+- return __genphy_config_aneg(phydev, ret);
+-}
+-
+-static void qca808x_link_change_notify(struct phy_device *phydev)
+-{
+- /* Assert interface sgmii fifo on link down, deassert it on link up,
+- * the interface device address is always phy address added by 1.
+- */
+- mdiobus_c45_modify_changed(phydev->mdio.bus, phydev->mdio.addr + 1,
+- MDIO_MMD_PMAPMD, QCA8081_PHY_SERDES_MMD1_FIFO_CTRL,
+- QCA8081_PHY_FIFO_RSTN,
+- phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
+-}
+-
+-static int qca808x_led_parse_netdev(struct phy_device *phydev, unsigned long rules,
+- u16 *offload_trigger)
+-{
+- /* Parsing specific to netdev trigger */
+- if (test_bit(TRIGGER_NETDEV_TX, &rules))
+- *offload_trigger |= QCA808X_LED_TX_BLINK;
+- if (test_bit(TRIGGER_NETDEV_RX, &rules))
+- *offload_trigger |= QCA808X_LED_RX_BLINK;
+- if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
+- *offload_trigger |= QCA808X_LED_SPEED10_ON;
+- if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
+- *offload_trigger |= QCA808X_LED_SPEED100_ON;
+- if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
+- *offload_trigger |= QCA808X_LED_SPEED1000_ON;
+- if (test_bit(TRIGGER_NETDEV_LINK_2500, &rules))
+- *offload_trigger |= QCA808X_LED_SPEED2500_ON;
+- if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
+- *offload_trigger |= QCA808X_LED_HALF_DUPLEX_ON;
+- if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
+- *offload_trigger |= QCA808X_LED_FULL_DUPLEX_ON;
+-
+- if (rules && !*offload_trigger)
+- return -EOPNOTSUPP;
+-
+- /* Enable BLINK_CHECK_BYPASS by default to make the LED
+- * blink even with duplex or speed mode not enabled.
+- */
+- *offload_trigger |= QCA808X_LED_BLINK_CHECK_BYPASS;
+-
+- return 0;
+-}
+-
+-static int qca808x_led_hw_control_enable(struct phy_device *phydev, u8 index)
+-{
+- u16 reg;
+-
+- if (index > 2)
+- return -EINVAL;
+-
+- reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+- return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
+- QCA808X_LED_FORCE_EN);
+-}
+-
+-static int qca808x_led_hw_is_supported(struct phy_device *phydev, u8 index,
+- unsigned long rules)
+-{
+- u16 offload_trigger = 0;
+-
+- if (index > 2)
+- return -EINVAL;
+-
+- return qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
+-}
+-
+-static int qca808x_led_hw_control_set(struct phy_device *phydev, u8 index,
+- unsigned long rules)
+-{
+- u16 reg, offload_trigger = 0;
+- int ret;
+-
+- if (index > 2)
+- return -EINVAL;
+-
+- reg = QCA808X_MMD7_LED_CTRL(index);
+-
+- ret = qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
+- if (ret)
+- return ret;
+-
+- ret = qca808x_led_hw_control_enable(phydev, index);
+- if (ret)
+- return ret;
+-
+- return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
+- QCA808X_LED_PATTERN_MASK,
+- offload_trigger);
+-}
+-
+-static bool qca808x_led_hw_control_status(struct phy_device *phydev, u8 index)
+-{
+- u16 reg;
+- int val;
+-
+- if (index > 2)
+- return false;
+-
+- reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
+-
+- return !(val & QCA808X_LED_FORCE_EN);
+-}
+-
+-static int qca808x_led_hw_control_get(struct phy_device *phydev, u8 index,
+- unsigned long *rules)
+-{
+- u16 reg;
+- int val;
+-
+- if (index > 2)
+- return -EINVAL;
+-
+- /* Check if we have hw control enabled */
+- if (qca808x_led_hw_control_status(phydev, index))
+- return -EINVAL;
+-
+- reg = QCA808X_MMD7_LED_CTRL(index);
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
+- if (val & QCA808X_LED_TX_BLINK)
+- set_bit(TRIGGER_NETDEV_TX, rules);
+- if (val & QCA808X_LED_RX_BLINK)
+- set_bit(TRIGGER_NETDEV_RX, rules);
+- if (val & QCA808X_LED_SPEED10_ON)
+- set_bit(TRIGGER_NETDEV_LINK_10, rules);
+- if (val & QCA808X_LED_SPEED100_ON)
+- set_bit(TRIGGER_NETDEV_LINK_100, rules);
+- if (val & QCA808X_LED_SPEED1000_ON)
+- set_bit(TRIGGER_NETDEV_LINK_1000, rules);
+- if (val & QCA808X_LED_SPEED2500_ON)
+- set_bit(TRIGGER_NETDEV_LINK_2500, rules);
+- if (val & QCA808X_LED_HALF_DUPLEX_ON)
+- set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
+- if (val & QCA808X_LED_FULL_DUPLEX_ON)
+- set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
+-
+- return 0;
+-}
+-
+-static int qca808x_led_hw_control_reset(struct phy_device *phydev, u8 index)
+-{
+- u16 reg;
+-
+- if (index > 2)
+- return -EINVAL;
+-
+- reg = QCA808X_MMD7_LED_CTRL(index);
+-
+- return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
+- QCA808X_LED_PATTERN_MASK);
+-}
+-
+-static int qca808x_led_brightness_set(struct phy_device *phydev,
+- u8 index, enum led_brightness value)
+-{
+- u16 reg;
+- int ret;
+-
+- if (index > 2)
+- return -EINVAL;
+-
+- if (!value) {
+- ret = qca808x_led_hw_control_reset(phydev, index);
+- if (ret)
+- return ret;
+- }
+-
+- reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+- return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
+- QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
+- QCA808X_LED_FORCE_EN | value ? QCA808X_LED_FORCE_ON :
+- QCA808X_LED_FORCE_OFF);
+-}
+-
+-static int qca808x_led_blink_set(struct phy_device *phydev, u8 index,
+- unsigned long *delay_on,
+- unsigned long *delay_off)
+-{
+- int ret;
+- u16 reg;
+-
+- if (index > 2)
+- return -EINVAL;
+-
+- reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+- /* Set blink to 50% off, 50% on at 4Hz by default */
+- ret = phy_modify_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_LED_GLOBAL,
+- QCA808X_LED_BLINK_FREQ_MASK | QCA808X_LED_BLINK_DUTY_MASK,
+- QCA808X_LED_BLINK_FREQ_4HZ | QCA808X_LED_BLINK_DUTY_50_50);
+- if (ret)
+- return ret;
+-
+- /* We use BLINK_1 for normal blinking */
+- ret = phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
+- QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
+- QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_BLINK_1);
+- if (ret)
+- return ret;
+-
+- /* We set blink to 4Hz, aka 250ms */
+- *delay_on = 250 / 2;
+- *delay_off = 250 / 2;
+-
+- return 0;
+-}
+-
+-static int qca808x_led_polarity_set(struct phy_device *phydev, int index,
+- unsigned long modes)
+-{
+- struct at803x_priv *priv = phydev->priv;
+- bool active_low = false;
+- u32 mode;
+-
+- for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
+- switch (mode) {
+- case PHY_LED_ACTIVE_LOW:
+- active_low = true;
+- break;
+- default:
+- return -EINVAL;
+- }
+- }
+-
+- /* PHY polarity is global and can't be set per LED.
+- * To detect this, check if last requested polarity mode
+- * match the new one.
+- */
+- if (priv->led_polarity_mode >= 0 &&
+- priv->led_polarity_mode != active_low) {
+- phydev_err(phydev, "PHY polarity is global. Mismatched polarity on different LED\n");
+- return -EINVAL;
+- }
+-
+- /* Save the last PHY polarity mode */
+- priv->led_polarity_mode = active_low;
+-
+- return phy_modify_mmd(phydev, MDIO_MMD_AN,
+- QCA808X_MMD7_LED_POLARITY_CTRL,
+- QCA808X_LED_ACTIVE_HIGH,
+- active_low ? 0 : QCA808X_LED_ACTIVE_HIGH);
+-}
+-
+-static struct phy_driver at803x_driver[] = {
+-{
+- /* Qualcomm Atheros AR8035 */
+- PHY_ID_MATCH_EXACT(ATH8035_PHY_ID),
+- .name = "Qualcomm Atheros AR8035",
+- .flags = PHY_POLL_CABLE_TEST,
+- .probe = at8035_probe,
+- .config_aneg = at803x_config_aneg,
+- .config_init = at803x_config_init,
+- .soft_reset = genphy_soft_reset,
+- .set_wol = at803x_set_wol,
+- .get_wol = at803x_get_wol,
+- .suspend = at803x_suspend,
+- .resume = at803x_resume,
+- /* PHY_GBIT_FEATURES */
+- .read_status = at803x_read_status,
+- .config_intr = at803x_config_intr,
+- .handle_interrupt = at803x_handle_interrupt,
+- .get_tunable = at803x_get_tunable,
+- .set_tunable = at803x_set_tunable,
+- .cable_test_start = at8031_cable_test_start,
+- .cable_test_get_status = at8031_cable_test_get_status,
+-}, {
+- /* Qualcomm Atheros AR8030 */
+- .phy_id = ATH8030_PHY_ID,
+- .name = "Qualcomm Atheros AR8030",
+- .phy_id_mask = AT8030_PHY_ID_MASK,
+- .probe = at8035_probe,
+- .config_init = at803x_config_init,
+- .link_change_notify = at803x_link_change_notify,
+- .set_wol = at803x_set_wol,
+- .get_wol = at803x_get_wol,
+- .suspend = at803x_suspend,
+- .resume = at803x_resume,
+- /* PHY_BASIC_FEATURES */
+- .config_intr = at803x_config_intr,
+- .handle_interrupt = at803x_handle_interrupt,
+-}, {
+- /* Qualcomm Atheros AR8031/AR8033 */
+- PHY_ID_MATCH_EXACT(ATH8031_PHY_ID),
+- .name = "Qualcomm Atheros AR8031/AR8033",
+- .flags = PHY_POLL_CABLE_TEST,
+- .probe = at8031_probe,
+- .config_init = at8031_config_init,
+- .config_aneg = at803x_config_aneg,
+- .soft_reset = genphy_soft_reset,
+- .set_wol = at8031_set_wol,
+- .get_wol = at803x_get_wol,
+- .suspend = at803x_suspend,
+- .resume = at803x_resume,
+- .read_page = at803x_read_page,
+- .write_page = at803x_write_page,
+- .get_features = at803x_get_features,
+- .read_status = at8031_read_status,
+- .config_intr = at8031_config_intr,
+- .handle_interrupt = at803x_handle_interrupt,
+- .get_tunable = at803x_get_tunable,
+- .set_tunable = at803x_set_tunable,
+- .cable_test_start = at8031_cable_test_start,
+- .cable_test_get_status = at8031_cable_test_get_status,
+-}, {
+- /* Qualcomm Atheros AR8032 */
+- PHY_ID_MATCH_EXACT(ATH8032_PHY_ID),
+- .name = "Qualcomm Atheros AR8032",
+- .probe = at803x_probe,
+- .flags = PHY_POLL_CABLE_TEST,
+- .config_init = at803x_config_init,
+- .link_change_notify = at803x_link_change_notify,
+- .suspend = at803x_suspend,
+- .resume = at803x_resume,
+- /* PHY_BASIC_FEATURES */
+- .config_intr = at803x_config_intr,
+- .handle_interrupt = at803x_handle_interrupt,
+- .cable_test_start = at803x_cable_test_start,
+- .cable_test_get_status = at8032_cable_test_get_status,
+-}, {
+- /* ATHEROS AR9331 */
+- PHY_ID_MATCH_EXACT(ATH9331_PHY_ID),
+- .name = "Qualcomm Atheros AR9331 built-in PHY",
+- .probe = at803x_probe,
+- .suspend = at803x_suspend,
+- .resume = at803x_resume,
+- .flags = PHY_POLL_CABLE_TEST,
+- /* PHY_BASIC_FEATURES */
+- .config_intr = at803x_config_intr,
+- .handle_interrupt = at803x_handle_interrupt,
+- .cable_test_start = at803x_cable_test_start,
+- .cable_test_get_status = at8032_cable_test_get_status,
+- .read_status = at803x_read_status,
+- .soft_reset = genphy_soft_reset,
+- .config_aneg = at803x_config_aneg,
+-}, {
+- /* Qualcomm Atheros QCA9561 */
+- PHY_ID_MATCH_EXACT(QCA9561_PHY_ID),
+- .name = "Qualcomm Atheros QCA9561 built-in PHY",
+- .probe = at803x_probe,
+- .suspend = at803x_suspend,
+- .resume = at803x_resume,
+- .flags = PHY_POLL_CABLE_TEST,
+- /* PHY_BASIC_FEATURES */
+- .config_intr = at803x_config_intr,
+- .handle_interrupt = at803x_handle_interrupt,
+- .cable_test_start = at803x_cable_test_start,
+- .cable_test_get_status = at8032_cable_test_get_status,
+- .read_status = at803x_read_status,
+- .soft_reset = genphy_soft_reset,
+- .config_aneg = at803x_config_aneg,
+-}, {
+- /* QCA8337 */
+- .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 = qca83xx_get_sset_count,
+- .get_strings = qca83xx_get_strings,
+- .get_stats = qca83xx_get_stats,
+- .suspend = qca8337_suspend,
+- .resume = qca83xx_resume,
+-}, {
+- /* QCA8327-A from switch QCA8327-AL1A */
+- .phy_id = QCA8327_A_PHY_ID,
+- .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 = qca8327_config_init,
+- .soft_reset = genphy_soft_reset,
+- .get_sset_count = qca83xx_get_sset_count,
+- .get_strings = qca83xx_get_strings,
+- .get_stats = qca83xx_get_stats,
+- .suspend = qca8327_suspend,
+- .resume = qca83xx_resume,
+-}, {
+- /* QCA8327-B from switch QCA8327-BL1A */
+- .phy_id = QCA8327_B_PHY_ID,
+- .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 = qca8327_config_init,
+- .soft_reset = genphy_soft_reset,
+- .get_sset_count = qca83xx_get_sset_count,
+- .get_strings = qca83xx_get_strings,
+- .get_stats = qca83xx_get_stats,
+- .suspend = qca8327_suspend,
+- .resume = qca83xx_resume,
+-}, {
+- /* Qualcomm QCA8081 */
+- PHY_ID_MATCH_EXACT(QCA8081_PHY_ID),
+- .name = "Qualcomm QCA8081",
+- .flags = PHY_POLL_CABLE_TEST,
+- .probe = at803x_probe,
+- .config_intr = at803x_config_intr,
+- .handle_interrupt = at803x_handle_interrupt,
+- .get_tunable = at803x_get_tunable,
+- .set_tunable = at803x_set_tunable,
+- .set_wol = at803x_set_wol,
+- .get_wol = at803x_get_wol,
+- .get_features = qca808x_get_features,
+- .config_aneg = qca808x_config_aneg,
+- .suspend = genphy_suspend,
+- .resume = genphy_resume,
+- .read_status = qca808x_read_status,
+- .config_init = qca808x_config_init,
+- .soft_reset = qca808x_soft_reset,
+- .cable_test_start = qca808x_cable_test_start,
+- .cable_test_get_status = qca808x_cable_test_get_status,
+- .link_change_notify = qca808x_link_change_notify,
+- .led_brightness_set = qca808x_led_brightness_set,
+- .led_blink_set = qca808x_led_blink_set,
+- .led_hw_is_supported = qca808x_led_hw_is_supported,
+- .led_hw_control_set = qca808x_led_hw_control_set,
+- .led_hw_control_get = qca808x_led_hw_control_get,
+- .led_polarity_set = qca808x_led_polarity_set,
+-}, };
+-
+-module_phy_driver(at803x_driver);
+-
+-static struct mdio_device_id __maybe_unused atheros_tbl[] = {
+- { ATH8030_PHY_ID, AT8030_PHY_ID_MASK },
+- { PHY_ID_MATCH_EXACT(ATH8031_PHY_ID) },
+- { 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_A_PHY_ID) },
+- { PHY_ID_MATCH_EXACT(QCA8327_B_PHY_ID) },
+- { PHY_ID_MATCH_EXACT(QCA9561_PHY_ID) },
+- { PHY_ID_MATCH_EXACT(QCA8081_PHY_ID) },
+- { }
+-};
+-
+-MODULE_DEVICE_TABLE(mdio, atheros_tbl);
+--- /dev/null
++++ b/drivers/net/phy/qcom/at803x.c
+@@ -0,0 +1,2759 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * drivers/net/phy/at803x.c
++ *
++ * Driver for Qualcomm Atheros AR803x PHY
++ *
++ * Author: Matus Ujhelyi <ujhelyi.m@gmail.com>
++ */
++
++#include <linux/phy.h>
++#include <linux/module.h>
++#include <linux/string.h>
++#include <linux/netdevice.h>
++#include <linux/etherdevice.h>
++#include <linux/ethtool_netlink.h>
++#include <linux/bitfield.h>
++#include <linux/regulator/of_regulator.h>
++#include <linux/regulator/driver.h>
++#include <linux/regulator/consumer.h>
++#include <linux/of.h>
++#include <linux/phylink.h>
++#include <linux/sfp.h>
++#include <dt-bindings/net/qca-ar803x.h>
++
++#define AT803X_SPECIFIC_FUNCTION_CONTROL 0x10
++#define AT803X_SFC_ASSERT_CRS BIT(11)
++#define AT803X_SFC_FORCE_LINK BIT(10)
++#define AT803X_SFC_MDI_CROSSOVER_MODE_M GENMASK(6, 5)
++#define AT803X_SFC_AUTOMATIC_CROSSOVER 0x3
++#define AT803X_SFC_MANUAL_MDIX 0x1
++#define AT803X_SFC_MANUAL_MDI 0x0
++#define AT803X_SFC_SQE_TEST BIT(2)
++#define AT803X_SFC_POLARITY_REVERSAL BIT(1)
++#define AT803X_SFC_DISABLE_JABBER BIT(0)
++
++#define AT803X_SPECIFIC_STATUS 0x11
++#define AT803X_SS_SPEED_MASK GENMASK(15, 14)
++#define AT803X_SS_SPEED_1000 2
++#define AT803X_SS_SPEED_100 1
++#define AT803X_SS_SPEED_10 0
++#define AT803X_SS_DUPLEX BIT(13)
++#define AT803X_SS_SPEED_DUPLEX_RESOLVED BIT(11)
++#define AT803X_SS_MDIX BIT(6)
++
++#define QCA808X_SS_SPEED_MASK GENMASK(9, 7)
++#define QCA808X_SS_SPEED_2500 4
++
++#define AT803X_INTR_ENABLE 0x12
++#define AT803X_INTR_ENABLE_AUTONEG_ERR BIT(15)
++#define AT803X_INTR_ENABLE_SPEED_CHANGED BIT(14)
++#define AT803X_INTR_ENABLE_DUPLEX_CHANGED BIT(13)
++#define AT803X_INTR_ENABLE_PAGE_RECEIVED BIT(12)
++#define AT803X_INTR_ENABLE_LINK_FAIL BIT(11)
++#define AT803X_INTR_ENABLE_LINK_SUCCESS BIT(10)
++#define AT803X_INTR_ENABLE_LINK_FAIL_BX BIT(8)
++#define AT803X_INTR_ENABLE_LINK_SUCCESS_BX BIT(7)
++#define AT803X_INTR_ENABLE_WIRESPEED_DOWNGRADE BIT(5)
++#define AT803X_INTR_ENABLE_POLARITY_CHANGED BIT(1)
++#define AT803X_INTR_ENABLE_WOL BIT(0)
++
++#define AT803X_INTR_STATUS 0x13
++
++#define AT803X_SMART_SPEED 0x14
++#define AT803X_SMART_SPEED_ENABLE BIT(5)
++#define AT803X_SMART_SPEED_RETRY_LIMIT_MASK GENMASK(4, 2)
++#define AT803X_SMART_SPEED_BYPASS_TIMER BIT(1)
++#define AT803X_CDT 0x16
++#define AT803X_CDT_MDI_PAIR_MASK GENMASK(9, 8)
++#define AT803X_CDT_ENABLE_TEST BIT(0)
++#define AT803X_CDT_STATUS 0x1c
++#define AT803X_CDT_STATUS_STAT_NORMAL 0
++#define AT803X_CDT_STATUS_STAT_SHORT 1
++#define AT803X_CDT_STATUS_STAT_OPEN 2
++#define AT803X_CDT_STATUS_STAT_FAIL 3
++#define AT803X_CDT_STATUS_STAT_MASK GENMASK(9, 8)
++#define AT803X_CDT_STATUS_DELTA_TIME_MASK GENMASK(7, 0)
++#define AT803X_LED_CONTROL 0x18
++
++#define AT803X_PHY_MMD3_WOL_CTRL 0x8012
++#define AT803X_WOL_EN BIT(5)
++#define AT803X_LOC_MAC_ADDR_0_15_OFFSET 0x804C
++#define AT803X_LOC_MAC_ADDR_16_31_OFFSET 0x804B
++#define AT803X_LOC_MAC_ADDR_32_47_OFFSET 0x804A
++#define AT803X_REG_CHIP_CONFIG 0x1f
++#define AT803X_BT_BX_REG_SEL 0x8000
++
++#define AT803X_DEBUG_ADDR 0x1D
++#define AT803X_DEBUG_DATA 0x1E
++
++#define AT803X_MODE_CFG_MASK 0x0F
++#define AT803X_MODE_CFG_BASET_RGMII 0x00
++#define AT803X_MODE_CFG_BASET_SGMII 0x01
++#define AT803X_MODE_CFG_BX1000_RGMII_50OHM 0x02
++#define AT803X_MODE_CFG_BX1000_RGMII_75OHM 0x03
++#define AT803X_MODE_CFG_BX1000_CONV_50OHM 0x04
++#define AT803X_MODE_CFG_BX1000_CONV_75OHM 0x05
++#define AT803X_MODE_CFG_FX100_RGMII_50OHM 0x06
++#define AT803X_MODE_CFG_FX100_CONV_50OHM 0x07
++#define AT803X_MODE_CFG_RGMII_AUTO_MDET 0x0B
++#define AT803X_MODE_CFG_FX100_RGMII_75OHM 0x0E
++#define AT803X_MODE_CFG_FX100_CONV_75OHM 0x0F
++
++#define AT803X_PSSR 0x11 /*PHY-Specific Status Register*/
++#define AT803X_PSSR_MR_AN_COMPLETE 0x0200
++
++#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_SYSTEM_CTRL_MODE 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_HIB_CTRL_PS_HIB_EN BIT(15)
++
++#define AT803X_DEBUG_REG_3C 0x3C
++
++#define AT803X_DEBUG_REG_GREEN 0x3D
++#define AT803X_DEBUG_GATE_CLK_IN1000 BIT(6)
++
++#define AT803X_DEBUG_REG_1F 0x1F
++#define AT803X_DEBUG_PLL_ON BIT(2)
++#define AT803X_DEBUG_RGMII_1V8 BIT(3)
++
++#define MDIO_AZ_DEBUG 0x800D
++
++/* AT803x supports either the XTAL input pad, an internal PLL or the
++ * DSP as clock reference for the clock output pad. The XTAL reference
++ * is only used for 25 MHz output, all other frequencies need the PLL.
++ * The DSP as a clock reference is used in synchronous ethernet
++ * applications.
++ *
++ * By default the PLL is only enabled if there is a link. Otherwise
++ * the PHY will go into low power state and disabled the PLL. You can
++ * set the PLL_ON bit (see debug register 0x1f) to keep the PLL always
++ * enabled.
++ */
++#define AT803X_MMD7_CLK25M 0x8016
++#define AT803X_CLK_OUT_MASK GENMASK(4, 2)
++#define AT803X_CLK_OUT_25MHZ_XTAL 0
++#define AT803X_CLK_OUT_25MHZ_DSP 1
++#define AT803X_CLK_OUT_50MHZ_PLL 2
++#define AT803X_CLK_OUT_50MHZ_DSP 3
++#define AT803X_CLK_OUT_62_5MHZ_PLL 4
++#define AT803X_CLK_OUT_62_5MHZ_DSP 5
++#define AT803X_CLK_OUT_125MHZ_PLL 6
++#define AT803X_CLK_OUT_125MHZ_DSP 7
++
++/* The AR8035 has another mask which is compatible with the AR8031/AR8033 mask
++ * but doesn't support choosing between XTAL/PLL and DSP.
++ */
++#define AT8035_CLK_OUT_MASK GENMASK(4, 3)
++
++#define AT803X_CLK_OUT_STRENGTH_MASK GENMASK(8, 7)
++#define AT803X_CLK_OUT_STRENGTH_FULL 0
++#define AT803X_CLK_OUT_STRENGTH_HALF 1
++#define AT803X_CLK_OUT_STRENGTH_QUARTER 2
++
++#define AT803X_DEFAULT_DOWNSHIFT 5
++#define AT803X_MIN_DOWNSHIFT 2
++#define AT803X_MAX_DOWNSHIFT 9
++
++#define AT803X_MMD3_SMARTEEE_CTL1 0x805b
++#define AT803X_MMD3_SMARTEEE_CTL2 0x805c
++#define AT803X_MMD3_SMARTEEE_CTL3 0x805d
++#define AT803X_MMD3_SMARTEEE_CTL3_LPI_EN BIT(8)
++
++#define ATH9331_PHY_ID 0x004dd041
++#define ATH8030_PHY_ID 0x004dd076
++#define ATH8031_PHY_ID 0x004dd074
++#define ATH8032_PHY_ID 0x004dd023
++#define ATH8035_PHY_ID 0x004dd072
++#define AT8030_PHY_ID_MASK 0xffffffef
++
++#define QCA8081_PHY_ID 0x004dd101
++
++#define QCA8327_A_PHY_ID 0x004dd033
++#define QCA8327_B_PHY_ID 0x004dd034
++#define QCA8337_PHY_ID 0x004dd036
++#define QCA9561_PHY_ID 0x004dd042
++#define QCA8K_PHY_ID_MASK 0xffffffff
++
++#define QCA8K_DEVFLAGS_REVISION_MASK GENMASK(2, 0)
++
++#define AT803X_PAGE_FIBER 0
++#define AT803X_PAGE_COPPER 1
++
++/* don't turn off internal PLL */
++#define AT803X_KEEP_PLL_ENABLED BIT(0)
++#define AT803X_DISABLE_SMARTEEE BIT(1)
++
++/* disable hibernation mode */
++#define AT803X_DISABLE_HIBERNATION_MODE BIT(2)
++
++/* ADC threshold */
++#define QCA808X_PHY_DEBUG_ADC_THRESHOLD 0x2c80
++#define QCA808X_ADC_THRESHOLD_MASK GENMASK(7, 0)
++#define QCA808X_ADC_THRESHOLD_80MV 0
++#define QCA808X_ADC_THRESHOLD_100MV 0xf0
++#define QCA808X_ADC_THRESHOLD_200MV 0x0f
++#define QCA808X_ADC_THRESHOLD_300MV 0xff
++
++/* CLD control */
++#define QCA808X_PHY_MMD3_ADDR_CLD_CTRL7 0x8007
++#define QCA808X_8023AZ_AFE_CTRL_MASK GENMASK(8, 4)
++#define QCA808X_8023AZ_AFE_EN 0x90
++
++/* AZ control */
++#define QCA808X_PHY_MMD3_AZ_TRAINING_CTRL 0x8008
++#define QCA808X_MMD3_AZ_TRAINING_VAL 0x1c32
++
++#define QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB 0x8014
++#define QCA808X_MSE_THRESHOLD_20DB_VALUE 0x529
++
++#define QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB 0x800E
++#define QCA808X_MSE_THRESHOLD_17DB_VALUE 0x341
++
++#define QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB 0x801E
++#define QCA808X_MSE_THRESHOLD_27DB_VALUE 0x419
++
++#define QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB 0x8020
++#define QCA808X_MSE_THRESHOLD_28DB_VALUE 0x341
++
++#define QCA808X_PHY_MMD7_TOP_OPTION1 0x901c
++#define QCA808X_TOP_OPTION1_DATA 0x0
++
++#define QCA808X_PHY_MMD3_DEBUG_1 0xa100
++#define QCA808X_MMD3_DEBUG_1_VALUE 0x9203
++#define QCA808X_PHY_MMD3_DEBUG_2 0xa101
++#define QCA808X_MMD3_DEBUG_2_VALUE 0x48ad
++#define QCA808X_PHY_MMD3_DEBUG_3 0xa103
++#define QCA808X_MMD3_DEBUG_3_VALUE 0x1698
++#define QCA808X_PHY_MMD3_DEBUG_4 0xa105
++#define QCA808X_MMD3_DEBUG_4_VALUE 0x8001
++#define QCA808X_PHY_MMD3_DEBUG_5 0xa106
++#define QCA808X_MMD3_DEBUG_5_VALUE 0x1111
++#define QCA808X_PHY_MMD3_DEBUG_6 0xa011
++#define QCA808X_MMD3_DEBUG_6_VALUE 0x5f85
++
++/* master/slave seed config */
++#define QCA808X_PHY_DEBUG_LOCAL_SEED 9
++#define QCA808X_MASTER_SLAVE_SEED_ENABLE BIT(1)
++#define QCA808X_MASTER_SLAVE_SEED_CFG GENMASK(12, 2)
++#define QCA808X_MASTER_SLAVE_SEED_RANGE 0x32
++
++/* Hibernation yields lower power consumpiton in contrast with normal operation mode.
++ * when the copper cable is unplugged, the PHY enters into hibernation mode in about 10s.
++ */
++#define QCA808X_DBG_AN_TEST 0xb
++#define QCA808X_HIBERNATION_EN BIT(15)
++
++#define QCA808X_CDT_ENABLE_TEST BIT(15)
++#define QCA808X_CDT_INTER_CHECK_DIS BIT(13)
++#define QCA808X_CDT_STATUS BIT(11)
++#define QCA808X_CDT_LENGTH_UNIT BIT(10)
++
++#define QCA808X_MMD3_CDT_STATUS 0x8064
++#define QCA808X_MMD3_CDT_DIAG_PAIR_A 0x8065
++#define QCA808X_MMD3_CDT_DIAG_PAIR_B 0x8066
++#define QCA808X_MMD3_CDT_DIAG_PAIR_C 0x8067
++#define QCA808X_MMD3_CDT_DIAG_PAIR_D 0x8068
++#define QCA808X_CDT_DIAG_LENGTH_SAME_SHORT GENMASK(15, 8)
++#define QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT GENMASK(7, 0)
++
++#define QCA808X_CDT_CODE_PAIR_A GENMASK(15, 12)
++#define QCA808X_CDT_CODE_PAIR_B GENMASK(11, 8)
++#define QCA808X_CDT_CODE_PAIR_C GENMASK(7, 4)
++#define QCA808X_CDT_CODE_PAIR_D GENMASK(3, 0)
++
++#define QCA808X_CDT_STATUS_STAT_TYPE GENMASK(1, 0)
++#define QCA808X_CDT_STATUS_STAT_FAIL FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 0)
++#define QCA808X_CDT_STATUS_STAT_NORMAL FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 1)
++#define QCA808X_CDT_STATUS_STAT_SAME_OPEN FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 2)
++#define QCA808X_CDT_STATUS_STAT_SAME_SHORT FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 3)
++
++#define QCA808X_CDT_STATUS_STAT_MDI GENMASK(3, 2)
++#define QCA808X_CDT_STATUS_STAT_MDI1 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 1)
++#define QCA808X_CDT_STATUS_STAT_MDI2 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 2)
++#define QCA808X_CDT_STATUS_STAT_MDI3 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 3)
++
++/* NORMAL are MDI with type set to 0 */
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI1
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++ QCA808X_CDT_STATUS_STAT_MDI1)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++ QCA808X_CDT_STATUS_STAT_MDI1)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI2
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++ QCA808X_CDT_STATUS_STAT_MDI2)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++ QCA808X_CDT_STATUS_STAT_MDI2)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI3
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++ QCA808X_CDT_STATUS_STAT_MDI3)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++ QCA808X_CDT_STATUS_STAT_MDI3)
++
++/* Added for reference of existence but should be handled by wait_for_completion already */
++#define QCA808X_CDT_STATUS_STAT_BUSY (BIT(1) | BIT(3))
++
++#define QCA808X_MMD7_LED_GLOBAL 0x8073
++#define QCA808X_LED_BLINK_1 GENMASK(11, 6)
++#define QCA808X_LED_BLINK_2 GENMASK(5, 0)
++/* Values are the same for both BLINK_1 and BLINK_2 */
++#define QCA808X_LED_BLINK_FREQ_MASK GENMASK(5, 3)
++#define QCA808X_LED_BLINK_FREQ_2HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x0)
++#define QCA808X_LED_BLINK_FREQ_4HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x1)
++#define QCA808X_LED_BLINK_FREQ_8HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x2)
++#define QCA808X_LED_BLINK_FREQ_16HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x3)
++#define QCA808X_LED_BLINK_FREQ_32HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x4)
++#define QCA808X_LED_BLINK_FREQ_64HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x5)
++#define QCA808X_LED_BLINK_FREQ_128HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x6)
++#define QCA808X_LED_BLINK_FREQ_256HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x7)
++#define QCA808X_LED_BLINK_DUTY_MASK GENMASK(2, 0)
++#define QCA808X_LED_BLINK_DUTY_50_50 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x0)
++#define QCA808X_LED_BLINK_DUTY_75_25 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x1)
++#define QCA808X_LED_BLINK_DUTY_25_75 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x2)
++#define QCA808X_LED_BLINK_DUTY_33_67 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x3)
++#define QCA808X_LED_BLINK_DUTY_67_33 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x4)
++#define QCA808X_LED_BLINK_DUTY_17_83 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x5)
++#define QCA808X_LED_BLINK_DUTY_83_17 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x6)
++#define QCA808X_LED_BLINK_DUTY_8_92 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x7)
++
++#define QCA808X_MMD7_LED2_CTRL 0x8074
++#define QCA808X_MMD7_LED2_FORCE_CTRL 0x8075
++#define QCA808X_MMD7_LED1_CTRL 0x8076
++#define QCA808X_MMD7_LED1_FORCE_CTRL 0x8077
++#define QCA808X_MMD7_LED0_CTRL 0x8078
++#define QCA808X_MMD7_LED_CTRL(x) (0x8078 - ((x) * 2))
++
++/* LED hw control pattern is the same for every LED */
++#define QCA808X_LED_PATTERN_MASK GENMASK(15, 0)
++#define QCA808X_LED_SPEED2500_ON BIT(15)
++#define QCA808X_LED_SPEED2500_BLINK BIT(14)
++/* Follow blink trigger even if duplex or speed condition doesn't match */
++#define QCA808X_LED_BLINK_CHECK_BYPASS BIT(13)
++#define QCA808X_LED_FULL_DUPLEX_ON BIT(12)
++#define QCA808X_LED_HALF_DUPLEX_ON BIT(11)
++#define QCA808X_LED_TX_BLINK BIT(10)
++#define QCA808X_LED_RX_BLINK BIT(9)
++#define QCA808X_LED_TX_ON_10MS BIT(8)
++#define QCA808X_LED_RX_ON_10MS BIT(7)
++#define QCA808X_LED_SPEED1000_ON BIT(6)
++#define QCA808X_LED_SPEED100_ON BIT(5)
++#define QCA808X_LED_SPEED10_ON BIT(4)
++#define QCA808X_LED_COLLISION_BLINK BIT(3)
++#define QCA808X_LED_SPEED1000_BLINK BIT(2)
++#define QCA808X_LED_SPEED100_BLINK BIT(1)
++#define QCA808X_LED_SPEED10_BLINK BIT(0)
++
++#define QCA808X_MMD7_LED0_FORCE_CTRL 0x8079
++#define QCA808X_MMD7_LED_FORCE_CTRL(x) (0x8079 - ((x) * 2))
++
++/* LED force ctrl is the same for every LED
++ * No documentation exist for this, not even internal one
++ * with NDA as QCOM gives only info about configuring
++ * hw control pattern rules and doesn't indicate any way
++ * to force the LED to specific mode.
++ * These define comes from reverse and testing and maybe
++ * lack of some info or some info are not entirely correct.
++ * For the basic LED control and hw control these finding
++ * are enough to support LED control in all the required APIs.
++ *
++ * On doing some comparison with implementation with qca807x,
++ * it was found that it's 1:1 equal to it and confirms all the
++ * reverse done. It was also found further specification with the
++ * force mode and the blink modes.
++ */
++#define QCA808X_LED_FORCE_EN BIT(15)
++#define QCA808X_LED_FORCE_MODE_MASK GENMASK(14, 13)
++#define QCA808X_LED_FORCE_BLINK_1 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x3)
++#define QCA808X_LED_FORCE_BLINK_2 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x2)
++#define QCA808X_LED_FORCE_ON FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x1)
++#define QCA808X_LED_FORCE_OFF FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x0)
++
++#define QCA808X_MMD7_LED_POLARITY_CTRL 0x901a
++/* QSDK sets by default 0x46 to this reg that sets BIT 6 for
++ * LED to active high. It's not clear what BIT 3 and BIT 4 does.
++ */
++#define QCA808X_LED_ACTIVE_HIGH BIT(6)
++
++/* QCA808X 1G chip type */
++#define QCA808X_PHY_MMD7_CHIP_TYPE 0x901d
++#define QCA808X_PHY_CHIP_TYPE_1G BIT(0)
++
++#define QCA8081_PHY_SERDES_MMD1_FIFO_CTRL 0x9072
++#define QCA8081_PHY_FIFO_RSTN BIT(11)
++
++MODULE_DESCRIPTION("Qualcomm Atheros AR803x and QCA808X PHY driver");
++MODULE_AUTHOR("Matus Ujhelyi");
++MODULE_LICENSE("GPL");
++
++enum stat_access_type {
++ PHY,
++ MMD
++};
++
++struct at803x_hw_stat {
++ const char *string;
++ u8 reg;
++ u32 mask;
++ enum stat_access_type access_type;
++};
++
++static struct at803x_hw_stat qca83xx_hw_stats[] = {
++ { "phy_idle_errors", 0xa, GENMASK(7, 0), PHY},
++ { "phy_receive_errors", 0x15, GENMASK(15, 0), PHY},
++ { "eee_wake_errors", 0x16, GENMASK(15, 0), MMD},
++};
++
++struct at803x_ss_mask {
++ u16 speed_mask;
++ u8 speed_shift;
++};
++
++struct at803x_priv {
++ int flags;
++ u16 clk_25m_reg;
++ u16 clk_25m_mask;
++ u8 smarteee_lpi_tw_1g;
++ u8 smarteee_lpi_tw_100m;
++ bool is_fiber;
++ bool is_1000basex;
++ struct regulator_dev *vddio_rdev;
++ struct regulator_dev *vddh_rdev;
++ u64 stats[ARRAY_SIZE(qca83xx_hw_stats)];
++ int led_polarity_mode;
++};
++
++struct at803x_context {
++ u16 bmcr;
++ u16 advertise;
++ u16 control1000;
++ u16 int_enable;
++ u16 smart_speed;
++ u16 led_control;
++};
++
++static int at803x_debug_reg_write(struct phy_device *phydev, u16 reg, u16 data)
++{
++ int ret;
++
++ ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
++ if (ret < 0)
++ return ret;
++
++ return phy_write(phydev, AT803X_DEBUG_DATA, data);
++}
++
++static int at803x_debug_reg_read(struct phy_device *phydev, u16 reg)
++{
++ int ret;
++
++ ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
++ if (ret < 0)
++ return ret;
++
++ return phy_read(phydev, AT803X_DEBUG_DATA);
++}
++
++static int at803x_debug_reg_mask(struct phy_device *phydev, u16 reg,
++ u16 clear, u16 set)
++{
++ u16 val;
++ int ret;
++
++ ret = at803x_debug_reg_read(phydev, reg);
++ if (ret < 0)
++ return ret;
++
++ val = ret & 0xffff;
++ val &= ~clear;
++ val |= set;
++
++ return phy_write(phydev, AT803X_DEBUG_DATA, val);
++}
++
++static int at803x_write_page(struct phy_device *phydev, int page)
++{
++ int mask;
++ int set;
++
++ if (page == AT803X_PAGE_COPPER) {
++ set = AT803X_BT_BX_REG_SEL;
++ mask = 0;
++ } else {
++ set = 0;
++ mask = AT803X_BT_BX_REG_SEL;
++ }
++
++ return __phy_modify(phydev, AT803X_REG_CHIP_CONFIG, mask, set);
++}
++
++static int at803x_read_page(struct phy_device *phydev)
++{
++ int ccr = __phy_read(phydev, AT803X_REG_CHIP_CONFIG);
++
++ if (ccr < 0)
++ return ccr;
++
++ if (ccr & AT803X_BT_BX_REG_SEL)
++ return AT803X_PAGE_COPPER;
++
++ return AT803X_PAGE_FIBER;
++}
++
++static int at803x_enable_rx_delay(struct phy_device *phydev)
++{
++ 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_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_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_SYSTEM_CTRL_MODE,
++ AT803X_DEBUG_TX_CLK_DLY_EN, 0);
++}
++
++/* save relevant PHY registers to private copy */
++static void at803x_context_save(struct phy_device *phydev,
++ struct at803x_context *context)
++{
++ context->bmcr = phy_read(phydev, MII_BMCR);
++ context->advertise = phy_read(phydev, MII_ADVERTISE);
++ context->control1000 = phy_read(phydev, MII_CTRL1000);
++ context->int_enable = phy_read(phydev, AT803X_INTR_ENABLE);
++ context->smart_speed = phy_read(phydev, AT803X_SMART_SPEED);
++ context->led_control = phy_read(phydev, AT803X_LED_CONTROL);
++}
++
++/* restore relevant PHY registers from private copy */
++static void at803x_context_restore(struct phy_device *phydev,
++ const struct at803x_context *context)
++{
++ phy_write(phydev, MII_BMCR, context->bmcr);
++ phy_write(phydev, MII_ADVERTISE, context->advertise);
++ phy_write(phydev, MII_CTRL1000, context->control1000);
++ phy_write(phydev, AT803X_INTR_ENABLE, context->int_enable);
++ phy_write(phydev, AT803X_SMART_SPEED, context->smart_speed);
++ phy_write(phydev, AT803X_LED_CONTROL, context->led_control);
++}
++
++static int at803x_set_wol(struct phy_device *phydev,
++ struct ethtool_wolinfo *wol)
++{
++ int ret, irq_enabled;
++
++ if (wol->wolopts & WAKE_MAGIC) {
++ struct net_device *ndev = phydev->attached_dev;
++ const u8 *mac;
++ unsigned int i;
++ static const unsigned int offsets[] = {
++ AT803X_LOC_MAC_ADDR_32_47_OFFSET,
++ AT803X_LOC_MAC_ADDR_16_31_OFFSET,
++ AT803X_LOC_MAC_ADDR_0_15_OFFSET,
++ };
++
++ if (!ndev)
++ return -ENODEV;
++
++ mac = (const u8 *)ndev->dev_addr;
++
++ if (!is_valid_ether_addr(mac))
++ return -EINVAL;
++
++ for (i = 0; i < 3; i++)
++ phy_write_mmd(phydev, MDIO_MMD_PCS, offsets[i],
++ mac[(i * 2) + 1] | (mac[(i * 2)] << 8));
++
++ /* Enable WOL interrupt */
++ ret = phy_modify(phydev, AT803X_INTR_ENABLE, 0, AT803X_INTR_ENABLE_WOL);
++ if (ret)
++ return ret;
++ } else {
++ /* Disable WOL interrupt */
++ ret = phy_modify(phydev, AT803X_INTR_ENABLE, AT803X_INTR_ENABLE_WOL, 0);
++ if (ret)
++ return ret;
++ }
++
++ /* Clear WOL status */
++ ret = phy_read(phydev, AT803X_INTR_STATUS);
++ if (ret < 0)
++ return ret;
++
++ /* Check if there are other interrupts except for WOL triggered when PHY is
++ * in interrupt mode, only the interrupts enabled by AT803X_INTR_ENABLE can
++ * be passed up to the interrupt PIN.
++ */
++ irq_enabled = phy_read(phydev, AT803X_INTR_ENABLE);
++ if (irq_enabled < 0)
++ return irq_enabled;
++
++ irq_enabled &= ~AT803X_INTR_ENABLE_WOL;
++ if (ret & irq_enabled && !phy_polling_mode(phydev))
++ phy_trigger_machine(phydev);
++
++ return 0;
++}
++
++static void at803x_get_wol(struct phy_device *phydev,
++ struct ethtool_wolinfo *wol)
++{
++ int value;
++
++ wol->supported = WAKE_MAGIC;
++ wol->wolopts = 0;
++
++ value = phy_read(phydev, AT803X_INTR_ENABLE);
++ if (value < 0)
++ return;
++
++ if (value & AT803X_INTR_ENABLE_WOL)
++ wol->wolopts |= WAKE_MAGIC;
++}
++
++static int qca83xx_get_sset_count(struct phy_device *phydev)
++{
++ return ARRAY_SIZE(qca83xx_hw_stats);
++}
++
++static void qca83xx_get_strings(struct phy_device *phydev, u8 *data)
++{
++ int i;
++
++ for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++) {
++ strscpy(data + i * ETH_GSTRING_LEN,
++ qca83xx_hw_stats[i].string, ETH_GSTRING_LEN);
++ }
++}
++
++static u64 qca83xx_get_stat(struct phy_device *phydev, int i)
++{
++ struct at803x_hw_stat stat = qca83xx_hw_stats[i];
++ struct at803x_priv *priv = phydev->priv;
++ int val;
++ u64 ret;
++
++ if (stat.access_type == MMD)
++ val = phy_read_mmd(phydev, MDIO_MMD_PCS, stat.reg);
++ else
++ val = phy_read(phydev, stat.reg);
++
++ if (val < 0) {
++ ret = U64_MAX;
++ } else {
++ val = val & stat.mask;
++ priv->stats[i] += val;
++ ret = priv->stats[i];
++ }
++
++ return ret;
++}
++
++static void qca83xx_get_stats(struct phy_device *phydev,
++ struct ethtool_stats *stats, u64 *data)
++{
++ int i;
++
++ for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++)
++ data[i] = qca83xx_get_stat(phydev, i);
++}
++
++static int at803x_suspend(struct phy_device *phydev)
++{
++ int value;
++ int wol_enabled;
++
++ value = phy_read(phydev, AT803X_INTR_ENABLE);
++ wol_enabled = value & AT803X_INTR_ENABLE_WOL;
++
++ if (wol_enabled)
++ value = BMCR_ISOLATE;
++ else
++ value = BMCR_PDOWN;
++
++ phy_modify(phydev, MII_BMCR, 0, value);
++
++ return 0;
++}
++
++static int at803x_resume(struct phy_device *phydev)
++{
++ return phy_modify(phydev, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE, 0);
++}
++
++static int at803x_parse_dt(struct phy_device *phydev)
++{
++ struct device_node *node = phydev->mdio.dev.of_node;
++ struct at803x_priv *priv = phydev->priv;
++ u32 freq, strength, tw;
++ unsigned int sel;
++ int ret;
++
++ if (!IS_ENABLED(CONFIG_OF_MDIO))
++ return 0;
++
++ if (of_property_read_bool(node, "qca,disable-smarteee"))
++ priv->flags |= AT803X_DISABLE_SMARTEEE;
++
++ if (of_property_read_bool(node, "qca,disable-hibernation-mode"))
++ priv->flags |= AT803X_DISABLE_HIBERNATION_MODE;
++
++ if (!of_property_read_u32(node, "qca,smarteee-tw-us-1g", &tw)) {
++ if (!tw || tw > 255) {
++ phydev_err(phydev, "invalid qca,smarteee-tw-us-1g\n");
++ return -EINVAL;
++ }
++ priv->smarteee_lpi_tw_1g = tw;
++ }
++
++ if (!of_property_read_u32(node, "qca,smarteee-tw-us-100m", &tw)) {
++ if (!tw || tw > 255) {
++ phydev_err(phydev, "invalid qca,smarteee-tw-us-100m\n");
++ return -EINVAL;
++ }
++ priv->smarteee_lpi_tw_100m = tw;
++ }
++
++ ret = of_property_read_u32(node, "qca,clk-out-frequency", &freq);
++ if (!ret) {
++ switch (freq) {
++ case 25000000:
++ sel = AT803X_CLK_OUT_25MHZ_XTAL;
++ break;
++ case 50000000:
++ sel = AT803X_CLK_OUT_50MHZ_PLL;
++ break;
++ case 62500000:
++ sel = AT803X_CLK_OUT_62_5MHZ_PLL;
++ break;
++ case 125000000:
++ sel = AT803X_CLK_OUT_125MHZ_PLL;
++ break;
++ default:
++ phydev_err(phydev, "invalid qca,clk-out-frequency\n");
++ return -EINVAL;
++ }
++
++ priv->clk_25m_reg |= FIELD_PREP(AT803X_CLK_OUT_MASK, sel);
++ priv->clk_25m_mask |= AT803X_CLK_OUT_MASK;
++ }
++
++ ret = of_property_read_u32(node, "qca,clk-out-strength", &strength);
++ if (!ret) {
++ priv->clk_25m_mask |= AT803X_CLK_OUT_STRENGTH_MASK;
++ switch (strength) {
++ case AR803X_STRENGTH_FULL:
++ priv->clk_25m_reg |= AT803X_CLK_OUT_STRENGTH_FULL;
++ break;
++ case AR803X_STRENGTH_HALF:
++ priv->clk_25m_reg |= AT803X_CLK_OUT_STRENGTH_HALF;
++ break;
++ case AR803X_STRENGTH_QUARTER:
++ priv->clk_25m_reg |= AT803X_CLK_OUT_STRENGTH_QUARTER;
++ break;
++ default:
++ phydev_err(phydev, "invalid qca,clk-out-strength\n");
++ return -EINVAL;
++ }
++ }
++
++ return 0;
++}
++
++static int at803x_probe(struct phy_device *phydev)
++{
++ struct device *dev = &phydev->mdio.dev;
++ struct at803x_priv *priv;
++ int ret;
++
++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
++
++ /* Init LED polarity mode to -1 */
++ priv->led_polarity_mode = -1;
++
++ phydev->priv = priv;
++
++ ret = at803x_parse_dt(phydev);
++ if (ret)
++ return ret;
++
++ return 0;
++}
++
++static int at803x_get_features(struct phy_device *phydev)
++{
++ struct at803x_priv *priv = phydev->priv;
++ int err;
++
++ err = genphy_read_abilities(phydev);
++ if (err)
++ return err;
++
++ if (phydev->drv->phy_id != ATH8031_PHY_ID)
++ return 0;
++
++ /* AR8031/AR8033 have different status registers
++ * for copper and fiber operation. However, the
++ * extended status register is the same for both
++ * operation modes.
++ *
++ * As a result of that, ESTATUS_1000_XFULL is set
++ * to 1 even when operating in copper TP mode.
++ *
++ * Remove this mode from the supported link modes
++ * when not operating in 1000BaseX mode.
++ */
++ if (!priv->is_1000basex)
++ linkmode_clear_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
++ phydev->supported);
++
++ return 0;
++}
++
++static int at803x_smarteee_config(struct phy_device *phydev)
++{
++ struct at803x_priv *priv = phydev->priv;
++ u16 mask = 0, val = 0;
++ int ret;
++
++ if (priv->flags & AT803X_DISABLE_SMARTEEE)
++ return phy_modify_mmd(phydev, MDIO_MMD_PCS,
++ AT803X_MMD3_SMARTEEE_CTL3,
++ AT803X_MMD3_SMARTEEE_CTL3_LPI_EN, 0);
++
++ if (priv->smarteee_lpi_tw_1g) {
++ mask |= 0xff00;
++ val |= priv->smarteee_lpi_tw_1g << 8;
++ }
++ if (priv->smarteee_lpi_tw_100m) {
++ mask |= 0x00ff;
++ val |= priv->smarteee_lpi_tw_100m;
++ }
++ if (!mask)
++ return 0;
++
++ ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, AT803X_MMD3_SMARTEEE_CTL1,
++ mask, val);
++ if (ret)
++ return ret;
++
++ return phy_modify_mmd(phydev, MDIO_MMD_PCS, AT803X_MMD3_SMARTEEE_CTL3,
++ AT803X_MMD3_SMARTEEE_CTL3_LPI_EN,
++ AT803X_MMD3_SMARTEEE_CTL3_LPI_EN);
++}
++
++static int at803x_clk_out_config(struct phy_device *phydev)
++{
++ struct at803x_priv *priv = phydev->priv;
++
++ if (!priv->clk_25m_mask)
++ return 0;
++
++ return phy_modify_mmd(phydev, MDIO_MMD_AN, AT803X_MMD7_CLK25M,
++ priv->clk_25m_mask, priv->clk_25m_reg);
++}
++
++static int at8031_pll_config(struct phy_device *phydev)
++{
++ struct at803x_priv *priv = phydev->priv;
++
++ /* The default after hardware reset is PLL OFF. After a soft reset, the
++ * values are retained.
++ */
++ if (priv->flags & AT803X_KEEP_PLL_ENABLED)
++ return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
++ 0, AT803X_DEBUG_PLL_ON);
++ else
++ return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
++ AT803X_DEBUG_PLL_ON, 0);
++}
++
++static int at803x_hibernation_mode_config(struct phy_device *phydev)
++{
++ struct at803x_priv *priv = phydev->priv;
++
++ /* The default after hardware reset is hibernation mode enabled. After
++ * software reset, the value is retained.
++ */
++ if (!(priv->flags & AT803X_DISABLE_HIBERNATION_MODE))
++ return 0;
++
++ return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_HIB_CTRL,
++ AT803X_DEBUG_HIB_CTRL_PS_HIB_EN, 0);
++}
++
++static int at803x_config_init(struct phy_device *phydev)
++{
++ int ret;
++
++ /* The RX and TX delay default is:
++ * after HW reset: RX delay enabled and TX delay disabled
++ * after SW reset: RX delay enabled, while TX delay retains the
++ * value before reset.
++ */
++ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
++ phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID)
++ ret = at803x_enable_rx_delay(phydev);
++ else
++ ret = at803x_disable_rx_delay(phydev);
++ if (ret < 0)
++ return ret;
++
++ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
++ phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
++ ret = at803x_enable_tx_delay(phydev);
++ else
++ ret = at803x_disable_tx_delay(phydev);
++ if (ret < 0)
++ return ret;
++
++ ret = at803x_smarteee_config(phydev);
++ if (ret < 0)
++ return ret;
++
++ ret = at803x_clk_out_config(phydev);
++ if (ret < 0)
++ return ret;
++
++ ret = at803x_hibernation_mode_config(phydev);
++ if (ret < 0)
++ return ret;
++
++ /* Ar803x extended next page bit is enabled by default. Cisco
++ * multigig switches read this bit and attempt to negotiate 10Gbps
++ * rates even if the next page bit is disabled. This is incorrect
++ * behaviour but we still need to accommodate it. XNP is only needed
++ * for 10Gbps support, so disable XNP.
++ */
++ return phy_modify(phydev, MII_ADVERTISE, MDIO_AN_CTRL1_XNP, 0);
++}
++
++static int at803x_ack_interrupt(struct phy_device *phydev)
++{
++ int err;
++
++ err = phy_read(phydev, AT803X_INTR_STATUS);
++
++ return (err < 0) ? err : 0;
++}
++
++static int at803x_config_intr(struct phy_device *phydev)
++{
++ int err;
++ int value;
++
++ value = phy_read(phydev, AT803X_INTR_ENABLE);
++
++ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
++ /* Clear any pending interrupts */
++ err = at803x_ack_interrupt(phydev);
++ if (err)
++ return err;
++
++ value |= AT803X_INTR_ENABLE_AUTONEG_ERR;
++ value |= AT803X_INTR_ENABLE_SPEED_CHANGED;
++ value |= AT803X_INTR_ENABLE_DUPLEX_CHANGED;
++ value |= AT803X_INTR_ENABLE_LINK_FAIL;
++ value |= AT803X_INTR_ENABLE_LINK_SUCCESS;
++
++ err = phy_write(phydev, AT803X_INTR_ENABLE, value);
++ } else {
++ err = phy_write(phydev, AT803X_INTR_ENABLE, 0);
++ if (err)
++ return err;
++
++ /* Clear any pending interrupts */
++ err = at803x_ack_interrupt(phydev);
++ }
++
++ return err;
++}
++
++static irqreturn_t at803x_handle_interrupt(struct phy_device *phydev)
++{
++ int irq_status, int_enabled;
++
++ irq_status = phy_read(phydev, AT803X_INTR_STATUS);
++ if (irq_status < 0) {
++ phy_error(phydev);
++ return IRQ_NONE;
++ }
++
++ /* Read the current enabled interrupts */
++ int_enabled = phy_read(phydev, AT803X_INTR_ENABLE);
++ if (int_enabled < 0) {
++ phy_error(phydev);
++ return IRQ_NONE;
++ }
++
++ /* See if this was one of our enabled interrupts */
++ if (!(irq_status & int_enabled))
++ return IRQ_NONE;
++
++ phy_trigger_machine(phydev);
++
++ return IRQ_HANDLED;
++}
++
++static void at803x_link_change_notify(struct phy_device *phydev)
++{
++ /*
++ * Conduct a hardware reset for AT8030 every time a link loss is
++ * signalled. This is necessary to circumvent a hardware bug that
++ * occurs when the cable is unplugged while TX packets are pending
++ * in the FIFO. In such cases, the FIFO enters an error mode it
++ * cannot recover from by software.
++ */
++ if (phydev->state == PHY_NOLINK && phydev->mdio.reset_gpio) {
++ struct at803x_context context;
++
++ at803x_context_save(phydev, &context);
++
++ phy_device_reset(phydev, 1);
++ usleep_range(1000, 2000);
++ phy_device_reset(phydev, 0);
++ usleep_range(1000, 2000);
++
++ at803x_context_restore(phydev, &context);
++
++ phydev_dbg(phydev, "%s(): phy was reset\n", __func__);
++ }
++}
++
++static int at803x_read_specific_status(struct phy_device *phydev,
++ struct at803x_ss_mask ss_mask)
++{
++ int ss;
++
++ /* Read the AT8035 PHY-Specific Status register, which indicates the
++ * speed and duplex that the PHY is actually using, irrespective of
++ * whether we are in autoneg mode or not.
++ */
++ ss = phy_read(phydev, AT803X_SPECIFIC_STATUS);
++ if (ss < 0)
++ return ss;
++
++ if (ss & AT803X_SS_SPEED_DUPLEX_RESOLVED) {
++ int sfc, speed;
++
++ sfc = phy_read(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL);
++ if (sfc < 0)
++ return sfc;
++
++ speed = ss & ss_mask.speed_mask;
++ speed >>= ss_mask.speed_shift;
++
++ switch (speed) {
++ case AT803X_SS_SPEED_10:
++ phydev->speed = SPEED_10;
++ break;
++ case AT803X_SS_SPEED_100:
++ phydev->speed = SPEED_100;
++ break;
++ case AT803X_SS_SPEED_1000:
++ phydev->speed = SPEED_1000;
++ break;
++ case QCA808X_SS_SPEED_2500:
++ phydev->speed = SPEED_2500;
++ break;
++ }
++ if (ss & AT803X_SS_DUPLEX)
++ phydev->duplex = DUPLEX_FULL;
++ else
++ phydev->duplex = DUPLEX_HALF;
++
++ if (ss & AT803X_SS_MDIX)
++ phydev->mdix = ETH_TP_MDI_X;
++ else
++ phydev->mdix = ETH_TP_MDI;
++
++ switch (FIELD_GET(AT803X_SFC_MDI_CROSSOVER_MODE_M, sfc)) {
++ case AT803X_SFC_MANUAL_MDI:
++ phydev->mdix_ctrl = ETH_TP_MDI;
++ break;
++ case AT803X_SFC_MANUAL_MDIX:
++ phydev->mdix_ctrl = ETH_TP_MDI_X;
++ break;
++ case AT803X_SFC_AUTOMATIC_CROSSOVER:
++ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
++ break;
++ }
++ }
++
++ return 0;
++}
++
++static int at803x_read_status(struct phy_device *phydev)
++{
++ struct at803x_ss_mask ss_mask = { 0 };
++ int err, old_link = phydev->link;
++
++ /* Update the link, but return if there was an error */
++ err = genphy_update_link(phydev);
++ if (err)
++ return err;
++
++ /* why bother the PHY if nothing can have changed */
++ if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
++ return 0;
++
++ phydev->speed = SPEED_UNKNOWN;
++ phydev->duplex = DUPLEX_UNKNOWN;
++ phydev->pause = 0;
++ phydev->asym_pause = 0;
++
++ err = genphy_read_lpa(phydev);
++ if (err < 0)
++ return err;
++
++ ss_mask.speed_mask = AT803X_SS_SPEED_MASK;
++ ss_mask.speed_shift = __bf_shf(AT803X_SS_SPEED_MASK);
++ err = at803x_read_specific_status(phydev, ss_mask);
++ if (err < 0)
++ return err;
++
++ if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete)
++ phy_resolve_aneg_pause(phydev);
++
++ return 0;
++}
++
++static int at803x_config_mdix(struct phy_device *phydev, u8 ctrl)
++{
++ u16 val;
++
++ switch (ctrl) {
++ case ETH_TP_MDI:
++ val = AT803X_SFC_MANUAL_MDI;
++ break;
++ case ETH_TP_MDI_X:
++ val = AT803X_SFC_MANUAL_MDIX;
++ break;
++ case ETH_TP_MDI_AUTO:
++ val = AT803X_SFC_AUTOMATIC_CROSSOVER;
++ break;
++ default:
++ return 0;
++ }
++
++ return phy_modify_changed(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL,
++ AT803X_SFC_MDI_CROSSOVER_MODE_M,
++ FIELD_PREP(AT803X_SFC_MDI_CROSSOVER_MODE_M, val));
++}
++
++static int at803x_prepare_config_aneg(struct phy_device *phydev)
++{
++ int ret;
++
++ ret = at803x_config_mdix(phydev, phydev->mdix_ctrl);
++ if (ret < 0)
++ return ret;
++
++ /* Changes of the midx bits are disruptive to the normal operation;
++ * therefore any changes to these registers must be followed by a
++ * software reset to take effect.
++ */
++ if (ret == 1) {
++ ret = genphy_soft_reset(phydev);
++ if (ret < 0)
++ return ret;
++ }
++
++ return 0;
++}
++
++static int at803x_config_aneg(struct phy_device *phydev)
++{
++ struct at803x_priv *priv = phydev->priv;
++ int ret;
++
++ ret = at803x_prepare_config_aneg(phydev);
++ if (ret)
++ return ret;
++
++ if (priv->is_1000basex)
++ return genphy_c37_config_aneg(phydev);
++
++ return genphy_config_aneg(phydev);
++}
++
++static int at803x_get_downshift(struct phy_device *phydev, u8 *d)
++{
++ int val;
++
++ val = phy_read(phydev, AT803X_SMART_SPEED);
++ if (val < 0)
++ return val;
++
++ if (val & AT803X_SMART_SPEED_ENABLE)
++ *d = FIELD_GET(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, val) + 2;
++ else
++ *d = DOWNSHIFT_DEV_DISABLE;
++
++ return 0;
++}
++
++static int at803x_set_downshift(struct phy_device *phydev, u8 cnt)
++{
++ u16 mask, set;
++ int ret;
++
++ switch (cnt) {
++ case DOWNSHIFT_DEV_DEFAULT_COUNT:
++ cnt = AT803X_DEFAULT_DOWNSHIFT;
++ fallthrough;
++ case AT803X_MIN_DOWNSHIFT ... AT803X_MAX_DOWNSHIFT:
++ set = AT803X_SMART_SPEED_ENABLE |
++ AT803X_SMART_SPEED_BYPASS_TIMER |
++ FIELD_PREP(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, cnt - 2);
++ mask = AT803X_SMART_SPEED_RETRY_LIMIT_MASK;
++ break;
++ case DOWNSHIFT_DEV_DISABLE:
++ set = 0;
++ mask = AT803X_SMART_SPEED_ENABLE |
++ AT803X_SMART_SPEED_BYPASS_TIMER;
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ ret = phy_modify_changed(phydev, AT803X_SMART_SPEED, mask, set);
++
++ /* After changing the smart speed settings, we need to perform a
++ * software reset, use phy_init_hw() to make sure we set the
++ * reapply any values which might got lost during software reset.
++ */
++ if (ret == 1)
++ ret = phy_init_hw(phydev);
++
++ return ret;
++}
++
++static int at803x_get_tunable(struct phy_device *phydev,
++ struct ethtool_tunable *tuna, void *data)
++{
++ switch (tuna->id) {
++ case ETHTOOL_PHY_DOWNSHIFT:
++ return at803x_get_downshift(phydev, data);
++ default:
++ return -EOPNOTSUPP;
++ }
++}
++
++static int at803x_set_tunable(struct phy_device *phydev,
++ struct ethtool_tunable *tuna, const void *data)
++{
++ switch (tuna->id) {
++ case ETHTOOL_PHY_DOWNSHIFT:
++ return at803x_set_downshift(phydev, *(const u8 *)data);
++ default:
++ return -EOPNOTSUPP;
++ }
++}
++
++static int at803x_cable_test_result_trans(u16 status)
++{
++ switch (FIELD_GET(AT803X_CDT_STATUS_STAT_MASK, status)) {
++ case AT803X_CDT_STATUS_STAT_NORMAL:
++ return ETHTOOL_A_CABLE_RESULT_CODE_OK;
++ case AT803X_CDT_STATUS_STAT_SHORT:
++ return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
++ case AT803X_CDT_STATUS_STAT_OPEN:
++ return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
++ case AT803X_CDT_STATUS_STAT_FAIL:
++ default:
++ return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
++ }
++}
++
++static bool at803x_cdt_test_failed(u16 status)
++{
++ return FIELD_GET(AT803X_CDT_STATUS_STAT_MASK, status) ==
++ AT803X_CDT_STATUS_STAT_FAIL;
++}
++
++static bool at803x_cdt_fault_length_valid(u16 status)
++{
++ switch (FIELD_GET(AT803X_CDT_STATUS_STAT_MASK, status)) {
++ case AT803X_CDT_STATUS_STAT_OPEN:
++ case AT803X_CDT_STATUS_STAT_SHORT:
++ return true;
++ }
++ return false;
++}
++
++static int at803x_cdt_fault_length(int dt)
++{
++ /* According to the datasheet the distance to the fault is
++ * DELTA_TIME * 0.824 meters.
++ *
++ * The author suspect the correct formula is:
++ *
++ * fault_distance = DELTA_TIME * (c * VF) / 125MHz / 2
++ *
++ * where c is the speed of light, VF is the velocity factor of
++ * the twisted pair cable, 125MHz the counter frequency and
++ * we need to divide by 2 because the hardware will measure the
++ * round trip time to the fault and back to the PHY.
++ *
++ * With a VF of 0.69 we get the factor 0.824 mentioned in the
++ * datasheet.
++ */
++ return (dt * 824) / 10;
++}
++
++static int at803x_cdt_start(struct phy_device *phydev,
++ u32 cdt_start)
++{
++ return phy_write(phydev, AT803X_CDT, cdt_start);
++}
++
++static int at803x_cdt_wait_for_completion(struct phy_device *phydev,
++ u32 cdt_en)
++{
++ int val, ret;
++
++ /* One test run takes about 25ms */
++ ret = phy_read_poll_timeout(phydev, AT803X_CDT, val,
++ !(val & cdt_en),
++ 30000, 100000, true);
++
++ return ret < 0 ? ret : 0;
++}
++
++static int at803x_cable_test_one_pair(struct phy_device *phydev, int pair)
++{
++ static const int ethtool_pair[] = {
++ ETHTOOL_A_CABLE_PAIR_A,
++ ETHTOOL_A_CABLE_PAIR_B,
++ ETHTOOL_A_CABLE_PAIR_C,
++ ETHTOOL_A_CABLE_PAIR_D,
++ };
++ int ret, val;
++
++ val = FIELD_PREP(AT803X_CDT_MDI_PAIR_MASK, pair) |
++ AT803X_CDT_ENABLE_TEST;
++ ret = at803x_cdt_start(phydev, val);
++ if (ret)
++ return ret;
++
++ ret = at803x_cdt_wait_for_completion(phydev, AT803X_CDT_ENABLE_TEST);
++ if (ret)
++ return ret;
++
++ val = phy_read(phydev, AT803X_CDT_STATUS);
++ if (val < 0)
++ return val;
++
++ if (at803x_cdt_test_failed(val))
++ return 0;
++
++ ethnl_cable_test_result(phydev, ethtool_pair[pair],
++ at803x_cable_test_result_trans(val));
++
++ if (at803x_cdt_fault_length_valid(val)) {
++ val = FIELD_GET(AT803X_CDT_STATUS_DELTA_TIME_MASK, val);
++ ethnl_cable_test_fault_length(phydev, ethtool_pair[pair],
++ at803x_cdt_fault_length(val));
++ }
++
++ return 1;
++}
++
++static int at803x_cable_test_get_status(struct phy_device *phydev,
++ bool *finished, unsigned long pair_mask)
++{
++ int retries = 20;
++ int pair, ret;
++
++ *finished = false;
++
++ /* According to the datasheet the CDT can be performed when
++ * there is no link partner or when the link partner is
++ * auto-negotiating. Starting the test will restart the AN
++ * automatically. It seems that doing this repeatedly we will
++ * get a slot where our link partner won't disturb our
++ * measurement.
++ */
++ while (pair_mask && retries--) {
++ for_each_set_bit(pair, &pair_mask, 4) {
++ ret = at803x_cable_test_one_pair(phydev, pair);
++ if (ret < 0)
++ return ret;
++ if (ret)
++ clear_bit(pair, &pair_mask);
++ }
++ if (pair_mask)
++ msleep(250);
++ }
++
++ *finished = true;
++
++ return 0;
++}
++
++static void at803x_cable_test_autoneg(struct phy_device *phydev)
++{
++ /* Enable auto-negotiation, but advertise no capabilities, no link
++ * will be established. A restart of the auto-negotiation is not
++ * required, because the cable test will automatically break the link.
++ */
++ phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
++ phy_write(phydev, MII_ADVERTISE, ADVERTISE_CSMA);
++}
++
++static int at803x_cable_test_start(struct phy_device *phydev)
++{
++ at803x_cable_test_autoneg(phydev);
++ /* we do all the (time consuming) work later */
++ return 0;
++}
++
++static int at8031_rgmii_reg_set_voltage_sel(struct regulator_dev *rdev,
++ unsigned int selector)
++{
++ struct phy_device *phydev = rdev_get_drvdata(rdev);
++
++ if (selector)
++ return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
++ 0, AT803X_DEBUG_RGMII_1V8);
++ else
++ return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
++ AT803X_DEBUG_RGMII_1V8, 0);
++}
++
++static int at8031_rgmii_reg_get_voltage_sel(struct regulator_dev *rdev)
++{
++ struct phy_device *phydev = rdev_get_drvdata(rdev);
++ int val;
++
++ val = at803x_debug_reg_read(phydev, AT803X_DEBUG_REG_1F);
++ if (val < 0)
++ return val;
++
++ return (val & AT803X_DEBUG_RGMII_1V8) ? 1 : 0;
++}
++
++static const struct regulator_ops vddio_regulator_ops = {
++ .list_voltage = regulator_list_voltage_table,
++ .set_voltage_sel = at8031_rgmii_reg_set_voltage_sel,
++ .get_voltage_sel = at8031_rgmii_reg_get_voltage_sel,
++};
++
++static const unsigned int vddio_voltage_table[] = {
++ 1500000,
++ 1800000,
++};
++
++static const struct regulator_desc vddio_desc = {
++ .name = "vddio",
++ .of_match = of_match_ptr("vddio-regulator"),
++ .n_voltages = ARRAY_SIZE(vddio_voltage_table),
++ .volt_table = vddio_voltage_table,
++ .ops = &vddio_regulator_ops,
++ .type = REGULATOR_VOLTAGE,
++ .owner = THIS_MODULE,
++};
++
++static const struct regulator_ops vddh_regulator_ops = {
++};
++
++static const struct regulator_desc vddh_desc = {
++ .name = "vddh",
++ .of_match = of_match_ptr("vddh-regulator"),
++ .n_voltages = 1,
++ .fixed_uV = 2500000,
++ .ops = &vddh_regulator_ops,
++ .type = REGULATOR_VOLTAGE,
++ .owner = THIS_MODULE,
++};
++
++static int at8031_register_regulators(struct phy_device *phydev)
++{
++ struct at803x_priv *priv = phydev->priv;
++ struct device *dev = &phydev->mdio.dev;
++ struct regulator_config config = { };
++
++ config.dev = dev;
++ config.driver_data = phydev;
++
++ priv->vddio_rdev = devm_regulator_register(dev, &vddio_desc, &config);
++ if (IS_ERR(priv->vddio_rdev)) {
++ phydev_err(phydev, "failed to register VDDIO regulator\n");
++ return PTR_ERR(priv->vddio_rdev);
++ }
++
++ priv->vddh_rdev = devm_regulator_register(dev, &vddh_desc, &config);
++ if (IS_ERR(priv->vddh_rdev)) {
++ phydev_err(phydev, "failed to register VDDH regulator\n");
++ return PTR_ERR(priv->vddh_rdev);
++ }
++
++ return 0;
++}
++
++static int at8031_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
++{
++ struct phy_device *phydev = upstream;
++ __ETHTOOL_DECLARE_LINK_MODE_MASK(phy_support);
++ __ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
++ DECLARE_PHY_INTERFACE_MASK(interfaces);
++ phy_interface_t iface;
++
++ linkmode_zero(phy_support);
++ phylink_set(phy_support, 1000baseX_Full);
++ phylink_set(phy_support, 1000baseT_Full);
++ phylink_set(phy_support, Autoneg);
++ phylink_set(phy_support, Pause);
++ phylink_set(phy_support, Asym_Pause);
++
++ linkmode_zero(sfp_support);
++ sfp_parse_support(phydev->sfp_bus, id, sfp_support, interfaces);
++ /* Some modules support 10G modes as well as others we support.
++ * Mask out non-supported modes so the correct interface is picked.
++ */
++ linkmode_and(sfp_support, phy_support, sfp_support);
++
++ if (linkmode_empty(sfp_support)) {
++ dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n");
++ return -EINVAL;
++ }
++
++ iface = sfp_select_interface(phydev->sfp_bus, sfp_support);
++
++ /* Only 1000Base-X is supported by AR8031/8033 as the downstream SerDes
++ * interface for use with SFP modules.
++ * However, some copper modules detected as having a preferred SGMII
++ * interface do default to and function in 1000Base-X mode, so just
++ * print a warning and allow such modules, as they may have some chance
++ * of working.
++ */
++ if (iface == PHY_INTERFACE_MODE_SGMII)
++ dev_warn(&phydev->mdio.dev, "module may not function if 1000Base-X not supported\n");
++ else if (iface != PHY_INTERFACE_MODE_1000BASEX)
++ return -EINVAL;
++
++ return 0;
++}
++
++static const struct sfp_upstream_ops at8031_sfp_ops = {
++ .attach = phy_sfp_attach,
++ .detach = phy_sfp_detach,
++ .module_insert = at8031_sfp_insert,
++};
++
++static int at8031_parse_dt(struct phy_device *phydev)
++{
++ struct device_node *node = phydev->mdio.dev.of_node;
++ struct at803x_priv *priv = phydev->priv;
++ int ret;
++
++ if (of_property_read_bool(node, "qca,keep-pll-enabled"))
++ priv->flags |= AT803X_KEEP_PLL_ENABLED;
++
++ ret = at8031_register_regulators(phydev);
++ if (ret < 0)
++ return ret;
++
++ ret = devm_regulator_get_enable_optional(&phydev->mdio.dev,
++ "vddio");
++ if (ret) {
++ phydev_err(phydev, "failed to get VDDIO regulator\n");
++ return ret;
++ }
++
++ /* Only AR8031/8033 support 1000Base-X for SFP modules */
++ return phy_sfp_probe(phydev, &at8031_sfp_ops);
++}
++
++static int at8031_probe(struct phy_device *phydev)
++{
++ struct at803x_priv *priv = phydev->priv;
++ int mode_cfg;
++ int ccr;
++ int ret;
++
++ ret = at803x_probe(phydev);
++ if (ret)
++ return ret;
++
++ /* Only supported on AR8031/AR8033, the AR8030/AR8035 use strapping
++ * options.
++ */
++ ret = at8031_parse_dt(phydev);
++ if (ret)
++ return ret;
++
++ ccr = phy_read(phydev, AT803X_REG_CHIP_CONFIG);
++ if (ccr < 0)
++ return ccr;
++ mode_cfg = ccr & AT803X_MODE_CFG_MASK;
++
++ switch (mode_cfg) {
++ case AT803X_MODE_CFG_BX1000_RGMII_50OHM:
++ case AT803X_MODE_CFG_BX1000_RGMII_75OHM:
++ priv->is_1000basex = true;
++ fallthrough;
++ case AT803X_MODE_CFG_FX100_RGMII_50OHM:
++ case AT803X_MODE_CFG_FX100_RGMII_75OHM:
++ priv->is_fiber = true;
++ break;
++ }
++
++ /* Disable WoL in 1588 register which is enabled
++ * by default
++ */
++ return phy_modify_mmd(phydev, MDIO_MMD_PCS,
++ AT803X_PHY_MMD3_WOL_CTRL,
++ AT803X_WOL_EN, 0);
++}
++
++static int at8031_config_init(struct phy_device *phydev)
++{
++ struct at803x_priv *priv = phydev->priv;
++ int ret;
++
++ /* Some bootloaders leave the fiber page selected.
++ * Switch to the appropriate page (fiber or copper), as otherwise we
++ * read the PHY capabilities from the wrong page.
++ */
++ phy_lock_mdio_bus(phydev);
++ ret = at803x_write_page(phydev,
++ priv->is_fiber ? AT803X_PAGE_FIBER :
++ AT803X_PAGE_COPPER);
++ phy_unlock_mdio_bus(phydev);
++ if (ret)
++ return ret;
++
++ ret = at8031_pll_config(phydev);
++ if (ret < 0)
++ return ret;
++
++ return at803x_config_init(phydev);
++}
++
++static int at8031_set_wol(struct phy_device *phydev,
++ struct ethtool_wolinfo *wol)
++{
++ int ret;
++
++ /* First setup MAC address and enable WOL interrupt */
++ ret = at803x_set_wol(phydev, wol);
++ if (ret)
++ return ret;
++
++ if (wol->wolopts & WAKE_MAGIC)
++ /* Enable WOL function for 1588 */
++ ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
++ AT803X_PHY_MMD3_WOL_CTRL,
++ 0, AT803X_WOL_EN);
++ else
++ /* Disable WoL function for 1588 */
++ ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
++ AT803X_PHY_MMD3_WOL_CTRL,
++ AT803X_WOL_EN, 0);
++
++ return ret;
++}
++
++static int at8031_config_intr(struct phy_device *phydev)
++{
++ struct at803x_priv *priv = phydev->priv;
++ int err, value = 0;
++
++ if (phydev->interrupts == PHY_INTERRUPT_ENABLED &&
++ priv->is_fiber) {
++ /* Clear any pending interrupts */
++ err = at803x_ack_interrupt(phydev);
++ if (err)
++ return err;
++
++ value |= AT803X_INTR_ENABLE_LINK_FAIL_BX;
++ value |= AT803X_INTR_ENABLE_LINK_SUCCESS_BX;
++
++ err = phy_set_bits(phydev, AT803X_INTR_ENABLE, value);
++ if (err)
++ return err;
++ }
++
++ return at803x_config_intr(phydev);
++}
++
++/* AR8031 and AR8033 share the same read status logic */
++static int at8031_read_status(struct phy_device *phydev)
++{
++ struct at803x_priv *priv = phydev->priv;
++
++ if (priv->is_1000basex)
++ return genphy_c37_read_status(phydev);
++
++ return at803x_read_status(phydev);
++}
++
++/* AR8031 and AR8035 share the same cable test get status reg */
++static int at8031_cable_test_get_status(struct phy_device *phydev,
++ bool *finished)
++{
++ return at803x_cable_test_get_status(phydev, finished, 0xf);
++}
++
++/* AR8031 and AR8035 share the same cable test start logic */
++static int at8031_cable_test_start(struct phy_device *phydev)
++{
++ at803x_cable_test_autoneg(phydev);
++ phy_write(phydev, MII_CTRL1000, 0);
++ /* we do all the (time consuming) work later */
++ return 0;
++}
++
++/* AR8032, AR9331 and QCA9561 share the same cable test get status reg */
++static int at8032_cable_test_get_status(struct phy_device *phydev,
++ bool *finished)
++{
++ return at803x_cable_test_get_status(phydev, finished, 0x3);
++}
++
++static int at8035_parse_dt(struct phy_device *phydev)
++{
++ struct at803x_priv *priv = phydev->priv;
++
++ /* Mask is set by the generic at803x_parse_dt
++ * if property is set. Assume property is set
++ * with the mask not zero.
++ */
++ if (priv->clk_25m_mask) {
++ /* Fixup for the AR8030/AR8035. This chip has another mask and
++ * doesn't support the DSP reference. Eg. the lowest bit of the
++ * mask. The upper two bits select the same frequencies. Mask
++ * the lowest bit here.
++ *
++ * Warning:
++ * There was no datasheet for the AR8030 available so this is
++ * just a guess. But the AR8035 is listed as pin compatible
++ * to the AR8030 so there might be a good chance it works on
++ * the AR8030 too.
++ */
++ priv->clk_25m_reg &= AT8035_CLK_OUT_MASK;
++ priv->clk_25m_mask &= AT8035_CLK_OUT_MASK;
++ }
++
++ return 0;
++}
++
++/* AR8030 and AR8035 shared the same special mask for clk_25m */
++static int at8035_probe(struct phy_device *phydev)
++{
++ int ret;
++
++ ret = at803x_probe(phydev);
++ if (ret)
++ return ret;
++
++ return at8035_parse_dt(phydev);
++}
++
++static int qca83xx_config_init(struct phy_device *phydev)
++{
++ u8 switch_revision;
++
++ switch_revision = phydev->dev_flags & QCA8K_DEVFLAGS_REVISION_MASK;
++
++ switch (switch_revision) {
++ case 1:
++ /* For 100M waveform */
++ at803x_debug_reg_write(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, 0x02ea);
++ /* Turn on Gigabit clock */
++ at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x68a0);
++ break;
++
++ case 2:
++ phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0);
++ fallthrough;
++ case 4:
++ phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_AZ_DEBUG, 0x803f);
++ 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;
++ }
++
++ /* Following original QCA sourcecode set port to prefer master */
++ phy_set_bits(phydev, MII_CTRL1000, CTL1000_PREFER_MASTER);
++
++ return 0;
++}
++
++static int qca8327_config_init(struct phy_device *phydev)
++{
++ /* QCA8327 require DAC amplitude adjustment for 100m set to +6%.
++ * Disable on init and enable only with 100m speed following
++ * qca original source code.
++ */
++ at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
++ QCA8327_DEBUG_MANU_CTRL_EN, 0);
++
++ return qca83xx_config_init(phydev);
++}
++
++static void qca83xx_link_change_notify(struct phy_device *phydev)
++{
++ /* 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_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_ANALOG_TEST_CTRL,
++ QCA8327_DEBUG_MANU_CTRL_EN, 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;
++
++ usleep_range(1000, 2000);
++
++ return 0;
++}
++
++static int qca83xx_suspend(struct phy_device *phydev)
++{
++ 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,
++ AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE |
++ AT803X_DEBUG_HIB_CTRL_SEL_RST_80U, 0);
++
++ return 0;
++}
++
++static int qca8337_suspend(struct phy_device *phydev)
++{
++ /* Only QCA8337 support actual suspend. */
++ genphy_suspend(phydev);
++
++ return qca83xx_suspend(phydev);
++}
++
++static int qca8327_suspend(struct phy_device *phydev)
++{
++ u16 mask = 0;
++
++ /* QCA8327 cause port unreliability when phy suspend
++ * is set.
++ */
++ mask |= ~(BMCR_SPEED1000 | BMCR_FULLDPLX);
++ phy_modify(phydev, MII_BMCR, mask, 0);
++
++ return qca83xx_suspend(phydev);
++}
++
++static int qca808x_phy_fast_retrain_config(struct phy_device *phydev)
++{
++ int ret;
++
++ /* Enable fast retrain */
++ ret = genphy_c45_fast_retrain(phydev, true);
++ if (ret)
++ return ret;
++
++ phy_write_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_TOP_OPTION1,
++ QCA808X_TOP_OPTION1_DATA);
++ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB,
++ QCA808X_MSE_THRESHOLD_20DB_VALUE);
++ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB,
++ QCA808X_MSE_THRESHOLD_17DB_VALUE);
++ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB,
++ QCA808X_MSE_THRESHOLD_27DB_VALUE);
++ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB,
++ QCA808X_MSE_THRESHOLD_28DB_VALUE);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_1,
++ QCA808X_MMD3_DEBUG_1_VALUE);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_4,
++ QCA808X_MMD3_DEBUG_4_VALUE);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_5,
++ QCA808X_MMD3_DEBUG_5_VALUE);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_3,
++ QCA808X_MMD3_DEBUG_3_VALUE);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_6,
++ QCA808X_MMD3_DEBUG_6_VALUE);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_2,
++ QCA808X_MMD3_DEBUG_2_VALUE);
++
++ return 0;
++}
++
++static int qca808x_phy_ms_seed_enable(struct phy_device *phydev, bool enable)
++{
++ u16 seed_value;
++
++ if (!enable)
++ return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
++ QCA808X_MASTER_SLAVE_SEED_ENABLE, 0);
++
++ seed_value = prandom_u32_max(QCA808X_MASTER_SLAVE_SEED_RANGE);
++ return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
++ QCA808X_MASTER_SLAVE_SEED_CFG | QCA808X_MASTER_SLAVE_SEED_ENABLE,
++ FIELD_PREP(QCA808X_MASTER_SLAVE_SEED_CFG, seed_value) |
++ QCA808X_MASTER_SLAVE_SEED_ENABLE);
++}
++
++static bool qca808x_is_prefer_master(struct phy_device *phydev)
++{
++ return (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_FORCE) ||
++ (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_PREFERRED);
++}
++
++static bool qca808x_has_fast_retrain_or_slave_seed(struct phy_device *phydev)
++{
++ return linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
++}
++
++static int qca808x_config_init(struct phy_device *phydev)
++{
++ int ret;
++
++ /* Active adc&vga on 802.3az for the link 1000M and 100M */
++ ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_ADDR_CLD_CTRL7,
++ QCA808X_8023AZ_AFE_CTRL_MASK, QCA808X_8023AZ_AFE_EN);
++ if (ret)
++ return ret;
++
++ /* Adjust the threshold on 802.3az for the link 1000M */
++ ret = phy_write_mmd(phydev, MDIO_MMD_PCS,
++ QCA808X_PHY_MMD3_AZ_TRAINING_CTRL,
++ QCA808X_MMD3_AZ_TRAINING_VAL);
++ if (ret)
++ return ret;
++
++ if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
++ /* Config the fast retrain for the link 2500M */
++ ret = qca808x_phy_fast_retrain_config(phydev);
++ if (ret)
++ return ret;
++
++ ret = genphy_read_master_slave(phydev);
++ if (ret < 0)
++ return ret;
++
++ if (!qca808x_is_prefer_master(phydev)) {
++ /* Enable seed and configure lower ramdom seed to make phy
++ * linked as slave mode.
++ */
++ ret = qca808x_phy_ms_seed_enable(phydev, true);
++ if (ret)
++ return ret;
++ }
++ }
++
++ /* Configure adc threshold as 100mv for the link 10M */
++ return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_ADC_THRESHOLD,
++ QCA808X_ADC_THRESHOLD_MASK,
++ QCA808X_ADC_THRESHOLD_100MV);
++}
++
++static int qca808x_read_status(struct phy_device *phydev)
++{
++ struct at803x_ss_mask ss_mask = { 0 };
++ int ret;
++
++ ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
++ if (ret < 0)
++ return ret;
++
++ linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->lp_advertising,
++ ret & MDIO_AN_10GBT_STAT_LP2_5G);
++
++ ret = genphy_read_status(phydev);
++ if (ret)
++ return ret;
++
++ /* qca8081 takes the different bits for speed value from at803x */
++ ss_mask.speed_mask = QCA808X_SS_SPEED_MASK;
++ ss_mask.speed_shift = __bf_shf(QCA808X_SS_SPEED_MASK);
++ ret = at803x_read_specific_status(phydev, ss_mask);
++ if (ret < 0)
++ return ret;
++
++ if (phydev->link) {
++ if (phydev->speed == SPEED_2500)
++ phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
++ else
++ phydev->interface = PHY_INTERFACE_MODE_SGMII;
++ } else {
++ /* generate seed as a lower random value to make PHY linked as SLAVE easily,
++ * except for master/slave configuration fault detected or the master mode
++ * preferred.
++ *
++ * the reason for not putting this code into the function link_change_notify is
++ * the corner case where the link partner is also the qca8081 PHY and the seed
++ * value is configured as the same value, the link can't be up and no link change
++ * occurs.
++ */
++ if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
++ if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR ||
++ qca808x_is_prefer_master(phydev)) {
++ qca808x_phy_ms_seed_enable(phydev, false);
++ } else {
++ qca808x_phy_ms_seed_enable(phydev, true);
++ }
++ }
++ }
++
++ return 0;
++}
++
++static int qca808x_soft_reset(struct phy_device *phydev)
++{
++ int ret;
++
++ ret = genphy_soft_reset(phydev);
++ if (ret < 0)
++ return ret;
++
++ if (qca808x_has_fast_retrain_or_slave_seed(phydev))
++ ret = qca808x_phy_ms_seed_enable(phydev, true);
++
++ return ret;
++}
++
++static bool qca808x_cdt_fault_length_valid(int cdt_code)
++{
++ switch (cdt_code) {
++ case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
++ return true;
++ default:
++ return false;
++ }
++}
++
++static int qca808x_cable_test_result_trans(int cdt_code)
++{
++ switch (cdt_code) {
++ case QCA808X_CDT_STATUS_STAT_NORMAL:
++ return ETHTOOL_A_CABLE_RESULT_CODE_OK;
++ case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
++ return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
++ case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
++ return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
++ return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
++ case QCA808X_CDT_STATUS_STAT_FAIL:
++ default:
++ return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
++ }
++}
++
++static int qca808x_cdt_fault_length(struct phy_device *phydev, int pair,
++ int result)
++{
++ int val;
++ u32 cdt_length_reg = 0;
++
++ switch (pair) {
++ case ETHTOOL_A_CABLE_PAIR_A:
++ cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_A;
++ break;
++ case ETHTOOL_A_CABLE_PAIR_B:
++ cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_B;
++ break;
++ case ETHTOOL_A_CABLE_PAIR_C:
++ cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_C;
++ break;
++ case ETHTOOL_A_CABLE_PAIR_D:
++ cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_D;
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ val = phy_read_mmd(phydev, MDIO_MMD_PCS, cdt_length_reg);
++ if (val < 0)
++ return val;
++
++ if (result == ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT)
++ val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_SAME_SHORT, val);
++ else
++ val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT, val);
++
++ return at803x_cdt_fault_length(val);
++}
++
++static int qca808x_cable_test_start(struct phy_device *phydev)
++{
++ int ret;
++
++ /* perform CDT with the following configs:
++ * 1. disable hibernation.
++ * 2. force PHY working in MDI mode.
++ * 3. for PHY working in 1000BaseT.
++ * 4. configure the threshold.
++ */
++
++ ret = at803x_debug_reg_mask(phydev, QCA808X_DBG_AN_TEST, QCA808X_HIBERNATION_EN, 0);
++ if (ret < 0)
++ return ret;
++
++ ret = at803x_config_mdix(phydev, ETH_TP_MDI);
++ if (ret < 0)
++ return ret;
++
++ /* Force 1000base-T needs to configure PMA/PMD and MII_BMCR */
++ phydev->duplex = DUPLEX_FULL;
++ phydev->speed = SPEED_1000;
++ ret = genphy_c45_pma_setup_forced(phydev);
++ if (ret < 0)
++ return ret;
++
++ ret = genphy_setup_forced(phydev);
++ if (ret < 0)
++ return ret;
++
++ /* configure the thresholds for open, short, pair ok test */
++ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8074, 0xc040);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8076, 0xc040);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8077, 0xa060);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8078, 0xc050);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807a, 0xc060);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807e, 0xb060);
++
++ return 0;
++}
++
++static int qca808x_cable_test_get_pair_status(struct phy_device *phydev, u8 pair,
++ u16 status)
++{
++ int length, result;
++ u16 pair_code;
++
++ switch (pair) {
++ case ETHTOOL_A_CABLE_PAIR_A:
++ pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, status);
++ break;
++ case ETHTOOL_A_CABLE_PAIR_B:
++ pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, status);
++ break;
++ case ETHTOOL_A_CABLE_PAIR_C:
++ pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, status);
++ break;
++ case ETHTOOL_A_CABLE_PAIR_D:
++ pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, status);
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ result = qca808x_cable_test_result_trans(pair_code);
++ ethnl_cable_test_result(phydev, pair, result);
++
++ if (qca808x_cdt_fault_length_valid(pair_code)) {
++ length = qca808x_cdt_fault_length(phydev, pair, result);
++ ethnl_cable_test_fault_length(phydev, pair, length);
++ }
++
++ return 0;
++}
++
++static int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished)
++{
++ int ret, val;
++
++ *finished = false;
++
++ val = QCA808X_CDT_ENABLE_TEST |
++ QCA808X_CDT_LENGTH_UNIT;
++ ret = at803x_cdt_start(phydev, val);
++ if (ret)
++ return ret;
++
++ ret = at803x_cdt_wait_for_completion(phydev, QCA808X_CDT_ENABLE_TEST);
++ if (ret)
++ return ret;
++
++ val = phy_read_mmd(phydev, MDIO_MMD_PCS, QCA808X_MMD3_CDT_STATUS);
++ if (val < 0)
++ return val;
++
++ ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_A, val);
++ if (ret)
++ return ret;
++
++ ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_B, val);
++ if (ret)
++ return ret;
++
++ ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_C, val);
++ if (ret)
++ return ret;
++
++ ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_D, val);
++ if (ret)
++ return ret;
++
++ *finished = true;
++
++ return 0;
++}
++
++static int qca808x_get_features(struct phy_device *phydev)
++{
++ int ret;
++
++ ret = genphy_c45_pma_read_abilities(phydev);
++ if (ret)
++ return ret;
++
++ /* The autoneg ability is not existed in bit3 of MMD7.1,
++ * but it is supported by qca808x PHY, so we add it here
++ * manually.
++ */
++ linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
++
++ /* As for the qca8081 1G version chip, the 2500baseT ability is also
++ * existed in the bit0 of MMD1.21, we need to remove it manually if
++ * it is the qca8081 1G chip according to the bit0 of MMD7.0x901d.
++ */
++ ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_CHIP_TYPE);
++ if (ret < 0)
++ return ret;
++
++ if (QCA808X_PHY_CHIP_TYPE_1G & ret)
++ linkmode_clear_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
++
++ return 0;
++}
++
++static int qca808x_config_aneg(struct phy_device *phydev)
++{
++ int phy_ctrl = 0;
++ int ret;
++
++ ret = at803x_prepare_config_aneg(phydev);
++ if (ret)
++ return ret;
++
++ /* The reg MII_BMCR also needs to be configured for force mode, the
++ * genphy_config_aneg is also needed.
++ */
++ if (phydev->autoneg == AUTONEG_DISABLE)
++ genphy_c45_pma_setup_forced(phydev);
++
++ if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->advertising))
++ phy_ctrl = MDIO_AN_10GBT_CTRL_ADV2_5G;
++
++ ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
++ MDIO_AN_10GBT_CTRL_ADV2_5G, phy_ctrl);
++ if (ret < 0)
++ return ret;
++
++ return __genphy_config_aneg(phydev, ret);
++}
++
++static void qca808x_link_change_notify(struct phy_device *phydev)
++{
++ /* Assert interface sgmii fifo on link down, deassert it on link up,
++ * the interface device address is always phy address added by 1.
++ */
++ mdiobus_c45_modify_changed(phydev->mdio.bus, phydev->mdio.addr + 1,
++ MDIO_MMD_PMAPMD, QCA8081_PHY_SERDES_MMD1_FIFO_CTRL,
++ QCA8081_PHY_FIFO_RSTN,
++ phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
++}
++
++static int qca808x_led_parse_netdev(struct phy_device *phydev, unsigned long rules,
++ u16 *offload_trigger)
++{
++ /* Parsing specific to netdev trigger */
++ if (test_bit(TRIGGER_NETDEV_TX, &rules))
++ *offload_trigger |= QCA808X_LED_TX_BLINK;
++ if (test_bit(TRIGGER_NETDEV_RX, &rules))
++ *offload_trigger |= QCA808X_LED_RX_BLINK;
++ if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
++ *offload_trigger |= QCA808X_LED_SPEED10_ON;
++ if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
++ *offload_trigger |= QCA808X_LED_SPEED100_ON;
++ if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
++ *offload_trigger |= QCA808X_LED_SPEED1000_ON;
++ if (test_bit(TRIGGER_NETDEV_LINK_2500, &rules))
++ *offload_trigger |= QCA808X_LED_SPEED2500_ON;
++ if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
++ *offload_trigger |= QCA808X_LED_HALF_DUPLEX_ON;
++ if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
++ *offload_trigger |= QCA808X_LED_FULL_DUPLEX_ON;
++
++ if (rules && !*offload_trigger)
++ return -EOPNOTSUPP;
++
++ /* Enable BLINK_CHECK_BYPASS by default to make the LED
++ * blink even with duplex or speed mode not enabled.
++ */
++ *offload_trigger |= QCA808X_LED_BLINK_CHECK_BYPASS;
++
++ return 0;
++}
++
++static int qca808x_led_hw_control_enable(struct phy_device *phydev, u8 index)
++{
++ u16 reg;
++
++ if (index > 2)
++ return -EINVAL;
++
++ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++ return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
++ QCA808X_LED_FORCE_EN);
++}
++
++static int qca808x_led_hw_is_supported(struct phy_device *phydev, u8 index,
++ unsigned long rules)
++{
++ u16 offload_trigger = 0;
++
++ if (index > 2)
++ return -EINVAL;
++
++ return qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
++}
++
++static int qca808x_led_hw_control_set(struct phy_device *phydev, u8 index,
++ unsigned long rules)
++{
++ u16 reg, offload_trigger = 0;
++ int ret;
++
++ if (index > 2)
++ return -EINVAL;
++
++ reg = QCA808X_MMD7_LED_CTRL(index);
++
++ ret = qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
++ if (ret)
++ return ret;
++
++ ret = qca808x_led_hw_control_enable(phydev, index);
++ if (ret)
++ return ret;
++
++ return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++ QCA808X_LED_PATTERN_MASK,
++ offload_trigger);
++}
++
++static bool qca808x_led_hw_control_status(struct phy_device *phydev, u8 index)
++{
++ u16 reg;
++ int val;
++
++ if (index > 2)
++ return false;
++
++ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++ val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
++
++ return !(val & QCA808X_LED_FORCE_EN);
++}
++
++static int qca808x_led_hw_control_get(struct phy_device *phydev, u8 index,
++ unsigned long *rules)
++{
++ u16 reg;
++ int val;
++
++ if (index > 2)
++ return -EINVAL;
++
++ /* Check if we have hw control enabled */
++ if (qca808x_led_hw_control_status(phydev, index))
++ return -EINVAL;
++
++ reg = QCA808X_MMD7_LED_CTRL(index);
++
++ val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
++ if (val & QCA808X_LED_TX_BLINK)
++ set_bit(TRIGGER_NETDEV_TX, rules);
++ if (val & QCA808X_LED_RX_BLINK)
++ set_bit(TRIGGER_NETDEV_RX, rules);
++ if (val & QCA808X_LED_SPEED10_ON)
++ set_bit(TRIGGER_NETDEV_LINK_10, rules);
++ if (val & QCA808X_LED_SPEED100_ON)
++ set_bit(TRIGGER_NETDEV_LINK_100, rules);
++ if (val & QCA808X_LED_SPEED1000_ON)
++ set_bit(TRIGGER_NETDEV_LINK_1000, rules);
++ if (val & QCA808X_LED_SPEED2500_ON)
++ set_bit(TRIGGER_NETDEV_LINK_2500, rules);
++ if (val & QCA808X_LED_HALF_DUPLEX_ON)
++ set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
++ if (val & QCA808X_LED_FULL_DUPLEX_ON)
++ set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
++
++ return 0;
++}
++
++static int qca808x_led_hw_control_reset(struct phy_device *phydev, u8 index)
++{
++ u16 reg;
++
++ if (index > 2)
++ return -EINVAL;
++
++ reg = QCA808X_MMD7_LED_CTRL(index);
++
++ return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
++ QCA808X_LED_PATTERN_MASK);
++}
++
++static int qca808x_led_brightness_set(struct phy_device *phydev,
++ u8 index, enum led_brightness value)
++{
++ u16 reg;
++ int ret;
++
++ if (index > 2)
++ return -EINVAL;
++
++ if (!value) {
++ ret = qca808x_led_hw_control_reset(phydev, index);
++ if (ret)
++ return ret;
++ }
++
++ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++ return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++ QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
++ QCA808X_LED_FORCE_EN | value ? QCA808X_LED_FORCE_ON :
++ QCA808X_LED_FORCE_OFF);
++}
++
++static int qca808x_led_blink_set(struct phy_device *phydev, u8 index,
++ unsigned long *delay_on,
++ unsigned long *delay_off)
++{
++ int ret;
++ u16 reg;
++
++ if (index > 2)
++ return -EINVAL;
++
++ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++ /* Set blink to 50% off, 50% on at 4Hz by default */
++ ret = phy_modify_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_LED_GLOBAL,
++ QCA808X_LED_BLINK_FREQ_MASK | QCA808X_LED_BLINK_DUTY_MASK,
++ QCA808X_LED_BLINK_FREQ_4HZ | QCA808X_LED_BLINK_DUTY_50_50);
++ if (ret)
++ return ret;
++
++ /* We use BLINK_1 for normal blinking */
++ ret = phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++ QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
++ QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_BLINK_1);
++ if (ret)
++ return ret;
++
++ /* We set blink to 4Hz, aka 250ms */
++ *delay_on = 250 / 2;
++ *delay_off = 250 / 2;
++
++ return 0;
++}
++
++static int qca808x_led_polarity_set(struct phy_device *phydev, int index,
++ unsigned long modes)
++{
++ struct at803x_priv *priv = phydev->priv;
++ bool active_low = false;
++ u32 mode;
++
++ for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
++ switch (mode) {
++ case PHY_LED_ACTIVE_LOW:
++ active_low = true;
++ break;
++ default:
++ return -EINVAL;
++ }
++ }
++
++ /* PHY polarity is global and can't be set per LED.
++ * To detect this, check if last requested polarity mode
++ * match the new one.
++ */
++ if (priv->led_polarity_mode >= 0 &&
++ priv->led_polarity_mode != active_low) {
++ phydev_err(phydev, "PHY polarity is global. Mismatched polarity on different LED\n");
++ return -EINVAL;
++ }
++
++ /* Save the last PHY polarity mode */
++ priv->led_polarity_mode = active_low;
++
++ return phy_modify_mmd(phydev, MDIO_MMD_AN,
++ QCA808X_MMD7_LED_POLARITY_CTRL,
++ QCA808X_LED_ACTIVE_HIGH,
++ active_low ? 0 : QCA808X_LED_ACTIVE_HIGH);
++}
++
++static struct phy_driver at803x_driver[] = {
++{
++ /* Qualcomm Atheros AR8035 */
++ PHY_ID_MATCH_EXACT(ATH8035_PHY_ID),
++ .name = "Qualcomm Atheros AR8035",
++ .flags = PHY_POLL_CABLE_TEST,
++ .probe = at8035_probe,
++ .config_aneg = at803x_config_aneg,
++ .config_init = at803x_config_init,
++ .soft_reset = genphy_soft_reset,
++ .set_wol = at803x_set_wol,
++ .get_wol = at803x_get_wol,
++ .suspend = at803x_suspend,
++ .resume = at803x_resume,
++ /* PHY_GBIT_FEATURES */
++ .read_status = at803x_read_status,
++ .config_intr = at803x_config_intr,
++ .handle_interrupt = at803x_handle_interrupt,
++ .get_tunable = at803x_get_tunable,
++ .set_tunable = at803x_set_tunable,
++ .cable_test_start = at8031_cable_test_start,
++ .cable_test_get_status = at8031_cable_test_get_status,
++}, {
++ /* Qualcomm Atheros AR8030 */
++ .phy_id = ATH8030_PHY_ID,
++ .name = "Qualcomm Atheros AR8030",
++ .phy_id_mask = AT8030_PHY_ID_MASK,
++ .probe = at8035_probe,
++ .config_init = at803x_config_init,
++ .link_change_notify = at803x_link_change_notify,
++ .set_wol = at803x_set_wol,
++ .get_wol = at803x_get_wol,
++ .suspend = at803x_suspend,
++ .resume = at803x_resume,
++ /* PHY_BASIC_FEATURES */
++ .config_intr = at803x_config_intr,
++ .handle_interrupt = at803x_handle_interrupt,
++}, {
++ /* Qualcomm Atheros AR8031/AR8033 */
++ PHY_ID_MATCH_EXACT(ATH8031_PHY_ID),
++ .name = "Qualcomm Atheros AR8031/AR8033",
++ .flags = PHY_POLL_CABLE_TEST,
++ .probe = at8031_probe,
++ .config_init = at8031_config_init,
++ .config_aneg = at803x_config_aneg,
++ .soft_reset = genphy_soft_reset,
++ .set_wol = at8031_set_wol,
++ .get_wol = at803x_get_wol,
++ .suspend = at803x_suspend,
++ .resume = at803x_resume,
++ .read_page = at803x_read_page,
++ .write_page = at803x_write_page,
++ .get_features = at803x_get_features,
++ .read_status = at8031_read_status,
++ .config_intr = at8031_config_intr,
++ .handle_interrupt = at803x_handle_interrupt,
++ .get_tunable = at803x_get_tunable,
++ .set_tunable = at803x_set_tunable,
++ .cable_test_start = at8031_cable_test_start,
++ .cable_test_get_status = at8031_cable_test_get_status,
++}, {
++ /* Qualcomm Atheros AR8032 */
++ PHY_ID_MATCH_EXACT(ATH8032_PHY_ID),
++ .name = "Qualcomm Atheros AR8032",
++ .probe = at803x_probe,
++ .flags = PHY_POLL_CABLE_TEST,
++ .config_init = at803x_config_init,
++ .link_change_notify = at803x_link_change_notify,
++ .suspend = at803x_suspend,
++ .resume = at803x_resume,
++ /* PHY_BASIC_FEATURES */
++ .config_intr = at803x_config_intr,
++ .handle_interrupt = at803x_handle_interrupt,
++ .cable_test_start = at803x_cable_test_start,
++ .cable_test_get_status = at8032_cable_test_get_status,
++}, {
++ /* ATHEROS AR9331 */
++ PHY_ID_MATCH_EXACT(ATH9331_PHY_ID),
++ .name = "Qualcomm Atheros AR9331 built-in PHY",
++ .probe = at803x_probe,
++ .suspend = at803x_suspend,
++ .resume = at803x_resume,
++ .flags = PHY_POLL_CABLE_TEST,
++ /* PHY_BASIC_FEATURES */
++ .config_intr = at803x_config_intr,
++ .handle_interrupt = at803x_handle_interrupt,
++ .cable_test_start = at803x_cable_test_start,
++ .cable_test_get_status = at8032_cable_test_get_status,
++ .read_status = at803x_read_status,
++ .soft_reset = genphy_soft_reset,
++ .config_aneg = at803x_config_aneg,
++}, {
++ /* Qualcomm Atheros QCA9561 */
++ PHY_ID_MATCH_EXACT(QCA9561_PHY_ID),
++ .name = "Qualcomm Atheros QCA9561 built-in PHY",
++ .probe = at803x_probe,
++ .suspend = at803x_suspend,
++ .resume = at803x_resume,
++ .flags = PHY_POLL_CABLE_TEST,
++ /* PHY_BASIC_FEATURES */
++ .config_intr = at803x_config_intr,
++ .handle_interrupt = at803x_handle_interrupt,
++ .cable_test_start = at803x_cable_test_start,
++ .cable_test_get_status = at8032_cable_test_get_status,
++ .read_status = at803x_read_status,
++ .soft_reset = genphy_soft_reset,
++ .config_aneg = at803x_config_aneg,
++}, {
++ /* QCA8337 */
++ .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 = qca83xx_get_sset_count,
++ .get_strings = qca83xx_get_strings,
++ .get_stats = qca83xx_get_stats,
++ .suspend = qca8337_suspend,
++ .resume = qca83xx_resume,
++}, {
++ /* QCA8327-A from switch QCA8327-AL1A */
++ .phy_id = QCA8327_A_PHY_ID,
++ .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 = qca8327_config_init,
++ .soft_reset = genphy_soft_reset,
++ .get_sset_count = qca83xx_get_sset_count,
++ .get_strings = qca83xx_get_strings,
++ .get_stats = qca83xx_get_stats,
++ .suspend = qca8327_suspend,
++ .resume = qca83xx_resume,
++}, {
++ /* QCA8327-B from switch QCA8327-BL1A */
++ .phy_id = QCA8327_B_PHY_ID,
++ .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 = qca8327_config_init,
++ .soft_reset = genphy_soft_reset,
++ .get_sset_count = qca83xx_get_sset_count,
++ .get_strings = qca83xx_get_strings,
++ .get_stats = qca83xx_get_stats,
++ .suspend = qca8327_suspend,
++ .resume = qca83xx_resume,
++}, {
++ /* Qualcomm QCA8081 */
++ PHY_ID_MATCH_EXACT(QCA8081_PHY_ID),
++ .name = "Qualcomm QCA8081",
++ .flags = PHY_POLL_CABLE_TEST,
++ .probe = at803x_probe,
++ .config_intr = at803x_config_intr,
++ .handle_interrupt = at803x_handle_interrupt,
++ .get_tunable = at803x_get_tunable,
++ .set_tunable = at803x_set_tunable,
++ .set_wol = at803x_set_wol,
++ .get_wol = at803x_get_wol,
++ .get_features = qca808x_get_features,
++ .config_aneg = qca808x_config_aneg,
++ .suspend = genphy_suspend,
++ .resume = genphy_resume,
++ .read_status = qca808x_read_status,
++ .config_init = qca808x_config_init,
++ .soft_reset = qca808x_soft_reset,
++ .cable_test_start = qca808x_cable_test_start,
++ .cable_test_get_status = qca808x_cable_test_get_status,
++ .link_change_notify = qca808x_link_change_notify,
++ .led_brightness_set = qca808x_led_brightness_set,
++ .led_blink_set = qca808x_led_blink_set,
++ .led_hw_is_supported = qca808x_led_hw_is_supported,
++ .led_hw_control_set = qca808x_led_hw_control_set,
++ .led_hw_control_get = qca808x_led_hw_control_get,
++ .led_polarity_set = qca808x_led_polarity_set,
++}, };
++
++module_phy_driver(at803x_driver);
++
++static struct mdio_device_id __maybe_unused atheros_tbl[] = {
++ { ATH8030_PHY_ID, AT8030_PHY_ID_MASK },
++ { PHY_ID_MATCH_EXACT(ATH8031_PHY_ID) },
++ { 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_A_PHY_ID) },
++ { PHY_ID_MATCH_EXACT(QCA8327_B_PHY_ID) },
++ { PHY_ID_MATCH_EXACT(QCA9561_PHY_ID) },
++ { PHY_ID_MATCH_EXACT(QCA8081_PHY_ID) },
++ { }
++};
++
++MODULE_DEVICE_TABLE(mdio, atheros_tbl);
diff --git a/target/linux/generic/backport-6.6/713-v6.9-02-net-phy-qcom-create-and-move-functions-to-shared-lib.patch b/target/linux/generic/backport-6.6/713-v6.9-02-net-phy-qcom-create-and-move-functions-to-shared-lib.patch
new file mode 100644
index 0000000000..7d0e1f4a28
--- /dev/null
+++ b/target/linux/generic/backport-6.6/713-v6.9-02-net-phy-qcom-create-and-move-functions-to-shared-lib.patch
@@ -0,0 +1,243 @@
+From 6fb760972c49490b03f3db2ad64cf30bdd28c54a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 Jan 2024 15:15:20 +0100
+Subject: [PATCH 2/5] net: phy: qcom: create and move functions to shared
+ library
+
+Create and move functions to shared library in preparation for qca83xx
+PHY Family to be detached from at803x driver.
+
+Only the shared defines are moved to the shared qcom.h header.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/20240129141600.2592-3-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/qcom/Kconfig | 4 ++
+ drivers/net/phy/qcom/Makefile | 1 +
+ drivers/net/phy/qcom/at803x.c | 69 +----------------------------
+ drivers/net/phy/qcom/qcom-phy-lib.c | 53 ++++++++++++++++++++++
+ drivers/net/phy/qcom/qcom.h | 34 ++++++++++++++
+ 5 files changed, 94 insertions(+), 67 deletions(-)
+ create mode 100644 drivers/net/phy/qcom/qcom-phy-lib.c
+ create mode 100644 drivers/net/phy/qcom/qcom.h
+
+--- a/drivers/net/phy/qcom/Kconfig
++++ b/drivers/net/phy/qcom/Kconfig
+@@ -1,6 +1,10 @@
+ # SPDX-License-Identifier: GPL-2.0-only
++config QCOM_NET_PHYLIB
++ tristate
++
+ config AT803X_PHY
+ tristate "Qualcomm Atheros AR803X PHYs and QCA833x PHYs"
++ select QCOM_NET_PHYLIB
+ depends on REGULATOR
+ help
+ Currently supports the AR8030, AR8031, AR8033, AR8035 and internal
+--- a/drivers/net/phy/qcom/Makefile
++++ b/drivers/net/phy/qcom/Makefile
+@@ -1,2 +1,3 @@
+ # SPDX-License-Identifier: GPL-2.0
++obj-$(CONFIG_QCOM_NET_PHYLIB) += qcom-phy-lib.o
+ obj-$(CONFIG_AT803X_PHY) += at803x.o
+--- a/drivers/net/phy/qcom/at803x.c
++++ b/drivers/net/phy/qcom/at803x.c
+@@ -22,6 +22,8 @@
+ #include <linux/sfp.h>
+ #include <dt-bindings/net/qca-ar803x.h>
+
++#include "qcom.h"
++
+ #define AT803X_SPECIFIC_FUNCTION_CONTROL 0x10
+ #define AT803X_SFC_ASSERT_CRS BIT(11)
+ #define AT803X_SFC_FORCE_LINK BIT(10)
+@@ -84,9 +86,6 @@
+ #define AT803X_REG_CHIP_CONFIG 0x1f
+ #define AT803X_BT_BX_REG_SEL 0x8000
+
+-#define AT803X_DEBUG_ADDR 0x1D
+-#define AT803X_DEBUG_DATA 0x1E
+-
+ #define AT803X_MODE_CFG_MASK 0x0F
+ #define AT803X_MODE_CFG_BASET_RGMII 0x00
+ #define AT803X_MODE_CFG_BASET_SGMII 0x01
+@@ -103,19 +102,6 @@
+ #define AT803X_PSSR 0x11 /*PHY-Specific Status Register*/
+ #define AT803X_PSSR_MR_AN_COMPLETE 0x0200
+
+-#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_SYSTEM_CTRL_MODE 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_HIB_CTRL_PS_HIB_EN BIT(15)
+-
+ #define AT803X_DEBUG_REG_3C 0x3C
+
+ #define AT803X_DEBUG_REG_GREEN 0x3D
+@@ -393,18 +379,6 @@ MODULE_DESCRIPTION("Qualcomm Atheros AR8
+ MODULE_AUTHOR("Matus Ujhelyi");
+ MODULE_LICENSE("GPL");
+
+-enum stat_access_type {
+- PHY,
+- MMD
+-};
+-
+-struct at803x_hw_stat {
+- const char *string;
+- u8 reg;
+- u32 mask;
+- enum stat_access_type access_type;
+-};
+-
+ static struct at803x_hw_stat qca83xx_hw_stats[] = {
+ { "phy_idle_errors", 0xa, GENMASK(7, 0), PHY},
+ { "phy_receive_errors", 0x15, GENMASK(15, 0), PHY},
+@@ -439,45 +413,6 @@ struct at803x_context {
+ u16 led_control;
+ };
+
+-static int at803x_debug_reg_write(struct phy_device *phydev, u16 reg, u16 data)
+-{
+- int ret;
+-
+- ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
+- if (ret < 0)
+- return ret;
+-
+- return phy_write(phydev, AT803X_DEBUG_DATA, data);
+-}
+-
+-static int at803x_debug_reg_read(struct phy_device *phydev, u16 reg)
+-{
+- int ret;
+-
+- ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
+- if (ret < 0)
+- return ret;
+-
+- return phy_read(phydev, AT803X_DEBUG_DATA);
+-}
+-
+-static int at803x_debug_reg_mask(struct phy_device *phydev, u16 reg,
+- u16 clear, u16 set)
+-{
+- u16 val;
+- int ret;
+-
+- ret = at803x_debug_reg_read(phydev, reg);
+- if (ret < 0)
+- return ret;
+-
+- val = ret & 0xffff;
+- val &= ~clear;
+- val |= set;
+-
+- return phy_write(phydev, AT803X_DEBUG_DATA, val);
+-}
+-
+ static int at803x_write_page(struct phy_device *phydev, int page)
+ {
+ int mask;
+--- /dev/null
++++ b/drivers/net/phy/qcom/qcom-phy-lib.c
+@@ -0,0 +1,53 @@
++// SPDX-License-Identifier: GPL-2.0
++
++#include <linux/phy.h>
++#include <linux/module.h>
++
++#include "qcom.h"
++
++MODULE_DESCRIPTION("Qualcomm PHY driver Common Functions");
++MODULE_AUTHOR("Matus Ujhelyi");
++MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
++MODULE_LICENSE("GPL");
++
++int at803x_debug_reg_read(struct phy_device *phydev, u16 reg)
++{
++ int ret;
++
++ ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
++ if (ret < 0)
++ return ret;
++
++ return phy_read(phydev, AT803X_DEBUG_DATA);
++}
++EXPORT_SYMBOL_GPL(at803x_debug_reg_read);
++
++int at803x_debug_reg_mask(struct phy_device *phydev, u16 reg,
++ u16 clear, u16 set)
++{
++ u16 val;
++ int ret;
++
++ ret = at803x_debug_reg_read(phydev, reg);
++ if (ret < 0)
++ return ret;
++
++ val = ret & 0xffff;
++ val &= ~clear;
++ val |= set;
++
++ return phy_write(phydev, AT803X_DEBUG_DATA, val);
++}
++EXPORT_SYMBOL_GPL(at803x_debug_reg_mask);
++
++int at803x_debug_reg_write(struct phy_device *phydev, u16 reg, u16 data)
++{
++ int ret;
++
++ ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
++ if (ret < 0)
++ return ret;
++
++ return phy_write(phydev, AT803X_DEBUG_DATA, data);
++}
++EXPORT_SYMBOL_GPL(at803x_debug_reg_write);
+--- /dev/null
++++ b/drivers/net/phy/qcom/qcom.h
+@@ -0,0 +1,34 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++
++#define AT803X_DEBUG_ADDR 0x1D
++#define AT803X_DEBUG_DATA 0x1E
++
++#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_SYSTEM_CTRL_MODE 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_HIB_CTRL_PS_HIB_EN BIT(15)
++
++enum stat_access_type {
++ PHY,
++ MMD
++};
++
++struct at803x_hw_stat {
++ const char *string;
++ u8 reg;
++ u32 mask;
++ enum stat_access_type access_type;
++};
++
++int at803x_debug_reg_read(struct phy_device *phydev, u16 reg);
++int at803x_debug_reg_mask(struct phy_device *phydev, u16 reg,
++ u16 clear, u16 set);
++int at803x_debug_reg_write(struct phy_device *phydev, u16 reg, u16 data);
diff --git a/target/linux/generic/backport-6.6/713-v6.9-03-net-phy-qcom-deatch-qca83xx-PHY-driver-from-at803x.patch b/target/linux/generic/backport-6.6/713-v6.9-03-net-phy-qcom-deatch-qca83xx-PHY-driver-from-at803x.patch
new file mode 100644
index 0000000000..6ac09dcb9a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/713-v6.9-03-net-phy-qcom-deatch-qca83xx-PHY-driver-from-at803x.patch
@@ -0,0 +1,638 @@
+From 2e45d404d99d43bb7127b74b5dea8818df64996c Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 Jan 2024 15:15:21 +0100
+Subject: [PATCH 3/5] net: phy: qcom: deatch qca83xx PHY driver from at803x
+
+Deatch qca83xx PHY driver from at803x.
+
+The QCA83xx PHYs implement specific function and doesn't use generic
+at803x so it can be detached from the driver and moved to a dedicated
+one.
+
+Probe function and priv struct is reimplemented to allocate and use
+only the qca83xx specific data. Unused data from at803x PHY driver
+are dropped from at803x priv struct.
+
+This is to make slimmer PHY drivers instead of including lots of bloat
+that would never be used in specific SoC.
+
+A new Kconfig flag QCA83XX_PHY is introduced to compile the new
+introduced PHY driver.
+
+As the Kconfig name starts with Qualcomm the same order is kept.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/20240129141600.2592-4-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/qcom/Kconfig | 11 +-
+ drivers/net/phy/qcom/Makefile | 1 +
+ drivers/net/phy/qcom/at803x.c | 235 ----------------------------
+ drivers/net/phy/qcom/qca83xx.c | 275 +++++++++++++++++++++++++++++++++
+ 4 files changed, 284 insertions(+), 238 deletions(-)
+ create mode 100644 drivers/net/phy/qcom/qca83xx.c
+
+--- a/drivers/net/phy/qcom/Kconfig
++++ b/drivers/net/phy/qcom/Kconfig
+@@ -3,9 +3,14 @@ config QCOM_NET_PHYLIB
+ tristate
+
+ config AT803X_PHY
+- tristate "Qualcomm Atheros AR803X PHYs and QCA833x PHYs"
++ tristate "Qualcomm Atheros AR803X PHYs"
+ select QCOM_NET_PHYLIB
+ depends on REGULATOR
+ help
+- Currently supports the AR8030, AR8031, AR8033, AR8035 and internal
+- QCA8337(Internal qca8k PHY) model
++ Currently supports the AR8030, AR8031, AR8033, AR8035 model
++
++config QCA83XX_PHY
++ tristate "Qualcomm Atheros QCA833x PHYs"
++ select QCOM_NET_PHYLIB
++ help
++ Currently supports the internal QCA8337(Internal qca8k PHY) model
+--- a/drivers/net/phy/qcom/Makefile
++++ b/drivers/net/phy/qcom/Makefile
+@@ -1,3 +1,4 @@
+ # SPDX-License-Identifier: GPL-2.0
+ obj-$(CONFIG_QCOM_NET_PHYLIB) += qcom-phy-lib.o
+ obj-$(CONFIG_AT803X_PHY) += at803x.o
++obj-$(CONFIG_QCA83XX_PHY) += qca83xx.o
+--- a/drivers/net/phy/qcom/at803x.c
++++ b/drivers/net/phy/qcom/at803x.c
+@@ -102,17 +102,10 @@
+ #define AT803X_PSSR 0x11 /*PHY-Specific Status Register*/
+ #define AT803X_PSSR_MR_AN_COMPLETE 0x0200
+
+-#define AT803X_DEBUG_REG_3C 0x3C
+-
+-#define AT803X_DEBUG_REG_GREEN 0x3D
+-#define AT803X_DEBUG_GATE_CLK_IN1000 BIT(6)
+-
+ #define AT803X_DEBUG_REG_1F 0x1F
+ #define AT803X_DEBUG_PLL_ON BIT(2)
+ #define AT803X_DEBUG_RGMII_1V8 BIT(3)
+
+-#define MDIO_AZ_DEBUG 0x800D
+-
+ /* AT803x supports either the XTAL input pad, an internal PLL or the
+ * DSP as clock reference for the clock output pad. The XTAL reference
+ * is only used for 25 MHz output, all other frequencies need the PLL.
+@@ -163,13 +156,7 @@
+
+ #define QCA8081_PHY_ID 0x004dd101
+
+-#define QCA8327_A_PHY_ID 0x004dd033
+-#define QCA8327_B_PHY_ID 0x004dd034
+-#define QCA8337_PHY_ID 0x004dd036
+ #define QCA9561_PHY_ID 0x004dd042
+-#define QCA8K_PHY_ID_MASK 0xffffffff
+-
+-#define QCA8K_DEVFLAGS_REVISION_MASK GENMASK(2, 0)
+
+ #define AT803X_PAGE_FIBER 0
+ #define AT803X_PAGE_COPPER 1
+@@ -379,12 +366,6 @@ MODULE_DESCRIPTION("Qualcomm Atheros AR8
+ MODULE_AUTHOR("Matus Ujhelyi");
+ MODULE_LICENSE("GPL");
+
+-static struct at803x_hw_stat qca83xx_hw_stats[] = {
+- { "phy_idle_errors", 0xa, GENMASK(7, 0), PHY},
+- { "phy_receive_errors", 0x15, GENMASK(15, 0), PHY},
+- { "eee_wake_errors", 0x16, GENMASK(15, 0), MMD},
+-};
+-
+ struct at803x_ss_mask {
+ u16 speed_mask;
+ u8 speed_shift;
+@@ -400,7 +381,6 @@ struct at803x_priv {
+ bool is_1000basex;
+ struct regulator_dev *vddio_rdev;
+ struct regulator_dev *vddh_rdev;
+- u64 stats[ARRAY_SIZE(qca83xx_hw_stats)];
+ int led_polarity_mode;
+ };
+
+@@ -564,53 +544,6 @@ static void at803x_get_wol(struct phy_de
+ wol->wolopts |= WAKE_MAGIC;
+ }
+
+-static int qca83xx_get_sset_count(struct phy_device *phydev)
+-{
+- return ARRAY_SIZE(qca83xx_hw_stats);
+-}
+-
+-static void qca83xx_get_strings(struct phy_device *phydev, u8 *data)
+-{
+- int i;
+-
+- for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++) {
+- strscpy(data + i * ETH_GSTRING_LEN,
+- qca83xx_hw_stats[i].string, ETH_GSTRING_LEN);
+- }
+-}
+-
+-static u64 qca83xx_get_stat(struct phy_device *phydev, int i)
+-{
+- struct at803x_hw_stat stat = qca83xx_hw_stats[i];
+- struct at803x_priv *priv = phydev->priv;
+- int val;
+- u64 ret;
+-
+- if (stat.access_type == MMD)
+- val = phy_read_mmd(phydev, MDIO_MMD_PCS, stat.reg);
+- else
+- val = phy_read(phydev, stat.reg);
+-
+- if (val < 0) {
+- ret = U64_MAX;
+- } else {
+- val = val & stat.mask;
+- priv->stats[i] += val;
+- ret = priv->stats[i];
+- }
+-
+- return ret;
+-}
+-
+-static void qca83xx_get_stats(struct phy_device *phydev,
+- struct ethtool_stats *stats, u64 *data)
+-{
+- int i;
+-
+- for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++)
+- data[i] = qca83xx_get_stat(phydev, i);
+-}
+-
+ static int at803x_suspend(struct phy_device *phydev)
+ {
+ int value;
+@@ -1707,124 +1640,6 @@ static int at8035_probe(struct phy_devic
+ return at8035_parse_dt(phydev);
+ }
+
+-static int qca83xx_config_init(struct phy_device *phydev)
+-{
+- u8 switch_revision;
+-
+- switch_revision = phydev->dev_flags & QCA8K_DEVFLAGS_REVISION_MASK;
+-
+- switch (switch_revision) {
+- case 1:
+- /* For 100M waveform */
+- at803x_debug_reg_write(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, 0x02ea);
+- /* Turn on Gigabit clock */
+- at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x68a0);
+- break;
+-
+- case 2:
+- phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0);
+- fallthrough;
+- case 4:
+- phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_AZ_DEBUG, 0x803f);
+- 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;
+- }
+-
+- /* Following original QCA sourcecode set port to prefer master */
+- phy_set_bits(phydev, MII_CTRL1000, CTL1000_PREFER_MASTER);
+-
+- return 0;
+-}
+-
+-static int qca8327_config_init(struct phy_device *phydev)
+-{
+- /* QCA8327 require DAC amplitude adjustment for 100m set to +6%.
+- * Disable on init and enable only with 100m speed following
+- * qca original source code.
+- */
+- at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
+- QCA8327_DEBUG_MANU_CTRL_EN, 0);
+-
+- return qca83xx_config_init(phydev);
+-}
+-
+-static void qca83xx_link_change_notify(struct phy_device *phydev)
+-{
+- /* 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_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_ANALOG_TEST_CTRL,
+- QCA8327_DEBUG_MANU_CTRL_EN, 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;
+-
+- usleep_range(1000, 2000);
+-
+- return 0;
+-}
+-
+-static int qca83xx_suspend(struct phy_device *phydev)
+-{
+- 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,
+- AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE |
+- AT803X_DEBUG_HIB_CTRL_SEL_RST_80U, 0);
+-
+- return 0;
+-}
+-
+-static int qca8337_suspend(struct phy_device *phydev)
+-{
+- /* Only QCA8337 support actual suspend. */
+- genphy_suspend(phydev);
+-
+- return qca83xx_suspend(phydev);
+-}
+-
+-static int qca8327_suspend(struct phy_device *phydev)
+-{
+- u16 mask = 0;
+-
+- /* QCA8327 cause port unreliability when phy suspend
+- * is set.
+- */
+- mask |= ~(BMCR_SPEED1000 | BMCR_FULLDPLX);
+- phy_modify(phydev, MII_BMCR, mask, 0);
+-
+- return qca83xx_suspend(phydev);
+-}
+-
+ static int qca808x_phy_fast_retrain_config(struct phy_device *phydev)
+ {
+ int ret;
+@@ -2599,53 +2414,6 @@ static struct phy_driver at803x_driver[]
+ .soft_reset = genphy_soft_reset,
+ .config_aneg = at803x_config_aneg,
+ }, {
+- /* QCA8337 */
+- .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 = qca83xx_get_sset_count,
+- .get_strings = qca83xx_get_strings,
+- .get_stats = qca83xx_get_stats,
+- .suspend = qca8337_suspend,
+- .resume = qca83xx_resume,
+-}, {
+- /* QCA8327-A from switch QCA8327-AL1A */
+- .phy_id = QCA8327_A_PHY_ID,
+- .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 = qca8327_config_init,
+- .soft_reset = genphy_soft_reset,
+- .get_sset_count = qca83xx_get_sset_count,
+- .get_strings = qca83xx_get_strings,
+- .get_stats = qca83xx_get_stats,
+- .suspend = qca8327_suspend,
+- .resume = qca83xx_resume,
+-}, {
+- /* QCA8327-B from switch QCA8327-BL1A */
+- .phy_id = QCA8327_B_PHY_ID,
+- .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 = qca8327_config_init,
+- .soft_reset = genphy_soft_reset,
+- .get_sset_count = qca83xx_get_sset_count,
+- .get_strings = qca83xx_get_strings,
+- .get_stats = qca83xx_get_stats,
+- .suspend = qca8327_suspend,
+- .resume = qca83xx_resume,
+-}, {
+ /* Qualcomm QCA8081 */
+ PHY_ID_MATCH_EXACT(QCA8081_PHY_ID),
+ .name = "Qualcomm QCA8081",
+@@ -2683,9 +2451,6 @@ 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_A_PHY_ID) },
+- { PHY_ID_MATCH_EXACT(QCA8327_B_PHY_ID) },
+ { PHY_ID_MATCH_EXACT(QCA9561_PHY_ID) },
+ { PHY_ID_MATCH_EXACT(QCA8081_PHY_ID) },
+ { }
+--- /dev/null
++++ b/drivers/net/phy/qcom/qca83xx.c
+@@ -0,0 +1,275 @@
++// SPDX-License-Identifier: GPL-2.0+
++
++#include <linux/phy.h>
++#include <linux/module.h>
++
++#include "qcom.h"
++
++#define AT803X_DEBUG_REG_3C 0x3C
++
++#define AT803X_DEBUG_REG_GREEN 0x3D
++#define AT803X_DEBUG_GATE_CLK_IN1000 BIT(6)
++
++#define MDIO_AZ_DEBUG 0x800D
++
++#define QCA8327_A_PHY_ID 0x004dd033
++#define QCA8327_B_PHY_ID 0x004dd034
++#define QCA8337_PHY_ID 0x004dd036
++#define QCA8K_PHY_ID_MASK 0xffffffff
++
++#define QCA8K_DEVFLAGS_REVISION_MASK GENMASK(2, 0)
++
++static struct at803x_hw_stat qca83xx_hw_stats[] = {
++ { "phy_idle_errors", 0xa, GENMASK(7, 0), PHY},
++ { "phy_receive_errors", 0x15, GENMASK(15, 0), PHY},
++ { "eee_wake_errors", 0x16, GENMASK(15, 0), MMD},
++};
++
++struct qca83xx_priv {
++ u64 stats[ARRAY_SIZE(qca83xx_hw_stats)];
++};
++
++MODULE_DESCRIPTION("Qualcomm Atheros QCA83XX PHY driver");
++MODULE_AUTHOR("Matus Ujhelyi");
++MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
++MODULE_LICENSE("GPL");
++
++static int qca83xx_get_sset_count(struct phy_device *phydev)
++{
++ return ARRAY_SIZE(qca83xx_hw_stats);
++}
++
++static void qca83xx_get_strings(struct phy_device *phydev, u8 *data)
++{
++ int i;
++
++ for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++) {
++ strscpy(data + i * ETH_GSTRING_LEN,
++ qca83xx_hw_stats[i].string, ETH_GSTRING_LEN);
++ }
++}
++
++static u64 qca83xx_get_stat(struct phy_device *phydev, int i)
++{
++ struct at803x_hw_stat stat = qca83xx_hw_stats[i];
++ struct qca83xx_priv *priv = phydev->priv;
++ int val;
++ u64 ret;
++
++ if (stat.access_type == MMD)
++ val = phy_read_mmd(phydev, MDIO_MMD_PCS, stat.reg);
++ else
++ val = phy_read(phydev, stat.reg);
++
++ if (val < 0) {
++ ret = U64_MAX;
++ } else {
++ val = val & stat.mask;
++ priv->stats[i] += val;
++ ret = priv->stats[i];
++ }
++
++ return ret;
++}
++
++static void qca83xx_get_stats(struct phy_device *phydev,
++ struct ethtool_stats *stats, u64 *data)
++{
++ int i;
++
++ for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++)
++ data[i] = qca83xx_get_stat(phydev, i);
++}
++
++static int qca83xx_probe(struct phy_device *phydev)
++{
++ struct device *dev = &phydev->mdio.dev;
++ struct qca83xx_priv *priv;
++
++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
++
++ phydev->priv = priv;
++
++ return 0;
++}
++
++static int qca83xx_config_init(struct phy_device *phydev)
++{
++ u8 switch_revision;
++
++ switch_revision = phydev->dev_flags & QCA8K_DEVFLAGS_REVISION_MASK;
++
++ switch (switch_revision) {
++ case 1:
++ /* For 100M waveform */
++ at803x_debug_reg_write(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, 0x02ea);
++ /* Turn on Gigabit clock */
++ at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x68a0);
++ break;
++
++ case 2:
++ phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0);
++ fallthrough;
++ case 4:
++ phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_AZ_DEBUG, 0x803f);
++ 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;
++ }
++
++ /* Following original QCA sourcecode set port to prefer master */
++ phy_set_bits(phydev, MII_CTRL1000, CTL1000_PREFER_MASTER);
++
++ return 0;
++}
++
++static int qca8327_config_init(struct phy_device *phydev)
++{
++ /* QCA8327 require DAC amplitude adjustment for 100m set to +6%.
++ * Disable on init and enable only with 100m speed following
++ * qca original source code.
++ */
++ at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
++ QCA8327_DEBUG_MANU_CTRL_EN, 0);
++
++ return qca83xx_config_init(phydev);
++}
++
++static void qca83xx_link_change_notify(struct phy_device *phydev)
++{
++ /* 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_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_ANALOG_TEST_CTRL,
++ QCA8327_DEBUG_MANU_CTRL_EN, 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;
++
++ usleep_range(1000, 2000);
++
++ return 0;
++}
++
++static int qca83xx_suspend(struct phy_device *phydev)
++{
++ 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,
++ AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE |
++ AT803X_DEBUG_HIB_CTRL_SEL_RST_80U, 0);
++
++ return 0;
++}
++
++static int qca8337_suspend(struct phy_device *phydev)
++{
++ /* Only QCA8337 support actual suspend. */
++ genphy_suspend(phydev);
++
++ return qca83xx_suspend(phydev);
++}
++
++static int qca8327_suspend(struct phy_device *phydev)
++{
++ u16 mask = 0;
++
++ /* QCA8327 cause port unreliability when phy suspend
++ * is set.
++ */
++ mask |= ~(BMCR_SPEED1000 | BMCR_FULLDPLX);
++ phy_modify(phydev, MII_BMCR, mask, 0);
++
++ return qca83xx_suspend(phydev);
++}
++
++static struct phy_driver qca83xx_driver[] = {
++{
++ /* QCA8337 */
++ .phy_id = QCA8337_PHY_ID,
++ .phy_id_mask = QCA8K_PHY_ID_MASK,
++ .name = "Qualcomm Atheros 8337 internal PHY",
++ /* PHY_GBIT_FEATURES */
++ .probe = qca83xx_probe,
++ .flags = PHY_IS_INTERNAL,
++ .config_init = qca83xx_config_init,
++ .soft_reset = genphy_soft_reset,
++ .get_sset_count = qca83xx_get_sset_count,
++ .get_strings = qca83xx_get_strings,
++ .get_stats = qca83xx_get_stats,
++ .suspend = qca8337_suspend,
++ .resume = qca83xx_resume,
++}, {
++ /* QCA8327-A from switch QCA8327-AL1A */
++ .phy_id = QCA8327_A_PHY_ID,
++ .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 = qca83xx_probe,
++ .flags = PHY_IS_INTERNAL,
++ .config_init = qca8327_config_init,
++ .soft_reset = genphy_soft_reset,
++ .get_sset_count = qca83xx_get_sset_count,
++ .get_strings = qca83xx_get_strings,
++ .get_stats = qca83xx_get_stats,
++ .suspend = qca8327_suspend,
++ .resume = qca83xx_resume,
++}, {
++ /* QCA8327-B from switch QCA8327-BL1A */
++ .phy_id = QCA8327_B_PHY_ID,
++ .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 = qca83xx_probe,
++ .flags = PHY_IS_INTERNAL,
++ .config_init = qca8327_config_init,
++ .soft_reset = genphy_soft_reset,
++ .get_sset_count = qca83xx_get_sset_count,
++ .get_strings = qca83xx_get_strings,
++ .get_stats = qca83xx_get_stats,
++ .suspend = qca8327_suspend,
++ .resume = qca83xx_resume,
++}, };
++
++module_phy_driver(qca83xx_driver);
++
++static struct mdio_device_id __maybe_unused qca83xx_tbl[] = {
++ { PHY_ID_MATCH_EXACT(QCA8337_PHY_ID) },
++ { PHY_ID_MATCH_EXACT(QCA8327_A_PHY_ID) },
++ { PHY_ID_MATCH_EXACT(QCA8327_B_PHY_ID) },
++ { }
++};
++
++MODULE_DEVICE_TABLE(mdio, qca83xx_tbl);
diff --git a/target/linux/generic/backport-6.6/713-v6.9-04-net-phy-qcom-move-additional-functions-to-shared-lib.patch b/target/linux/generic/backport-6.6/713-v6.9-04-net-phy-qcom-move-additional-functions-to-shared-lib.patch
new file mode 100644
index 0000000000..9c43ad13b4
--- /dev/null
+++ b/target/linux/generic/backport-6.6/713-v6.9-04-net-phy-qcom-move-additional-functions-to-shared-lib.patch
@@ -0,0 +1,1014 @@
+From 249d2b80e4db0e38503ed0ec2af6c7401bc099b9 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 Jan 2024 15:15:22 +0100
+Subject: [PATCH 4/5] net: phy: qcom: move additional functions to shared
+ library
+
+Move additional functions to shared library in preparation for qca808x
+PHY Family to be detached from at803x driver.
+
+Only the shared defines are moved to the shared qcom.h header.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/20240129141600.2592-5-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/qcom/at803x.c | 426 +---------------------------
+ drivers/net/phy/qcom/qcom-phy-lib.c | 376 ++++++++++++++++++++++++
+ drivers/net/phy/qcom/qcom.h | 86 ++++++
+ 3 files changed, 463 insertions(+), 425 deletions(-)
+
+--- a/drivers/net/phy/qcom/at803x.c
++++ b/drivers/net/phy/qcom/at803x.c
+@@ -24,65 +24,11 @@
+
+ #include "qcom.h"
+
+-#define AT803X_SPECIFIC_FUNCTION_CONTROL 0x10
+-#define AT803X_SFC_ASSERT_CRS BIT(11)
+-#define AT803X_SFC_FORCE_LINK BIT(10)
+-#define AT803X_SFC_MDI_CROSSOVER_MODE_M GENMASK(6, 5)
+-#define AT803X_SFC_AUTOMATIC_CROSSOVER 0x3
+-#define AT803X_SFC_MANUAL_MDIX 0x1
+-#define AT803X_SFC_MANUAL_MDI 0x0
+-#define AT803X_SFC_SQE_TEST BIT(2)
+-#define AT803X_SFC_POLARITY_REVERSAL BIT(1)
+-#define AT803X_SFC_DISABLE_JABBER BIT(0)
+-
+-#define AT803X_SPECIFIC_STATUS 0x11
+-#define AT803X_SS_SPEED_MASK GENMASK(15, 14)
+-#define AT803X_SS_SPEED_1000 2
+-#define AT803X_SS_SPEED_100 1
+-#define AT803X_SS_SPEED_10 0
+-#define AT803X_SS_DUPLEX BIT(13)
+-#define AT803X_SS_SPEED_DUPLEX_RESOLVED BIT(11)
+-#define AT803X_SS_MDIX BIT(6)
+-
+-#define QCA808X_SS_SPEED_MASK GENMASK(9, 7)
+-#define QCA808X_SS_SPEED_2500 4
+-
+-#define AT803X_INTR_ENABLE 0x12
+-#define AT803X_INTR_ENABLE_AUTONEG_ERR BIT(15)
+-#define AT803X_INTR_ENABLE_SPEED_CHANGED BIT(14)
+-#define AT803X_INTR_ENABLE_DUPLEX_CHANGED BIT(13)
+-#define AT803X_INTR_ENABLE_PAGE_RECEIVED BIT(12)
+-#define AT803X_INTR_ENABLE_LINK_FAIL BIT(11)
+-#define AT803X_INTR_ENABLE_LINK_SUCCESS BIT(10)
+-#define AT803X_INTR_ENABLE_LINK_FAIL_BX BIT(8)
+-#define AT803X_INTR_ENABLE_LINK_SUCCESS_BX BIT(7)
+-#define AT803X_INTR_ENABLE_WIRESPEED_DOWNGRADE BIT(5)
+-#define AT803X_INTR_ENABLE_POLARITY_CHANGED BIT(1)
+-#define AT803X_INTR_ENABLE_WOL BIT(0)
+-
+-#define AT803X_INTR_STATUS 0x13
+-
+-#define AT803X_SMART_SPEED 0x14
+-#define AT803X_SMART_SPEED_ENABLE BIT(5)
+-#define AT803X_SMART_SPEED_RETRY_LIMIT_MASK GENMASK(4, 2)
+-#define AT803X_SMART_SPEED_BYPASS_TIMER BIT(1)
+-#define AT803X_CDT 0x16
+-#define AT803X_CDT_MDI_PAIR_MASK GENMASK(9, 8)
+-#define AT803X_CDT_ENABLE_TEST BIT(0)
+-#define AT803X_CDT_STATUS 0x1c
+-#define AT803X_CDT_STATUS_STAT_NORMAL 0
+-#define AT803X_CDT_STATUS_STAT_SHORT 1
+-#define AT803X_CDT_STATUS_STAT_OPEN 2
+-#define AT803X_CDT_STATUS_STAT_FAIL 3
+-#define AT803X_CDT_STATUS_STAT_MASK GENMASK(9, 8)
+-#define AT803X_CDT_STATUS_DELTA_TIME_MASK GENMASK(7, 0)
+ #define AT803X_LED_CONTROL 0x18
+
+ #define AT803X_PHY_MMD3_WOL_CTRL 0x8012
+ #define AT803X_WOL_EN BIT(5)
+-#define AT803X_LOC_MAC_ADDR_0_15_OFFSET 0x804C
+-#define AT803X_LOC_MAC_ADDR_16_31_OFFSET 0x804B
+-#define AT803X_LOC_MAC_ADDR_32_47_OFFSET 0x804A
++
+ #define AT803X_REG_CHIP_CONFIG 0x1f
+ #define AT803X_BT_BX_REG_SEL 0x8000
+
+@@ -138,10 +84,6 @@
+ #define AT803X_CLK_OUT_STRENGTH_HALF 1
+ #define AT803X_CLK_OUT_STRENGTH_QUARTER 2
+
+-#define AT803X_DEFAULT_DOWNSHIFT 5
+-#define AT803X_MIN_DOWNSHIFT 2
+-#define AT803X_MAX_DOWNSHIFT 9
+-
+ #define AT803X_MMD3_SMARTEEE_CTL1 0x805b
+ #define AT803X_MMD3_SMARTEEE_CTL2 0x805c
+ #define AT803X_MMD3_SMARTEEE_CTL3 0x805d
+@@ -366,11 +308,6 @@ MODULE_DESCRIPTION("Qualcomm Atheros AR8
+ MODULE_AUTHOR("Matus Ujhelyi");
+ MODULE_LICENSE("GPL");
+
+-struct at803x_ss_mask {
+- u16 speed_mask;
+- u8 speed_shift;
+-};
+-
+ struct at803x_priv {
+ int flags;
+ u16 clk_25m_reg;
+@@ -470,80 +407,6 @@ static void at803x_context_restore(struc
+ phy_write(phydev, AT803X_LED_CONTROL, context->led_control);
+ }
+
+-static int at803x_set_wol(struct phy_device *phydev,
+- struct ethtool_wolinfo *wol)
+-{
+- int ret, irq_enabled;
+-
+- if (wol->wolopts & WAKE_MAGIC) {
+- struct net_device *ndev = phydev->attached_dev;
+- const u8 *mac;
+- unsigned int i;
+- static const unsigned int offsets[] = {
+- AT803X_LOC_MAC_ADDR_32_47_OFFSET,
+- AT803X_LOC_MAC_ADDR_16_31_OFFSET,
+- AT803X_LOC_MAC_ADDR_0_15_OFFSET,
+- };
+-
+- if (!ndev)
+- return -ENODEV;
+-
+- mac = (const u8 *)ndev->dev_addr;
+-
+- if (!is_valid_ether_addr(mac))
+- return -EINVAL;
+-
+- for (i = 0; i < 3; i++)
+- phy_write_mmd(phydev, MDIO_MMD_PCS, offsets[i],
+- mac[(i * 2) + 1] | (mac[(i * 2)] << 8));
+-
+- /* Enable WOL interrupt */
+- ret = phy_modify(phydev, AT803X_INTR_ENABLE, 0, AT803X_INTR_ENABLE_WOL);
+- if (ret)
+- return ret;
+- } else {
+- /* Disable WOL interrupt */
+- ret = phy_modify(phydev, AT803X_INTR_ENABLE, AT803X_INTR_ENABLE_WOL, 0);
+- if (ret)
+- return ret;
+- }
+-
+- /* Clear WOL status */
+- ret = phy_read(phydev, AT803X_INTR_STATUS);
+- if (ret < 0)
+- return ret;
+-
+- /* Check if there are other interrupts except for WOL triggered when PHY is
+- * in interrupt mode, only the interrupts enabled by AT803X_INTR_ENABLE can
+- * be passed up to the interrupt PIN.
+- */
+- irq_enabled = phy_read(phydev, AT803X_INTR_ENABLE);
+- if (irq_enabled < 0)
+- return irq_enabled;
+-
+- irq_enabled &= ~AT803X_INTR_ENABLE_WOL;
+- if (ret & irq_enabled && !phy_polling_mode(phydev))
+- phy_trigger_machine(phydev);
+-
+- return 0;
+-}
+-
+-static void at803x_get_wol(struct phy_device *phydev,
+- struct ethtool_wolinfo *wol)
+-{
+- int value;
+-
+- wol->supported = WAKE_MAGIC;
+- wol->wolopts = 0;
+-
+- value = phy_read(phydev, AT803X_INTR_ENABLE);
+- if (value < 0)
+- return;
+-
+- if (value & AT803X_INTR_ENABLE_WOL)
+- wol->wolopts |= WAKE_MAGIC;
+-}
+-
+ static int at803x_suspend(struct phy_device *phydev)
+ {
+ int value;
+@@ -816,73 +679,6 @@ static int at803x_config_init(struct phy
+ return phy_modify(phydev, MII_ADVERTISE, MDIO_AN_CTRL1_XNP, 0);
+ }
+
+-static int at803x_ack_interrupt(struct phy_device *phydev)
+-{
+- int err;
+-
+- err = phy_read(phydev, AT803X_INTR_STATUS);
+-
+- return (err < 0) ? err : 0;
+-}
+-
+-static int at803x_config_intr(struct phy_device *phydev)
+-{
+- int err;
+- int value;
+-
+- value = phy_read(phydev, AT803X_INTR_ENABLE);
+-
+- if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+- /* Clear any pending interrupts */
+- err = at803x_ack_interrupt(phydev);
+- if (err)
+- return err;
+-
+- value |= AT803X_INTR_ENABLE_AUTONEG_ERR;
+- value |= AT803X_INTR_ENABLE_SPEED_CHANGED;
+- value |= AT803X_INTR_ENABLE_DUPLEX_CHANGED;
+- value |= AT803X_INTR_ENABLE_LINK_FAIL;
+- value |= AT803X_INTR_ENABLE_LINK_SUCCESS;
+-
+- err = phy_write(phydev, AT803X_INTR_ENABLE, value);
+- } else {
+- err = phy_write(phydev, AT803X_INTR_ENABLE, 0);
+- if (err)
+- return err;
+-
+- /* Clear any pending interrupts */
+- err = at803x_ack_interrupt(phydev);
+- }
+-
+- return err;
+-}
+-
+-static irqreturn_t at803x_handle_interrupt(struct phy_device *phydev)
+-{
+- int irq_status, int_enabled;
+-
+- irq_status = phy_read(phydev, AT803X_INTR_STATUS);
+- if (irq_status < 0) {
+- phy_error(phydev);
+- return IRQ_NONE;
+- }
+-
+- /* Read the current enabled interrupts */
+- int_enabled = phy_read(phydev, AT803X_INTR_ENABLE);
+- if (int_enabled < 0) {
+- phy_error(phydev);
+- return IRQ_NONE;
+- }
+-
+- /* See if this was one of our enabled interrupts */
+- if (!(irq_status & int_enabled))
+- return IRQ_NONE;
+-
+- phy_trigger_machine(phydev);
+-
+- return IRQ_HANDLED;
+-}
+-
+ static void at803x_link_change_notify(struct phy_device *phydev)
+ {
+ /*
+@@ -908,69 +704,6 @@ static void at803x_link_change_notify(st
+ }
+ }
+
+-static int at803x_read_specific_status(struct phy_device *phydev,
+- struct at803x_ss_mask ss_mask)
+-{
+- int ss;
+-
+- /* Read the AT8035 PHY-Specific Status register, which indicates the
+- * speed and duplex that the PHY is actually using, irrespective of
+- * whether we are in autoneg mode or not.
+- */
+- ss = phy_read(phydev, AT803X_SPECIFIC_STATUS);
+- if (ss < 0)
+- return ss;
+-
+- if (ss & AT803X_SS_SPEED_DUPLEX_RESOLVED) {
+- int sfc, speed;
+-
+- sfc = phy_read(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL);
+- if (sfc < 0)
+- return sfc;
+-
+- speed = ss & ss_mask.speed_mask;
+- speed >>= ss_mask.speed_shift;
+-
+- switch (speed) {
+- case AT803X_SS_SPEED_10:
+- phydev->speed = SPEED_10;
+- break;
+- case AT803X_SS_SPEED_100:
+- phydev->speed = SPEED_100;
+- break;
+- case AT803X_SS_SPEED_1000:
+- phydev->speed = SPEED_1000;
+- break;
+- case QCA808X_SS_SPEED_2500:
+- phydev->speed = SPEED_2500;
+- break;
+- }
+- if (ss & AT803X_SS_DUPLEX)
+- phydev->duplex = DUPLEX_FULL;
+- else
+- phydev->duplex = DUPLEX_HALF;
+-
+- if (ss & AT803X_SS_MDIX)
+- phydev->mdix = ETH_TP_MDI_X;
+- else
+- phydev->mdix = ETH_TP_MDI;
+-
+- switch (FIELD_GET(AT803X_SFC_MDI_CROSSOVER_MODE_M, sfc)) {
+- case AT803X_SFC_MANUAL_MDI:
+- phydev->mdix_ctrl = ETH_TP_MDI;
+- break;
+- case AT803X_SFC_MANUAL_MDIX:
+- phydev->mdix_ctrl = ETH_TP_MDI_X;
+- break;
+- case AT803X_SFC_AUTOMATIC_CROSSOVER:
+- phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+- break;
+- }
+- }
+-
+- return 0;
+-}
+-
+ static int at803x_read_status(struct phy_device *phydev)
+ {
+ struct at803x_ss_mask ss_mask = { 0 };
+@@ -1006,50 +739,6 @@ static int at803x_read_status(struct phy
+ return 0;
+ }
+
+-static int at803x_config_mdix(struct phy_device *phydev, u8 ctrl)
+-{
+- u16 val;
+-
+- switch (ctrl) {
+- case ETH_TP_MDI:
+- val = AT803X_SFC_MANUAL_MDI;
+- break;
+- case ETH_TP_MDI_X:
+- val = AT803X_SFC_MANUAL_MDIX;
+- break;
+- case ETH_TP_MDI_AUTO:
+- val = AT803X_SFC_AUTOMATIC_CROSSOVER;
+- break;
+- default:
+- return 0;
+- }
+-
+- return phy_modify_changed(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL,
+- AT803X_SFC_MDI_CROSSOVER_MODE_M,
+- FIELD_PREP(AT803X_SFC_MDI_CROSSOVER_MODE_M, val));
+-}
+-
+-static int at803x_prepare_config_aneg(struct phy_device *phydev)
+-{
+- int ret;
+-
+- ret = at803x_config_mdix(phydev, phydev->mdix_ctrl);
+- if (ret < 0)
+- return ret;
+-
+- /* Changes of the midx bits are disruptive to the normal operation;
+- * therefore any changes to these registers must be followed by a
+- * software reset to take effect.
+- */
+- if (ret == 1) {
+- ret = genphy_soft_reset(phydev);
+- if (ret < 0)
+- return ret;
+- }
+-
+- return 0;
+-}
+-
+ static int at803x_config_aneg(struct phy_device *phydev)
+ {
+ struct at803x_priv *priv = phydev->priv;
+@@ -1065,80 +754,6 @@ static int at803x_config_aneg(struct phy
+ return genphy_config_aneg(phydev);
+ }
+
+-static int at803x_get_downshift(struct phy_device *phydev, u8 *d)
+-{
+- int val;
+-
+- val = phy_read(phydev, AT803X_SMART_SPEED);
+- if (val < 0)
+- return val;
+-
+- if (val & AT803X_SMART_SPEED_ENABLE)
+- *d = FIELD_GET(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, val) + 2;
+- else
+- *d = DOWNSHIFT_DEV_DISABLE;
+-
+- return 0;
+-}
+-
+-static int at803x_set_downshift(struct phy_device *phydev, u8 cnt)
+-{
+- u16 mask, set;
+- int ret;
+-
+- switch (cnt) {
+- case DOWNSHIFT_DEV_DEFAULT_COUNT:
+- cnt = AT803X_DEFAULT_DOWNSHIFT;
+- fallthrough;
+- case AT803X_MIN_DOWNSHIFT ... AT803X_MAX_DOWNSHIFT:
+- set = AT803X_SMART_SPEED_ENABLE |
+- AT803X_SMART_SPEED_BYPASS_TIMER |
+- FIELD_PREP(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, cnt - 2);
+- mask = AT803X_SMART_SPEED_RETRY_LIMIT_MASK;
+- break;
+- case DOWNSHIFT_DEV_DISABLE:
+- set = 0;
+- mask = AT803X_SMART_SPEED_ENABLE |
+- AT803X_SMART_SPEED_BYPASS_TIMER;
+- break;
+- default:
+- return -EINVAL;
+- }
+-
+- ret = phy_modify_changed(phydev, AT803X_SMART_SPEED, mask, set);
+-
+- /* After changing the smart speed settings, we need to perform a
+- * software reset, use phy_init_hw() to make sure we set the
+- * reapply any values which might got lost during software reset.
+- */
+- if (ret == 1)
+- ret = phy_init_hw(phydev);
+-
+- return ret;
+-}
+-
+-static int at803x_get_tunable(struct phy_device *phydev,
+- struct ethtool_tunable *tuna, void *data)
+-{
+- switch (tuna->id) {
+- case ETHTOOL_PHY_DOWNSHIFT:
+- return at803x_get_downshift(phydev, data);
+- default:
+- return -EOPNOTSUPP;
+- }
+-}
+-
+-static int at803x_set_tunable(struct phy_device *phydev,
+- struct ethtool_tunable *tuna, const void *data)
+-{
+- switch (tuna->id) {
+- case ETHTOOL_PHY_DOWNSHIFT:
+- return at803x_set_downshift(phydev, *(const u8 *)data);
+- default:
+- return -EOPNOTSUPP;
+- }
+-}
+-
+ static int at803x_cable_test_result_trans(u16 status)
+ {
+ switch (FIELD_GET(AT803X_CDT_STATUS_STAT_MASK, status)) {
+@@ -1170,45 +785,6 @@ static bool at803x_cdt_fault_length_vali
+ return false;
+ }
+
+-static int at803x_cdt_fault_length(int dt)
+-{
+- /* According to the datasheet the distance to the fault is
+- * DELTA_TIME * 0.824 meters.
+- *
+- * The author suspect the correct formula is:
+- *
+- * fault_distance = DELTA_TIME * (c * VF) / 125MHz / 2
+- *
+- * where c is the speed of light, VF is the velocity factor of
+- * the twisted pair cable, 125MHz the counter frequency and
+- * we need to divide by 2 because the hardware will measure the
+- * round trip time to the fault and back to the PHY.
+- *
+- * With a VF of 0.69 we get the factor 0.824 mentioned in the
+- * datasheet.
+- */
+- return (dt * 824) / 10;
+-}
+-
+-static int at803x_cdt_start(struct phy_device *phydev,
+- u32 cdt_start)
+-{
+- return phy_write(phydev, AT803X_CDT, cdt_start);
+-}
+-
+-static int at803x_cdt_wait_for_completion(struct phy_device *phydev,
+- u32 cdt_en)
+-{
+- int val, ret;
+-
+- /* One test run takes about 25ms */
+- ret = phy_read_poll_timeout(phydev, AT803X_CDT, val,
+- !(val & cdt_en),
+- 30000, 100000, true);
+-
+- return ret < 0 ? ret : 0;
+-}
+-
+ static int at803x_cable_test_one_pair(struct phy_device *phydev, int pair)
+ {
+ static const int ethtool_pair[] = {
+--- a/drivers/net/phy/qcom/qcom-phy-lib.c
++++ b/drivers/net/phy/qcom/qcom-phy-lib.c
+@@ -3,6 +3,9 @@
+ #include <linux/phy.h>
+ #include <linux/module.h>
+
++#include <linux/netdevice.h>
++#include <linux/etherdevice.h>
++
+ #include "qcom.h"
+
+ MODULE_DESCRIPTION("Qualcomm PHY driver Common Functions");
+@@ -51,3 +54,376 @@ int at803x_debug_reg_write(struct phy_de
+ return phy_write(phydev, AT803X_DEBUG_DATA, data);
+ }
+ EXPORT_SYMBOL_GPL(at803x_debug_reg_write);
++
++int at803x_set_wol(struct phy_device *phydev,
++ struct ethtool_wolinfo *wol)
++{
++ int ret, irq_enabled;
++
++ if (wol->wolopts & WAKE_MAGIC) {
++ struct net_device *ndev = phydev->attached_dev;
++ const u8 *mac;
++ unsigned int i;
++ static const unsigned int offsets[] = {
++ AT803X_LOC_MAC_ADDR_32_47_OFFSET,
++ AT803X_LOC_MAC_ADDR_16_31_OFFSET,
++ AT803X_LOC_MAC_ADDR_0_15_OFFSET,
++ };
++
++ if (!ndev)
++ return -ENODEV;
++
++ mac = (const u8 *)ndev->dev_addr;
++
++ if (!is_valid_ether_addr(mac))
++ return -EINVAL;
++
++ for (i = 0; i < 3; i++)
++ phy_write_mmd(phydev, MDIO_MMD_PCS, offsets[i],
++ mac[(i * 2) + 1] | (mac[(i * 2)] << 8));
++
++ /* Enable WOL interrupt */
++ ret = phy_modify(phydev, AT803X_INTR_ENABLE, 0, AT803X_INTR_ENABLE_WOL);
++ if (ret)
++ return ret;
++ } else {
++ /* Disable WOL interrupt */
++ ret = phy_modify(phydev, AT803X_INTR_ENABLE, AT803X_INTR_ENABLE_WOL, 0);
++ if (ret)
++ return ret;
++ }
++
++ /* Clear WOL status */
++ ret = phy_read(phydev, AT803X_INTR_STATUS);
++ if (ret < 0)
++ return ret;
++
++ /* Check if there are other interrupts except for WOL triggered when PHY is
++ * in interrupt mode, only the interrupts enabled by AT803X_INTR_ENABLE can
++ * be passed up to the interrupt PIN.
++ */
++ irq_enabled = phy_read(phydev, AT803X_INTR_ENABLE);
++ if (irq_enabled < 0)
++ return irq_enabled;
++
++ irq_enabled &= ~AT803X_INTR_ENABLE_WOL;
++ if (ret & irq_enabled && !phy_polling_mode(phydev))
++ phy_trigger_machine(phydev);
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(at803x_set_wol);
++
++void at803x_get_wol(struct phy_device *phydev,
++ struct ethtool_wolinfo *wol)
++{
++ int value;
++
++ wol->supported = WAKE_MAGIC;
++ wol->wolopts = 0;
++
++ value = phy_read(phydev, AT803X_INTR_ENABLE);
++ if (value < 0)
++ return;
++
++ if (value & AT803X_INTR_ENABLE_WOL)
++ wol->wolopts |= WAKE_MAGIC;
++}
++EXPORT_SYMBOL_GPL(at803x_get_wol);
++
++int at803x_ack_interrupt(struct phy_device *phydev)
++{
++ int err;
++
++ err = phy_read(phydev, AT803X_INTR_STATUS);
++
++ return (err < 0) ? err : 0;
++}
++EXPORT_SYMBOL_GPL(at803x_ack_interrupt);
++
++int at803x_config_intr(struct phy_device *phydev)
++{
++ int err;
++ int value;
++
++ value = phy_read(phydev, AT803X_INTR_ENABLE);
++
++ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
++ /* Clear any pending interrupts */
++ err = at803x_ack_interrupt(phydev);
++ if (err)
++ return err;
++
++ value |= AT803X_INTR_ENABLE_AUTONEG_ERR;
++ value |= AT803X_INTR_ENABLE_SPEED_CHANGED;
++ value |= AT803X_INTR_ENABLE_DUPLEX_CHANGED;
++ value |= AT803X_INTR_ENABLE_LINK_FAIL;
++ value |= AT803X_INTR_ENABLE_LINK_SUCCESS;
++
++ err = phy_write(phydev, AT803X_INTR_ENABLE, value);
++ } else {
++ err = phy_write(phydev, AT803X_INTR_ENABLE, 0);
++ if (err)
++ return err;
++
++ /* Clear any pending interrupts */
++ err = at803x_ack_interrupt(phydev);
++ }
++
++ return err;
++}
++EXPORT_SYMBOL_GPL(at803x_config_intr);
++
++irqreturn_t at803x_handle_interrupt(struct phy_device *phydev)
++{
++ int irq_status, int_enabled;
++
++ irq_status = phy_read(phydev, AT803X_INTR_STATUS);
++ if (irq_status < 0) {
++ phy_error(phydev);
++ return IRQ_NONE;
++ }
++
++ /* Read the current enabled interrupts */
++ int_enabled = phy_read(phydev, AT803X_INTR_ENABLE);
++ if (int_enabled < 0) {
++ phy_error(phydev);
++ return IRQ_NONE;
++ }
++
++ /* See if this was one of our enabled interrupts */
++ if (!(irq_status & int_enabled))
++ return IRQ_NONE;
++
++ phy_trigger_machine(phydev);
++
++ return IRQ_HANDLED;
++}
++EXPORT_SYMBOL_GPL(at803x_handle_interrupt);
++
++int at803x_read_specific_status(struct phy_device *phydev,
++ struct at803x_ss_mask ss_mask)
++{
++ int ss;
++
++ /* Read the AT8035 PHY-Specific Status register, which indicates the
++ * speed and duplex that the PHY is actually using, irrespective of
++ * whether we are in autoneg mode or not.
++ */
++ ss = phy_read(phydev, AT803X_SPECIFIC_STATUS);
++ if (ss < 0)
++ return ss;
++
++ if (ss & AT803X_SS_SPEED_DUPLEX_RESOLVED) {
++ int sfc, speed;
++
++ sfc = phy_read(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL);
++ if (sfc < 0)
++ return sfc;
++
++ speed = ss & ss_mask.speed_mask;
++ speed >>= ss_mask.speed_shift;
++
++ switch (speed) {
++ case AT803X_SS_SPEED_10:
++ phydev->speed = SPEED_10;
++ break;
++ case AT803X_SS_SPEED_100:
++ phydev->speed = SPEED_100;
++ break;
++ case AT803X_SS_SPEED_1000:
++ phydev->speed = SPEED_1000;
++ break;
++ case QCA808X_SS_SPEED_2500:
++ phydev->speed = SPEED_2500;
++ break;
++ }
++ if (ss & AT803X_SS_DUPLEX)
++ phydev->duplex = DUPLEX_FULL;
++ else
++ phydev->duplex = DUPLEX_HALF;
++
++ if (ss & AT803X_SS_MDIX)
++ phydev->mdix = ETH_TP_MDI_X;
++ else
++ phydev->mdix = ETH_TP_MDI;
++
++ switch (FIELD_GET(AT803X_SFC_MDI_CROSSOVER_MODE_M, sfc)) {
++ case AT803X_SFC_MANUAL_MDI:
++ phydev->mdix_ctrl = ETH_TP_MDI;
++ break;
++ case AT803X_SFC_MANUAL_MDIX:
++ phydev->mdix_ctrl = ETH_TP_MDI_X;
++ break;
++ case AT803X_SFC_AUTOMATIC_CROSSOVER:
++ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
++ break;
++ }
++ }
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(at803x_read_specific_status);
++
++int at803x_config_mdix(struct phy_device *phydev, u8 ctrl)
++{
++ u16 val;
++
++ switch (ctrl) {
++ case ETH_TP_MDI:
++ val = AT803X_SFC_MANUAL_MDI;
++ break;
++ case ETH_TP_MDI_X:
++ val = AT803X_SFC_MANUAL_MDIX;
++ break;
++ case ETH_TP_MDI_AUTO:
++ val = AT803X_SFC_AUTOMATIC_CROSSOVER;
++ break;
++ default:
++ return 0;
++ }
++
++ return phy_modify_changed(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL,
++ AT803X_SFC_MDI_CROSSOVER_MODE_M,
++ FIELD_PREP(AT803X_SFC_MDI_CROSSOVER_MODE_M, val));
++}
++EXPORT_SYMBOL_GPL(at803x_config_mdix);
++
++int at803x_prepare_config_aneg(struct phy_device *phydev)
++{
++ int ret;
++
++ ret = at803x_config_mdix(phydev, phydev->mdix_ctrl);
++ if (ret < 0)
++ return ret;
++
++ /* Changes of the midx bits are disruptive to the normal operation;
++ * therefore any changes to these registers must be followed by a
++ * software reset to take effect.
++ */
++ if (ret == 1) {
++ ret = genphy_soft_reset(phydev);
++ if (ret < 0)
++ return ret;
++ }
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(at803x_prepare_config_aneg);
++
++static int at803x_get_downshift(struct phy_device *phydev, u8 *d)
++{
++ int val;
++
++ val = phy_read(phydev, AT803X_SMART_SPEED);
++ if (val < 0)
++ return val;
++
++ if (val & AT803X_SMART_SPEED_ENABLE)
++ *d = FIELD_GET(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, val) + 2;
++ else
++ *d = DOWNSHIFT_DEV_DISABLE;
++
++ return 0;
++}
++
++static int at803x_set_downshift(struct phy_device *phydev, u8 cnt)
++{
++ u16 mask, set;
++ int ret;
++
++ switch (cnt) {
++ case DOWNSHIFT_DEV_DEFAULT_COUNT:
++ cnt = AT803X_DEFAULT_DOWNSHIFT;
++ fallthrough;
++ case AT803X_MIN_DOWNSHIFT ... AT803X_MAX_DOWNSHIFT:
++ set = AT803X_SMART_SPEED_ENABLE |
++ AT803X_SMART_SPEED_BYPASS_TIMER |
++ FIELD_PREP(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, cnt - 2);
++ mask = AT803X_SMART_SPEED_RETRY_LIMIT_MASK;
++ break;
++ case DOWNSHIFT_DEV_DISABLE:
++ set = 0;
++ mask = AT803X_SMART_SPEED_ENABLE |
++ AT803X_SMART_SPEED_BYPASS_TIMER;
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ ret = phy_modify_changed(phydev, AT803X_SMART_SPEED, mask, set);
++
++ /* After changing the smart speed settings, we need to perform a
++ * software reset, use phy_init_hw() to make sure we set the
++ * reapply any values which might got lost during software reset.
++ */
++ if (ret == 1)
++ ret = phy_init_hw(phydev);
++
++ return ret;
++}
++
++int at803x_get_tunable(struct phy_device *phydev,
++ struct ethtool_tunable *tuna, void *data)
++{
++ switch (tuna->id) {
++ case ETHTOOL_PHY_DOWNSHIFT:
++ return at803x_get_downshift(phydev, data);
++ default:
++ return -EOPNOTSUPP;
++ }
++}
++EXPORT_SYMBOL_GPL(at803x_get_tunable);
++
++int at803x_set_tunable(struct phy_device *phydev,
++ struct ethtool_tunable *tuna, const void *data)
++{
++ switch (tuna->id) {
++ case ETHTOOL_PHY_DOWNSHIFT:
++ return at803x_set_downshift(phydev, *(const u8 *)data);
++ default:
++ return -EOPNOTSUPP;
++ }
++}
++EXPORT_SYMBOL_GPL(at803x_set_tunable);
++
++int at803x_cdt_fault_length(int dt)
++{
++ /* According to the datasheet the distance to the fault is
++ * DELTA_TIME * 0.824 meters.
++ *
++ * The author suspect the correct formula is:
++ *
++ * fault_distance = DELTA_TIME * (c * VF) / 125MHz / 2
++ *
++ * where c is the speed of light, VF is the velocity factor of
++ * the twisted pair cable, 125MHz the counter frequency and
++ * we need to divide by 2 because the hardware will measure the
++ * round trip time to the fault and back to the PHY.
++ *
++ * With a VF of 0.69 we get the factor 0.824 mentioned in the
++ * datasheet.
++ */
++ return (dt * 824) / 10;
++}
++EXPORT_SYMBOL_GPL(at803x_cdt_fault_length);
++
++int at803x_cdt_start(struct phy_device *phydev, u32 cdt_start)
++{
++ return phy_write(phydev, AT803X_CDT, cdt_start);
++}
++EXPORT_SYMBOL_GPL(at803x_cdt_start);
++
++int at803x_cdt_wait_for_completion(struct phy_device *phydev,
++ u32 cdt_en)
++{
++ int val, ret;
++
++ /* One test run takes about 25ms */
++ ret = phy_read_poll_timeout(phydev, AT803X_CDT, val,
++ !(val & cdt_en),
++ 30000, 100000, true);
++
++ return ret < 0 ? ret : 0;
++}
++EXPORT_SYMBOL_GPL(at803x_cdt_wait_for_completion);
+--- a/drivers/net/phy/qcom/qcom.h
++++ b/drivers/net/phy/qcom/qcom.h
+@@ -1,5 +1,63 @@
+ /* SPDX-License-Identifier: GPL-2.0 */
+
++#define AT803X_SPECIFIC_FUNCTION_CONTROL 0x10
++#define AT803X_SFC_ASSERT_CRS BIT(11)
++#define AT803X_SFC_FORCE_LINK BIT(10)
++#define AT803X_SFC_MDI_CROSSOVER_MODE_M GENMASK(6, 5)
++#define AT803X_SFC_AUTOMATIC_CROSSOVER 0x3
++#define AT803X_SFC_MANUAL_MDIX 0x1
++#define AT803X_SFC_MANUAL_MDI 0x0
++#define AT803X_SFC_SQE_TEST BIT(2)
++#define AT803X_SFC_POLARITY_REVERSAL BIT(1)
++#define AT803X_SFC_DISABLE_JABBER BIT(0)
++
++#define AT803X_SPECIFIC_STATUS 0x11
++#define AT803X_SS_SPEED_MASK GENMASK(15, 14)
++#define AT803X_SS_SPEED_1000 2
++#define AT803X_SS_SPEED_100 1
++#define AT803X_SS_SPEED_10 0
++#define AT803X_SS_DUPLEX BIT(13)
++#define AT803X_SS_SPEED_DUPLEX_RESOLVED BIT(11)
++#define AT803X_SS_MDIX BIT(6)
++
++#define QCA808X_SS_SPEED_MASK GENMASK(9, 7)
++#define QCA808X_SS_SPEED_2500 4
++
++#define AT803X_INTR_ENABLE 0x12
++#define AT803X_INTR_ENABLE_AUTONEG_ERR BIT(15)
++#define AT803X_INTR_ENABLE_SPEED_CHANGED BIT(14)
++#define AT803X_INTR_ENABLE_DUPLEX_CHANGED BIT(13)
++#define AT803X_INTR_ENABLE_PAGE_RECEIVED BIT(12)
++#define AT803X_INTR_ENABLE_LINK_FAIL BIT(11)
++#define AT803X_INTR_ENABLE_LINK_SUCCESS BIT(10)
++#define AT803X_INTR_ENABLE_LINK_FAIL_BX BIT(8)
++#define AT803X_INTR_ENABLE_LINK_SUCCESS_BX BIT(7)
++#define AT803X_INTR_ENABLE_WIRESPEED_DOWNGRADE BIT(5)
++#define AT803X_INTR_ENABLE_POLARITY_CHANGED BIT(1)
++#define AT803X_INTR_ENABLE_WOL BIT(0)
++
++#define AT803X_INTR_STATUS 0x13
++
++#define AT803X_SMART_SPEED 0x14
++#define AT803X_SMART_SPEED_ENABLE BIT(5)
++#define AT803X_SMART_SPEED_RETRY_LIMIT_MASK GENMASK(4, 2)
++#define AT803X_SMART_SPEED_BYPASS_TIMER BIT(1)
++
++#define AT803X_CDT 0x16
++#define AT803X_CDT_MDI_PAIR_MASK GENMASK(9, 8)
++#define AT803X_CDT_ENABLE_TEST BIT(0)
++#define AT803X_CDT_STATUS 0x1c
++#define AT803X_CDT_STATUS_STAT_NORMAL 0
++#define AT803X_CDT_STATUS_STAT_SHORT 1
++#define AT803X_CDT_STATUS_STAT_OPEN 2
++#define AT803X_CDT_STATUS_STAT_FAIL 3
++#define AT803X_CDT_STATUS_STAT_MASK GENMASK(9, 8)
++#define AT803X_CDT_STATUS_DELTA_TIME_MASK GENMASK(7, 0)
++
++#define AT803X_LOC_MAC_ADDR_0_15_OFFSET 0x804C
++#define AT803X_LOC_MAC_ADDR_16_31_OFFSET 0x804B
++#define AT803X_LOC_MAC_ADDR_32_47_OFFSET 0x804A
++
+ #define AT803X_DEBUG_ADDR 0x1D
+ #define AT803X_DEBUG_DATA 0x1E
+
+@@ -16,6 +74,10 @@
+ #define AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE BIT(13)
+ #define AT803X_DEBUG_HIB_CTRL_PS_HIB_EN BIT(15)
+
++#define AT803X_DEFAULT_DOWNSHIFT 5
++#define AT803X_MIN_DOWNSHIFT 2
++#define AT803X_MAX_DOWNSHIFT 9
++
+ enum stat_access_type {
+ PHY,
+ MMD
+@@ -28,7 +90,31 @@ struct at803x_hw_stat {
+ enum stat_access_type access_type;
+ };
+
++struct at803x_ss_mask {
++ u16 speed_mask;
++ u8 speed_shift;
++};
++
+ int at803x_debug_reg_read(struct phy_device *phydev, u16 reg);
+ int at803x_debug_reg_mask(struct phy_device *phydev, u16 reg,
+ u16 clear, u16 set);
+ int at803x_debug_reg_write(struct phy_device *phydev, u16 reg, u16 data);
++int at803x_set_wol(struct phy_device *phydev,
++ struct ethtool_wolinfo *wol);
++void at803x_get_wol(struct phy_device *phydev,
++ struct ethtool_wolinfo *wol);
++int at803x_ack_interrupt(struct phy_device *phydev);
++int at803x_config_intr(struct phy_device *phydev);
++irqreturn_t at803x_handle_interrupt(struct phy_device *phydev);
++int at803x_read_specific_status(struct phy_device *phydev,
++ struct at803x_ss_mask ss_mask);
++int at803x_config_mdix(struct phy_device *phydev, u8 ctrl);
++int at803x_prepare_config_aneg(struct phy_device *phydev);
++int at803x_get_tunable(struct phy_device *phydev,
++ struct ethtool_tunable *tuna, void *data);
++int at803x_set_tunable(struct phy_device *phydev,
++ struct ethtool_tunable *tuna, const void *data);
++int at803x_cdt_fault_length(int dt);
++int at803x_cdt_start(struct phy_device *phydev, u32 cdt_start);
++int at803x_cdt_wait_for_completion(struct phy_device *phydev,
++ u32 cdt_en);
diff --git a/target/linux/generic/backport-6.6/713-v6.9-05-net-phy-qcom-detach-qca808x-PHY-driver-from-at803x.patch b/target/linux/generic/backport-6.6/713-v6.9-05-net-phy-qcom-detach-qca808x-PHY-driver-from-at803x.patch
new file mode 100644
index 0000000000..597dcea4c0
--- /dev/null
+++ b/target/linux/generic/backport-6.6/713-v6.9-05-net-phy-qcom-detach-qca808x-PHY-driver-from-at803x.patch
@@ -0,0 +1,1936 @@
+From c89414adf2ec7cd9e7080c419aa5847f1db1009c Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 Jan 2024 15:15:23 +0100
+Subject: [PATCH 5/5] net: phy: qcom: detach qca808x PHY driver from at803x
+
+Almost all the QCA8081 PHY driver OPs are specific and only some of them
+use the generic at803x.
+
+To make the at803x code slimmer, move all the specific qca808x regs and
+functions to a dedicated PHY driver.
+
+Probe function and priv struct is reworked to allocate and use only the
+qca808x specific data. Unused data from at803x PHY driver are dropped
+from at803x priv struct.
+
+Also a new Kconfig is introduced QCA808X_PHY, to compile the newly
+introduced PHY driver for QCA8081 PHY.
+
+As the Kconfig name starts with Qualcomm the same order is kept.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/20240129141600.2592-6-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/qcom/Kconfig | 6 +
+ drivers/net/phy/qcom/Makefile | 1 +
+ drivers/net/phy/qcom/at803x.c | 897 +------------------------------
+ drivers/net/phy/qcom/qca808x.c | 934 +++++++++++++++++++++++++++++++++
+ 4 files changed, 942 insertions(+), 896 deletions(-)
+ create mode 100644 drivers/net/phy/qcom/qca808x.c
+
+--- a/drivers/net/phy/qcom/Kconfig
++++ b/drivers/net/phy/qcom/Kconfig
+@@ -14,3 +14,9 @@ config QCA83XX_PHY
+ select QCOM_NET_PHYLIB
+ help
+ Currently supports the internal QCA8337(Internal qca8k PHY) model
++
++config QCA808X_PHY
++ tristate "Qualcomm QCA808x PHYs"
++ select QCOM_NET_PHYLIB
++ help
++ Currently supports the QCA8081 model
+--- a/drivers/net/phy/qcom/Makefile
++++ b/drivers/net/phy/qcom/Makefile
+@@ -2,3 +2,4 @@
+ obj-$(CONFIG_QCOM_NET_PHYLIB) += qcom-phy-lib.o
+ obj-$(CONFIG_AT803X_PHY) += at803x.o
+ obj-$(CONFIG_QCA83XX_PHY) += qca83xx.o
++obj-$(CONFIG_QCA808X_PHY) += qca808x.o
+--- a/drivers/net/phy/qcom/at803x.c
++++ b/drivers/net/phy/qcom/at803x.c
+@@ -96,8 +96,6 @@
+ #define ATH8035_PHY_ID 0x004dd072
+ #define AT8030_PHY_ID_MASK 0xffffffef
+
+-#define QCA8081_PHY_ID 0x004dd101
+-
+ #define QCA9561_PHY_ID 0x004dd042
+
+ #define AT803X_PAGE_FIBER 0
+@@ -110,201 +108,7 @@
+ /* disable hibernation mode */
+ #define AT803X_DISABLE_HIBERNATION_MODE BIT(2)
+
+-/* ADC threshold */
+-#define QCA808X_PHY_DEBUG_ADC_THRESHOLD 0x2c80
+-#define QCA808X_ADC_THRESHOLD_MASK GENMASK(7, 0)
+-#define QCA808X_ADC_THRESHOLD_80MV 0
+-#define QCA808X_ADC_THRESHOLD_100MV 0xf0
+-#define QCA808X_ADC_THRESHOLD_200MV 0x0f
+-#define QCA808X_ADC_THRESHOLD_300MV 0xff
+-
+-/* CLD control */
+-#define QCA808X_PHY_MMD3_ADDR_CLD_CTRL7 0x8007
+-#define QCA808X_8023AZ_AFE_CTRL_MASK GENMASK(8, 4)
+-#define QCA808X_8023AZ_AFE_EN 0x90
+-
+-/* AZ control */
+-#define QCA808X_PHY_MMD3_AZ_TRAINING_CTRL 0x8008
+-#define QCA808X_MMD3_AZ_TRAINING_VAL 0x1c32
+-
+-#define QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB 0x8014
+-#define QCA808X_MSE_THRESHOLD_20DB_VALUE 0x529
+-
+-#define QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB 0x800E
+-#define QCA808X_MSE_THRESHOLD_17DB_VALUE 0x341
+-
+-#define QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB 0x801E
+-#define QCA808X_MSE_THRESHOLD_27DB_VALUE 0x419
+-
+-#define QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB 0x8020
+-#define QCA808X_MSE_THRESHOLD_28DB_VALUE 0x341
+-
+-#define QCA808X_PHY_MMD7_TOP_OPTION1 0x901c
+-#define QCA808X_TOP_OPTION1_DATA 0x0
+-
+-#define QCA808X_PHY_MMD3_DEBUG_1 0xa100
+-#define QCA808X_MMD3_DEBUG_1_VALUE 0x9203
+-#define QCA808X_PHY_MMD3_DEBUG_2 0xa101
+-#define QCA808X_MMD3_DEBUG_2_VALUE 0x48ad
+-#define QCA808X_PHY_MMD3_DEBUG_3 0xa103
+-#define QCA808X_MMD3_DEBUG_3_VALUE 0x1698
+-#define QCA808X_PHY_MMD3_DEBUG_4 0xa105
+-#define QCA808X_MMD3_DEBUG_4_VALUE 0x8001
+-#define QCA808X_PHY_MMD3_DEBUG_5 0xa106
+-#define QCA808X_MMD3_DEBUG_5_VALUE 0x1111
+-#define QCA808X_PHY_MMD3_DEBUG_6 0xa011
+-#define QCA808X_MMD3_DEBUG_6_VALUE 0x5f85
+-
+-/* master/slave seed config */
+-#define QCA808X_PHY_DEBUG_LOCAL_SEED 9
+-#define QCA808X_MASTER_SLAVE_SEED_ENABLE BIT(1)
+-#define QCA808X_MASTER_SLAVE_SEED_CFG GENMASK(12, 2)
+-#define QCA808X_MASTER_SLAVE_SEED_RANGE 0x32
+-
+-/* Hibernation yields lower power consumpiton in contrast with normal operation mode.
+- * when the copper cable is unplugged, the PHY enters into hibernation mode in about 10s.
+- */
+-#define QCA808X_DBG_AN_TEST 0xb
+-#define QCA808X_HIBERNATION_EN BIT(15)
+-
+-#define QCA808X_CDT_ENABLE_TEST BIT(15)
+-#define QCA808X_CDT_INTER_CHECK_DIS BIT(13)
+-#define QCA808X_CDT_STATUS BIT(11)
+-#define QCA808X_CDT_LENGTH_UNIT BIT(10)
+-
+-#define QCA808X_MMD3_CDT_STATUS 0x8064
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_A 0x8065
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_B 0x8066
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_C 0x8067
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_D 0x8068
+-#define QCA808X_CDT_DIAG_LENGTH_SAME_SHORT GENMASK(15, 8)
+-#define QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT GENMASK(7, 0)
+-
+-#define QCA808X_CDT_CODE_PAIR_A GENMASK(15, 12)
+-#define QCA808X_CDT_CODE_PAIR_B GENMASK(11, 8)
+-#define QCA808X_CDT_CODE_PAIR_C GENMASK(7, 4)
+-#define QCA808X_CDT_CODE_PAIR_D GENMASK(3, 0)
+-
+-#define QCA808X_CDT_STATUS_STAT_TYPE GENMASK(1, 0)
+-#define QCA808X_CDT_STATUS_STAT_FAIL FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 0)
+-#define QCA808X_CDT_STATUS_STAT_NORMAL FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 1)
+-#define QCA808X_CDT_STATUS_STAT_SAME_OPEN FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 2)
+-#define QCA808X_CDT_STATUS_STAT_SAME_SHORT FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 3)
+-
+-#define QCA808X_CDT_STATUS_STAT_MDI GENMASK(3, 2)
+-#define QCA808X_CDT_STATUS_STAT_MDI1 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 1)
+-#define QCA808X_CDT_STATUS_STAT_MDI2 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 2)
+-#define QCA808X_CDT_STATUS_STAT_MDI3 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 3)
+-
+-/* NORMAL are MDI with type set to 0 */
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI1
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
+- QCA808X_CDT_STATUS_STAT_MDI1)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
+- QCA808X_CDT_STATUS_STAT_MDI1)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI2
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
+- QCA808X_CDT_STATUS_STAT_MDI2)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
+- QCA808X_CDT_STATUS_STAT_MDI2)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI3
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
+- QCA808X_CDT_STATUS_STAT_MDI3)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
+- QCA808X_CDT_STATUS_STAT_MDI3)
+-
+-/* Added for reference of existence but should be handled by wait_for_completion already */
+-#define QCA808X_CDT_STATUS_STAT_BUSY (BIT(1) | BIT(3))
+-
+-#define QCA808X_MMD7_LED_GLOBAL 0x8073
+-#define QCA808X_LED_BLINK_1 GENMASK(11, 6)
+-#define QCA808X_LED_BLINK_2 GENMASK(5, 0)
+-/* Values are the same for both BLINK_1 and BLINK_2 */
+-#define QCA808X_LED_BLINK_FREQ_MASK GENMASK(5, 3)
+-#define QCA808X_LED_BLINK_FREQ_2HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x0)
+-#define QCA808X_LED_BLINK_FREQ_4HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x1)
+-#define QCA808X_LED_BLINK_FREQ_8HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x2)
+-#define QCA808X_LED_BLINK_FREQ_16HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x3)
+-#define QCA808X_LED_BLINK_FREQ_32HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x4)
+-#define QCA808X_LED_BLINK_FREQ_64HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x5)
+-#define QCA808X_LED_BLINK_FREQ_128HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x6)
+-#define QCA808X_LED_BLINK_FREQ_256HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x7)
+-#define QCA808X_LED_BLINK_DUTY_MASK GENMASK(2, 0)
+-#define QCA808X_LED_BLINK_DUTY_50_50 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x0)
+-#define QCA808X_LED_BLINK_DUTY_75_25 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x1)
+-#define QCA808X_LED_BLINK_DUTY_25_75 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x2)
+-#define QCA808X_LED_BLINK_DUTY_33_67 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x3)
+-#define QCA808X_LED_BLINK_DUTY_67_33 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x4)
+-#define QCA808X_LED_BLINK_DUTY_17_83 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x5)
+-#define QCA808X_LED_BLINK_DUTY_83_17 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x6)
+-#define QCA808X_LED_BLINK_DUTY_8_92 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x7)
+-
+-#define QCA808X_MMD7_LED2_CTRL 0x8074
+-#define QCA808X_MMD7_LED2_FORCE_CTRL 0x8075
+-#define QCA808X_MMD7_LED1_CTRL 0x8076
+-#define QCA808X_MMD7_LED1_FORCE_CTRL 0x8077
+-#define QCA808X_MMD7_LED0_CTRL 0x8078
+-#define QCA808X_MMD7_LED_CTRL(x) (0x8078 - ((x) * 2))
+-
+-/* LED hw control pattern is the same for every LED */
+-#define QCA808X_LED_PATTERN_MASK GENMASK(15, 0)
+-#define QCA808X_LED_SPEED2500_ON BIT(15)
+-#define QCA808X_LED_SPEED2500_BLINK BIT(14)
+-/* Follow blink trigger even if duplex or speed condition doesn't match */
+-#define QCA808X_LED_BLINK_CHECK_BYPASS BIT(13)
+-#define QCA808X_LED_FULL_DUPLEX_ON BIT(12)
+-#define QCA808X_LED_HALF_DUPLEX_ON BIT(11)
+-#define QCA808X_LED_TX_BLINK BIT(10)
+-#define QCA808X_LED_RX_BLINK BIT(9)
+-#define QCA808X_LED_TX_ON_10MS BIT(8)
+-#define QCA808X_LED_RX_ON_10MS BIT(7)
+-#define QCA808X_LED_SPEED1000_ON BIT(6)
+-#define QCA808X_LED_SPEED100_ON BIT(5)
+-#define QCA808X_LED_SPEED10_ON BIT(4)
+-#define QCA808X_LED_COLLISION_BLINK BIT(3)
+-#define QCA808X_LED_SPEED1000_BLINK BIT(2)
+-#define QCA808X_LED_SPEED100_BLINK BIT(1)
+-#define QCA808X_LED_SPEED10_BLINK BIT(0)
+-
+-#define QCA808X_MMD7_LED0_FORCE_CTRL 0x8079
+-#define QCA808X_MMD7_LED_FORCE_CTRL(x) (0x8079 - ((x) * 2))
+-
+-/* LED force ctrl is the same for every LED
+- * No documentation exist for this, not even internal one
+- * with NDA as QCOM gives only info about configuring
+- * hw control pattern rules and doesn't indicate any way
+- * to force the LED to specific mode.
+- * These define comes from reverse and testing and maybe
+- * lack of some info or some info are not entirely correct.
+- * For the basic LED control and hw control these finding
+- * are enough to support LED control in all the required APIs.
+- *
+- * On doing some comparison with implementation with qca807x,
+- * it was found that it's 1:1 equal to it and confirms all the
+- * reverse done. It was also found further specification with the
+- * force mode and the blink modes.
+- */
+-#define QCA808X_LED_FORCE_EN BIT(15)
+-#define QCA808X_LED_FORCE_MODE_MASK GENMASK(14, 13)
+-#define QCA808X_LED_FORCE_BLINK_1 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x3)
+-#define QCA808X_LED_FORCE_BLINK_2 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x2)
+-#define QCA808X_LED_FORCE_ON FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x1)
+-#define QCA808X_LED_FORCE_OFF FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x0)
+-
+-#define QCA808X_MMD7_LED_POLARITY_CTRL 0x901a
+-/* QSDK sets by default 0x46 to this reg that sets BIT 6 for
+- * LED to active high. It's not clear what BIT 3 and BIT 4 does.
+- */
+-#define QCA808X_LED_ACTIVE_HIGH BIT(6)
+-
+-/* QCA808X 1G chip type */
+-#define QCA808X_PHY_MMD7_CHIP_TYPE 0x901d
+-#define QCA808X_PHY_CHIP_TYPE_1G BIT(0)
+-
+-#define QCA8081_PHY_SERDES_MMD1_FIFO_CTRL 0x9072
+-#define QCA8081_PHY_FIFO_RSTN BIT(11)
+-
+-MODULE_DESCRIPTION("Qualcomm Atheros AR803x and QCA808X PHY driver");
++MODULE_DESCRIPTION("Qualcomm Atheros AR803x PHY driver");
+ MODULE_AUTHOR("Matus Ujhelyi");
+ MODULE_LICENSE("GPL");
+
+@@ -318,7 +122,6 @@ struct at803x_priv {
+ bool is_1000basex;
+ struct regulator_dev *vddio_rdev;
+ struct regulator_dev *vddh_rdev;
+- int led_polarity_mode;
+ };
+
+ struct at803x_context {
+@@ -519,9 +322,6 @@ static int at803x_probe(struct phy_devic
+ if (!priv)
+ return -ENOMEM;
+
+- /* Init LED polarity mode to -1 */
+- priv->led_polarity_mode = -1;
+-
+ phydev->priv = priv;
+
+ ret = at803x_parse_dt(phydev);
+@@ -1216,672 +1016,6 @@ static int at8035_probe(struct phy_devic
+ return at8035_parse_dt(phydev);
+ }
+
+-static int qca808x_phy_fast_retrain_config(struct phy_device *phydev)
+-{
+- int ret;
+-
+- /* Enable fast retrain */
+- ret = genphy_c45_fast_retrain(phydev, true);
+- if (ret)
+- return ret;
+-
+- phy_write_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_TOP_OPTION1,
+- QCA808X_TOP_OPTION1_DATA);
+- phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB,
+- QCA808X_MSE_THRESHOLD_20DB_VALUE);
+- phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB,
+- QCA808X_MSE_THRESHOLD_17DB_VALUE);
+- phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB,
+- QCA808X_MSE_THRESHOLD_27DB_VALUE);
+- phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB,
+- QCA808X_MSE_THRESHOLD_28DB_VALUE);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_1,
+- QCA808X_MMD3_DEBUG_1_VALUE);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_4,
+- QCA808X_MMD3_DEBUG_4_VALUE);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_5,
+- QCA808X_MMD3_DEBUG_5_VALUE);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_3,
+- QCA808X_MMD3_DEBUG_3_VALUE);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_6,
+- QCA808X_MMD3_DEBUG_6_VALUE);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_2,
+- QCA808X_MMD3_DEBUG_2_VALUE);
+-
+- return 0;
+-}
+-
+-static int qca808x_phy_ms_seed_enable(struct phy_device *phydev, bool enable)
+-{
+- u16 seed_value;
+-
+- if (!enable)
+- return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
+- QCA808X_MASTER_SLAVE_SEED_ENABLE, 0);
+-
+- seed_value = prandom_u32_max(QCA808X_MASTER_SLAVE_SEED_RANGE);
+- return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
+- QCA808X_MASTER_SLAVE_SEED_CFG | QCA808X_MASTER_SLAVE_SEED_ENABLE,
+- FIELD_PREP(QCA808X_MASTER_SLAVE_SEED_CFG, seed_value) |
+- QCA808X_MASTER_SLAVE_SEED_ENABLE);
+-}
+-
+-static bool qca808x_is_prefer_master(struct phy_device *phydev)
+-{
+- return (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_FORCE) ||
+- (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_PREFERRED);
+-}
+-
+-static bool qca808x_has_fast_retrain_or_slave_seed(struct phy_device *phydev)
+-{
+- return linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
+-}
+-
+-static int qca808x_config_init(struct phy_device *phydev)
+-{
+- int ret;
+-
+- /* Active adc&vga on 802.3az for the link 1000M and 100M */
+- ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_ADDR_CLD_CTRL7,
+- QCA808X_8023AZ_AFE_CTRL_MASK, QCA808X_8023AZ_AFE_EN);
+- if (ret)
+- return ret;
+-
+- /* Adjust the threshold on 802.3az for the link 1000M */
+- ret = phy_write_mmd(phydev, MDIO_MMD_PCS,
+- QCA808X_PHY_MMD3_AZ_TRAINING_CTRL,
+- QCA808X_MMD3_AZ_TRAINING_VAL);
+- if (ret)
+- return ret;
+-
+- if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
+- /* Config the fast retrain for the link 2500M */
+- ret = qca808x_phy_fast_retrain_config(phydev);
+- if (ret)
+- return ret;
+-
+- ret = genphy_read_master_slave(phydev);
+- if (ret < 0)
+- return ret;
+-
+- if (!qca808x_is_prefer_master(phydev)) {
+- /* Enable seed and configure lower ramdom seed to make phy
+- * linked as slave mode.
+- */
+- ret = qca808x_phy_ms_seed_enable(phydev, true);
+- if (ret)
+- return ret;
+- }
+- }
+-
+- /* Configure adc threshold as 100mv for the link 10M */
+- return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_ADC_THRESHOLD,
+- QCA808X_ADC_THRESHOLD_MASK,
+- QCA808X_ADC_THRESHOLD_100MV);
+-}
+-
+-static int qca808x_read_status(struct phy_device *phydev)
+-{
+- struct at803x_ss_mask ss_mask = { 0 };
+- int ret;
+-
+- ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
+- if (ret < 0)
+- return ret;
+-
+- linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->lp_advertising,
+- ret & MDIO_AN_10GBT_STAT_LP2_5G);
+-
+- ret = genphy_read_status(phydev);
+- if (ret)
+- return ret;
+-
+- /* qca8081 takes the different bits for speed value from at803x */
+- ss_mask.speed_mask = QCA808X_SS_SPEED_MASK;
+- ss_mask.speed_shift = __bf_shf(QCA808X_SS_SPEED_MASK);
+- ret = at803x_read_specific_status(phydev, ss_mask);
+- if (ret < 0)
+- return ret;
+-
+- if (phydev->link) {
+- if (phydev->speed == SPEED_2500)
+- phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+- else
+- phydev->interface = PHY_INTERFACE_MODE_SGMII;
+- } else {
+- /* generate seed as a lower random value to make PHY linked as SLAVE easily,
+- * except for master/slave configuration fault detected or the master mode
+- * preferred.
+- *
+- * the reason for not putting this code into the function link_change_notify is
+- * the corner case where the link partner is also the qca8081 PHY and the seed
+- * value is configured as the same value, the link can't be up and no link change
+- * occurs.
+- */
+- if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
+- if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR ||
+- qca808x_is_prefer_master(phydev)) {
+- qca808x_phy_ms_seed_enable(phydev, false);
+- } else {
+- qca808x_phy_ms_seed_enable(phydev, true);
+- }
+- }
+- }
+-
+- return 0;
+-}
+-
+-static int qca808x_soft_reset(struct phy_device *phydev)
+-{
+- int ret;
+-
+- ret = genphy_soft_reset(phydev);
+- if (ret < 0)
+- return ret;
+-
+- if (qca808x_has_fast_retrain_or_slave_seed(phydev))
+- ret = qca808x_phy_ms_seed_enable(phydev, true);
+-
+- return ret;
+-}
+-
+-static bool qca808x_cdt_fault_length_valid(int cdt_code)
+-{
+- switch (cdt_code) {
+- case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
+- case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
+- return true;
+- default:
+- return false;
+- }
+-}
+-
+-static int qca808x_cable_test_result_trans(int cdt_code)
+-{
+- switch (cdt_code) {
+- case QCA808X_CDT_STATUS_STAT_NORMAL:
+- return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+- case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
+- return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+- case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
+- return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
+- return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
+- case QCA808X_CDT_STATUS_STAT_FAIL:
+- default:
+- return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+- }
+-}
+-
+-static int qca808x_cdt_fault_length(struct phy_device *phydev, int pair,
+- int result)
+-{
+- int val;
+- u32 cdt_length_reg = 0;
+-
+- switch (pair) {
+- case ETHTOOL_A_CABLE_PAIR_A:
+- cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_A;
+- break;
+- case ETHTOOL_A_CABLE_PAIR_B:
+- cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_B;
+- break;
+- case ETHTOOL_A_CABLE_PAIR_C:
+- cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_C;
+- break;
+- case ETHTOOL_A_CABLE_PAIR_D:
+- cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_D;
+- break;
+- default:
+- return -EINVAL;
+- }
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_PCS, cdt_length_reg);
+- if (val < 0)
+- return val;
+-
+- if (result == ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT)
+- val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_SAME_SHORT, val);
+- else
+- val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT, val);
+-
+- return at803x_cdt_fault_length(val);
+-}
+-
+-static int qca808x_cable_test_start(struct phy_device *phydev)
+-{
+- int ret;
+-
+- /* perform CDT with the following configs:
+- * 1. disable hibernation.
+- * 2. force PHY working in MDI mode.
+- * 3. for PHY working in 1000BaseT.
+- * 4. configure the threshold.
+- */
+-
+- ret = at803x_debug_reg_mask(phydev, QCA808X_DBG_AN_TEST, QCA808X_HIBERNATION_EN, 0);
+- if (ret < 0)
+- return ret;
+-
+- ret = at803x_config_mdix(phydev, ETH_TP_MDI);
+- if (ret < 0)
+- return ret;
+-
+- /* Force 1000base-T needs to configure PMA/PMD and MII_BMCR */
+- phydev->duplex = DUPLEX_FULL;
+- phydev->speed = SPEED_1000;
+- ret = genphy_c45_pma_setup_forced(phydev);
+- if (ret < 0)
+- return ret;
+-
+- ret = genphy_setup_forced(phydev);
+- if (ret < 0)
+- return ret;
+-
+- /* configure the thresholds for open, short, pair ok test */
+- phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8074, 0xc040);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8076, 0xc040);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8077, 0xa060);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8078, 0xc050);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807a, 0xc060);
+- phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807e, 0xb060);
+-
+- return 0;
+-}
+-
+-static int qca808x_cable_test_get_pair_status(struct phy_device *phydev, u8 pair,
+- u16 status)
+-{
+- int length, result;
+- u16 pair_code;
+-
+- switch (pair) {
+- case ETHTOOL_A_CABLE_PAIR_A:
+- pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, status);
+- break;
+- case ETHTOOL_A_CABLE_PAIR_B:
+- pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, status);
+- break;
+- case ETHTOOL_A_CABLE_PAIR_C:
+- pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, status);
+- break;
+- case ETHTOOL_A_CABLE_PAIR_D:
+- pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, status);
+- break;
+- default:
+- return -EINVAL;
+- }
+-
+- result = qca808x_cable_test_result_trans(pair_code);
+- ethnl_cable_test_result(phydev, pair, result);
+-
+- if (qca808x_cdt_fault_length_valid(pair_code)) {
+- length = qca808x_cdt_fault_length(phydev, pair, result);
+- ethnl_cable_test_fault_length(phydev, pair, length);
+- }
+-
+- return 0;
+-}
+-
+-static int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished)
+-{
+- int ret, val;
+-
+- *finished = false;
+-
+- val = QCA808X_CDT_ENABLE_TEST |
+- QCA808X_CDT_LENGTH_UNIT;
+- ret = at803x_cdt_start(phydev, val);
+- if (ret)
+- return ret;
+-
+- ret = at803x_cdt_wait_for_completion(phydev, QCA808X_CDT_ENABLE_TEST);
+- if (ret)
+- return ret;
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_PCS, QCA808X_MMD3_CDT_STATUS);
+- if (val < 0)
+- return val;
+-
+- ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_A, val);
+- if (ret)
+- return ret;
+-
+- ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_B, val);
+- if (ret)
+- return ret;
+-
+- ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_C, val);
+- if (ret)
+- return ret;
+-
+- ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_D, val);
+- if (ret)
+- return ret;
+-
+- *finished = true;
+-
+- return 0;
+-}
+-
+-static int qca808x_get_features(struct phy_device *phydev)
+-{
+- int ret;
+-
+- ret = genphy_c45_pma_read_abilities(phydev);
+- if (ret)
+- return ret;
+-
+- /* The autoneg ability is not existed in bit3 of MMD7.1,
+- * but it is supported by qca808x PHY, so we add it here
+- * manually.
+- */
+- linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
+-
+- /* As for the qca8081 1G version chip, the 2500baseT ability is also
+- * existed in the bit0 of MMD1.21, we need to remove it manually if
+- * it is the qca8081 1G chip according to the bit0 of MMD7.0x901d.
+- */
+- ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_CHIP_TYPE);
+- if (ret < 0)
+- return ret;
+-
+- if (QCA808X_PHY_CHIP_TYPE_1G & ret)
+- linkmode_clear_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
+-
+- return 0;
+-}
+-
+-static int qca808x_config_aneg(struct phy_device *phydev)
+-{
+- int phy_ctrl = 0;
+- int ret;
+-
+- ret = at803x_prepare_config_aneg(phydev);
+- if (ret)
+- return ret;
+-
+- /* The reg MII_BMCR also needs to be configured for force mode, the
+- * genphy_config_aneg is also needed.
+- */
+- if (phydev->autoneg == AUTONEG_DISABLE)
+- genphy_c45_pma_setup_forced(phydev);
+-
+- if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->advertising))
+- phy_ctrl = MDIO_AN_10GBT_CTRL_ADV2_5G;
+-
+- ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
+- MDIO_AN_10GBT_CTRL_ADV2_5G, phy_ctrl);
+- if (ret < 0)
+- return ret;
+-
+- return __genphy_config_aneg(phydev, ret);
+-}
+-
+-static void qca808x_link_change_notify(struct phy_device *phydev)
+-{
+- /* Assert interface sgmii fifo on link down, deassert it on link up,
+- * the interface device address is always phy address added by 1.
+- */
+- mdiobus_c45_modify_changed(phydev->mdio.bus, phydev->mdio.addr + 1,
+- MDIO_MMD_PMAPMD, QCA8081_PHY_SERDES_MMD1_FIFO_CTRL,
+- QCA8081_PHY_FIFO_RSTN,
+- phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
+-}
+-
+-static int qca808x_led_parse_netdev(struct phy_device *phydev, unsigned long rules,
+- u16 *offload_trigger)
+-{
+- /* Parsing specific to netdev trigger */
+- if (test_bit(TRIGGER_NETDEV_TX, &rules))
+- *offload_trigger |= QCA808X_LED_TX_BLINK;
+- if (test_bit(TRIGGER_NETDEV_RX, &rules))
+- *offload_trigger |= QCA808X_LED_RX_BLINK;
+- if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
+- *offload_trigger |= QCA808X_LED_SPEED10_ON;
+- if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
+- *offload_trigger |= QCA808X_LED_SPEED100_ON;
+- if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
+- *offload_trigger |= QCA808X_LED_SPEED1000_ON;
+- if (test_bit(TRIGGER_NETDEV_LINK_2500, &rules))
+- *offload_trigger |= QCA808X_LED_SPEED2500_ON;
+- if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
+- *offload_trigger |= QCA808X_LED_HALF_DUPLEX_ON;
+- if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
+- *offload_trigger |= QCA808X_LED_FULL_DUPLEX_ON;
+-
+- if (rules && !*offload_trigger)
+- return -EOPNOTSUPP;
+-
+- /* Enable BLINK_CHECK_BYPASS by default to make the LED
+- * blink even with duplex or speed mode not enabled.
+- */
+- *offload_trigger |= QCA808X_LED_BLINK_CHECK_BYPASS;
+-
+- return 0;
+-}
+-
+-static int qca808x_led_hw_control_enable(struct phy_device *phydev, u8 index)
+-{
+- u16 reg;
+-
+- if (index > 2)
+- return -EINVAL;
+-
+- reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+- return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
+- QCA808X_LED_FORCE_EN);
+-}
+-
+-static int qca808x_led_hw_is_supported(struct phy_device *phydev, u8 index,
+- unsigned long rules)
+-{
+- u16 offload_trigger = 0;
+-
+- if (index > 2)
+- return -EINVAL;
+-
+- return qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
+-}
+-
+-static int qca808x_led_hw_control_set(struct phy_device *phydev, u8 index,
+- unsigned long rules)
+-{
+- u16 reg, offload_trigger = 0;
+- int ret;
+-
+- if (index > 2)
+- return -EINVAL;
+-
+- reg = QCA808X_MMD7_LED_CTRL(index);
+-
+- ret = qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
+- if (ret)
+- return ret;
+-
+- ret = qca808x_led_hw_control_enable(phydev, index);
+- if (ret)
+- return ret;
+-
+- return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
+- QCA808X_LED_PATTERN_MASK,
+- offload_trigger);
+-}
+-
+-static bool qca808x_led_hw_control_status(struct phy_device *phydev, u8 index)
+-{
+- u16 reg;
+- int val;
+-
+- if (index > 2)
+- return false;
+-
+- reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
+-
+- return !(val & QCA808X_LED_FORCE_EN);
+-}
+-
+-static int qca808x_led_hw_control_get(struct phy_device *phydev, u8 index,
+- unsigned long *rules)
+-{
+- u16 reg;
+- int val;
+-
+- if (index > 2)
+- return -EINVAL;
+-
+- /* Check if we have hw control enabled */
+- if (qca808x_led_hw_control_status(phydev, index))
+- return -EINVAL;
+-
+- reg = QCA808X_MMD7_LED_CTRL(index);
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
+- if (val & QCA808X_LED_TX_BLINK)
+- set_bit(TRIGGER_NETDEV_TX, rules);
+- if (val & QCA808X_LED_RX_BLINK)
+- set_bit(TRIGGER_NETDEV_RX, rules);
+- if (val & QCA808X_LED_SPEED10_ON)
+- set_bit(TRIGGER_NETDEV_LINK_10, rules);
+- if (val & QCA808X_LED_SPEED100_ON)
+- set_bit(TRIGGER_NETDEV_LINK_100, rules);
+- if (val & QCA808X_LED_SPEED1000_ON)
+- set_bit(TRIGGER_NETDEV_LINK_1000, rules);
+- if (val & QCA808X_LED_SPEED2500_ON)
+- set_bit(TRIGGER_NETDEV_LINK_2500, rules);
+- if (val & QCA808X_LED_HALF_DUPLEX_ON)
+- set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
+- if (val & QCA808X_LED_FULL_DUPLEX_ON)
+- set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
+-
+- return 0;
+-}
+-
+-static int qca808x_led_hw_control_reset(struct phy_device *phydev, u8 index)
+-{
+- u16 reg;
+-
+- if (index > 2)
+- return -EINVAL;
+-
+- reg = QCA808X_MMD7_LED_CTRL(index);
+-
+- return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
+- QCA808X_LED_PATTERN_MASK);
+-}
+-
+-static int qca808x_led_brightness_set(struct phy_device *phydev,
+- u8 index, enum led_brightness value)
+-{
+- u16 reg;
+- int ret;
+-
+- if (index > 2)
+- return -EINVAL;
+-
+- if (!value) {
+- ret = qca808x_led_hw_control_reset(phydev, index);
+- if (ret)
+- return ret;
+- }
+-
+- reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+- return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
+- QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
+- QCA808X_LED_FORCE_EN | value ? QCA808X_LED_FORCE_ON :
+- QCA808X_LED_FORCE_OFF);
+-}
+-
+-static int qca808x_led_blink_set(struct phy_device *phydev, u8 index,
+- unsigned long *delay_on,
+- unsigned long *delay_off)
+-{
+- int ret;
+- u16 reg;
+-
+- if (index > 2)
+- return -EINVAL;
+-
+- reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+- /* Set blink to 50% off, 50% on at 4Hz by default */
+- ret = phy_modify_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_LED_GLOBAL,
+- QCA808X_LED_BLINK_FREQ_MASK | QCA808X_LED_BLINK_DUTY_MASK,
+- QCA808X_LED_BLINK_FREQ_4HZ | QCA808X_LED_BLINK_DUTY_50_50);
+- if (ret)
+- return ret;
+-
+- /* We use BLINK_1 for normal blinking */
+- ret = phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
+- QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
+- QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_BLINK_1);
+- if (ret)
+- return ret;
+-
+- /* We set blink to 4Hz, aka 250ms */
+- *delay_on = 250 / 2;
+- *delay_off = 250 / 2;
+-
+- return 0;
+-}
+-
+-static int qca808x_led_polarity_set(struct phy_device *phydev, int index,
+- unsigned long modes)
+-{
+- struct at803x_priv *priv = phydev->priv;
+- bool active_low = false;
+- u32 mode;
+-
+- for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
+- switch (mode) {
+- case PHY_LED_ACTIVE_LOW:
+- active_low = true;
+- break;
+- default:
+- return -EINVAL;
+- }
+- }
+-
+- /* PHY polarity is global and can't be set per LED.
+- * To detect this, check if last requested polarity mode
+- * match the new one.
+- */
+- if (priv->led_polarity_mode >= 0 &&
+- priv->led_polarity_mode != active_low) {
+- phydev_err(phydev, "PHY polarity is global. Mismatched polarity on different LED\n");
+- return -EINVAL;
+- }
+-
+- /* Save the last PHY polarity mode */
+- priv->led_polarity_mode = active_low;
+-
+- return phy_modify_mmd(phydev, MDIO_MMD_AN,
+- QCA808X_MMD7_LED_POLARITY_CTRL,
+- QCA808X_LED_ACTIVE_HIGH,
+- active_low ? 0 : QCA808X_LED_ACTIVE_HIGH);
+-}
+-
+ static struct phy_driver at803x_driver[] = {
+ {
+ /* Qualcomm Atheros AR8035 */
+@@ -1989,34 +1123,6 @@ static struct phy_driver at803x_driver[]
+ .read_status = at803x_read_status,
+ .soft_reset = genphy_soft_reset,
+ .config_aneg = at803x_config_aneg,
+-}, {
+- /* Qualcomm QCA8081 */
+- PHY_ID_MATCH_EXACT(QCA8081_PHY_ID),
+- .name = "Qualcomm QCA8081",
+- .flags = PHY_POLL_CABLE_TEST,
+- .probe = at803x_probe,
+- .config_intr = at803x_config_intr,
+- .handle_interrupt = at803x_handle_interrupt,
+- .get_tunable = at803x_get_tunable,
+- .set_tunable = at803x_set_tunable,
+- .set_wol = at803x_set_wol,
+- .get_wol = at803x_get_wol,
+- .get_features = qca808x_get_features,
+- .config_aneg = qca808x_config_aneg,
+- .suspend = genphy_suspend,
+- .resume = genphy_resume,
+- .read_status = qca808x_read_status,
+- .config_init = qca808x_config_init,
+- .soft_reset = qca808x_soft_reset,
+- .cable_test_start = qca808x_cable_test_start,
+- .cable_test_get_status = qca808x_cable_test_get_status,
+- .link_change_notify = qca808x_link_change_notify,
+- .led_brightness_set = qca808x_led_brightness_set,
+- .led_blink_set = qca808x_led_blink_set,
+- .led_hw_is_supported = qca808x_led_hw_is_supported,
+- .led_hw_control_set = qca808x_led_hw_control_set,
+- .led_hw_control_get = qca808x_led_hw_control_get,
+- .led_polarity_set = qca808x_led_polarity_set,
+ }, };
+
+ module_phy_driver(at803x_driver);
+@@ -2028,7 +1134,6 @@ 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(QCA9561_PHY_ID) },
+- { PHY_ID_MATCH_EXACT(QCA8081_PHY_ID) },
+ { }
+ };
+
+--- /dev/null
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -0,0 +1,934 @@
++// SPDX-License-Identifier: GPL-2.0+
++
++#include <linux/phy.h>
++#include <linux/module.h>
++#include <linux/ethtool_netlink.h>
++
++#include "qcom.h"
++
++/* ADC threshold */
++#define QCA808X_PHY_DEBUG_ADC_THRESHOLD 0x2c80
++#define QCA808X_ADC_THRESHOLD_MASK GENMASK(7, 0)
++#define QCA808X_ADC_THRESHOLD_80MV 0
++#define QCA808X_ADC_THRESHOLD_100MV 0xf0
++#define QCA808X_ADC_THRESHOLD_200MV 0x0f
++#define QCA808X_ADC_THRESHOLD_300MV 0xff
++
++/* CLD control */
++#define QCA808X_PHY_MMD3_ADDR_CLD_CTRL7 0x8007
++#define QCA808X_8023AZ_AFE_CTRL_MASK GENMASK(8, 4)
++#define QCA808X_8023AZ_AFE_EN 0x90
++
++/* AZ control */
++#define QCA808X_PHY_MMD3_AZ_TRAINING_CTRL 0x8008
++#define QCA808X_MMD3_AZ_TRAINING_VAL 0x1c32
++
++#define QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB 0x8014
++#define QCA808X_MSE_THRESHOLD_20DB_VALUE 0x529
++
++#define QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB 0x800E
++#define QCA808X_MSE_THRESHOLD_17DB_VALUE 0x341
++
++#define QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB 0x801E
++#define QCA808X_MSE_THRESHOLD_27DB_VALUE 0x419
++
++#define QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB 0x8020
++#define QCA808X_MSE_THRESHOLD_28DB_VALUE 0x341
++
++#define QCA808X_PHY_MMD7_TOP_OPTION1 0x901c
++#define QCA808X_TOP_OPTION1_DATA 0x0
++
++#define QCA808X_PHY_MMD3_DEBUG_1 0xa100
++#define QCA808X_MMD3_DEBUG_1_VALUE 0x9203
++#define QCA808X_PHY_MMD3_DEBUG_2 0xa101
++#define QCA808X_MMD3_DEBUG_2_VALUE 0x48ad
++#define QCA808X_PHY_MMD3_DEBUG_3 0xa103
++#define QCA808X_MMD3_DEBUG_3_VALUE 0x1698
++#define QCA808X_PHY_MMD3_DEBUG_4 0xa105
++#define QCA808X_MMD3_DEBUG_4_VALUE 0x8001
++#define QCA808X_PHY_MMD3_DEBUG_5 0xa106
++#define QCA808X_MMD3_DEBUG_5_VALUE 0x1111
++#define QCA808X_PHY_MMD3_DEBUG_6 0xa011
++#define QCA808X_MMD3_DEBUG_6_VALUE 0x5f85
++
++/* master/slave seed config */
++#define QCA808X_PHY_DEBUG_LOCAL_SEED 9
++#define QCA808X_MASTER_SLAVE_SEED_ENABLE BIT(1)
++#define QCA808X_MASTER_SLAVE_SEED_CFG GENMASK(12, 2)
++#define QCA808X_MASTER_SLAVE_SEED_RANGE 0x32
++
++/* Hibernation yields lower power consumpiton in contrast with normal operation mode.
++ * when the copper cable is unplugged, the PHY enters into hibernation mode in about 10s.
++ */
++#define QCA808X_DBG_AN_TEST 0xb
++#define QCA808X_HIBERNATION_EN BIT(15)
++
++#define QCA808X_CDT_ENABLE_TEST BIT(15)
++#define QCA808X_CDT_INTER_CHECK_DIS BIT(13)
++#define QCA808X_CDT_STATUS BIT(11)
++#define QCA808X_CDT_LENGTH_UNIT BIT(10)
++
++#define QCA808X_MMD3_CDT_STATUS 0x8064
++#define QCA808X_MMD3_CDT_DIAG_PAIR_A 0x8065
++#define QCA808X_MMD3_CDT_DIAG_PAIR_B 0x8066
++#define QCA808X_MMD3_CDT_DIAG_PAIR_C 0x8067
++#define QCA808X_MMD3_CDT_DIAG_PAIR_D 0x8068
++#define QCA808X_CDT_DIAG_LENGTH_SAME_SHORT GENMASK(15, 8)
++#define QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT GENMASK(7, 0)
++
++#define QCA808X_CDT_CODE_PAIR_A GENMASK(15, 12)
++#define QCA808X_CDT_CODE_PAIR_B GENMASK(11, 8)
++#define QCA808X_CDT_CODE_PAIR_C GENMASK(7, 4)
++#define QCA808X_CDT_CODE_PAIR_D GENMASK(3, 0)
++
++#define QCA808X_CDT_STATUS_STAT_TYPE GENMASK(1, 0)
++#define QCA808X_CDT_STATUS_STAT_FAIL FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 0)
++#define QCA808X_CDT_STATUS_STAT_NORMAL FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 1)
++#define QCA808X_CDT_STATUS_STAT_SAME_OPEN FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 2)
++#define QCA808X_CDT_STATUS_STAT_SAME_SHORT FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 3)
++
++#define QCA808X_CDT_STATUS_STAT_MDI GENMASK(3, 2)
++#define QCA808X_CDT_STATUS_STAT_MDI1 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 1)
++#define QCA808X_CDT_STATUS_STAT_MDI2 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 2)
++#define QCA808X_CDT_STATUS_STAT_MDI3 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 3)
++
++/* NORMAL are MDI with type set to 0 */
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI1
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++ QCA808X_CDT_STATUS_STAT_MDI1)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++ QCA808X_CDT_STATUS_STAT_MDI1)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI2
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++ QCA808X_CDT_STATUS_STAT_MDI2)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++ QCA808X_CDT_STATUS_STAT_MDI2)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI3
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++ QCA808X_CDT_STATUS_STAT_MDI3)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++ QCA808X_CDT_STATUS_STAT_MDI3)
++
++/* Added for reference of existence but should be handled by wait_for_completion already */
++#define QCA808X_CDT_STATUS_STAT_BUSY (BIT(1) | BIT(3))
++
++#define QCA808X_MMD7_LED_GLOBAL 0x8073
++#define QCA808X_LED_BLINK_1 GENMASK(11, 6)
++#define QCA808X_LED_BLINK_2 GENMASK(5, 0)
++/* Values are the same for both BLINK_1 and BLINK_2 */
++#define QCA808X_LED_BLINK_FREQ_MASK GENMASK(5, 3)
++#define QCA808X_LED_BLINK_FREQ_2HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x0)
++#define QCA808X_LED_BLINK_FREQ_4HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x1)
++#define QCA808X_LED_BLINK_FREQ_8HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x2)
++#define QCA808X_LED_BLINK_FREQ_16HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x3)
++#define QCA808X_LED_BLINK_FREQ_32HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x4)
++#define QCA808X_LED_BLINK_FREQ_64HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x5)
++#define QCA808X_LED_BLINK_FREQ_128HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x6)
++#define QCA808X_LED_BLINK_FREQ_256HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x7)
++#define QCA808X_LED_BLINK_DUTY_MASK GENMASK(2, 0)
++#define QCA808X_LED_BLINK_DUTY_50_50 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x0)
++#define QCA808X_LED_BLINK_DUTY_75_25 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x1)
++#define QCA808X_LED_BLINK_DUTY_25_75 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x2)
++#define QCA808X_LED_BLINK_DUTY_33_67 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x3)
++#define QCA808X_LED_BLINK_DUTY_67_33 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x4)
++#define QCA808X_LED_BLINK_DUTY_17_83 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x5)
++#define QCA808X_LED_BLINK_DUTY_83_17 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x6)
++#define QCA808X_LED_BLINK_DUTY_8_92 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x7)
++
++#define QCA808X_MMD7_LED2_CTRL 0x8074
++#define QCA808X_MMD7_LED2_FORCE_CTRL 0x8075
++#define QCA808X_MMD7_LED1_CTRL 0x8076
++#define QCA808X_MMD7_LED1_FORCE_CTRL 0x8077
++#define QCA808X_MMD7_LED0_CTRL 0x8078
++#define QCA808X_MMD7_LED_CTRL(x) (0x8078 - ((x) * 2))
++
++/* LED hw control pattern is the same for every LED */
++#define QCA808X_LED_PATTERN_MASK GENMASK(15, 0)
++#define QCA808X_LED_SPEED2500_ON BIT(15)
++#define QCA808X_LED_SPEED2500_BLINK BIT(14)
++/* Follow blink trigger even if duplex or speed condition doesn't match */
++#define QCA808X_LED_BLINK_CHECK_BYPASS BIT(13)
++#define QCA808X_LED_FULL_DUPLEX_ON BIT(12)
++#define QCA808X_LED_HALF_DUPLEX_ON BIT(11)
++#define QCA808X_LED_TX_BLINK BIT(10)
++#define QCA808X_LED_RX_BLINK BIT(9)
++#define QCA808X_LED_TX_ON_10MS BIT(8)
++#define QCA808X_LED_RX_ON_10MS BIT(7)
++#define QCA808X_LED_SPEED1000_ON BIT(6)
++#define QCA808X_LED_SPEED100_ON BIT(5)
++#define QCA808X_LED_SPEED10_ON BIT(4)
++#define QCA808X_LED_COLLISION_BLINK BIT(3)
++#define QCA808X_LED_SPEED1000_BLINK BIT(2)
++#define QCA808X_LED_SPEED100_BLINK BIT(1)
++#define QCA808X_LED_SPEED10_BLINK BIT(0)
++
++#define QCA808X_MMD7_LED0_FORCE_CTRL 0x8079
++#define QCA808X_MMD7_LED_FORCE_CTRL(x) (0x8079 - ((x) * 2))
++
++/* LED force ctrl is the same for every LED
++ * No documentation exist for this, not even internal one
++ * with NDA as QCOM gives only info about configuring
++ * hw control pattern rules and doesn't indicate any way
++ * to force the LED to specific mode.
++ * These define comes from reverse and testing and maybe
++ * lack of some info or some info are not entirely correct.
++ * For the basic LED control and hw control these finding
++ * are enough to support LED control in all the required APIs.
++ *
++ * On doing some comparison with implementation with qca807x,
++ * it was found that it's 1:1 equal to it and confirms all the
++ * reverse done. It was also found further specification with the
++ * force mode and the blink modes.
++ */
++#define QCA808X_LED_FORCE_EN BIT(15)
++#define QCA808X_LED_FORCE_MODE_MASK GENMASK(14, 13)
++#define QCA808X_LED_FORCE_BLINK_1 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x3)
++#define QCA808X_LED_FORCE_BLINK_2 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x2)
++#define QCA808X_LED_FORCE_ON FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x1)
++#define QCA808X_LED_FORCE_OFF FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x0)
++
++#define QCA808X_MMD7_LED_POLARITY_CTRL 0x901a
++/* QSDK sets by default 0x46 to this reg that sets BIT 6 for
++ * LED to active high. It's not clear what BIT 3 and BIT 4 does.
++ */
++#define QCA808X_LED_ACTIVE_HIGH BIT(6)
++
++/* QCA808X 1G chip type */
++#define QCA808X_PHY_MMD7_CHIP_TYPE 0x901d
++#define QCA808X_PHY_CHIP_TYPE_1G BIT(0)
++
++#define QCA8081_PHY_SERDES_MMD1_FIFO_CTRL 0x9072
++#define QCA8081_PHY_FIFO_RSTN BIT(11)
++
++#define QCA8081_PHY_ID 0x004dd101
++
++MODULE_DESCRIPTION("Qualcomm Atheros QCA808X PHY driver");
++MODULE_AUTHOR("Matus Ujhelyi");
++MODULE_LICENSE("GPL");
++
++struct qca808x_priv {
++ int led_polarity_mode;
++};
++
++static int qca808x_phy_fast_retrain_config(struct phy_device *phydev)
++{
++ int ret;
++
++ /* Enable fast retrain */
++ ret = genphy_c45_fast_retrain(phydev, true);
++ if (ret)
++ return ret;
++
++ phy_write_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_TOP_OPTION1,
++ QCA808X_TOP_OPTION1_DATA);
++ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB,
++ QCA808X_MSE_THRESHOLD_20DB_VALUE);
++ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB,
++ QCA808X_MSE_THRESHOLD_17DB_VALUE);
++ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB,
++ QCA808X_MSE_THRESHOLD_27DB_VALUE);
++ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB,
++ QCA808X_MSE_THRESHOLD_28DB_VALUE);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_1,
++ QCA808X_MMD3_DEBUG_1_VALUE);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_4,
++ QCA808X_MMD3_DEBUG_4_VALUE);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_5,
++ QCA808X_MMD3_DEBUG_5_VALUE);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_3,
++ QCA808X_MMD3_DEBUG_3_VALUE);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_6,
++ QCA808X_MMD3_DEBUG_6_VALUE);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_2,
++ QCA808X_MMD3_DEBUG_2_VALUE);
++
++ return 0;
++}
++
++static int qca808x_phy_ms_seed_enable(struct phy_device *phydev, bool enable)
++{
++ u16 seed_value;
++
++ if (!enable)
++ return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
++ QCA808X_MASTER_SLAVE_SEED_ENABLE, 0);
++
++ seed_value = prandom_u32_max(QCA808X_MASTER_SLAVE_SEED_RANGE);
++ return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
++ QCA808X_MASTER_SLAVE_SEED_CFG | QCA808X_MASTER_SLAVE_SEED_ENABLE,
++ FIELD_PREP(QCA808X_MASTER_SLAVE_SEED_CFG, seed_value) |
++ QCA808X_MASTER_SLAVE_SEED_ENABLE);
++}
++
++static bool qca808x_is_prefer_master(struct phy_device *phydev)
++{
++ return (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_FORCE) ||
++ (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_PREFERRED);
++}
++
++static bool qca808x_has_fast_retrain_or_slave_seed(struct phy_device *phydev)
++{
++ return linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
++}
++
++static int qca808x_probe(struct phy_device *phydev)
++{
++ struct device *dev = &phydev->mdio.dev;
++ struct qca808x_priv *priv;
++
++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
++
++ /* Init LED polarity mode to -1 */
++ priv->led_polarity_mode = -1;
++
++ phydev->priv = priv;
++
++ return 0;
++}
++
++static int qca808x_config_init(struct phy_device *phydev)
++{
++ int ret;
++
++ /* Active adc&vga on 802.3az for the link 1000M and 100M */
++ ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_ADDR_CLD_CTRL7,
++ QCA808X_8023AZ_AFE_CTRL_MASK, QCA808X_8023AZ_AFE_EN);
++ if (ret)
++ return ret;
++
++ /* Adjust the threshold on 802.3az for the link 1000M */
++ ret = phy_write_mmd(phydev, MDIO_MMD_PCS,
++ QCA808X_PHY_MMD3_AZ_TRAINING_CTRL,
++ QCA808X_MMD3_AZ_TRAINING_VAL);
++ if (ret)
++ return ret;
++
++ if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
++ /* Config the fast retrain for the link 2500M */
++ ret = qca808x_phy_fast_retrain_config(phydev);
++ if (ret)
++ return ret;
++
++ ret = genphy_read_master_slave(phydev);
++ if (ret < 0)
++ return ret;
++
++ if (!qca808x_is_prefer_master(phydev)) {
++ /* Enable seed and configure lower ramdom seed to make phy
++ * linked as slave mode.
++ */
++ ret = qca808x_phy_ms_seed_enable(phydev, true);
++ if (ret)
++ return ret;
++ }
++ }
++
++ /* Configure adc threshold as 100mv for the link 10M */
++ return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_ADC_THRESHOLD,
++ QCA808X_ADC_THRESHOLD_MASK,
++ QCA808X_ADC_THRESHOLD_100MV);
++}
++
++static int qca808x_read_status(struct phy_device *phydev)
++{
++ struct at803x_ss_mask ss_mask = { 0 };
++ int ret;
++
++ ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
++ if (ret < 0)
++ return ret;
++
++ linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->lp_advertising,
++ ret & MDIO_AN_10GBT_STAT_LP2_5G);
++
++ ret = genphy_read_status(phydev);
++ if (ret)
++ return ret;
++
++ /* qca8081 takes the different bits for speed value from at803x */
++ ss_mask.speed_mask = QCA808X_SS_SPEED_MASK;
++ ss_mask.speed_shift = __bf_shf(QCA808X_SS_SPEED_MASK);
++ ret = at803x_read_specific_status(phydev, ss_mask);
++ if (ret < 0)
++ return ret;
++
++ if (phydev->link) {
++ if (phydev->speed == SPEED_2500)
++ phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
++ else
++ phydev->interface = PHY_INTERFACE_MODE_SGMII;
++ } else {
++ /* generate seed as a lower random value to make PHY linked as SLAVE easily,
++ * except for master/slave configuration fault detected or the master mode
++ * preferred.
++ *
++ * the reason for not putting this code into the function link_change_notify is
++ * the corner case where the link partner is also the qca8081 PHY and the seed
++ * value is configured as the same value, the link can't be up and no link change
++ * occurs.
++ */
++ if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
++ if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR ||
++ qca808x_is_prefer_master(phydev)) {
++ qca808x_phy_ms_seed_enable(phydev, false);
++ } else {
++ qca808x_phy_ms_seed_enable(phydev, true);
++ }
++ }
++ }
++
++ return 0;
++}
++
++static int qca808x_soft_reset(struct phy_device *phydev)
++{
++ int ret;
++
++ ret = genphy_soft_reset(phydev);
++ if (ret < 0)
++ return ret;
++
++ if (qca808x_has_fast_retrain_or_slave_seed(phydev))
++ ret = qca808x_phy_ms_seed_enable(phydev, true);
++
++ return ret;
++}
++
++static bool qca808x_cdt_fault_length_valid(int cdt_code)
++{
++ switch (cdt_code) {
++ case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
++ return true;
++ default:
++ return false;
++ }
++}
++
++static int qca808x_cable_test_result_trans(int cdt_code)
++{
++ switch (cdt_code) {
++ case QCA808X_CDT_STATUS_STAT_NORMAL:
++ return ETHTOOL_A_CABLE_RESULT_CODE_OK;
++ case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
++ return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
++ case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
++ return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
++ return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
++ case QCA808X_CDT_STATUS_STAT_FAIL:
++ default:
++ return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
++ }
++}
++
++static int qca808x_cdt_fault_length(struct phy_device *phydev, int pair,
++ int result)
++{
++ int val;
++ u32 cdt_length_reg = 0;
++
++ switch (pair) {
++ case ETHTOOL_A_CABLE_PAIR_A:
++ cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_A;
++ break;
++ case ETHTOOL_A_CABLE_PAIR_B:
++ cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_B;
++ break;
++ case ETHTOOL_A_CABLE_PAIR_C:
++ cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_C;
++ break;
++ case ETHTOOL_A_CABLE_PAIR_D:
++ cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_D;
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ val = phy_read_mmd(phydev, MDIO_MMD_PCS, cdt_length_reg);
++ if (val < 0)
++ return val;
++
++ if (result == ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT)
++ val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_SAME_SHORT, val);
++ else
++ val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT, val);
++
++ return at803x_cdt_fault_length(val);
++}
++
++static int qca808x_cable_test_start(struct phy_device *phydev)
++{
++ int ret;
++
++ /* perform CDT with the following configs:
++ * 1. disable hibernation.
++ * 2. force PHY working in MDI mode.
++ * 3. for PHY working in 1000BaseT.
++ * 4. configure the threshold.
++ */
++
++ ret = at803x_debug_reg_mask(phydev, QCA808X_DBG_AN_TEST, QCA808X_HIBERNATION_EN, 0);
++ if (ret < 0)
++ return ret;
++
++ ret = at803x_config_mdix(phydev, ETH_TP_MDI);
++ if (ret < 0)
++ return ret;
++
++ /* Force 1000base-T needs to configure PMA/PMD and MII_BMCR */
++ phydev->duplex = DUPLEX_FULL;
++ phydev->speed = SPEED_1000;
++ ret = genphy_c45_pma_setup_forced(phydev);
++ if (ret < 0)
++ return ret;
++
++ ret = genphy_setup_forced(phydev);
++ if (ret < 0)
++ return ret;
++
++ /* configure the thresholds for open, short, pair ok test */
++ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8074, 0xc040);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8076, 0xc040);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8077, 0xa060);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8078, 0xc050);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807a, 0xc060);
++ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807e, 0xb060);
++
++ return 0;
++}
++
++static int qca808x_cable_test_get_pair_status(struct phy_device *phydev, u8 pair,
++ u16 status)
++{
++ int length, result;
++ u16 pair_code;
++
++ switch (pair) {
++ case ETHTOOL_A_CABLE_PAIR_A:
++ pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, status);
++ break;
++ case ETHTOOL_A_CABLE_PAIR_B:
++ pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, status);
++ break;
++ case ETHTOOL_A_CABLE_PAIR_C:
++ pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, status);
++ break;
++ case ETHTOOL_A_CABLE_PAIR_D:
++ pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, status);
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ result = qca808x_cable_test_result_trans(pair_code);
++ ethnl_cable_test_result(phydev, pair, result);
++
++ if (qca808x_cdt_fault_length_valid(pair_code)) {
++ length = qca808x_cdt_fault_length(phydev, pair, result);
++ ethnl_cable_test_fault_length(phydev, pair, length);
++ }
++
++ return 0;
++}
++
++static int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished)
++{
++ int ret, val;
++
++ *finished = false;
++
++ val = QCA808X_CDT_ENABLE_TEST |
++ QCA808X_CDT_LENGTH_UNIT;
++ ret = at803x_cdt_start(phydev, val);
++ if (ret)
++ return ret;
++
++ ret = at803x_cdt_wait_for_completion(phydev, QCA808X_CDT_ENABLE_TEST);
++ if (ret)
++ return ret;
++
++ val = phy_read_mmd(phydev, MDIO_MMD_PCS, QCA808X_MMD3_CDT_STATUS);
++ if (val < 0)
++ return val;
++
++ ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_A, val);
++ if (ret)
++ return ret;
++
++ ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_B, val);
++ if (ret)
++ return ret;
++
++ ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_C, val);
++ if (ret)
++ return ret;
++
++ ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_D, val);
++ if (ret)
++ return ret;
++
++ *finished = true;
++
++ return 0;
++}
++
++static int qca808x_get_features(struct phy_device *phydev)
++{
++ int ret;
++
++ ret = genphy_c45_pma_read_abilities(phydev);
++ if (ret)
++ return ret;
++
++ /* The autoneg ability is not existed in bit3 of MMD7.1,
++ * but it is supported by qca808x PHY, so we add it here
++ * manually.
++ */
++ linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
++
++ /* As for the qca8081 1G version chip, the 2500baseT ability is also
++ * existed in the bit0 of MMD1.21, we need to remove it manually if
++ * it is the qca8081 1G chip according to the bit0 of MMD7.0x901d.
++ */
++ ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_CHIP_TYPE);
++ if (ret < 0)
++ return ret;
++
++ if (QCA808X_PHY_CHIP_TYPE_1G & ret)
++ linkmode_clear_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
++
++ return 0;
++}
++
++static int qca808x_config_aneg(struct phy_device *phydev)
++{
++ int phy_ctrl = 0;
++ int ret;
++
++ ret = at803x_prepare_config_aneg(phydev);
++ if (ret)
++ return ret;
++
++ /* The reg MII_BMCR also needs to be configured for force mode, the
++ * genphy_config_aneg is also needed.
++ */
++ if (phydev->autoneg == AUTONEG_DISABLE)
++ genphy_c45_pma_setup_forced(phydev);
++
++ if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->advertising))
++ phy_ctrl = MDIO_AN_10GBT_CTRL_ADV2_5G;
++
++ ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
++ MDIO_AN_10GBT_CTRL_ADV2_5G, phy_ctrl);
++ if (ret < 0)
++ return ret;
++
++ return __genphy_config_aneg(phydev, ret);
++}
++
++static void qca808x_link_change_notify(struct phy_device *phydev)
++{
++ /* Assert interface sgmii fifo on link down, deassert it on link up,
++ * the interface device address is always phy address added by 1.
++ */
++ mdiobus_c45_modify_changed(phydev->mdio.bus, phydev->mdio.addr + 1,
++ MDIO_MMD_PMAPMD, QCA8081_PHY_SERDES_MMD1_FIFO_CTRL,
++ QCA8081_PHY_FIFO_RSTN,
++ phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
++}
++
++static int qca808x_led_parse_netdev(struct phy_device *phydev, unsigned long rules,
++ u16 *offload_trigger)
++{
++ /* Parsing specific to netdev trigger */
++ if (test_bit(TRIGGER_NETDEV_TX, &rules))
++ *offload_trigger |= QCA808X_LED_TX_BLINK;
++ if (test_bit(TRIGGER_NETDEV_RX, &rules))
++ *offload_trigger |= QCA808X_LED_RX_BLINK;
++ if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
++ *offload_trigger |= QCA808X_LED_SPEED10_ON;
++ if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
++ *offload_trigger |= QCA808X_LED_SPEED100_ON;
++ if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
++ *offload_trigger |= QCA808X_LED_SPEED1000_ON;
++ if (test_bit(TRIGGER_NETDEV_LINK_2500, &rules))
++ *offload_trigger |= QCA808X_LED_SPEED2500_ON;
++ if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
++ *offload_trigger |= QCA808X_LED_HALF_DUPLEX_ON;
++ if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
++ *offload_trigger |= QCA808X_LED_FULL_DUPLEX_ON;
++
++ if (rules && !*offload_trigger)
++ return -EOPNOTSUPP;
++
++ /* Enable BLINK_CHECK_BYPASS by default to make the LED
++ * blink even with duplex or speed mode not enabled.
++ */
++ *offload_trigger |= QCA808X_LED_BLINK_CHECK_BYPASS;
++
++ return 0;
++}
++
++static int qca808x_led_hw_control_enable(struct phy_device *phydev, u8 index)
++{
++ u16 reg;
++
++ if (index > 2)
++ return -EINVAL;
++
++ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++ return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
++ QCA808X_LED_FORCE_EN);
++}
++
++static int qca808x_led_hw_is_supported(struct phy_device *phydev, u8 index,
++ unsigned long rules)
++{
++ u16 offload_trigger = 0;
++
++ if (index > 2)
++ return -EINVAL;
++
++ return qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
++}
++
++static int qca808x_led_hw_control_set(struct phy_device *phydev, u8 index,
++ unsigned long rules)
++{
++ u16 reg, offload_trigger = 0;
++ int ret;
++
++ if (index > 2)
++ return -EINVAL;
++
++ reg = QCA808X_MMD7_LED_CTRL(index);
++
++ ret = qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
++ if (ret)
++ return ret;
++
++ ret = qca808x_led_hw_control_enable(phydev, index);
++ if (ret)
++ return ret;
++
++ return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++ QCA808X_LED_PATTERN_MASK,
++ offload_trigger);
++}
++
++static bool qca808x_led_hw_control_status(struct phy_device *phydev, u8 index)
++{
++ u16 reg;
++ int val;
++
++ if (index > 2)
++ return false;
++
++ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++ val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
++
++ return !(val & QCA808X_LED_FORCE_EN);
++}
++
++static int qca808x_led_hw_control_get(struct phy_device *phydev, u8 index,
++ unsigned long *rules)
++{
++ u16 reg;
++ int val;
++
++ if (index > 2)
++ return -EINVAL;
++
++ /* Check if we have hw control enabled */
++ if (qca808x_led_hw_control_status(phydev, index))
++ return -EINVAL;
++
++ reg = QCA808X_MMD7_LED_CTRL(index);
++
++ val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
++ if (val & QCA808X_LED_TX_BLINK)
++ set_bit(TRIGGER_NETDEV_TX, rules);
++ if (val & QCA808X_LED_RX_BLINK)
++ set_bit(TRIGGER_NETDEV_RX, rules);
++ if (val & QCA808X_LED_SPEED10_ON)
++ set_bit(TRIGGER_NETDEV_LINK_10, rules);
++ if (val & QCA808X_LED_SPEED100_ON)
++ set_bit(TRIGGER_NETDEV_LINK_100, rules);
++ if (val & QCA808X_LED_SPEED1000_ON)
++ set_bit(TRIGGER_NETDEV_LINK_1000, rules);
++ if (val & QCA808X_LED_SPEED2500_ON)
++ set_bit(TRIGGER_NETDEV_LINK_2500, rules);
++ if (val & QCA808X_LED_HALF_DUPLEX_ON)
++ set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
++ if (val & QCA808X_LED_FULL_DUPLEX_ON)
++ set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
++
++ return 0;
++}
++
++static int qca808x_led_hw_control_reset(struct phy_device *phydev, u8 index)
++{
++ u16 reg;
++
++ if (index > 2)
++ return -EINVAL;
++
++ reg = QCA808X_MMD7_LED_CTRL(index);
++
++ return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
++ QCA808X_LED_PATTERN_MASK);
++}
++
++static int qca808x_led_brightness_set(struct phy_device *phydev,
++ u8 index, enum led_brightness value)
++{
++ u16 reg;
++ int ret;
++
++ if (index > 2)
++ return -EINVAL;
++
++ if (!value) {
++ ret = qca808x_led_hw_control_reset(phydev, index);
++ if (ret)
++ return ret;
++ }
++
++ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++ return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++ QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
++ QCA808X_LED_FORCE_EN | value ? QCA808X_LED_FORCE_ON :
++ QCA808X_LED_FORCE_OFF);
++}
++
++static int qca808x_led_blink_set(struct phy_device *phydev, u8 index,
++ unsigned long *delay_on,
++ unsigned long *delay_off)
++{
++ int ret;
++ u16 reg;
++
++ if (index > 2)
++ return -EINVAL;
++
++ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++ /* Set blink to 50% off, 50% on at 4Hz by default */
++ ret = phy_modify_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_LED_GLOBAL,
++ QCA808X_LED_BLINK_FREQ_MASK | QCA808X_LED_BLINK_DUTY_MASK,
++ QCA808X_LED_BLINK_FREQ_4HZ | QCA808X_LED_BLINK_DUTY_50_50);
++ if (ret)
++ return ret;
++
++ /* We use BLINK_1 for normal blinking */
++ ret = phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++ QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
++ QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_BLINK_1);
++ if (ret)
++ return ret;
++
++ /* We set blink to 4Hz, aka 250ms */
++ *delay_on = 250 / 2;
++ *delay_off = 250 / 2;
++
++ return 0;
++}
++
++static int qca808x_led_polarity_set(struct phy_device *phydev, int index,
++ unsigned long modes)
++{
++ struct qca808x_priv *priv = phydev->priv;
++ bool active_low = false;
++ u32 mode;
++
++ for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
++ switch (mode) {
++ case PHY_LED_ACTIVE_LOW:
++ active_low = true;
++ break;
++ default:
++ return -EINVAL;
++ }
++ }
++
++ /* PHY polarity is global and can't be set per LED.
++ * To detect this, check if last requested polarity mode
++ * match the new one.
++ */
++ if (priv->led_polarity_mode >= 0 &&
++ priv->led_polarity_mode != active_low) {
++ phydev_err(phydev, "PHY polarity is global. Mismatched polarity on different LED\n");
++ return -EINVAL;
++ }
++
++ /* Save the last PHY polarity mode */
++ priv->led_polarity_mode = active_low;
++
++ return phy_modify_mmd(phydev, MDIO_MMD_AN,
++ QCA808X_MMD7_LED_POLARITY_CTRL,
++ QCA808X_LED_ACTIVE_HIGH,
++ active_low ? 0 : QCA808X_LED_ACTIVE_HIGH);
++}
++
++static struct phy_driver qca808x_driver[] = {
++{
++ /* Qualcomm QCA8081 */
++ PHY_ID_MATCH_EXACT(QCA8081_PHY_ID),
++ .name = "Qualcomm QCA8081",
++ .flags = PHY_POLL_CABLE_TEST,
++ .probe = qca808x_probe,
++ .config_intr = at803x_config_intr,
++ .handle_interrupt = at803x_handle_interrupt,
++ .get_tunable = at803x_get_tunable,
++ .set_tunable = at803x_set_tunable,
++ .set_wol = at803x_set_wol,
++ .get_wol = at803x_get_wol,
++ .get_features = qca808x_get_features,
++ .config_aneg = qca808x_config_aneg,
++ .suspend = genphy_suspend,
++ .resume = genphy_resume,
++ .read_status = qca808x_read_status,
++ .config_init = qca808x_config_init,
++ .soft_reset = qca808x_soft_reset,
++ .cable_test_start = qca808x_cable_test_start,
++ .cable_test_get_status = qca808x_cable_test_get_status,
++ .link_change_notify = qca808x_link_change_notify,
++ .led_brightness_set = qca808x_led_brightness_set,
++ .led_blink_set = qca808x_led_blink_set,
++ .led_hw_is_supported = qca808x_led_hw_is_supported,
++ .led_hw_control_set = qca808x_led_hw_control_set,
++ .led_hw_control_get = qca808x_led_hw_control_get,
++ .led_polarity_set = qca808x_led_polarity_set,
++}, };
++
++module_phy_driver(qca808x_driver);
++
++static struct mdio_device_id __maybe_unused qca808x_tbl[] = {
++ { PHY_ID_MATCH_EXACT(QCA8081_PHY_ID) },
++ { }
++};
++
++MODULE_DEVICE_TABLE(mdio, qca808x_tbl);
diff --git a/target/linux/generic/backport-6.6/714-net-pcs-add-driver-for-MediaTek-SGMII-PCS.patch b/target/linux/generic/backport-6.6/714-net-pcs-add-driver-for-MediaTek-SGMII-PCS.patch
new file mode 100644
index 0000000000..d56a142451
--- /dev/null
+++ b/target/linux/generic/backport-6.6/714-net-pcs-add-driver-for-MediaTek-SGMII-PCS.patch
@@ -0,0 +1,394 @@
+From 4765a9722e09765866e131ec31f7b9cf4c1f4854 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sun, 19 Mar 2023 12:57:50 +0000
+Subject: [PATCH] net: pcs: add driver for MediaTek SGMII PCS
+
+The SGMII core found in several MediaTek SoCs is identical to what can
+also be found in MediaTek's MT7531 Ethernet switch IC.
+As this has not always been clear, both drivers developed different
+implementations to deal with the PCS.
+Recently Alexander Couzens pointed out this fact which lead to the
+development of this shared driver.
+
+Add a dedicated driver, mostly by copying the code now found in the
+Ethernet driver. The now redundant code will be removed by a follow-up
+commit.
+
+Suggested-by: Alexander Couzens <lynxis@fe80.eu>
+Suggested-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Tested-by: Frank Wunderlich <frank-w@public-files.de>
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ MAINTAINERS | 8 +
+ drivers/net/pcs/Kconfig | 7 +
+ drivers/net/pcs/Makefile | 1 +
+ drivers/net/pcs/pcs-mtk-lynxi.c | 305 ++++++++++++++++++++++++++++++
+ include/linux/pcs/pcs-mtk-lynxi.h | 13 ++
+ 5 files changed, 334 insertions(+)
+ create mode 100644 drivers/net/pcs/pcs-mtk-lynxi.c
+ create mode 100644 include/linux/pcs/pcs-mtk-lynxi.h
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -12928,6 +12928,14 @@ L: netdev@vger.kernel.org
+ S: Maintained
+ F: drivers/net/ethernet/mediatek/
+
++MEDIATEK ETHERNET PCS DRIVER
++M: Alexander Couzens <lynxis@fe80.eu>
++M: Daniel Golle <daniel@makrotopia.org>
++L: netdev@vger.kernel.org
++S: Maintained
++F: drivers/net/pcs/pcs-mtk-lynxi.c
++F: include/linux/pcs/pcs-mtk-lynxi.h
++
+ MEDIATEK I2C CONTROLLER DRIVER
+ M: Qii Wang <qii.wang@mediatek.com>
+ L: linux-i2c@vger.kernel.org
+--- a/drivers/net/pcs/Kconfig
++++ b/drivers/net/pcs/Kconfig
+@@ -32,4 +32,11 @@ config PCS_ALTERA_TSE
+ This module provides helper functions for the Altera Triple Speed
+ Ethernet SGMII PCS, that can be found on the Intel Socfpga family.
+
++config PCS_MTK_LYNXI
++ tristate
++ select REGMAP
++ help
++ This module provides helpers to phylink for managing the LynxI PCS
++ which is part of MediaTek's SoC and Ethernet switch ICs.
++
+ endmenu
+--- a/drivers/net/pcs/Makefile
++++ b/drivers/net/pcs/Makefile
+@@ -7,3 +7,4 @@ obj-$(CONFIG_PCS_XPCS) += pcs_xpcs.o
+ obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o
+ obj-$(CONFIG_PCS_RZN1_MIIC) += pcs-rzn1-miic.o
+ obj-$(CONFIG_PCS_ALTERA_TSE) += pcs-altera-tse.o
++obj-$(CONFIG_PCS_MTK_LYNXI) += pcs-mtk-lynxi.o
+--- /dev/null
++++ b/drivers/net/pcs/pcs-mtk-lynxi.c
+@@ -0,0 +1,305 @@
++// SPDX-License-Identifier: GPL-2.0
++// Copyright (c) 2018-2019 MediaTek Inc.
++/* A library for MediaTek SGMII circuit
++ *
++ * Author: Sean Wang <sean.wang@mediatek.com>
++ * Author: Alexander Couzens <lynxis@fe80.eu>
++ * Author: Daniel Golle <daniel@makrotopia.org>
++ *
++ */
++
++#include <linux/mdio.h>
++#include <linux/of.h>
++#include <linux/pcs/pcs-mtk-lynxi.h>
++#include <linux/phylink.h>
++#include <linux/regmap.h>
++
++/* SGMII subsystem config registers */
++/* BMCR (low 16) BMSR (high 16) */
++#define SGMSYS_PCS_CONTROL_1 0x0
++#define SGMII_BMCR GENMASK(15, 0)
++#define SGMII_BMSR GENMASK(31, 16)
++
++#define SGMSYS_PCS_DEVICE_ID 0x4
++#define SGMII_LYNXI_DEV_ID 0x4d544950
++
++#define SGMSYS_PCS_ADVERTISE 0x8
++#define SGMII_ADVERTISE GENMASK(15, 0)
++#define SGMII_LPA GENMASK(31, 16)
++
++#define SGMSYS_PCS_SCRATCH 0x14
++#define SGMII_DEV_VERSION GENMASK(31, 16)
++
++/* Register to programmable link timer, the unit in 2 * 8ns */
++#define SGMSYS_PCS_LINK_TIMER 0x18
++#define SGMII_LINK_TIMER_MASK GENMASK(19, 0)
++#define SGMII_LINK_TIMER_VAL(ns) FIELD_PREP(SGMII_LINK_TIMER_MASK, \
++ ((ns) / 2 / 8))
++
++/* Register to control remote fault */
++#define SGMSYS_SGMII_MODE 0x20
++#define SGMII_IF_MODE_SGMII BIT(0)
++#define SGMII_SPEED_DUPLEX_AN BIT(1)
++#define SGMII_SPEED_MASK GENMASK(3, 2)
++#define SGMII_SPEED_10 FIELD_PREP(SGMII_SPEED_MASK, 0)
++#define SGMII_SPEED_100 FIELD_PREP(SGMII_SPEED_MASK, 1)
++#define SGMII_SPEED_1000 FIELD_PREP(SGMII_SPEED_MASK, 2)
++#define SGMII_DUPLEX_HALF BIT(4)
++#define SGMII_REMOTE_FAULT_DIS BIT(8)
++
++/* Register to reset SGMII design */
++#define SGMSYS_RESERVED_0 0x34
++#define SGMII_SW_RESET BIT(0)
++
++/* Register to set SGMII speed, ANA RG_ Control Signals III */
++#define SGMII_PHY_SPEED_MASK GENMASK(3, 2)
++#define SGMII_PHY_SPEED_1_25G FIELD_PREP(SGMII_PHY_SPEED_MASK, 0)
++#define SGMII_PHY_SPEED_3_125G FIELD_PREP(SGMII_PHY_SPEED_MASK, 1)
++
++/* Register to power up QPHY */
++#define SGMSYS_QPHY_PWR_STATE_CTRL 0xe8
++#define SGMII_PHYA_PWD BIT(4)
++
++/* Register to QPHY wrapper control */
++#define SGMSYS_QPHY_WRAP_CTRL 0xec
++#define SGMII_PN_SWAP_MASK GENMASK(1, 0)
++#define SGMII_PN_SWAP_TX_RX (BIT(0) | BIT(1))
++
++/* struct mtk_pcs_lynxi - This structure holds each sgmii regmap andassociated
++ * data
++ * @regmap: The register map pointing at the range used to setup
++ * SGMII modes
++ * @dev: Pointer to device owning the PCS
++ * @ana_rgc3: The offset of register ANA_RGC3 relative to regmap
++ * @interface: Currently configured interface mode
++ * @pcs: Phylink PCS structure
++ * @flags: Flags indicating hardware properties
++ */
++struct mtk_pcs_lynxi {
++ struct regmap *regmap;
++ u32 ana_rgc3;
++ phy_interface_t interface;
++ struct phylink_pcs pcs;
++ u32 flags;
++};
++
++static struct mtk_pcs_lynxi *pcs_to_mtk_pcs_lynxi(struct phylink_pcs *pcs)
++{
++ return container_of(pcs, struct mtk_pcs_lynxi, pcs);
++}
++
++static void mtk_pcs_lynxi_get_state(struct phylink_pcs *pcs,
++ struct phylink_link_state *state)
++{
++ struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
++ unsigned int bm, adv;
++
++ /* Read the BMSR and LPA */
++ regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &bm);
++ regmap_read(mpcs->regmap, SGMSYS_PCS_ADVERTISE, &adv);
++
++ phylink_mii_c22_pcs_decode_state(state, FIELD_GET(SGMII_BMSR, bm),
++ FIELD_GET(SGMII_LPA, adv));
++}
++
++static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int mode,
++ phy_interface_t interface,
++ const unsigned long *advertising,
++ bool permit_pause_to_mac)
++{
++ struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
++ bool mode_changed = false, changed, use_an;
++ unsigned int rgc3, sgm_mode, bmcr;
++ int advertise, link_timer;
++
++ advertise = phylink_mii_c22_pcs_encode_advertisement(interface,
++ advertising);
++ if (advertise < 0)
++ return advertise;
++
++ /* Clearing IF_MODE_BIT0 switches the PCS to BASE-X mode, and
++ * we assume that fixes it's speed at bitrate = line rate (in
++ * other words, 1000Mbps or 2500Mbps).
++ */
++ if (interface == PHY_INTERFACE_MODE_SGMII) {
++ sgm_mode = SGMII_IF_MODE_SGMII;
++ if (phylink_autoneg_inband(mode)) {
++ sgm_mode |= SGMII_REMOTE_FAULT_DIS |
++ SGMII_SPEED_DUPLEX_AN;
++ use_an = true;
++ } else {
++ use_an = false;
++ }
++ } else if (phylink_autoneg_inband(mode)) {
++ /* 1000base-X or 2500base-X autoneg */
++ sgm_mode = SGMII_REMOTE_FAULT_DIS;
++ use_an = linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++ advertising);
++ } else {
++ /* 1000base-X or 2500base-X without autoneg */
++ sgm_mode = 0;
++ use_an = false;
++ }
++
++ if (use_an)
++ bmcr = BMCR_ANENABLE;
++ else
++ bmcr = 0;
++
++ if (mpcs->interface != interface) {
++ link_timer = phylink_get_link_timer_ns(interface);
++ if (link_timer < 0)
++ return link_timer;
++
++ /* PHYA power down */
++ regmap_set_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL,
++ SGMII_PHYA_PWD);
++
++ /* Reset SGMII PCS state */
++ regmap_set_bits(mpcs->regmap, SGMSYS_RESERVED_0,
++ SGMII_SW_RESET);
++
++ if (mpcs->flags & MTK_SGMII_FLAG_PN_SWAP)
++ regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_WRAP_CTRL,
++ SGMII_PN_SWAP_MASK,
++ SGMII_PN_SWAP_TX_RX);
++
++ if (interface == PHY_INTERFACE_MODE_2500BASEX)
++ rgc3 = SGMII_PHY_SPEED_3_125G;
++ else
++ rgc3 = SGMII_PHY_SPEED_1_25G;
++
++ /* Configure the underlying interface speed */
++ regmap_update_bits(mpcs->regmap, mpcs->ana_rgc3,
++ SGMII_PHY_SPEED_MASK, rgc3);
++
++ /* Setup the link timer */
++ regmap_write(mpcs->regmap, SGMSYS_PCS_LINK_TIMER,
++ SGMII_LINK_TIMER_VAL(link_timer));
++
++ mpcs->interface = interface;
++ mode_changed = true;
++ }
++
++ /* Update the advertisement, noting whether it has changed */
++ regmap_update_bits_check(mpcs->regmap, SGMSYS_PCS_ADVERTISE,
++ SGMII_ADVERTISE, advertise, &changed);
++
++ /* Update the sgmsys mode register */
++ regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
++ SGMII_REMOTE_FAULT_DIS | SGMII_SPEED_DUPLEX_AN |
++ SGMII_IF_MODE_SGMII, sgm_mode);
++
++ /* Update the BMCR */
++ regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1,
++ BMCR_ANENABLE, bmcr);
++
++ /* Release PHYA power down state
++ * Only removing bit SGMII_PHYA_PWD isn't enough.
++ * There are cases when the SGMII_PHYA_PWD register contains 0x9 which
++ * prevents SGMII from working. The SGMII still shows link but no traffic
++ * can flow. Writing 0x0 to the PHYA_PWD register fix the issue. 0x0 was
++ * taken from a good working state of the SGMII interface.
++ * Unknown how much the QPHY needs but it is racy without a sleep.
++ * Tested on mt7622 & mt7986.
++ */
++ usleep_range(50, 100);
++ regmap_write(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, 0);
++
++ return changed || mode_changed;
++}
++
++static void mtk_pcs_lynxi_restart_an(struct phylink_pcs *pcs)
++{
++ struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
++
++ regmap_set_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1, BMCR_ANRESTART);
++}
++
++static void mtk_pcs_lynxi_link_up(struct phylink_pcs *pcs, unsigned int mode,
++ phy_interface_t interface, int speed,
++ int duplex)
++{
++ struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
++ unsigned int sgm_mode;
++
++ if (!phylink_autoneg_inband(mode)) {
++ /* Force the speed and duplex setting */
++ if (speed == SPEED_10)
++ sgm_mode = SGMII_SPEED_10;
++ else if (speed == SPEED_100)
++ sgm_mode = SGMII_SPEED_100;
++ else
++ sgm_mode = SGMII_SPEED_1000;
++
++ if (duplex != DUPLEX_FULL)
++ sgm_mode |= SGMII_DUPLEX_HALF;
++
++ regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
++ SGMII_DUPLEX_HALF | SGMII_SPEED_MASK,
++ sgm_mode);
++ }
++}
++
++static const struct phylink_pcs_ops mtk_pcs_lynxi_ops = {
++ .pcs_get_state = mtk_pcs_lynxi_get_state,
++ .pcs_config = mtk_pcs_lynxi_config,
++ .pcs_an_restart = mtk_pcs_lynxi_restart_an,
++ .pcs_link_up = mtk_pcs_lynxi_link_up,
++};
++
++struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
++ struct regmap *regmap, u32 ana_rgc3,
++ u32 flags)
++{
++ struct mtk_pcs_lynxi *mpcs;
++ u32 id, ver;
++ int ret;
++
++ ret = regmap_read(regmap, SGMSYS_PCS_DEVICE_ID, &id);
++ if (ret < 0)
++ return NULL;
++
++ if (id != SGMII_LYNXI_DEV_ID) {
++ dev_err(dev, "unknown PCS device id %08x\n", id);
++ return NULL;
++ }
++
++ ret = regmap_read(regmap, SGMSYS_PCS_SCRATCH, &ver);
++ if (ret < 0)
++ return NULL;
++
++ ver = FIELD_GET(SGMII_DEV_VERSION, ver);
++ if (ver != 0x1) {
++ dev_err(dev, "unknown PCS device version %04x\n", ver);
++ return NULL;
++ }
++
++ dev_dbg(dev, "MediaTek LynxI SGMII PCS (id 0x%08x, ver 0x%04x)\n", id,
++ ver);
++
++ mpcs = kzalloc(sizeof(*mpcs), GFP_KERNEL);
++ if (!mpcs)
++ return NULL;
++
++ mpcs->ana_rgc3 = ana_rgc3;
++ mpcs->regmap = regmap;
++ mpcs->flags = flags;
++ mpcs->pcs.ops = &mtk_pcs_lynxi_ops;
++ mpcs->pcs.poll = true;
++ mpcs->interface = PHY_INTERFACE_MODE_NA;
++
++ return &mpcs->pcs;
++}
++EXPORT_SYMBOL(mtk_pcs_lynxi_create);
++
++void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs)
++{
++ if (!pcs)
++ return;
++
++ kfree(pcs_to_mtk_pcs_lynxi(pcs));
++}
++EXPORT_SYMBOL(mtk_pcs_lynxi_destroy);
++
++MODULE_LICENSE("GPL");
+--- /dev/null
++++ b/include/linux/pcs/pcs-mtk-lynxi.h
+@@ -0,0 +1,13 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++#ifndef __LINUX_PCS_MTK_LYNXI_H
++#define __LINUX_PCS_MTK_LYNXI_H
++
++#include <linux/phylink.h>
++#include <linux/regmap.h>
++
++#define MTK_SGMII_FLAG_PN_SWAP BIT(0)
++struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
++ struct regmap *regmap,
++ u32 ana_rgc3, u32 flags);
++void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs);
++#endif
diff --git a/target/linux/generic/backport-6.6/714-v6.8-01-net-phy-make-addr-type-u8-in-phy_package_shared-stru.patch b/target/linux/generic/backport-6.6/714-v6.8-01-net-phy-make-addr-type-u8-in-phy_package_shared-stru.patch
new file mode 100644
index 0000000000..6decc3430b
--- /dev/null
+++ b/target/linux/generic/backport-6.6/714-v6.8-01-net-phy-make-addr-type-u8-in-phy_package_shared-stru.patch
@@ -0,0 +1,28 @@
+From ebb30ccbbdbd6fae5177b676da4f4ac92bb4f635 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Fri, 15 Dec 2023 14:15:31 +0100
+Subject: [PATCH 1/4] net: phy: make addr type u8 in phy_package_shared struct
+
+Switch addr type in phy_package_shared struct to u8.
+
+The value is already checked to be non negative and to be less than
+PHY_MAX_ADDR, hence u8 is better suited than using int.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/linux/phy.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -330,7 +330,7 @@ struct mdio_bus_stats {
+ * phy_package_leave().
+ */
+ struct phy_package_shared {
+- int addr;
++ u8 addr;
+ refcount_t refcnt;
+ unsigned long flags;
+ size_t priv_size;
diff --git a/target/linux/generic/backport-6.6/714-v6.8-02-net-phy-extend-PHY-package-API-to-support-multiple-g.patch b/target/linux/generic/backport-6.6/714-v6.8-02-net-phy-extend-PHY-package-API-to-support-multiple-g.patch
new file mode 100644
index 0000000000..0cf01df3d3
--- /dev/null
+++ b/target/linux/generic/backport-6.6/714-v6.8-02-net-phy-extend-PHY-package-API-to-support-multiple-g.patch
@@ -0,0 +1,341 @@
+From 9eea577eb1155fe4a183bc5e7bf269b0b2e7a6ba Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Fri, 15 Dec 2023 14:15:32 +0100
+Subject: [PATCH 2/4] net: phy: extend PHY package API to support multiple
+ global address
+
+Current API for PHY package are limited to single address to configure
+global settings for the PHY package.
+
+It was found that some PHY package (for example the qca807x, a PHY
+package that is shipped with a bundle of 5 PHY) requires multiple PHY
+address to configure global settings. An example scenario is a PHY that
+have a dedicated PHY for PSGMII/serdes calibrarion and have a specific
+PHY in the package where the global PHY mode is set and affects every
+other PHY in the package.
+
+Change the API in the following way:
+- Change phy_package_join() to take the base addr of the PHY package
+ instead of the global PHY addr.
+- Make __/phy_package_write/read() require an additional arg that
+ select what global PHY address to use by passing the offset from the
+ base addr passed on phy_package_join().
+
+Each user of this API is updated to follow this new implementation
+following a pattern where an enum is defined to declare the offset of the
+addr.
+
+We also drop the check if shared is defined as any user of the
+phy_package_read/write is expected to use phy_package_join first. Misuse
+of this will correctly trigger a kernel panic for NULL pointer
+exception.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/bcm54140.c | 16 ++++++--
+ drivers/net/phy/mscc/mscc.h | 5 +++
+ drivers/net/phy/mscc/mscc_main.c | 4 +-
+ drivers/net/phy/phy_device.c | 35 +++++++++--------
+ include/linux/phy.h | 64 +++++++++++++++++++++-----------
+ 5 files changed, 80 insertions(+), 44 deletions(-)
+
+--- a/drivers/net/phy/bcm54140.c
++++ b/drivers/net/phy/bcm54140.c
+@@ -128,6 +128,10 @@
+ #define BCM54140_DEFAULT_DOWNSHIFT 5
+ #define BCM54140_MAX_DOWNSHIFT 9
+
++enum bcm54140_global_phy {
++ BCM54140_BASE_ADDR = 0,
++};
++
+ struct bcm54140_priv {
+ int port;
+ int base_addr;
+@@ -429,11 +433,13 @@ static int bcm54140_base_read_rdb(struct
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+- ret = __phy_package_write(phydev, MII_BCM54XX_RDB_ADDR, rdb);
++ ret = __phy_package_write(phydev, BCM54140_BASE_ADDR,
++ MII_BCM54XX_RDB_ADDR, rdb);
+ if (ret < 0)
+ goto out;
+
+- ret = __phy_package_read(phydev, MII_BCM54XX_RDB_DATA);
++ ret = __phy_package_read(phydev, BCM54140_BASE_ADDR,
++ MII_BCM54XX_RDB_DATA);
+
+ out:
+ phy_unlock_mdio_bus(phydev);
+@@ -446,11 +452,13 @@ static int bcm54140_base_write_rdb(struc
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+- ret = __phy_package_write(phydev, MII_BCM54XX_RDB_ADDR, rdb);
++ ret = __phy_package_write(phydev, BCM54140_BASE_ADDR,
++ MII_BCM54XX_RDB_ADDR, rdb);
+ if (ret < 0)
+ goto out;
+
+- ret = __phy_package_write(phydev, MII_BCM54XX_RDB_DATA, val);
++ ret = __phy_package_write(phydev, BCM54140_BASE_ADDR,
++ MII_BCM54XX_RDB_DATA, val);
+
+ out:
+ phy_unlock_mdio_bus(phydev);
+--- a/drivers/net/phy/mscc/mscc.h
++++ b/drivers/net/phy/mscc/mscc.h
+@@ -414,6 +414,11 @@ struct vsc8531_private {
+ * gpio_lock: used for PHC operations. Common for all PHYs as the load/save GPIO
+ * is shared.
+ */
++
++enum vsc85xx_global_phy {
++ VSC88XX_BASE_ADDR = 0,
++};
++
+ struct vsc85xx_shared_private {
+ struct mutex gpio_lock;
+ };
+--- a/drivers/net/phy/mscc/mscc_main.c
++++ b/drivers/net/phy/mscc/mscc_main.c
+@@ -700,7 +700,7 @@ int phy_base_write(struct phy_device *ph
+ dump_stack();
+ }
+
+- return __phy_package_write(phydev, regnum, val);
++ return __phy_package_write(phydev, VSC88XX_BASE_ADDR, regnum, val);
+ }
+
+ /* phydev->bus->mdio_lock should be locked when using this function */
+@@ -711,7 +711,7 @@ int phy_base_read(struct phy_device *phy
+ dump_stack();
+ }
+
+- return __phy_package_read(phydev, regnum);
++ return __phy_package_read(phydev, VSC88XX_BASE_ADDR, regnum);
+ }
+
+ u32 vsc85xx_csr_read(struct phy_device *phydev,
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -1602,20 +1602,22 @@ EXPORT_SYMBOL_GPL(phy_driver_is_genphy_1
+ /**
+ * phy_package_join - join a common PHY group
+ * @phydev: target phy_device struct
+- * @addr: cookie and PHY address for global register access
++ * @base_addr: cookie and base PHY address of PHY package for offset
++ * calculation of global register access
+ * @priv_size: if non-zero allocate this amount of bytes for private data
+ *
+ * This joins a PHY group and provides a shared storage for all phydevs in
+ * this group. This is intended to be used for packages which contain
+ * more than one PHY, for example a quad PHY transceiver.
+ *
+- * The addr parameter serves as a cookie which has to have the same value
+- * for all members of one group and as a PHY address to access generic
+- * registers of a PHY package. Usually, one of the PHY addresses of the
+- * different PHYs in the package provides access to these global registers.
++ * The base_addr parameter serves as cookie which has to have the same values
++ * for all members of one group and as the base PHY address of the PHY package
++ * for offset calculation to access generic registers of a PHY package.
++ * Usually, one of the PHY addresses of the different PHYs in the package
++ * provides access to these global registers.
+ * The address which is given here, will be used in the phy_package_read()
+- * and phy_package_write() convenience functions. If your PHY doesn't have
+- * global registers you can just pick any of the PHY addresses.
++ * and phy_package_write() convenience functions as base and added to the
++ * passed offset in those functions.
+ *
+ * This will set the shared pointer of the phydev to the shared storage.
+ * If this is the first call for a this cookie the shared storage will be
+@@ -1625,17 +1627,17 @@ EXPORT_SYMBOL_GPL(phy_driver_is_genphy_1
+ * Returns < 1 on error, 0 on success. Esp. calling phy_package_join()
+ * with the same cookie but a different priv_size is an error.
+ */
+-int phy_package_join(struct phy_device *phydev, int addr, size_t priv_size)
++int phy_package_join(struct phy_device *phydev, int base_addr, size_t priv_size)
+ {
+ struct mii_bus *bus = phydev->mdio.bus;
+ struct phy_package_shared *shared;
+ int ret;
+
+- if (addr < 0 || addr >= PHY_MAX_ADDR)
++ if (base_addr < 0 || base_addr >= PHY_MAX_ADDR)
+ return -EINVAL;
+
+ mutex_lock(&bus->shared_lock);
+- shared = bus->shared[addr];
++ shared = bus->shared[base_addr];
+ if (!shared) {
+ ret = -ENOMEM;
+ shared = kzalloc(sizeof(*shared), GFP_KERNEL);
+@@ -1647,9 +1649,9 @@ int phy_package_join(struct phy_device *
+ goto err_free;
+ shared->priv_size = priv_size;
+ }
+- shared->addr = addr;
++ shared->base_addr = base_addr;
+ refcount_set(&shared->refcnt, 1);
+- bus->shared[addr] = shared;
++ bus->shared[base_addr] = shared;
+ } else {
+ ret = -EINVAL;
+ if (priv_size && priv_size != shared->priv_size)
+@@ -1687,7 +1689,7 @@ void phy_package_leave(struct phy_device
+ return;
+
+ if (refcount_dec_and_mutex_lock(&shared->refcnt, &bus->shared_lock)) {
+- bus->shared[shared->addr] = NULL;
++ bus->shared[shared->base_addr] = NULL;
+ mutex_unlock(&bus->shared_lock);
+ kfree(shared->priv);
+ kfree(shared);
+@@ -1706,7 +1708,8 @@ static void devm_phy_package_leave(struc
+ * devm_phy_package_join - resource managed phy_package_join()
+ * @dev: device that is registering this PHY package
+ * @phydev: target phy_device struct
+- * @addr: cookie and PHY address for global register access
++ * @base_addr: cookie and base PHY address of PHY package for offset
++ * calculation of global register access
+ * @priv_size: if non-zero allocate this amount of bytes for private data
+ *
+ * Managed phy_package_join(). Shared storage fetched by this function,
+@@ -1714,7 +1717,7 @@ static void devm_phy_package_leave(struc
+ * phy_package_join() for more information.
+ */
+ int devm_phy_package_join(struct device *dev, struct phy_device *phydev,
+- int addr, size_t priv_size)
++ int base_addr, size_t priv_size)
+ {
+ struct phy_device **ptr;
+ int ret;
+@@ -1724,7 +1727,7 @@ int devm_phy_package_join(struct device
+ if (!ptr)
+ return -ENOMEM;
+
+- ret = phy_package_join(phydev, addr, priv_size);
++ ret = phy_package_join(phydev, base_addr, priv_size);
+
+ if (!ret) {
+ *ptr = phydev;
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -319,7 +319,8 @@ struct mdio_bus_stats {
+
+ /**
+ * struct phy_package_shared - Shared information in PHY packages
+- * @addr: Common PHY address used to combine PHYs in one package
++ * @base_addr: Base PHY address of PHY package used to combine PHYs
++ * in one package and for offset calculation of phy_package_read/write
+ * @refcnt: Number of PHYs connected to this shared data
+ * @flags: Initialization of PHY package
+ * @priv_size: Size of the shared private data @priv
+@@ -330,7 +331,7 @@ struct mdio_bus_stats {
+ * phy_package_leave().
+ */
+ struct phy_package_shared {
+- u8 addr;
++ u8 base_addr;
+ refcount_t refcnt;
+ unsigned long flags;
+ size_t priv_size;
+@@ -1763,10 +1764,10 @@ int phy_ethtool_get_link_ksettings(struc
+ int phy_ethtool_set_link_ksettings(struct net_device *ndev,
+ const struct ethtool_link_ksettings *cmd);
+ int phy_ethtool_nway_reset(struct net_device *ndev);
+-int phy_package_join(struct phy_device *phydev, int addr, size_t priv_size);
++int phy_package_join(struct phy_device *phydev, int base_addr, size_t priv_size);
+ void phy_package_leave(struct phy_device *phydev);
+ int devm_phy_package_join(struct device *dev, struct phy_device *phydev,
+- int addr, size_t priv_size);
++ int base_addr, size_t priv_size);
+
+ #if IS_ENABLED(CONFIG_PHYLIB)
+ int __init mdio_bus_init(void);
+@@ -1778,46 +1779,65 @@ int phy_ethtool_get_sset_count(struct ph
+ int phy_ethtool_get_stats(struct phy_device *phydev,
+ struct ethtool_stats *stats, u64 *data);
+
+-static inline int phy_package_read(struct phy_device *phydev, u32 regnum)
++static inline int phy_package_address(struct phy_device *phydev,
++ unsigned int addr_offset)
+ {
+ struct phy_package_shared *shared = phydev->shared;
++ u8 base_addr = shared->base_addr;
+
+- if (!shared)
++ if (addr_offset >= PHY_MAX_ADDR - base_addr)
+ return -EIO;
+
+- return mdiobus_read(phydev->mdio.bus, shared->addr, regnum);
++ /* we know that addr will be in the range 0..31 and thus the
++ * implicit cast to a signed int is not a problem.
++ */
++ return base_addr + addr_offset;
+ }
+
+-static inline int __phy_package_read(struct phy_device *phydev, u32 regnum)
++static inline int phy_package_read(struct phy_device *phydev,
++ unsigned int addr_offset, u32 regnum)
+ {
+- struct phy_package_shared *shared = phydev->shared;
++ int addr = phy_package_address(phydev, addr_offset);
+
+- if (!shared)
+- return -EIO;
++ if (addr < 0)
++ return addr;
++
++ return mdiobus_read(phydev->mdio.bus, addr, regnum);
++}
++
++static inline int __phy_package_read(struct phy_device *phydev,
++ unsigned int addr_offset, u32 regnum)
++{
++ int addr = phy_package_address(phydev, addr_offset);
++
++ if (addr < 0)
++ return addr;
+
+- return __mdiobus_read(phydev->mdio.bus, shared->addr, regnum);
++ return __mdiobus_read(phydev->mdio.bus, addr, regnum);
+ }
+
+ static inline int phy_package_write(struct phy_device *phydev,
+- u32 regnum, u16 val)
++ unsigned int addr_offset, u32 regnum,
++ u16 val)
+ {
+- struct phy_package_shared *shared = phydev->shared;
++ int addr = phy_package_address(phydev, addr_offset);
+
+- if (!shared)
+- return -EIO;
++ if (addr < 0)
++ return addr;
+
+- return mdiobus_write(phydev->mdio.bus, shared->addr, regnum, val);
++ return mdiobus_write(phydev->mdio.bus, addr, regnum, val);
+ }
+
+ static inline int __phy_package_write(struct phy_device *phydev,
+- u32 regnum, u16 val)
++ unsigned int addr_offset, u32 regnum,
++ u16 val)
+ {
+- struct phy_package_shared *shared = phydev->shared;
++ int addr = phy_package_address(phydev, addr_offset);
+
+- if (!shared)
+- return -EIO;
++ if (addr < 0)
++ return addr;
+
+- return __mdiobus_write(phydev->mdio.bus, shared->addr, regnum, val);
++ return __mdiobus_write(phydev->mdio.bus, addr, regnum, val);
+ }
+
+ static inline bool __phy_package_set_once(struct phy_device *phydev,
diff --git a/target/linux/generic/backport-6.6/714-v6.8-03-net-phy-restructure-__phy_write-read_mmd-to-helper-a.patch b/target/linux/generic/backport-6.6/714-v6.8-03-net-phy-restructure-__phy_write-read_mmd-to-helper-a.patch
new file mode 100644
index 0000000000..4a17d46453
--- /dev/null
+++ b/target/linux/generic/backport-6.6/714-v6.8-03-net-phy-restructure-__phy_write-read_mmd-to-helper-a.patch
@@ -0,0 +1,116 @@
+From 028672bd1d73cf65249a420c1de75e8d2acd2f6a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Fri, 15 Dec 2023 14:15:33 +0100
+Subject: [PATCH 3/4] net: phy: restructure __phy_write/read_mmd to helper and
+ phydev user
+
+Restructure phy_write_mmd and phy_read_mmd to implement generic helper
+for direct mdiobus access for mmd and use these helper for phydev user.
+
+This is needed in preparation of PHY package API that requires generic
+access to the mdiobus and are deatched from phydev struct but instead
+access them based on PHY package base_addr and offsets.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/phy-core.c | 64 ++++++++++++++++++--------------------
+ 1 file changed, 30 insertions(+), 34 deletions(-)
+
+--- a/drivers/net/phy/phy-core.c
++++ b/drivers/net/phy/phy-core.c
+@@ -528,6 +528,28 @@ static void mmd_phy_indirect(struct mii_
+ devad | MII_MMD_CTRL_NOINCR);
+ }
+
++static int mmd_phy_read(struct mii_bus *bus, int phy_addr, bool is_c45,
++ int devad, u32 regnum)
++{
++ if (is_c45)
++ return __mdiobus_c45_read(bus, phy_addr, devad, regnum);
++
++ mmd_phy_indirect(bus, phy_addr, devad, regnum);
++ /* Read the content of the MMD's selected register */
++ return __mdiobus_read(bus, phy_addr, MII_MMD_DATA);
++}
++
++static int mmd_phy_write(struct mii_bus *bus, int phy_addr, bool is_c45,
++ int devad, u32 regnum, u16 val)
++{
++ if (is_c45)
++ return __mdiobus_c45_write(bus, phy_addr, devad, regnum, val);
++
++ mmd_phy_indirect(bus, phy_addr, devad, regnum);
++ /* Write the data into MMD's selected register */
++ return __mdiobus_write(bus, phy_addr, MII_MMD_DATA, val);
++}
++
+ /**
+ * __phy_read_mmd - Convenience function for reading a register
+ * from an MMD on a given PHY.
+@@ -539,26 +561,14 @@ static void mmd_phy_indirect(struct mii_
+ */
+ int __phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum)
+ {
+- int val;
+-
+ if (regnum > (u16)~0 || devad > 32)
+ return -EINVAL;
+
+- if (phydev->drv && phydev->drv->read_mmd) {
+- val = phydev->drv->read_mmd(phydev, devad, regnum);
+- } else if (phydev->is_c45) {
+- val = __mdiobus_c45_read(phydev->mdio.bus, phydev->mdio.addr,
+- devad, regnum);
+- } else {
+- struct mii_bus *bus = phydev->mdio.bus;
+- int phy_addr = phydev->mdio.addr;
+-
+- mmd_phy_indirect(bus, phy_addr, devad, regnum);
+-
+- /* Read the content of the MMD's selected register */
+- val = __mdiobus_read(bus, phy_addr, MII_MMD_DATA);
+- }
+- return val;
++ if (phydev->drv && phydev->drv->read_mmd)
++ return phydev->drv->read_mmd(phydev, devad, regnum);
++
++ return mmd_phy_read(phydev->mdio.bus, phydev->mdio.addr,
++ phydev->is_c45, devad, regnum);
+ }
+ EXPORT_SYMBOL(__phy_read_mmd);
+
+@@ -595,28 +605,14 @@ EXPORT_SYMBOL(phy_read_mmd);
+ */
+ int __phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val)
+ {
+- int ret;
+-
+ if (regnum > (u16)~0 || devad > 32)
+ return -EINVAL;
+
+- if (phydev->drv && phydev->drv->write_mmd) {
+- ret = phydev->drv->write_mmd(phydev, devad, regnum, val);
+- } else if (phydev->is_c45) {
+- ret = __mdiobus_c45_write(phydev->mdio.bus, phydev->mdio.addr,
+- devad, regnum, val);
+- } else {
+- struct mii_bus *bus = phydev->mdio.bus;
+- int phy_addr = phydev->mdio.addr;
++ if (phydev->drv && phydev->drv->write_mmd)
++ return phydev->drv->write_mmd(phydev, devad, regnum, val);
+
+- mmd_phy_indirect(bus, phy_addr, devad, regnum);
+-
+- /* Write the data into MMD's selected register */
+- __mdiobus_write(bus, phy_addr, MII_MMD_DATA, val);
+-
+- ret = 0;
+- }
+- return ret;
++ return mmd_phy_write(phydev->mdio.bus, phydev->mdio.addr,
++ phydev->is_c45, devad, regnum, val);
+ }
+ EXPORT_SYMBOL(__phy_write_mmd);
+
diff --git a/target/linux/generic/backport-6.6/714-v6.8-04-net-phy-add-support-for-PHY-package-MMD-read-write.patch b/target/linux/generic/backport-6.6/714-v6.8-04-net-phy-add-support-for-PHY-package-MMD-read-write.patch
new file mode 100644
index 0000000000..a628a37929
--- /dev/null
+++ b/target/linux/generic/backport-6.6/714-v6.8-04-net-phy-add-support-for-PHY-package-MMD-read-write.patch
@@ -0,0 +1,196 @@
+From d63710fc0f1a501fd75a7025e3070a96ffa1645f Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Fri, 15 Dec 2023 14:15:34 +0100
+Subject: [PATCH 4/4] net: phy: add support for PHY package MMD read/write
+
+Some PHY in PHY package may require to read/write MMD regs to correctly
+configure the PHY package.
+
+Add support for these additional required function in both lock and no
+lock variant.
+
+It's assumed that the entire PHY package is either C22 or C45. We use
+C22 or C45 way of writing/reading to mmd regs based on the passed phydev
+whether it's C22 or C45.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/phy-core.c | 140 +++++++++++++++++++++++++++++++++++++
+ include/linux/phy.h | 16 +++++
+ 2 files changed, 156 insertions(+)
+
+--- a/drivers/net/phy/phy-core.c
++++ b/drivers/net/phy/phy-core.c
+@@ -639,6 +639,146 @@ int phy_write_mmd(struct phy_device *phy
+ EXPORT_SYMBOL(phy_write_mmd);
+
+ /**
++ * __phy_package_read_mmd - read MMD reg relative to PHY package base addr
++ * @phydev: The phy_device struct
++ * @addr_offset: The offset to be added to PHY package base_addr
++ * @devad: The MMD to read from
++ * @regnum: The register on the MMD to read
++ *
++ * Convenience helper for reading a register of an MMD on a given PHY
++ * using the PHY package base address. The base address is added to
++ * the addr_offset value.
++ *
++ * Same calling rules as for __phy_read();
++ *
++ * NOTE: It's assumed that the entire PHY package is either C22 or C45.
++ */
++int __phy_package_read_mmd(struct phy_device *phydev,
++ unsigned int addr_offset, int devad,
++ u32 regnum)
++{
++ int addr = phy_package_address(phydev, addr_offset);
++
++ if (addr < 0)
++ return addr;
++
++ if (regnum > (u16)~0 || devad > 32)
++ return -EINVAL;
++
++ return mmd_phy_read(phydev->mdio.bus, addr, phydev->is_c45, devad,
++ regnum);
++}
++EXPORT_SYMBOL(__phy_package_read_mmd);
++
++/**
++ * phy_package_read_mmd - read MMD reg relative to PHY package base addr
++ * @phydev: The phy_device struct
++ * @addr_offset: The offset to be added to PHY package base_addr
++ * @devad: The MMD to read from
++ * @regnum: The register on the MMD to read
++ *
++ * Convenience helper for reading a register of an MMD on a given PHY
++ * using the PHY package base address. The base address is added to
++ * the addr_offset value.
++ *
++ * Same calling rules as for phy_read();
++ *
++ * NOTE: It's assumed that the entire PHY package is either C22 or C45.
++ */
++int phy_package_read_mmd(struct phy_device *phydev,
++ unsigned int addr_offset, int devad,
++ u32 regnum)
++{
++ int addr = phy_package_address(phydev, addr_offset);
++ int val;
++
++ if (addr < 0)
++ return addr;
++
++ if (regnum > (u16)~0 || devad > 32)
++ return -EINVAL;
++
++ phy_lock_mdio_bus(phydev);
++ val = mmd_phy_read(phydev->mdio.bus, addr, phydev->is_c45, devad,
++ regnum);
++ phy_unlock_mdio_bus(phydev);
++
++ return val;
++}
++EXPORT_SYMBOL(phy_package_read_mmd);
++
++/**
++ * __phy_package_write_mmd - write MMD reg relative to PHY package base addr
++ * @phydev: The phy_device struct
++ * @addr_offset: The offset to be added to PHY package base_addr
++ * @devad: The MMD to write to
++ * @regnum: The register on the MMD to write
++ * @val: value to write to @regnum
++ *
++ * Convenience helper for writing a register of an MMD on a given PHY
++ * using the PHY package base address. The base address is added to
++ * the addr_offset value.
++ *
++ * Same calling rules as for __phy_write();
++ *
++ * NOTE: It's assumed that the entire PHY package is either C22 or C45.
++ */
++int __phy_package_write_mmd(struct phy_device *phydev,
++ unsigned int addr_offset, int devad,
++ u32 regnum, u16 val)
++{
++ int addr = phy_package_address(phydev, addr_offset);
++
++ if (addr < 0)
++ return addr;
++
++ if (regnum > (u16)~0 || devad > 32)
++ return -EINVAL;
++
++ return mmd_phy_write(phydev->mdio.bus, addr, phydev->is_c45, devad,
++ regnum, val);
++}
++EXPORT_SYMBOL(__phy_package_write_mmd);
++
++/**
++ * phy_package_write_mmd - write MMD reg relative to PHY package base addr
++ * @phydev: The phy_device struct
++ * @addr_offset: The offset to be added to PHY package base_addr
++ * @devad: The MMD to write to
++ * @regnum: The register on the MMD to write
++ * @val: value to write to @regnum
++ *
++ * Convenience helper for writing a register of an MMD on a given PHY
++ * using the PHY package base address. The base address is added to
++ * the addr_offset value.
++ *
++ * Same calling rules as for phy_write();
++ *
++ * NOTE: It's assumed that the entire PHY package is either C22 or C45.
++ */
++int phy_package_write_mmd(struct phy_device *phydev,
++ unsigned int addr_offset, int devad,
++ u32 regnum, u16 val)
++{
++ int addr = phy_package_address(phydev, addr_offset);
++ int ret;
++
++ if (addr < 0)
++ return addr;
++
++ if (regnum > (u16)~0 || devad > 32)
++ return -EINVAL;
++
++ phy_lock_mdio_bus(phydev);
++ ret = mmd_phy_write(phydev->mdio.bus, addr, phydev->is_c45, devad,
++ regnum, val);
++ phy_unlock_mdio_bus(phydev);
++
++ return ret;
++}
++EXPORT_SYMBOL(phy_package_write_mmd);
++
++/**
+ * phy_modify_changed - Function for modifying a PHY register
+ * @phydev: the phy_device struct
+ * @regnum: register number to modify
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -1840,6 +1840,22 @@ static inline int __phy_package_write(st
+ return __mdiobus_write(phydev->mdio.bus, addr, regnum, val);
+ }
+
++int __phy_package_read_mmd(struct phy_device *phydev,
++ unsigned int addr_offset, int devad,
++ u32 regnum);
++
++int phy_package_read_mmd(struct phy_device *phydev,
++ unsigned int addr_offset, int devad,
++ u32 regnum);
++
++int __phy_package_write_mmd(struct phy_device *phydev,
++ unsigned int addr_offset, int devad,
++ u32 regnum, u16 val);
++
++int phy_package_write_mmd(struct phy_device *phydev,
++ unsigned int addr_offset, int devad,
++ u32 regnum, u16 val);
++
+ static inline bool __phy_package_set_once(struct phy_device *phydev,
+ unsigned int b)
+ {
diff --git a/target/linux/generic/backport-6.6/715-01-v6.2-net-fman-memac-Add-serdes-support.patch b/target/linux/generic/backport-6.6/715-01-v6.2-net-fman-memac-Add-serdes-support.patch
new file mode 100644
index 0000000000..2886123f2d
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-01-v6.2-net-fman-memac-Add-serdes-support.patch
@@ -0,0 +1,103 @@
+From affa013f494486079c3c5ad2d00cebc41a3d7445 Mon Sep 17 00:00:00 2001
+From: Sean Anderson <sean.anderson@seco.com>
+Date: Mon, 17 Oct 2022 16:22:36 -0400
+Subject: [PATCH 01/21] net: fman: memac: Add serdes support
+
+This adds support for using a serdes which has to be configured. This is
+primarly in preparation for phylink conversion, which will then change the
+serdes mode dynamically.
+
+Signed-off-by: Sean Anderson <sean.anderson@seco.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ .../net/ethernet/freescale/fman/fman_memac.c | 49 ++++++++++++++++++-
+ 1 file changed, 47 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/ethernet/freescale/fman/fman_memac.c
++++ b/drivers/net/ethernet/freescale/fman/fman_memac.c
+@@ -13,6 +13,7 @@
+ #include <linux/io.h>
+ #include <linux/phy.h>
+ #include <linux/phy_fixed.h>
++#include <linux/phy/phy.h>
+ #include <linux/of_mdio.h>
+
+ /* PCS registers */
+@@ -324,6 +325,7 @@ struct fman_mac {
+ void *fm;
+ struct fman_rev_info fm_rev_info;
+ bool basex_if;
++ struct phy *serdes;
+ struct phy_device *pcsphy;
+ bool allmulti_enabled;
+ };
+@@ -1203,17 +1205,56 @@ int memac_initialization(struct mac_devi
+ }
+ }
+
++ memac->serdes = devm_of_phy_get(mac_dev->dev, mac_node, "serdes");
++ err = PTR_ERR(memac->serdes);
++ if (err == -ENODEV || err == -ENOSYS) {
++ dev_dbg(mac_dev->dev, "could not get (optional) serdes\n");
++ memac->serdes = NULL;
++ } else if (IS_ERR(memac->serdes)) {
++ dev_err_probe(mac_dev->dev, err, "could not get serdes\n");
++ goto _return_fm_mac_free;
++ } else {
++ err = phy_init(memac->serdes);
++ if (err) {
++ dev_err_probe(mac_dev->dev, err,
++ "could not initialize serdes\n");
++ goto _return_fm_mac_free;
++ }
++
++ err = phy_power_on(memac->serdes);
++ if (err) {
++ dev_err_probe(mac_dev->dev, err,
++ "could not power on serdes\n");
++ goto _return_phy_exit;
++ }
++
++ if (memac->phy_if == PHY_INTERFACE_MODE_SGMII ||
++ memac->phy_if == PHY_INTERFACE_MODE_1000BASEX ||
++ memac->phy_if == PHY_INTERFACE_MODE_2500BASEX ||
++ memac->phy_if == PHY_INTERFACE_MODE_QSGMII ||
++ memac->phy_if == PHY_INTERFACE_MODE_XGMII) {
++ err = phy_set_mode_ext(memac->serdes, PHY_MODE_ETHERNET,
++ memac->phy_if);
++ if (err) {
++ dev_err_probe(mac_dev->dev, err,
++ "could not set serdes mode to %s\n",
++ phy_modes(memac->phy_if));
++ goto _return_phy_power_off;
++ }
++ }
++ }
++
+ if (!mac_dev->phy_node && of_phy_is_fixed_link(mac_node)) {
+ struct phy_device *phy;
+
+ err = of_phy_register_fixed_link(mac_node);
+ if (err)
+- goto _return_fm_mac_free;
++ goto _return_phy_power_off;
+
+ fixed_link = kzalloc(sizeof(*fixed_link), GFP_KERNEL);
+ if (!fixed_link) {
+ err = -ENOMEM;
+- goto _return_fm_mac_free;
++ goto _return_phy_power_off;
+ }
+
+ mac_dev->phy_node = of_node_get(mac_node);
+@@ -1242,6 +1283,10 @@ int memac_initialization(struct mac_devi
+
+ goto _return;
+
++_return_phy_power_off:
++ phy_power_off(memac->serdes);
++_return_phy_exit:
++ phy_exit(memac->serdes);
+ _return_fixed_link_free:
+ kfree(fixed_link);
+ _return_fm_mac_free:
diff --git a/target/linux/generic/backport-6.6/715-02-v6.2-net-fman-memac-Use-lynx-pcs-driver.patch b/target/linux/generic/backport-6.6/715-02-v6.2-net-fman-memac-Use-lynx-pcs-driver.patch
new file mode 100644
index 0000000000..873debc080
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-02-v6.2-net-fman-memac-Use-lynx-pcs-driver.patch
@@ -0,0 +1,384 @@
+From fe60e7154d3a35af975c5e6570d6ec31aab9a731 Mon Sep 17 00:00:00 2001
+From: Sean Anderson <sean.anderson@seco.com>
+Date: Mon, 17 Oct 2022 16:22:37 -0400
+Subject: [PATCH 02/21] net: fman: memac: Use lynx pcs driver
+
+Although not stated in the datasheet, as far as I can tell PCS for mEMACs
+is a "Lynx." By reusing the existing driver, we can remove the PCS
+management code from the memac driver. This requires calling some PCS
+functions manually which phylink would usually do for us, but we will let
+it do that soon.
+
+One problem is that we don't actually have a PCS for QSGMII. We pretend
+that each mEMAC's MDIO bus has four QSGMII PCSs, but this is not the case.
+Only the "base" mEMAC's MDIO bus has the four QSGMII PCSs. This is not an
+issue yet, because we never get the PCS state. However, it will be once the
+conversion to phylink is complete, since the links will appear to never
+come up. To get around this, we allow specifying multiple PCSs in pcsphy.
+This breaks backwards compatibility with old device trees, but only for
+QSGMII. IMO this is the only reasonable way to figure out what the actual
+QSGMII PCS is.
+
+Additionally, we now also support a separate XFI PCS. This can allow the
+SerDes driver to set different addresses for the SGMII and XFI PCSs so they
+can be accessed at the same time.
+
+Signed-off-by: Sean Anderson <sean.anderson@seco.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/ethernet/freescale/fman/Kconfig | 3 +
+ .../net/ethernet/freescale/fman/fman_memac.c | 258 +++++++-----------
+ 2 files changed, 105 insertions(+), 156 deletions(-)
+
+--- a/drivers/net/ethernet/freescale/fman/Kconfig
++++ b/drivers/net/ethernet/freescale/fman/Kconfig
+@@ -4,6 +4,9 @@ config FSL_FMAN
+ depends on FSL_SOC || ARCH_LAYERSCAPE || COMPILE_TEST
+ select GENERIC_ALLOCATOR
+ select PHYLIB
++ select PHYLINK
++ select PCS
++ select PCS_LYNX
+ select CRC32
+ default n
+ help
+--- a/drivers/net/ethernet/freescale/fman/fman_memac.c
++++ b/drivers/net/ethernet/freescale/fman/fman_memac.c
+@@ -11,43 +11,12 @@
+
+ #include <linux/slab.h>
+ #include <linux/io.h>
++#include <linux/pcs-lynx.h>
+ #include <linux/phy.h>
+ #include <linux/phy_fixed.h>
+ #include <linux/phy/phy.h>
+ #include <linux/of_mdio.h>
+
+-/* PCS registers */
+-#define MDIO_SGMII_CR 0x00
+-#define MDIO_SGMII_DEV_ABIL_SGMII 0x04
+-#define MDIO_SGMII_LINK_TMR_L 0x12
+-#define MDIO_SGMII_LINK_TMR_H 0x13
+-#define MDIO_SGMII_IF_MODE 0x14
+-
+-/* SGMII Control defines */
+-#define SGMII_CR_AN_EN 0x1000
+-#define SGMII_CR_RESTART_AN 0x0200
+-#define SGMII_CR_FD 0x0100
+-#define SGMII_CR_SPEED_SEL1_1G 0x0040
+-#define SGMII_CR_DEF_VAL (SGMII_CR_AN_EN | SGMII_CR_FD | \
+- SGMII_CR_SPEED_SEL1_1G)
+-
+-/* SGMII Device Ability for SGMII defines */
+-#define MDIO_SGMII_DEV_ABIL_SGMII_MODE 0x4001
+-#define MDIO_SGMII_DEV_ABIL_BASEX_MODE 0x01A0
+-
+-/* Link timer define */
+-#define LINK_TMR_L 0xa120
+-#define LINK_TMR_H 0x0007
+-#define LINK_TMR_L_BASEX 0xaf08
+-#define LINK_TMR_H_BASEX 0x002f
+-
+-/* SGMII IF Mode defines */
+-#define IF_MODE_USE_SGMII_AN 0x0002
+-#define IF_MODE_SGMII_EN 0x0001
+-#define IF_MODE_SGMII_SPEED_100M 0x0004
+-#define IF_MODE_SGMII_SPEED_1G 0x0008
+-#define IF_MODE_SGMII_DUPLEX_HALF 0x0010
+-
+ /* Num of additional exact match MAC adr regs */
+ #define MEMAC_NUM_OF_PADDRS 7
+
+@@ -326,7 +295,9 @@ struct fman_mac {
+ struct fman_rev_info fm_rev_info;
+ bool basex_if;
+ struct phy *serdes;
+- struct phy_device *pcsphy;
++ struct phylink_pcs *sgmii_pcs;
++ struct phylink_pcs *qsgmii_pcs;
++ struct phylink_pcs *xfi_pcs;
+ bool allmulti_enabled;
+ };
+
+@@ -487,91 +458,22 @@ static u32 get_mac_addr_hash_code(u64 et
+ return xor_val;
+ }
+
+-static void setup_sgmii_internal_phy(struct fman_mac *memac,
+- struct fixed_phy_status *fixed_link)
+-{
+- u16 tmp_reg16;
+-
+- if (WARN_ON(!memac->pcsphy))
+- return;
+-
+- /* SGMII mode */
+- tmp_reg16 = IF_MODE_SGMII_EN;
+- if (!fixed_link)
+- /* AN enable */
+- tmp_reg16 |= IF_MODE_USE_SGMII_AN;
+- else {
+- switch (fixed_link->speed) {
+- case 10:
+- /* For 10M: IF_MODE[SPEED_10M] = 0 */
+- break;
+- case 100:
+- tmp_reg16 |= IF_MODE_SGMII_SPEED_100M;
+- break;
+- case 1000:
+- default:
+- tmp_reg16 |= IF_MODE_SGMII_SPEED_1G;
+- break;
+- }
+- if (!fixed_link->duplex)
+- tmp_reg16 |= IF_MODE_SGMII_DUPLEX_HALF;
+- }
+- phy_write(memac->pcsphy, MDIO_SGMII_IF_MODE, tmp_reg16);
+-
+- /* Device ability according to SGMII specification */
+- tmp_reg16 = MDIO_SGMII_DEV_ABIL_SGMII_MODE;
+- phy_write(memac->pcsphy, MDIO_SGMII_DEV_ABIL_SGMII, tmp_reg16);
+-
+- /* Adjust link timer for SGMII -
+- * According to Cisco SGMII specification the timer should be 1.6 ms.
+- * The link_timer register is configured in units of the clock.
+- * - When running as 1G SGMII, Serdes clock is 125 MHz, so
+- * unit = 1 / (125*10^6 Hz) = 8 ns.
+- * 1.6 ms in units of 8 ns = 1.6ms / 8ns = 2*10^5 = 0x30d40
+- * - When running as 2.5G SGMII, Serdes clock is 312.5 MHz, so
+- * unit = 1 / (312.5*10^6 Hz) = 3.2 ns.
+- * 1.6 ms in units of 3.2 ns = 1.6ms / 3.2ns = 5*10^5 = 0x7a120.
+- * Since link_timer value of 1G SGMII will be too short for 2.5 SGMII,
+- * we always set up here a value of 2.5 SGMII.
+- */
+- phy_write(memac->pcsphy, MDIO_SGMII_LINK_TMR_H, LINK_TMR_H);
+- phy_write(memac->pcsphy, MDIO_SGMII_LINK_TMR_L, LINK_TMR_L);
+-
+- if (!fixed_link)
+- /* Restart AN */
+- tmp_reg16 = SGMII_CR_DEF_VAL | SGMII_CR_RESTART_AN;
++static void setup_sgmii_internal(struct fman_mac *memac,
++ struct phylink_pcs *pcs,
++ struct fixed_phy_status *fixed_link)
++{
++ __ETHTOOL_DECLARE_LINK_MODE_MASK(advertising);
++ phy_interface_t iface = memac->basex_if ? PHY_INTERFACE_MODE_1000BASEX :
++ PHY_INTERFACE_MODE_SGMII;
++ unsigned int mode = fixed_link ? MLO_AN_FIXED : MLO_AN_INBAND;
++
++ linkmode_set_pause(advertising, true, true);
++ pcs->ops->pcs_config(pcs, mode, iface, advertising, true);
++ if (fixed_link)
++ pcs->ops->pcs_link_up(pcs, mode, iface, fixed_link->speed,
++ fixed_link->duplex);
+ else
+- /* AN disabled */
+- tmp_reg16 = SGMII_CR_DEF_VAL & ~SGMII_CR_AN_EN;
+- phy_write(memac->pcsphy, 0x0, tmp_reg16);
+-}
+-
+-static void setup_sgmii_internal_phy_base_x(struct fman_mac *memac)
+-{
+- u16 tmp_reg16;
+-
+- /* AN Device capability */
+- tmp_reg16 = MDIO_SGMII_DEV_ABIL_BASEX_MODE;
+- phy_write(memac->pcsphy, MDIO_SGMII_DEV_ABIL_SGMII, tmp_reg16);
+-
+- /* Adjust link timer for SGMII -
+- * For Serdes 1000BaseX auto-negotiation the timer should be 10 ms.
+- * The link_timer register is configured in units of the clock.
+- * - When running as 1G SGMII, Serdes clock is 125 MHz, so
+- * unit = 1 / (125*10^6 Hz) = 8 ns.
+- * 10 ms in units of 8 ns = 10ms / 8ns = 1250000 = 0x1312d0
+- * - When running as 2.5G SGMII, Serdes clock is 312.5 MHz, so
+- * unit = 1 / (312.5*10^6 Hz) = 3.2 ns.
+- * 10 ms in units of 3.2 ns = 10ms / 3.2ns = 3125000 = 0x2faf08.
+- * Since link_timer value of 1G SGMII will be too short for 2.5 SGMII,
+- * we always set up here a value of 2.5 SGMII.
+- */
+- phy_write(memac->pcsphy, MDIO_SGMII_LINK_TMR_H, LINK_TMR_H_BASEX);
+- phy_write(memac->pcsphy, MDIO_SGMII_LINK_TMR_L, LINK_TMR_L_BASEX);
+-
+- /* Restart AN */
+- tmp_reg16 = SGMII_CR_DEF_VAL | SGMII_CR_RESTART_AN;
+- phy_write(memac->pcsphy, 0x0, tmp_reg16);
++ pcs->ops->pcs_an_restart(pcs);
+ }
+
+ static int check_init_parameters(struct fman_mac *memac)
+@@ -983,7 +885,6 @@ static int memac_set_exception(struct fm
+ static int memac_init(struct fman_mac *memac)
+ {
+ struct memac_cfg *memac_drv_param;
+- u8 i;
+ enet_addr_t eth_addr;
+ bool slow_10g_if = false;
+ struct fixed_phy_status *fixed_link = NULL;
+@@ -1036,32 +937,10 @@ static int memac_init(struct fman_mac *m
+ iowrite32be(reg32, &memac->regs->command_config);
+ }
+
+- if (memac->phy_if == PHY_INTERFACE_MODE_SGMII) {
+- /* Configure internal SGMII PHY */
+- if (memac->basex_if)
+- setup_sgmii_internal_phy_base_x(memac);
+- else
+- setup_sgmii_internal_phy(memac, fixed_link);
+- } else if (memac->phy_if == PHY_INTERFACE_MODE_QSGMII) {
+- /* Configure 4 internal SGMII PHYs */
+- for (i = 0; i < 4; i++) {
+- u8 qsmgii_phy_addr, phy_addr;
+- /* QSGMII PHY address occupies 3 upper bits of 5-bit
+- * phy_address; the lower 2 bits are used to extend
+- * register address space and access each one of 4
+- * ports inside QSGMII.
+- */
+- phy_addr = memac->pcsphy->mdio.addr;
+- qsmgii_phy_addr = (u8)((phy_addr << 2) | i);
+- memac->pcsphy->mdio.addr = qsmgii_phy_addr;
+- if (memac->basex_if)
+- setup_sgmii_internal_phy_base_x(memac);
+- else
+- setup_sgmii_internal_phy(memac, fixed_link);
+-
+- memac->pcsphy->mdio.addr = phy_addr;
+- }
+- }
++ if (memac->phy_if == PHY_INTERFACE_MODE_SGMII)
++ setup_sgmii_internal(memac, memac->sgmii_pcs, fixed_link);
++ else if (memac->phy_if == PHY_INTERFACE_MODE_QSGMII)
++ setup_sgmii_internal(memac, memac->qsgmii_pcs, fixed_link);
+
+ /* Max Frame Length */
+ err = fman_set_mac_max_frame(memac->fm, memac->mac_id,
+@@ -1097,12 +976,25 @@ static int memac_init(struct fman_mac *m
+ return 0;
+ }
+
++static void pcs_put(struct phylink_pcs *pcs)
++{
++ struct mdio_device *mdiodev;
++
++ if (IS_ERR_OR_NULL(pcs))
++ return;
++
++ mdiodev = lynx_get_mdio_device(pcs);
++ lynx_pcs_destroy(pcs);
++ mdio_device_free(mdiodev);
++}
++
+ static int memac_free(struct fman_mac *memac)
+ {
+ free_init_resources(memac);
+
+- if (memac->pcsphy)
+- put_device(&memac->pcsphy->mdio.dev);
++ pcs_put(memac->sgmii_pcs);
++ pcs_put(memac->qsgmii_pcs);
++ pcs_put(memac->xfi_pcs);
+
+ kfree(memac->memac_drv_param);
+ kfree(memac);
+@@ -1153,12 +1045,31 @@ static struct fman_mac *memac_config(str
+ return memac;
+ }
+
++static struct phylink_pcs *memac_pcs_create(struct device_node *mac_node,
++ int index)
++{
++ struct device_node *node;
++ struct mdio_device *mdiodev = NULL;
++ struct phylink_pcs *pcs;
++
++ node = of_parse_phandle(mac_node, "pcsphy-handle", index);
++ if (node && of_device_is_available(node))
++ mdiodev = of_mdio_find_device(node);
++ of_node_put(node);
++
++ if (!mdiodev)
++ return ERR_PTR(-EPROBE_DEFER);
++
++ pcs = lynx_pcs_create(mdiodev);
++ return pcs;
++}
++
+ int memac_initialization(struct mac_device *mac_dev,
+ struct device_node *mac_node,
+ struct fman_mac_params *params)
+ {
+ int err;
+- struct device_node *phy_node;
++ struct phylink_pcs *pcs;
+ struct fixed_phy_status *fixed_link;
+ struct fman_mac *memac;
+
+@@ -1188,23 +1099,58 @@ int memac_initialization(struct mac_devi
+ memac = mac_dev->fman_mac;
+ memac->memac_drv_param->max_frame_length = fman_get_max_frm();
+ memac->memac_drv_param->reset_on_init = true;
+- if (memac->phy_if == PHY_INTERFACE_MODE_SGMII ||
+- memac->phy_if == PHY_INTERFACE_MODE_QSGMII) {
+- phy_node = of_parse_phandle(mac_node, "pcsphy-handle", 0);
+- if (!phy_node) {
+- pr_err("PCS PHY node is not available\n");
+- err = -EINVAL;
++
++ err = of_property_match_string(mac_node, "pcs-handle-names", "xfi");
++ if (err >= 0) {
++ memac->xfi_pcs = memac_pcs_create(mac_node, err);
++ if (IS_ERR(memac->xfi_pcs)) {
++ err = PTR_ERR(memac->xfi_pcs);
++ dev_err_probe(mac_dev->dev, err, "missing xfi pcs\n");
+ goto _return_fm_mac_free;
+ }
++ } else if (err != -EINVAL && err != -ENODATA) {
++ goto _return_fm_mac_free;
++ }
+
+- memac->pcsphy = of_phy_find_device(phy_node);
+- if (!memac->pcsphy) {
+- pr_err("of_phy_find_device (PCS PHY) failed\n");
+- err = -EINVAL;
++ err = of_property_match_string(mac_node, "pcs-handle-names", "qsgmii");
++ if (err >= 0) {
++ memac->qsgmii_pcs = memac_pcs_create(mac_node, err);
++ if (IS_ERR(memac->qsgmii_pcs)) {
++ err = PTR_ERR(memac->qsgmii_pcs);
++ dev_err_probe(mac_dev->dev, err,
++ "missing qsgmii pcs\n");
+ goto _return_fm_mac_free;
+ }
++ } else if (err != -EINVAL && err != -ENODATA) {
++ goto _return_fm_mac_free;
++ }
++
++ /* For compatibility, if pcs-handle-names is missing, we assume this
++ * phy is the first one in pcsphy-handle
++ */
++ err = of_property_match_string(mac_node, "pcs-handle-names", "sgmii");
++ if (err == -EINVAL || err == -ENODATA)
++ pcs = memac_pcs_create(mac_node, 0);
++ else if (err < 0)
++ goto _return_fm_mac_free;
++ else
++ pcs = memac_pcs_create(mac_node, err);
++
++ if (!pcs) {
++ dev_err(mac_dev->dev, "missing pcs\n");
++ err = -ENOENT;
++ goto _return_fm_mac_free;
+ }
+
++ /* If err is set here, it means that pcs-handle-names was missing above
++ * (and therefore that xfi_pcs cannot be set). If we are defaulting to
++ * XGMII, assume this is for XFI. Otherwise, assume it is for SGMII.
++ */
++ if (err && mac_dev->phy_if == PHY_INTERFACE_MODE_XGMII)
++ memac->xfi_pcs = pcs;
++ else
++ memac->sgmii_pcs = pcs;
++
+ memac->serdes = devm_of_phy_get(mac_dev->dev, mac_node, "serdes");
+ err = PTR_ERR(memac->serdes);
+ if (err == -ENODEV || err == -ENOSYS) {
diff --git a/target/linux/generic/backport-6.6/715-03-v6.2-net-dpaa-Convert-to-phylink.patch b/target/linux/generic/backport-6.6/715-03-v6.2-net-dpaa-Convert-to-phylink.patch
new file mode 100644
index 0000000000..63b651bb2d
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-03-v6.2-net-dpaa-Convert-to-phylink.patch
@@ -0,0 +1,2451 @@
+From 38e50fc3d43882a43115b4f1ca3eb88255163c5b Mon Sep 17 00:00:00 2001
+From: Sean Anderson <sean.anderson@seco.com>
+Date: Mon, 17 Oct 2022 16:22:38 -0400
+Subject: [PATCH 03/21] net: dpaa: Convert to phylink
+
+This converts DPAA to phylink. All macs are converted. This should work
+with no device tree modifications (including those made in this series),
+except for QSGMII (as noted previously).
+
+The mEMAC configuration is one of the tricker areas. I have tried to
+capture all the restrictions across the various models. Most of the time,
+we assume that if the serdes supports a mode or the phy-interface-mode
+specifies it, then we support it. The only place we can't do this is
+(RG)MII, since there's no serdes. In that case, we rely on a (new)
+devicetree property. There are also several cases where half-duplex is
+broken. Unfortunately, only a single compatible is used for the MAC, so we
+have to use the board compatible instead.
+
+The 10GEC conversion is very straightforward, since it only supports XAUI.
+There is generally nothing to configure.
+
+The dTSEC conversion is broadly similar to mEMAC, but is simpler because we
+don't support configuring the SerDes (though this can be easily added) and
+we don't have multiple PCSs. From what I can tell, there's nothing
+different in the driver or documentation between SGMII and 1000BASE-X
+except for the advertising. Similarly, I couldn't find anything about
+2500BASE-X. In both cases, I treat them like SGMII. These modes aren't used
+by any in-tree boards. Similarly, despite being mentioned in the driver, I
+couldn't find any documented SoCs which supported QSGMII. I have left it
+unimplemented for now.
+
+Signed-off-by: Sean Anderson <sean.anderson@seco.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/ethernet/freescale/dpaa/Kconfig | 4 +-
+ .../net/ethernet/freescale/dpaa/dpaa_eth.c | 89 +--
+ .../ethernet/freescale/dpaa/dpaa_ethtool.c | 90 +--
+ drivers/net/ethernet/freescale/fman/Kconfig | 1 -
+ .../net/ethernet/freescale/fman/fman_dtsec.c | 458 +++++++--------
+ .../net/ethernet/freescale/fman/fman_mac.h | 10 -
+ .../net/ethernet/freescale/fman/fman_memac.c | 547 +++++++++---------
+ .../net/ethernet/freescale/fman/fman_tgec.c | 131 ++---
+ drivers/net/ethernet/freescale/fman/mac.c | 168 +-----
+ drivers/net/ethernet/freescale/fman/mac.h | 23 +-
+ 10 files changed, 612 insertions(+), 909 deletions(-)
+
+--- a/drivers/net/ethernet/freescale/dpaa/Kconfig
++++ b/drivers/net/ethernet/freescale/dpaa/Kconfig
+@@ -2,8 +2,8 @@
+ menuconfig FSL_DPAA_ETH
+ tristate "DPAA Ethernet"
+ depends on FSL_DPAA && FSL_FMAN
+- select PHYLIB
+- select FIXED_PHY
++ select PHYLINK
++ select PCS_LYNX
+ help
+ Data Path Acceleration Architecture Ethernet driver,
+ supporting the Freescale QorIQ chips.
+--- a/drivers/net/ethernet/freescale/dpaa/dpaa_eth.c
++++ b/drivers/net/ethernet/freescale/dpaa/dpaa_eth.c
+@@ -264,8 +264,19 @@ static int dpaa_netdev_init(struct net_d
+ net_dev->needed_headroom = priv->tx_headroom;
+ net_dev->watchdog_timeo = msecs_to_jiffies(tx_timeout);
+
+- mac_dev->net_dev = net_dev;
++ /* The rest of the config is filled in by the mac device already */
++ mac_dev->phylink_config.dev = &net_dev->dev;
++ mac_dev->phylink_config.type = PHYLINK_NETDEV;
+ mac_dev->update_speed = dpaa_eth_cgr_set_speed;
++ mac_dev->phylink = phylink_create(&mac_dev->phylink_config,
++ dev_fwnode(mac_dev->dev),
++ mac_dev->phy_if,
++ mac_dev->phylink_ops);
++ if (IS_ERR(mac_dev->phylink)) {
++ err = PTR_ERR(mac_dev->phylink);
++ dev_err_probe(dev, err, "Could not create phylink\n");
++ return err;
++ }
+
+ /* start without the RUNNING flag, phylib controls it later */
+ netif_carrier_off(net_dev);
+@@ -273,6 +284,7 @@ static int dpaa_netdev_init(struct net_d
+ err = register_netdev(net_dev);
+ if (err < 0) {
+ dev_err(dev, "register_netdev() = %d\n", err);
++ phylink_destroy(mac_dev->phylink);
+ return err;
+ }
+
+@@ -295,8 +307,7 @@ static int dpaa_stop(struct net_device *
+ */
+ msleep(200);
+
+- if (mac_dev->phy_dev)
+- phy_stop(mac_dev->phy_dev);
++ phylink_stop(mac_dev->phylink);
+ mac_dev->disable(mac_dev->fman_mac);
+
+ for (i = 0; i < ARRAY_SIZE(mac_dev->port); i++) {
+@@ -305,8 +316,7 @@ static int dpaa_stop(struct net_device *
+ err = error;
+ }
+
+- if (net_dev->phydev)
+- phy_disconnect(net_dev->phydev);
++ phylink_disconnect_phy(mac_dev->phylink);
+ net_dev->phydev = NULL;
+
+ msleep(200);
+@@ -834,10 +844,10 @@ static int dpaa_eth_cgr_init(struct dpaa
+
+ /* Set different thresholds based on the configured MAC speed.
+ * This may turn suboptimal if the MAC is reconfigured at another
+- * speed, so MACs must call dpaa_eth_cgr_set_speed in their adjust_link
++ * speed, so MACs must call dpaa_eth_cgr_set_speed in their link_up
+ * callback.
+ */
+- if (priv->mac_dev->if_support & SUPPORTED_10000baseT_Full)
++ if (priv->mac_dev->phylink_config.mac_capabilities & MAC_10000FD)
+ cs_th = DPAA_CS_THRESHOLD_10G;
+ else
+ cs_th = DPAA_CS_THRESHOLD_1G;
+@@ -866,7 +876,7 @@ out_error:
+
+ static void dpaa_eth_cgr_set_speed(struct mac_device *mac_dev, int speed)
+ {
+- struct net_device *net_dev = mac_dev->net_dev;
++ struct net_device *net_dev = to_net_dev(mac_dev->phylink_config.dev);
+ struct dpaa_priv *priv = netdev_priv(net_dev);
+ struct qm_mcc_initcgr opts = { };
+ u32 cs_th;
+@@ -2905,58 +2915,6 @@ static void dpaa_eth_napi_disable(struct
+ }
+ }
+
+-static void dpaa_adjust_link(struct net_device *net_dev)
+-{
+- struct mac_device *mac_dev;
+- struct dpaa_priv *priv;
+-
+- priv = netdev_priv(net_dev);
+- mac_dev = priv->mac_dev;
+- mac_dev->adjust_link(mac_dev);
+-}
+-
+-/* The Aquantia PHYs are capable of performing rate adaptation */
+-#define PHY_VEND_AQUANTIA 0x03a1b400
+-#define PHY_VEND_AQUANTIA2 0x31c31c00
+-
+-static int dpaa_phy_init(struct net_device *net_dev)
+-{
+- __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
+- struct mac_device *mac_dev;
+- struct phy_device *phy_dev;
+- struct dpaa_priv *priv;
+- u32 phy_vendor;
+-
+- priv = netdev_priv(net_dev);
+- mac_dev = priv->mac_dev;
+-
+- phy_dev = of_phy_connect(net_dev, mac_dev->phy_node,
+- &dpaa_adjust_link, 0,
+- mac_dev->phy_if);
+- if (!phy_dev) {
+- netif_err(priv, ifup, net_dev, "init_phy() failed\n");
+- return -ENODEV;
+- }
+-
+- phy_vendor = phy_dev->drv->phy_id & GENMASK(31, 10);
+- /* Unless the PHY is capable of rate adaptation */
+- if (mac_dev->phy_if != PHY_INTERFACE_MODE_XGMII ||
+- (phy_vendor != PHY_VEND_AQUANTIA &&
+- phy_vendor != PHY_VEND_AQUANTIA2)) {
+- /* remove any features not supported by the controller */
+- ethtool_convert_legacy_u32_to_link_mode(mask,
+- mac_dev->if_support);
+- linkmode_and(phy_dev->supported, phy_dev->supported, mask);
+- }
+-
+- phy_support_asym_pause(phy_dev);
+-
+- mac_dev->phy_dev = phy_dev;
+- net_dev->phydev = phy_dev;
+-
+- return 0;
+-}
+-
+ static int dpaa_open(struct net_device *net_dev)
+ {
+ struct mac_device *mac_dev;
+@@ -2967,7 +2925,8 @@ static int dpaa_open(struct net_device *
+ mac_dev = priv->mac_dev;
+ dpaa_eth_napi_enable(priv);
+
+- err = dpaa_phy_init(net_dev);
++ err = phylink_of_phy_connect(mac_dev->phylink,
++ mac_dev->dev->of_node, 0);
+ if (err)
+ goto phy_init_failed;
+
+@@ -2982,7 +2941,7 @@ static int dpaa_open(struct net_device *
+ netif_err(priv, ifup, net_dev, "mac_dev->enable() = %d\n", err);
+ goto mac_start_failed;
+ }
+- phy_start(priv->mac_dev->phy_dev);
++ phylink_start(mac_dev->phylink);
+
+ netif_tx_start_all_queues(net_dev);
+
+@@ -2991,6 +2950,7 @@ static int dpaa_open(struct net_device *
+ mac_start_failed:
+ for (i = 0; i < ARRAY_SIZE(mac_dev->port); i++)
+ fman_port_disable(mac_dev->port[i]);
++ phylink_disconnect_phy(mac_dev->phylink);
+
+ phy_init_failed:
+ dpaa_eth_napi_disable(priv);
+@@ -3146,10 +3106,12 @@ static int dpaa_ts_ioctl(struct net_devi
+ static int dpaa_ioctl(struct net_device *net_dev, struct ifreq *rq, int cmd)
+ {
+ int ret = -EINVAL;
++ struct dpaa_priv *priv = netdev_priv(net_dev);
+
+ if (cmd == SIOCGMIIREG) {
+ if (net_dev->phydev)
+- return phy_mii_ioctl(net_dev->phydev, rq, cmd);
++ return phylink_mii_ioctl(priv->mac_dev->phylink, rq,
++ cmd);
+ }
+
+ if (cmd == SIOCSHWTSTAMP)
+@@ -3552,6 +3514,7 @@ static int dpaa_remove(struct platform_d
+
+ dev_set_drvdata(dev, NULL);
+ unregister_netdev(net_dev);
++ phylink_destroy(priv->mac_dev->phylink);
+
+ err = dpaa_fq_free(dev, &priv->dpaa_fq_list);
+
+--- a/drivers/net/ethernet/freescale/dpaa/dpaa_ethtool.c
++++ b/drivers/net/ethernet/freescale/dpaa/dpaa_ethtool.c
+@@ -54,27 +54,19 @@ static char dpaa_stats_global[][ETH_GSTR
+ static int dpaa_get_link_ksettings(struct net_device *net_dev,
+ struct ethtool_link_ksettings *cmd)
+ {
+- if (!net_dev->phydev)
+- return 0;
++ struct dpaa_priv *priv = netdev_priv(net_dev);
++ struct mac_device *mac_dev = priv->mac_dev;
+
+- phy_ethtool_ksettings_get(net_dev->phydev, cmd);
+-
+- return 0;
++ return phylink_ethtool_ksettings_get(mac_dev->phylink, cmd);
+ }
+
+ static int dpaa_set_link_ksettings(struct net_device *net_dev,
+ const struct ethtool_link_ksettings *cmd)
+ {
+- int err;
+-
+- if (!net_dev->phydev)
+- return -ENODEV;
++ struct dpaa_priv *priv = netdev_priv(net_dev);
++ struct mac_device *mac_dev = priv->mac_dev;
+
+- err = phy_ethtool_ksettings_set(net_dev->phydev, cmd);
+- if (err < 0)
+- netdev_err(net_dev, "phy_ethtool_ksettings_set() = %d\n", err);
+-
+- return err;
++ return phylink_ethtool_ksettings_set(mac_dev->phylink, cmd);
+ }
+
+ static void dpaa_get_drvinfo(struct net_device *net_dev,
+@@ -99,80 +91,28 @@ static void dpaa_set_msglevel(struct net
+
+ static int dpaa_nway_reset(struct net_device *net_dev)
+ {
+- int err;
+-
+- if (!net_dev->phydev)
+- return -ENODEV;
++ struct dpaa_priv *priv = netdev_priv(net_dev);
++ struct mac_device *mac_dev = priv->mac_dev;
+
+- err = 0;
+- if (net_dev->phydev->autoneg) {
+- err = phy_start_aneg(net_dev->phydev);
+- if (err < 0)
+- netdev_err(net_dev, "phy_start_aneg() = %d\n",
+- err);
+- }
+-
+- return err;
++ return phylink_ethtool_nway_reset(mac_dev->phylink);
+ }
+
+ static void dpaa_get_pauseparam(struct net_device *net_dev,
+ struct ethtool_pauseparam *epause)
+ {
+- struct mac_device *mac_dev;
+- struct dpaa_priv *priv;
+-
+- priv = netdev_priv(net_dev);
+- mac_dev = priv->mac_dev;
+-
+- if (!net_dev->phydev)
+- return;
++ struct dpaa_priv *priv = netdev_priv(net_dev);
++ struct mac_device *mac_dev = priv->mac_dev;
+
+- epause->autoneg = mac_dev->autoneg_pause;
+- epause->rx_pause = mac_dev->rx_pause_active;
+- epause->tx_pause = mac_dev->tx_pause_active;
++ phylink_ethtool_get_pauseparam(mac_dev->phylink, epause);
+ }
+
+ static int dpaa_set_pauseparam(struct net_device *net_dev,
+ struct ethtool_pauseparam *epause)
+ {
+- struct mac_device *mac_dev;
+- struct phy_device *phydev;
+- bool rx_pause, tx_pause;
+- struct dpaa_priv *priv;
+- int err;
+-
+- priv = netdev_priv(net_dev);
+- mac_dev = priv->mac_dev;
+-
+- phydev = net_dev->phydev;
+- if (!phydev) {
+- netdev_err(net_dev, "phy device not initialized\n");
+- return -ENODEV;
+- }
+-
+- if (!phy_validate_pause(phydev, epause))
+- return -EINVAL;
+-
+- /* The MAC should know how to handle PAUSE frame autonegotiation before
+- * adjust_link is triggered by a forced renegotiation of sym/asym PAUSE
+- * settings.
+- */
+- mac_dev->autoneg_pause = !!epause->autoneg;
+- mac_dev->rx_pause_req = !!epause->rx_pause;
+- mac_dev->tx_pause_req = !!epause->tx_pause;
+-
+- /* Determine the sym/asym advertised PAUSE capabilities from the desired
+- * rx/tx pause settings.
+- */
+-
+- phy_set_asym_pause(phydev, epause->rx_pause, epause->tx_pause);
+-
+- fman_get_pause_cfg(mac_dev, &rx_pause, &tx_pause);
+- err = fman_set_mac_active_pause(mac_dev, rx_pause, tx_pause);
+- if (err < 0)
+- netdev_err(net_dev, "set_mac_active_pause() = %d\n", err);
++ struct dpaa_priv *priv = netdev_priv(net_dev);
++ struct mac_device *mac_dev = priv->mac_dev;
+
+- return err;
++ return phylink_ethtool_set_pauseparam(mac_dev->phylink, epause);
+ }
+
+ static int dpaa_get_sset_count(struct net_device *net_dev, int type)
+--- a/drivers/net/ethernet/freescale/fman/Kconfig
++++ b/drivers/net/ethernet/freescale/fman/Kconfig
+@@ -3,7 +3,6 @@ config FSL_FMAN
+ tristate "FMan support"
+ depends on FSL_SOC || ARCH_LAYERSCAPE || COMPILE_TEST
+ select GENERIC_ALLOCATOR
+- select PHYLIB
+ select PHYLINK
+ select PCS
+ select PCS_LYNX
+--- a/drivers/net/ethernet/freescale/fman/fman_dtsec.c
++++ b/drivers/net/ethernet/freescale/fman/fman_dtsec.c
+@@ -17,6 +17,7 @@
+ #include <linux/crc32.h>
+ #include <linux/of_mdio.h>
+ #include <linux/mii.h>
++#include <linux/netdevice.h>
+
+ /* TBI register addresses */
+ #define MII_TBICON 0x11
+@@ -29,9 +30,6 @@
+ #define TBICON_CLK_SELECT 0x0020 /* Clock select */
+ #define TBICON_MI_MODE 0x0010 /* GMII mode (TBI if not set) */
+
+-#define TBIANA_SGMII 0x4001
+-#define TBIANA_1000X 0x01a0
+-
+ /* Interrupt Mask Register (IMASK) */
+ #define DTSEC_IMASK_BREN 0x80000000
+ #define DTSEC_IMASK_RXCEN 0x40000000
+@@ -92,9 +90,10 @@
+
+ #define DTSEC_ECNTRL_GMIIM 0x00000040
+ #define DTSEC_ECNTRL_TBIM 0x00000020
+-#define DTSEC_ECNTRL_SGMIIM 0x00000002
+ #define DTSEC_ECNTRL_RPM 0x00000010
+ #define DTSEC_ECNTRL_R100M 0x00000008
++#define DTSEC_ECNTRL_RMM 0x00000004
++#define DTSEC_ECNTRL_SGMIIM 0x00000002
+ #define DTSEC_ECNTRL_QSGMIIM 0x00000001
+
+ #define TCTRL_TTSE 0x00000040
+@@ -318,7 +317,8 @@ struct fman_mac {
+ void *fm;
+ struct fman_rev_info fm_rev_info;
+ bool basex_if;
+- struct phy_device *tbiphy;
++ struct mdio_device *tbidev;
++ struct phylink_pcs pcs;
+ };
+
+ static void set_dflts(struct dtsec_cfg *cfg)
+@@ -356,56 +356,14 @@ static int init(struct dtsec_regs __iome
+ phy_interface_t iface, u16 iface_speed, u64 addr,
+ u32 exception_mask, u8 tbi_addr)
+ {
+- bool is_rgmii, is_sgmii, is_qsgmii;
+ enet_addr_t eth_addr;
+- u32 tmp;
++ u32 tmp = 0;
+ int i;
+
+ /* Soft reset */
+ iowrite32be(MACCFG1_SOFT_RESET, &regs->maccfg1);
+ iowrite32be(0, &regs->maccfg1);
+
+- /* dtsec_id2 */
+- tmp = ioread32be(&regs->tsec_id2);
+-
+- /* check RGMII support */
+- if (iface == PHY_INTERFACE_MODE_RGMII ||
+- iface == PHY_INTERFACE_MODE_RGMII_ID ||
+- iface == PHY_INTERFACE_MODE_RGMII_RXID ||
+- iface == PHY_INTERFACE_MODE_RGMII_TXID ||
+- iface == PHY_INTERFACE_MODE_RMII)
+- if (tmp & DTSEC_ID2_INT_REDUCED_OFF)
+- return -EINVAL;
+-
+- if (iface == PHY_INTERFACE_MODE_SGMII ||
+- iface == PHY_INTERFACE_MODE_MII)
+- if (tmp & DTSEC_ID2_INT_REDUCED_OFF)
+- return -EINVAL;
+-
+- is_rgmii = iface == PHY_INTERFACE_MODE_RGMII ||
+- iface == PHY_INTERFACE_MODE_RGMII_ID ||
+- iface == PHY_INTERFACE_MODE_RGMII_RXID ||
+- iface == PHY_INTERFACE_MODE_RGMII_TXID;
+- is_sgmii = iface == PHY_INTERFACE_MODE_SGMII;
+- is_qsgmii = iface == PHY_INTERFACE_MODE_QSGMII;
+-
+- tmp = 0;
+- if (is_rgmii || iface == PHY_INTERFACE_MODE_GMII)
+- tmp |= DTSEC_ECNTRL_GMIIM;
+- if (is_sgmii)
+- tmp |= (DTSEC_ECNTRL_SGMIIM | DTSEC_ECNTRL_TBIM);
+- if (is_qsgmii)
+- tmp |= (DTSEC_ECNTRL_SGMIIM | DTSEC_ECNTRL_TBIM |
+- DTSEC_ECNTRL_QSGMIIM);
+- if (is_rgmii)
+- tmp |= DTSEC_ECNTRL_RPM;
+- if (iface_speed == SPEED_100)
+- tmp |= DTSEC_ECNTRL_R100M;
+-
+- iowrite32be(tmp, &regs->ecntrl);
+-
+- tmp = 0;
+-
+ if (cfg->tx_pause_time)
+ tmp |= cfg->tx_pause_time;
+ if (cfg->tx_pause_time_extd)
+@@ -446,17 +404,10 @@ static int init(struct dtsec_regs __iome
+
+ tmp = 0;
+
+- if (iface_speed < SPEED_1000)
+- tmp |= MACCFG2_NIBBLE_MODE;
+- else if (iface_speed == SPEED_1000)
+- tmp |= MACCFG2_BYTE_MODE;
+-
+ tmp |= (cfg->preamble_len << MACCFG2_PREAMBLE_LENGTH_SHIFT) &
+ MACCFG2_PREAMBLE_LENGTH_MASK;
+ if (cfg->tx_pad_crc)
+ tmp |= MACCFG2_PAD_CRC_EN;
+- /* Full Duplex */
+- tmp |= MACCFG2_FULL_DUPLEX;
+ iowrite32be(tmp, &regs->maccfg2);
+
+ tmp = (((cfg->non_back_to_back_ipg1 <<
+@@ -525,10 +476,6 @@ static void set_bucket(struct dtsec_regs
+
+ static int check_init_parameters(struct fman_mac *dtsec)
+ {
+- if (dtsec->max_speed >= SPEED_10000) {
+- pr_err("1G MAC driver supports 1G or lower speeds\n");
+- return -EINVAL;
+- }
+ if ((dtsec->dtsec_drv_param)->rx_prepend >
+ MAX_PACKET_ALIGNMENT) {
+ pr_err("packetAlignmentPadding can't be > than %d\n",
+@@ -630,22 +577,10 @@ static int get_exception_flag(enum fman_
+ return bit_mask;
+ }
+
+-static bool is_init_done(struct dtsec_cfg *dtsec_drv_params)
+-{
+- /* Checks if dTSEC driver parameters were initialized */
+- if (!dtsec_drv_params)
+- return true;
+-
+- return false;
+-}
+-
+ static u16 dtsec_get_max_frame_length(struct fman_mac *dtsec)
+ {
+ struct dtsec_regs __iomem *regs = dtsec->regs;
+
+- if (is_init_done(dtsec->dtsec_drv_param))
+- return 0;
+-
+ return (u16)ioread32be(&regs->maxfrm);
+ }
+
+@@ -682,6 +617,7 @@ static void dtsec_isr(void *handle)
+ dtsec->exception_cb(dtsec->dev_id, FM_MAC_EX_1G_COL_RET_LMT);
+ if (event & DTSEC_IMASK_XFUNEN) {
+ /* FM_TX_LOCKUP_ERRATA_DTSEC6 Errata workaround */
++ /* FIXME: This races with the rest of the driver! */
+ if (dtsec->fm_rev_info.major == 2) {
+ u32 tpkt1, tmp_reg1, tpkt2, tmp_reg2, i;
+ /* a. Write 0x00E0_0C00 to DTSEC_ID
+@@ -814,6 +750,43 @@ static void free_init_resources(struct f
+ dtsec->unicast_addr_hash = NULL;
+ }
+
++static struct fman_mac *pcs_to_dtsec(struct phylink_pcs *pcs)
++{
++ return container_of(pcs, struct fman_mac, pcs);
++}
++
++static void dtsec_pcs_get_state(struct phylink_pcs *pcs,
++ struct phylink_link_state *state)
++{
++ struct fman_mac *dtsec = pcs_to_dtsec(pcs);
++
++ phylink_mii_c22_pcs_get_state(dtsec->tbidev, state);
++}
++
++static int dtsec_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
++ phy_interface_t interface,
++ const unsigned long *advertising,
++ bool permit_pause_to_mac)
++{
++ struct fman_mac *dtsec = pcs_to_dtsec(pcs);
++
++ return phylink_mii_c22_pcs_config(dtsec->tbidev, mode, interface,
++ advertising);
++}
++
++static void dtsec_pcs_an_restart(struct phylink_pcs *pcs)
++{
++ struct fman_mac *dtsec = pcs_to_dtsec(pcs);
++
++ phylink_mii_c22_pcs_an_restart(dtsec->tbidev);
++}
++
++static const struct phylink_pcs_ops dtsec_pcs_ops = {
++ .pcs_get_state = dtsec_pcs_get_state,
++ .pcs_config = dtsec_pcs_config,
++ .pcs_an_restart = dtsec_pcs_an_restart,
++};
++
+ static void graceful_start(struct fman_mac *dtsec)
+ {
+ struct dtsec_regs __iomem *regs = dtsec->regs;
+@@ -854,36 +827,11 @@ static void graceful_stop(struct fman_ma
+
+ static int dtsec_enable(struct fman_mac *dtsec)
+ {
+- struct dtsec_regs __iomem *regs = dtsec->regs;
+- u32 tmp;
+-
+- if (!is_init_done(dtsec->dtsec_drv_param))
+- return -EINVAL;
+-
+- /* Enable */
+- tmp = ioread32be(&regs->maccfg1);
+- tmp |= MACCFG1_RX_EN | MACCFG1_TX_EN;
+- iowrite32be(tmp, &regs->maccfg1);
+-
+- /* Graceful start - clear the graceful Rx/Tx stop bit */
+- graceful_start(dtsec);
+-
+ return 0;
+ }
+
+ static void dtsec_disable(struct fman_mac *dtsec)
+ {
+- struct dtsec_regs __iomem *regs = dtsec->regs;
+- u32 tmp;
+-
+- WARN_ON_ONCE(!is_init_done(dtsec->dtsec_drv_param));
+-
+- /* Graceful stop - Assert the graceful Rx/Tx stop bit */
+- graceful_stop(dtsec);
+-
+- tmp = ioread32be(&regs->maccfg1);
+- tmp &= ~(MACCFG1_RX_EN | MACCFG1_TX_EN);
+- iowrite32be(tmp, &regs->maccfg1);
+ }
+
+ static int dtsec_set_tx_pause_frames(struct fman_mac *dtsec,
+@@ -894,11 +842,6 @@ static int dtsec_set_tx_pause_frames(str
+ struct dtsec_regs __iomem *regs = dtsec->regs;
+ u32 ptv = 0;
+
+- if (!is_init_done(dtsec->dtsec_drv_param))
+- return -EINVAL;
+-
+- graceful_stop(dtsec);
+-
+ if (pause_time) {
+ /* FM_BAD_TX_TS_IN_B_2_B_ERRATA_DTSEC_A003 Errata workaround */
+ if (dtsec->fm_rev_info.major == 2 && pause_time <= 320) {
+@@ -919,8 +862,6 @@ static int dtsec_set_tx_pause_frames(str
+ iowrite32be(ioread32be(&regs->maccfg1) & ~MACCFG1_TX_FLOW,
+ &regs->maccfg1);
+
+- graceful_start(dtsec);
+-
+ return 0;
+ }
+
+@@ -929,11 +870,6 @@ static int dtsec_accept_rx_pause_frames(
+ struct dtsec_regs __iomem *regs = dtsec->regs;
+ u32 tmp;
+
+- if (!is_init_done(dtsec->dtsec_drv_param))
+- return -EINVAL;
+-
+- graceful_stop(dtsec);
+-
+ tmp = ioread32be(&regs->maccfg1);
+ if (en)
+ tmp |= MACCFG1_RX_FLOW;
+@@ -941,17 +877,125 @@ static int dtsec_accept_rx_pause_frames(
+ tmp &= ~MACCFG1_RX_FLOW;
+ iowrite32be(tmp, &regs->maccfg1);
+
++ return 0;
++}
++
++static struct phylink_pcs *dtsec_select_pcs(struct phylink_config *config,
++ phy_interface_t iface)
++{
++ struct fman_mac *dtsec = fman_config_to_mac(config)->fman_mac;
++
++ switch (iface) {
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_1000BASEX:
++ case PHY_INTERFACE_MODE_2500BASEX:
++ return &dtsec->pcs;
++ default:
++ return NULL;
++ }
++}
++
++static void dtsec_mac_config(struct phylink_config *config, unsigned int mode,
++ const struct phylink_link_state *state)
++{
++ struct mac_device *mac_dev = fman_config_to_mac(config);
++ struct dtsec_regs __iomem *regs = mac_dev->fman_mac->regs;
++ u32 tmp;
++
++ switch (state->interface) {
++ case PHY_INTERFACE_MODE_RMII:
++ tmp = DTSEC_ECNTRL_RMM;
++ break;
++ case PHY_INTERFACE_MODE_RGMII:
++ case PHY_INTERFACE_MODE_RGMII_ID:
++ case PHY_INTERFACE_MODE_RGMII_RXID:
++ case PHY_INTERFACE_MODE_RGMII_TXID:
++ tmp = DTSEC_ECNTRL_GMIIM | DTSEC_ECNTRL_RPM;
++ break;
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_1000BASEX:
++ case PHY_INTERFACE_MODE_2500BASEX:
++ tmp = DTSEC_ECNTRL_TBIM | DTSEC_ECNTRL_SGMIIM;
++ break;
++ default:
++ dev_warn(mac_dev->dev, "cannot configure dTSEC for %s\n",
++ phy_modes(state->interface));
++ return;
++ }
++
++ iowrite32be(tmp, &regs->ecntrl);
++}
++
++static void dtsec_link_up(struct phylink_config *config, struct phy_device *phy,
++ unsigned int mode, phy_interface_t interface,
++ int speed, int duplex, bool tx_pause, bool rx_pause)
++{
++ struct mac_device *mac_dev = fman_config_to_mac(config);
++ struct fman_mac *dtsec = mac_dev->fman_mac;
++ struct dtsec_regs __iomem *regs = dtsec->regs;
++ u16 pause_time = tx_pause ? FSL_FM_PAUSE_TIME_ENABLE :
++ FSL_FM_PAUSE_TIME_DISABLE;
++ u32 tmp;
++
++ dtsec_set_tx_pause_frames(dtsec, 0, pause_time, 0);
++ dtsec_accept_rx_pause_frames(dtsec, rx_pause);
++
++ tmp = ioread32be(&regs->ecntrl);
++ if (speed == SPEED_100)
++ tmp |= DTSEC_ECNTRL_R100M;
++ else
++ tmp &= ~DTSEC_ECNTRL_R100M;
++ iowrite32be(tmp, &regs->ecntrl);
++
++ tmp = ioread32be(&regs->maccfg2);
++ tmp &= ~(MACCFG2_NIBBLE_MODE | MACCFG2_BYTE_MODE | MACCFG2_FULL_DUPLEX);
++ if (speed >= SPEED_1000)
++ tmp |= MACCFG2_BYTE_MODE;
++ else
++ tmp |= MACCFG2_NIBBLE_MODE;
++
++ if (duplex == DUPLEX_FULL)
++ tmp |= MACCFG2_FULL_DUPLEX;
++
++ iowrite32be(tmp, &regs->maccfg2);
++
++ mac_dev->update_speed(mac_dev, speed);
++
++ /* Enable */
++ tmp = ioread32be(&regs->maccfg1);
++ tmp |= MACCFG1_RX_EN | MACCFG1_TX_EN;
++ iowrite32be(tmp, &regs->maccfg1);
++
++ /* Graceful start - clear the graceful Rx/Tx stop bit */
+ graceful_start(dtsec);
++}
+
+- return 0;
++static void dtsec_link_down(struct phylink_config *config, unsigned int mode,
++ phy_interface_t interface)
++{
++ struct fman_mac *dtsec = fman_config_to_mac(config)->fman_mac;
++ struct dtsec_regs __iomem *regs = dtsec->regs;
++ u32 tmp;
++
++ /* Graceful stop - Assert the graceful Rx/Tx stop bit */
++ graceful_stop(dtsec);
++
++ tmp = ioread32be(&regs->maccfg1);
++ tmp &= ~(MACCFG1_RX_EN | MACCFG1_TX_EN);
++ iowrite32be(tmp, &regs->maccfg1);
+ }
+
++static const struct phylink_mac_ops dtsec_mac_ops = {
++ .validate = phylink_generic_validate,
++ .mac_select_pcs = dtsec_select_pcs,
++ .mac_config = dtsec_mac_config,
++ .mac_link_up = dtsec_link_up,
++ .mac_link_down = dtsec_link_down,
++};
++
+ static int dtsec_modify_mac_address(struct fman_mac *dtsec,
+ const enet_addr_t *enet_addr)
+ {
+- if (!is_init_done(dtsec->dtsec_drv_param))
+- return -EINVAL;
+-
+ graceful_stop(dtsec);
+
+ /* Initialize MAC Station Address registers (1 & 2)
+@@ -975,9 +1019,6 @@ static int dtsec_add_hash_mac_address(st
+ u32 crc = 0xFFFFFFFF;
+ bool mcast, ghtx;
+
+- if (!is_init_done(dtsec->dtsec_drv_param))
+- return -EINVAL;
+-
+ addr = ENET_ADDR_TO_UINT64(*eth_addr);
+
+ ghtx = (bool)((ioread32be(&regs->rctrl) & RCTRL_GHTX) ? true : false);
+@@ -1037,9 +1078,6 @@ static int dtsec_set_allmulti(struct fma
+ u32 tmp;
+ struct dtsec_regs __iomem *regs = dtsec->regs;
+
+- if (!is_init_done(dtsec->dtsec_drv_param))
+- return -EINVAL;
+-
+ tmp = ioread32be(&regs->rctrl);
+ if (enable)
+ tmp |= RCTRL_MPROM;
+@@ -1056,9 +1094,6 @@ static int dtsec_set_tstamp(struct fman_
+ struct dtsec_regs __iomem *regs = dtsec->regs;
+ u32 rctrl, tctrl;
+
+- if (!is_init_done(dtsec->dtsec_drv_param))
+- return -EINVAL;
+-
+ rctrl = ioread32be(&regs->rctrl);
+ tctrl = ioread32be(&regs->tctrl);
+
+@@ -1087,9 +1122,6 @@ static int dtsec_del_hash_mac_address(st
+ u32 crc = 0xFFFFFFFF;
+ bool mcast, ghtx;
+
+- if (!is_init_done(dtsec->dtsec_drv_param))
+- return -EINVAL;
+-
+ addr = ENET_ADDR_TO_UINT64(*eth_addr);
+
+ ghtx = (bool)((ioread32be(&regs->rctrl) & RCTRL_GHTX) ? true : false);
+@@ -1153,9 +1185,6 @@ static int dtsec_set_promiscuous(struct
+ struct dtsec_regs __iomem *regs = dtsec->regs;
+ u32 tmp;
+
+- if (!is_init_done(dtsec->dtsec_drv_param))
+- return -EINVAL;
+-
+ /* Set unicast promiscuous */
+ tmp = ioread32be(&regs->rctrl);
+ if (new_val)
+@@ -1177,90 +1206,12 @@ static int dtsec_set_promiscuous(struct
+ return 0;
+ }
+
+-static int dtsec_adjust_link(struct fman_mac *dtsec, u16 speed)
+-{
+- struct dtsec_regs __iomem *regs = dtsec->regs;
+- u32 tmp;
+-
+- if (!is_init_done(dtsec->dtsec_drv_param))
+- return -EINVAL;
+-
+- graceful_stop(dtsec);
+-
+- tmp = ioread32be(&regs->maccfg2);
+-
+- /* Full Duplex */
+- tmp |= MACCFG2_FULL_DUPLEX;
+-
+- tmp &= ~(MACCFG2_NIBBLE_MODE | MACCFG2_BYTE_MODE);
+- if (speed < SPEED_1000)
+- tmp |= MACCFG2_NIBBLE_MODE;
+- else if (speed == SPEED_1000)
+- tmp |= MACCFG2_BYTE_MODE;
+- iowrite32be(tmp, &regs->maccfg2);
+-
+- tmp = ioread32be(&regs->ecntrl);
+- if (speed == SPEED_100)
+- tmp |= DTSEC_ECNTRL_R100M;
+- else
+- tmp &= ~DTSEC_ECNTRL_R100M;
+- iowrite32be(tmp, &regs->ecntrl);
+-
+- graceful_start(dtsec);
+-
+- return 0;
+-}
+-
+-static int dtsec_restart_autoneg(struct fman_mac *dtsec)
+-{
+- u16 tmp_reg16;
+-
+- if (!is_init_done(dtsec->dtsec_drv_param))
+- return -EINVAL;
+-
+- tmp_reg16 = phy_read(dtsec->tbiphy, MII_BMCR);
+-
+- tmp_reg16 &= ~(BMCR_SPEED100 | BMCR_SPEED1000);
+- tmp_reg16 |= (BMCR_ANENABLE | BMCR_ANRESTART |
+- BMCR_FULLDPLX | BMCR_SPEED1000);
+-
+- phy_write(dtsec->tbiphy, MII_BMCR, tmp_reg16);
+-
+- return 0;
+-}
+-
+-static void adjust_link_dtsec(struct mac_device *mac_dev)
+-{
+- struct phy_device *phy_dev = mac_dev->phy_dev;
+- struct fman_mac *fman_mac;
+- bool rx_pause, tx_pause;
+- int err;
+-
+- fman_mac = mac_dev->fman_mac;
+- if (!phy_dev->link) {
+- dtsec_restart_autoneg(fman_mac);
+-
+- return;
+- }
+-
+- dtsec_adjust_link(fman_mac, phy_dev->speed);
+- mac_dev->update_speed(mac_dev, phy_dev->speed);
+- fman_get_pause_cfg(mac_dev, &rx_pause, &tx_pause);
+- err = fman_set_mac_active_pause(mac_dev, rx_pause, tx_pause);
+- if (err < 0)
+- dev_err(mac_dev->dev, "fman_set_mac_active_pause() = %d\n",
+- err);
+-}
+-
+ static int dtsec_set_exception(struct fman_mac *dtsec,
+ enum fman_mac_exceptions exception, bool enable)
+ {
+ struct dtsec_regs __iomem *regs = dtsec->regs;
+ u32 bit_mask = 0;
+
+- if (!is_init_done(dtsec->dtsec_drv_param))
+- return -EINVAL;
+-
+ if (exception != FM_MAC_EX_1G_1588_TS_RX_ERR) {
+ bit_mask = get_exception_flag(exception);
+ if (bit_mask) {
+@@ -1310,12 +1261,9 @@ static int dtsec_init(struct fman_mac *d
+ {
+ struct dtsec_regs __iomem *regs = dtsec->regs;
+ struct dtsec_cfg *dtsec_drv_param;
+- u16 max_frm_ln;
++ u16 max_frm_ln, tbicon;
+ int err;
+
+- if (is_init_done(dtsec->dtsec_drv_param))
+- return -EINVAL;
+-
+ if (DEFAULT_RESET_ON_INIT &&
+ (fman_reset_mac(dtsec->fm, dtsec->mac_id) != 0)) {
+ pr_err("Can't reset MAC!\n");
+@@ -1330,38 +1278,19 @@ static int dtsec_init(struct fman_mac *d
+
+ err = init(dtsec->regs, dtsec_drv_param, dtsec->phy_if,
+ dtsec->max_speed, dtsec->addr, dtsec->exceptions,
+- dtsec->tbiphy->mdio.addr);
++ dtsec->tbidev->addr);
+ if (err) {
+ free_init_resources(dtsec);
+ pr_err("DTSEC version doesn't support this i/f mode\n");
+ return err;
+ }
+
+- if (dtsec->phy_if == PHY_INTERFACE_MODE_SGMII) {
+- u16 tmp_reg16;
+-
+- /* Configure the TBI PHY Control Register */
+- tmp_reg16 = TBICON_CLK_SELECT | TBICON_SOFT_RESET;
+- phy_write(dtsec->tbiphy, MII_TBICON, tmp_reg16);
+-
+- tmp_reg16 = TBICON_CLK_SELECT;
+- phy_write(dtsec->tbiphy, MII_TBICON, tmp_reg16);
+-
+- tmp_reg16 = (BMCR_RESET | BMCR_ANENABLE |
+- BMCR_FULLDPLX | BMCR_SPEED1000);
+- phy_write(dtsec->tbiphy, MII_BMCR, tmp_reg16);
+-
+- if (dtsec->basex_if)
+- tmp_reg16 = TBIANA_1000X;
+- else
+- tmp_reg16 = TBIANA_SGMII;
+- phy_write(dtsec->tbiphy, MII_ADVERTISE, tmp_reg16);
++ /* Configure the TBI PHY Control Register */
++ tbicon = TBICON_CLK_SELECT | TBICON_SOFT_RESET;
++ mdiodev_write(dtsec->tbidev, MII_TBICON, tbicon);
+
+- tmp_reg16 = (BMCR_ANENABLE | BMCR_ANRESTART |
+- BMCR_FULLDPLX | BMCR_SPEED1000);
+-
+- phy_write(dtsec->tbiphy, MII_BMCR, tmp_reg16);
+- }
++ tbicon = TBICON_CLK_SELECT;
++ mdiodev_write(dtsec->tbidev, MII_TBICON, tbicon);
+
+ /* Max Frame Length */
+ max_frm_ln = (u16)ioread32be(&regs->maxfrm);
+@@ -1406,6 +1335,8 @@ static int dtsec_free(struct fman_mac *d
+
+ kfree(dtsec->dtsec_drv_param);
+ dtsec->dtsec_drv_param = NULL;
++ if (!IS_ERR_OR_NULL(dtsec->tbidev))
++ put_device(&dtsec->tbidev->dev);
+ kfree(dtsec);
+
+ return 0;
+@@ -1434,7 +1365,6 @@ static struct fman_mac *dtsec_config(str
+
+ dtsec->regs = mac_dev->vaddr;
+ dtsec->addr = ENET_ADDR_TO_UINT64(mac_dev->addr);
+- dtsec->max_speed = params->max_speed;
+ dtsec->phy_if = mac_dev->phy_if;
+ dtsec->mac_id = params->mac_id;
+ dtsec->exceptions = (DTSEC_IMASK_BREN |
+@@ -1457,7 +1387,6 @@ static struct fman_mac *dtsec_config(str
+ dtsec->en_tsu_err_exception = dtsec->dtsec_drv_param->ptp_exception_en;
+
+ dtsec->fm = params->fm;
+- dtsec->basex_if = params->basex_if;
+
+ /* Save FMan revision */
+ fman_get_revision(dtsec->fm, &dtsec->fm_rev_info);
+@@ -1476,18 +1405,18 @@ int dtsec_initialization(struct mac_devi
+ int err;
+ struct fman_mac *dtsec;
+ struct device_node *phy_node;
++ unsigned long capabilities;
++ unsigned long *supported;
+
++ mac_dev->phylink_ops = &dtsec_mac_ops;
+ mac_dev->set_promisc = dtsec_set_promiscuous;
+ mac_dev->change_addr = dtsec_modify_mac_address;
+ mac_dev->add_hash_mac_addr = dtsec_add_hash_mac_address;
+ mac_dev->remove_hash_mac_addr = dtsec_del_hash_mac_address;
+- mac_dev->set_tx_pause = dtsec_set_tx_pause_frames;
+- mac_dev->set_rx_pause = dtsec_accept_rx_pause_frames;
+ mac_dev->set_exception = dtsec_set_exception;
+ mac_dev->set_allmulti = dtsec_set_allmulti;
+ mac_dev->set_tstamp = dtsec_set_tstamp;
+ mac_dev->set_multi = fman_set_multi;
+- mac_dev->adjust_link = adjust_link_dtsec;
+ mac_dev->enable = dtsec_enable;
+ mac_dev->disable = dtsec_disable;
+
+@@ -1502,19 +1431,56 @@ int dtsec_initialization(struct mac_devi
+ dtsec->dtsec_drv_param->tx_pad_crc = true;
+
+ phy_node = of_parse_phandle(mac_node, "tbi-handle", 0);
+- if (!phy_node) {
+- pr_err("TBI PHY node is not available\n");
++ if (!phy_node || of_device_is_available(phy_node)) {
++ of_node_put(phy_node);
+ err = -EINVAL;
++ dev_err_probe(mac_dev->dev, err,
++ "TBI PCS node is not available\n");
+ goto _return_fm_mac_free;
+ }
+
+- dtsec->tbiphy = of_phy_find_device(phy_node);
+- if (!dtsec->tbiphy) {
+- pr_err("of_phy_find_device (TBI PHY) failed\n");
+- err = -EINVAL;
++ dtsec->tbidev = of_mdio_find_device(phy_node);
++ of_node_put(phy_node);
++ if (!dtsec->tbidev) {
++ err = -EPROBE_DEFER;
++ dev_err_probe(mac_dev->dev, err,
++ "could not find mdiodev for PCS\n");
+ goto _return_fm_mac_free;
+ }
+- put_device(&dtsec->tbiphy->mdio.dev);
++ dtsec->pcs.ops = &dtsec_pcs_ops;
++ dtsec->pcs.poll = true;
++
++ supported = mac_dev->phylink_config.supported_interfaces;
++
++ /* FIXME: Can we use DTSEC_ID2_INT_FULL_OFF to determine if these are
++ * supported? If not, we can determine support via the phy if SerDes
++ * support is added.
++ */
++ if (mac_dev->phy_if == PHY_INTERFACE_MODE_SGMII ||
++ mac_dev->phy_if == PHY_INTERFACE_MODE_1000BASEX) {
++ __set_bit(PHY_INTERFACE_MODE_SGMII, supported);
++ __set_bit(PHY_INTERFACE_MODE_1000BASEX, supported);
++ } else if (mac_dev->phy_if == PHY_INTERFACE_MODE_2500BASEX) {
++ __set_bit(PHY_INTERFACE_MODE_2500BASEX, supported);
++ }
++
++ if (!(ioread32be(&dtsec->regs->tsec_id2) & DTSEC_ID2_INT_REDUCED_OFF)) {
++ phy_interface_set_rgmii(supported);
++
++ /* DTSEC_ID2_INT_REDUCED_OFF indicates that the dTSEC supports
++ * RMII and RGMII. However, the only SoCs which support RMII
++ * are the P1017 and P1023. Avoid advertising this mode on
++ * other SoCs. This is a bit of a moot point, since there's no
++ * in-tree support for ethernet on these platforms...
++ */
++ if (of_machine_is_compatible("fsl,P1023") ||
++ of_machine_is_compatible("fsl,P1023RDB"))
++ __set_bit(PHY_INTERFACE_MODE_RMII, supported);
++ }
++
++ capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE;
++ capabilities |= MAC_10 | MAC_100 | MAC_1000FD | MAC_2500FD;
++ mac_dev->phylink_config.mac_capabilities = capabilities;
+
+ err = dtsec_init(dtsec);
+ if (err < 0)
+--- a/drivers/net/ethernet/freescale/fman/fman_mac.h
++++ b/drivers/net/ethernet/freescale/fman/fman_mac.h
+@@ -170,20 +170,10 @@ struct fman_mac_params {
+ * 0 - FM_MAX_NUM_OF_10G_MACS
+ */
+ u8 mac_id;
+- /* Note that the speed should indicate the maximum rate that
+- * this MAC should support rather than the actual speed;
+- */
+- u16 max_speed;
+ /* A handle to the FM object this port related to */
+ void *fm;
+ fman_mac_exception_cb *event_cb; /* MDIO Events Callback Routine */
+ fman_mac_exception_cb *exception_cb;/* Exception Callback Routine */
+- /* SGMII/QSGII interface with 1000BaseX auto-negotiation between MAC
+- * and phy or backplane; Note: 1000BaseX auto-negotiation relates only
+- * to interface between MAC and phy/backplane, SGMII phy can still
+- * synchronize with far-end phy at 10Mbps, 100Mbps or 1000Mbps
+- */
+- bool basex_if;
+ };
+
+ struct eth_hash_t {
+--- a/drivers/net/ethernet/freescale/fman/fman_memac.c
++++ b/drivers/net/ethernet/freescale/fman/fman_memac.c
+@@ -278,9 +278,6 @@ struct fman_mac {
+ struct memac_regs __iomem *regs;
+ /* MAC address of device */
+ u64 addr;
+- /* Ethernet physical interface */
+- phy_interface_t phy_if;
+- u16 max_speed;
+ struct mac_device *dev_id; /* device cookie used by the exception cbs */
+ fman_mac_exception_cb *exception_cb;
+ fman_mac_exception_cb *event_cb;
+@@ -293,12 +290,12 @@ struct fman_mac {
+ struct memac_cfg *memac_drv_param;
+ void *fm;
+ struct fman_rev_info fm_rev_info;
+- bool basex_if;
+ struct phy *serdes;
+ struct phylink_pcs *sgmii_pcs;
+ struct phylink_pcs *qsgmii_pcs;
+ struct phylink_pcs *xfi_pcs;
+ bool allmulti_enabled;
++ bool rgmii_no_half_duplex;
+ };
+
+ static void add_addr_in_paddr(struct memac_regs __iomem *regs, const u8 *adr,
+@@ -356,7 +353,6 @@ static void set_exception(struct memac_r
+ }
+
+ static int init(struct memac_regs __iomem *regs, struct memac_cfg *cfg,
+- phy_interface_t phy_if, u16 speed, bool slow_10g_if,
+ u32 exceptions)
+ {
+ u32 tmp;
+@@ -384,41 +380,6 @@ static int init(struct memac_regs __iome
+ iowrite32be((u32)cfg->pause_quanta, &regs->pause_quanta[0]);
+ iowrite32be((u32)0, &regs->pause_thresh[0]);
+
+- /* IF_MODE */
+- tmp = 0;
+- switch (phy_if) {
+- case PHY_INTERFACE_MODE_XGMII:
+- tmp |= IF_MODE_10G;
+- break;
+- case PHY_INTERFACE_MODE_MII:
+- tmp |= IF_MODE_MII;
+- break;
+- default:
+- tmp |= IF_MODE_GMII;
+- if (phy_if == PHY_INTERFACE_MODE_RGMII ||
+- phy_if == PHY_INTERFACE_MODE_RGMII_ID ||
+- phy_if == PHY_INTERFACE_MODE_RGMII_RXID ||
+- phy_if == PHY_INTERFACE_MODE_RGMII_TXID)
+- tmp |= IF_MODE_RGMII | IF_MODE_RGMII_AUTO;
+- }
+- iowrite32be(tmp, &regs->if_mode);
+-
+- /* TX_FIFO_SECTIONS */
+- tmp = 0;
+- if (phy_if == PHY_INTERFACE_MODE_XGMII) {
+- if (slow_10g_if) {
+- tmp |= (TX_FIFO_SECTIONS_TX_AVAIL_SLOW_10G |
+- TX_FIFO_SECTIONS_TX_EMPTY_DEFAULT_10G);
+- } else {
+- tmp |= (TX_FIFO_SECTIONS_TX_AVAIL_10G |
+- TX_FIFO_SECTIONS_TX_EMPTY_DEFAULT_10G);
+- }
+- } else {
+- tmp |= (TX_FIFO_SECTIONS_TX_AVAIL_1G |
+- TX_FIFO_SECTIONS_TX_EMPTY_DEFAULT_1G);
+- }
+- iowrite32be(tmp, &regs->tx_fifo_sections);
+-
+ /* clear all pending events and set-up interrupts */
+ iowrite32be(0xffffffff, &regs->ievent);
+ set_exception(regs, exceptions, true);
+@@ -458,24 +419,6 @@ static u32 get_mac_addr_hash_code(u64 et
+ return xor_val;
+ }
+
+-static void setup_sgmii_internal(struct fman_mac *memac,
+- struct phylink_pcs *pcs,
+- struct fixed_phy_status *fixed_link)
+-{
+- __ETHTOOL_DECLARE_LINK_MODE_MASK(advertising);
+- phy_interface_t iface = memac->basex_if ? PHY_INTERFACE_MODE_1000BASEX :
+- PHY_INTERFACE_MODE_SGMII;
+- unsigned int mode = fixed_link ? MLO_AN_FIXED : MLO_AN_INBAND;
+-
+- linkmode_set_pause(advertising, true, true);
+- pcs->ops->pcs_config(pcs, mode, iface, advertising, true);
+- if (fixed_link)
+- pcs->ops->pcs_link_up(pcs, mode, iface, fixed_link->speed,
+- fixed_link->duplex);
+- else
+- pcs->ops->pcs_an_restart(pcs);
+-}
+-
+ static int check_init_parameters(struct fman_mac *memac)
+ {
+ if (!memac->exception_cb) {
+@@ -581,41 +524,31 @@ static void free_init_resources(struct f
+ memac->unicast_addr_hash = NULL;
+ }
+
+-static bool is_init_done(struct memac_cfg *memac_drv_params)
+-{
+- /* Checks if mEMAC driver parameters were initialized */
+- if (!memac_drv_params)
+- return true;
+-
+- return false;
+-}
+-
+ static int memac_enable(struct fman_mac *memac)
+ {
+- struct memac_regs __iomem *regs = memac->regs;
+- u32 tmp;
++ int ret;
+
+- if (!is_init_done(memac->memac_drv_param))
+- return -EINVAL;
++ ret = phy_init(memac->serdes);
++ if (ret) {
++ dev_err(memac->dev_id->dev,
++ "could not initialize serdes: %pe\n", ERR_PTR(ret));
++ return ret;
++ }
+
+- tmp = ioread32be(&regs->command_config);
+- tmp |= CMD_CFG_RX_EN | CMD_CFG_TX_EN;
+- iowrite32be(tmp, &regs->command_config);
++ ret = phy_power_on(memac->serdes);
++ if (ret) {
++ dev_err(memac->dev_id->dev,
++ "could not power on serdes: %pe\n", ERR_PTR(ret));
++ phy_exit(memac->serdes);
++ }
+
+- return 0;
++ return ret;
+ }
+
+ static void memac_disable(struct fman_mac *memac)
+-
+ {
+- struct memac_regs __iomem *regs = memac->regs;
+- u32 tmp;
+-
+- WARN_ON_ONCE(!is_init_done(memac->memac_drv_param));
+-
+- tmp = ioread32be(&regs->command_config);
+- tmp &= ~(CMD_CFG_RX_EN | CMD_CFG_TX_EN);
+- iowrite32be(tmp, &regs->command_config);
++ phy_power_off(memac->serdes);
++ phy_exit(memac->serdes);
+ }
+
+ static int memac_set_promiscuous(struct fman_mac *memac, bool new_val)
+@@ -623,9 +556,6 @@ static int memac_set_promiscuous(struct
+ struct memac_regs __iomem *regs = memac->regs;
+ u32 tmp;
+
+- if (!is_init_done(memac->memac_drv_param))
+- return -EINVAL;
+-
+ tmp = ioread32be(&regs->command_config);
+ if (new_val)
+ tmp |= CMD_CFG_PROMIS_EN;
+@@ -637,73 +567,12 @@ static int memac_set_promiscuous(struct
+ return 0;
+ }
+
+-static int memac_adjust_link(struct fman_mac *memac, u16 speed)
+-{
+- struct memac_regs __iomem *regs = memac->regs;
+- u32 tmp;
+-
+- if (!is_init_done(memac->memac_drv_param))
+- return -EINVAL;
+-
+- tmp = ioread32be(&regs->if_mode);
+-
+- /* Set full duplex */
+- tmp &= ~IF_MODE_HD;
+-
+- if (phy_interface_mode_is_rgmii(memac->phy_if)) {
+- /* Configure RGMII in manual mode */
+- tmp &= ~IF_MODE_RGMII_AUTO;
+- tmp &= ~IF_MODE_RGMII_SP_MASK;
+- /* Full duplex */
+- tmp |= IF_MODE_RGMII_FD;
+-
+- switch (speed) {
+- case SPEED_1000:
+- tmp |= IF_MODE_RGMII_1000;
+- break;
+- case SPEED_100:
+- tmp |= IF_MODE_RGMII_100;
+- break;
+- case SPEED_10:
+- tmp |= IF_MODE_RGMII_10;
+- break;
+- default:
+- break;
+- }
+- }
+-
+- iowrite32be(tmp, &regs->if_mode);
+-
+- return 0;
+-}
+-
+-static void adjust_link_memac(struct mac_device *mac_dev)
+-{
+- struct phy_device *phy_dev = mac_dev->phy_dev;
+- struct fman_mac *fman_mac;
+- bool rx_pause, tx_pause;
+- int err;
+-
+- fman_mac = mac_dev->fman_mac;
+- memac_adjust_link(fman_mac, phy_dev->speed);
+- mac_dev->update_speed(mac_dev, phy_dev->speed);
+-
+- fman_get_pause_cfg(mac_dev, &rx_pause, &tx_pause);
+- err = fman_set_mac_active_pause(mac_dev, rx_pause, tx_pause);
+- if (err < 0)
+- dev_err(mac_dev->dev, "fman_set_mac_active_pause() = %d\n",
+- err);
+-}
+-
+ static int memac_set_tx_pause_frames(struct fman_mac *memac, u8 priority,
+ u16 pause_time, u16 thresh_time)
+ {
+ struct memac_regs __iomem *regs = memac->regs;
+ u32 tmp;
+
+- if (!is_init_done(memac->memac_drv_param))
+- return -EINVAL;
+-
+ tmp = ioread32be(&regs->tx_fifo_sections);
+
+ GET_TX_EMPTY_DEFAULT_VALUE(tmp);
+@@ -738,9 +607,6 @@ static int memac_accept_rx_pause_frames(
+ struct memac_regs __iomem *regs = memac->regs;
+ u32 tmp;
+
+- if (!is_init_done(memac->memac_drv_param))
+- return -EINVAL;
+-
+ tmp = ioread32be(&regs->command_config);
+ if (en)
+ tmp &= ~CMD_CFG_PAUSE_IGNORE;
+@@ -752,12 +618,175 @@ static int memac_accept_rx_pause_frames(
+ return 0;
+ }
+
++static void memac_validate(struct phylink_config *config,
++ unsigned long *supported,
++ struct phylink_link_state *state)
++{
++ struct fman_mac *memac = fman_config_to_mac(config)->fman_mac;
++ unsigned long caps = config->mac_capabilities;
++
++ if (phy_interface_mode_is_rgmii(state->interface) &&
++ memac->rgmii_no_half_duplex)
++ caps &= ~(MAC_10HD | MAC_100HD);
++
++ phylink_validate_mask_caps(supported, state, caps);
++}
++
++/**
++ * memac_if_mode() - Convert an interface mode into an IF_MODE config
++ * @interface: A phy interface mode
++ *
++ * Return: A configuration word, suitable for programming into the lower bits
++ * of %IF_MODE.
++ */
++static u32 memac_if_mode(phy_interface_t interface)
++{
++ switch (interface) {
++ case PHY_INTERFACE_MODE_MII:
++ return IF_MODE_MII;
++ case PHY_INTERFACE_MODE_RGMII:
++ case PHY_INTERFACE_MODE_RGMII_ID:
++ case PHY_INTERFACE_MODE_RGMII_RXID:
++ case PHY_INTERFACE_MODE_RGMII_TXID:
++ return IF_MODE_GMII | IF_MODE_RGMII;
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_1000BASEX:
++ case PHY_INTERFACE_MODE_QSGMII:
++ return IF_MODE_GMII;
++ case PHY_INTERFACE_MODE_10GBASER:
++ return IF_MODE_10G;
++ default:
++ WARN_ON_ONCE(1);
++ return 0;
++ }
++}
++
++static struct phylink_pcs *memac_select_pcs(struct phylink_config *config,
++ phy_interface_t iface)
++{
++ struct fman_mac *memac = fman_config_to_mac(config)->fman_mac;
++
++ switch (iface) {
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_1000BASEX:
++ return memac->sgmii_pcs;
++ case PHY_INTERFACE_MODE_QSGMII:
++ return memac->qsgmii_pcs;
++ case PHY_INTERFACE_MODE_10GBASER:
++ return memac->xfi_pcs;
++ default:
++ return NULL;
++ }
++}
++
++static int memac_prepare(struct phylink_config *config, unsigned int mode,
++ phy_interface_t iface)
++{
++ struct fman_mac *memac = fman_config_to_mac(config)->fman_mac;
++
++ switch (iface) {
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_1000BASEX:
++ case PHY_INTERFACE_MODE_QSGMII:
++ case PHY_INTERFACE_MODE_10GBASER:
++ return phy_set_mode_ext(memac->serdes, PHY_MODE_ETHERNET,
++ iface);
++ default:
++ return 0;
++ }
++}
++
++static void memac_mac_config(struct phylink_config *config, unsigned int mode,
++ const struct phylink_link_state *state)
++{
++ struct mac_device *mac_dev = fman_config_to_mac(config);
++ struct memac_regs __iomem *regs = mac_dev->fman_mac->regs;
++ u32 tmp = ioread32be(&regs->if_mode);
++
++ tmp &= ~(IF_MODE_MASK | IF_MODE_RGMII);
++ tmp |= memac_if_mode(state->interface);
++ if (phylink_autoneg_inband(mode))
++ tmp |= IF_MODE_RGMII_AUTO;
++ iowrite32be(tmp, &regs->if_mode);
++}
++
++static void memac_link_up(struct phylink_config *config, struct phy_device *phy,
++ unsigned int mode, phy_interface_t interface,
++ int speed, int duplex, bool tx_pause, bool rx_pause)
++{
++ struct mac_device *mac_dev = fman_config_to_mac(config);
++ struct fman_mac *memac = mac_dev->fman_mac;
++ struct memac_regs __iomem *regs = memac->regs;
++ u32 tmp = memac_if_mode(interface);
++ u16 pause_time = tx_pause ? FSL_FM_PAUSE_TIME_ENABLE :
++ FSL_FM_PAUSE_TIME_DISABLE;
++
++ memac_set_tx_pause_frames(memac, 0, pause_time, 0);
++ memac_accept_rx_pause_frames(memac, rx_pause);
++
++ if (duplex == DUPLEX_HALF)
++ tmp |= IF_MODE_HD;
++
++ switch (speed) {
++ case SPEED_1000:
++ tmp |= IF_MODE_RGMII_1000;
++ break;
++ case SPEED_100:
++ tmp |= IF_MODE_RGMII_100;
++ break;
++ case SPEED_10:
++ tmp |= IF_MODE_RGMII_10;
++ break;
++ }
++ iowrite32be(tmp, &regs->if_mode);
++
++ /* TODO: EEE? */
++
++ if (speed == SPEED_10000) {
++ if (memac->fm_rev_info.major == 6 &&
++ memac->fm_rev_info.minor == 4)
++ tmp = TX_FIFO_SECTIONS_TX_AVAIL_SLOW_10G;
++ else
++ tmp = TX_FIFO_SECTIONS_TX_AVAIL_10G;
++ tmp |= TX_FIFO_SECTIONS_TX_EMPTY_DEFAULT_10G;
++ } else {
++ tmp = TX_FIFO_SECTIONS_TX_AVAIL_1G |
++ TX_FIFO_SECTIONS_TX_EMPTY_DEFAULT_1G;
++ }
++ iowrite32be(tmp, &regs->tx_fifo_sections);
++
++ mac_dev->update_speed(mac_dev, speed);
++
++ tmp = ioread32be(&regs->command_config);
++ tmp |= CMD_CFG_RX_EN | CMD_CFG_TX_EN;
++ iowrite32be(tmp, &regs->command_config);
++}
++
++static void memac_link_down(struct phylink_config *config, unsigned int mode,
++ phy_interface_t interface)
++{
++ struct fman_mac *memac = fman_config_to_mac(config)->fman_mac;
++ struct memac_regs __iomem *regs = memac->regs;
++ u32 tmp;
++
++ /* TODO: graceful */
++ tmp = ioread32be(&regs->command_config);
++ tmp &= ~(CMD_CFG_RX_EN | CMD_CFG_TX_EN);
++ iowrite32be(tmp, &regs->command_config);
++}
++
++static const struct phylink_mac_ops memac_mac_ops = {
++ .validate = memac_validate,
++ .mac_select_pcs = memac_select_pcs,
++ .mac_prepare = memac_prepare,
++ .mac_config = memac_mac_config,
++ .mac_link_up = memac_link_up,
++ .mac_link_down = memac_link_down,
++};
++
+ static int memac_modify_mac_address(struct fman_mac *memac,
+ const enet_addr_t *enet_addr)
+ {
+- if (!is_init_done(memac->memac_drv_param))
+- return -EINVAL;
+-
+ add_addr_in_paddr(memac->regs, (const u8 *)(*enet_addr), 0);
+
+ return 0;
+@@ -771,9 +800,6 @@ static int memac_add_hash_mac_address(st
+ u32 hash;
+ u64 addr;
+
+- if (!is_init_done(memac->memac_drv_param))
+- return -EINVAL;
+-
+ addr = ENET_ADDR_TO_UINT64(*eth_addr);
+
+ if (!(addr & GROUP_ADDRESS)) {
+@@ -802,9 +828,6 @@ static int memac_set_allmulti(struct fma
+ u32 entry;
+ struct memac_regs __iomem *regs = memac->regs;
+
+- if (!is_init_done(memac->memac_drv_param))
+- return -EINVAL;
+-
+ if (enable) {
+ for (entry = 0; entry < HASH_TABLE_SIZE; entry++)
+ iowrite32be(entry | HASH_CTRL_MCAST_EN,
+@@ -834,9 +857,6 @@ static int memac_del_hash_mac_address(st
+ u32 hash;
+ u64 addr;
+
+- if (!is_init_done(memac->memac_drv_param))
+- return -EINVAL;
+-
+ addr = ENET_ADDR_TO_UINT64(*eth_addr);
+
+ hash = get_mac_addr_hash_code(addr) & HASH_CTRL_ADDR_MASK;
+@@ -864,9 +884,6 @@ static int memac_set_exception(struct fm
+ {
+ u32 bit_mask = 0;
+
+- if (!is_init_done(memac->memac_drv_param))
+- return -EINVAL;
+-
+ bit_mask = get_exception_flag(exception);
+ if (bit_mask) {
+ if (enable)
+@@ -886,23 +903,15 @@ static int memac_init(struct fman_mac *m
+ {
+ struct memac_cfg *memac_drv_param;
+ enet_addr_t eth_addr;
+- bool slow_10g_if = false;
+- struct fixed_phy_status *fixed_link = NULL;
+ int err;
+ u32 reg32 = 0;
+
+- if (is_init_done(memac->memac_drv_param))
+- return -EINVAL;
+-
+ err = check_init_parameters(memac);
+ if (err)
+ return err;
+
+ memac_drv_param = memac->memac_drv_param;
+
+- if (memac->fm_rev_info.major == 6 && memac->fm_rev_info.minor == 4)
+- slow_10g_if = true;
+-
+ /* First, reset the MAC if desired. */
+ if (memac_drv_param->reset_on_init) {
+ err = reset(memac->regs);
+@@ -918,10 +927,7 @@ static int memac_init(struct fman_mac *m
+ add_addr_in_paddr(memac->regs, (const u8 *)eth_addr, 0);
+ }
+
+- fixed_link = memac_drv_param->fixed_link;
+-
+- init(memac->regs, memac->memac_drv_param, memac->phy_if,
+- memac->max_speed, slow_10g_if, memac->exceptions);
++ init(memac->regs, memac->memac_drv_param, memac->exceptions);
+
+ /* FM_RX_FIFO_CORRUPT_ERRATA_10GMAC_A006320 errata workaround
+ * Exists only in FMan 6.0 and 6.3.
+@@ -937,11 +943,6 @@ static int memac_init(struct fman_mac *m
+ iowrite32be(reg32, &memac->regs->command_config);
+ }
+
+- if (memac->phy_if == PHY_INTERFACE_MODE_SGMII)
+- setup_sgmii_internal(memac, memac->sgmii_pcs, fixed_link);
+- else if (memac->phy_if == PHY_INTERFACE_MODE_QSGMII)
+- setup_sgmii_internal(memac, memac->qsgmii_pcs, fixed_link);
+-
+ /* Max Frame Length */
+ err = fman_set_mac_max_frame(memac->fm, memac->mac_id,
+ memac_drv_param->max_frame_length);
+@@ -970,9 +971,6 @@ static int memac_init(struct fman_mac *m
+ fman_register_intr(memac->fm, FMAN_MOD_MAC, memac->mac_id,
+ FMAN_INTR_TYPE_NORMAL, memac_exception, memac);
+
+- kfree(memac_drv_param);
+- memac->memac_drv_param = NULL;
+-
+ return 0;
+ }
+
+@@ -995,7 +993,6 @@ static int memac_free(struct fman_mac *m
+ pcs_put(memac->sgmii_pcs);
+ pcs_put(memac->qsgmii_pcs);
+ pcs_put(memac->xfi_pcs);
+-
+ kfree(memac->memac_drv_param);
+ kfree(memac);
+
+@@ -1028,8 +1025,6 @@ static struct fman_mac *memac_config(str
+ memac->addr = ENET_ADDR_TO_UINT64(mac_dev->addr);
+
+ memac->regs = mac_dev->vaddr;
+- memac->max_speed = params->max_speed;
+- memac->phy_if = mac_dev->phy_if;
+ memac->mac_id = params->mac_id;
+ memac->exceptions = (MEMAC_IMASK_TSECC_ER | MEMAC_IMASK_TECC_ER |
+ MEMAC_IMASK_RECC_ER | MEMAC_IMASK_MGI);
+@@ -1037,7 +1032,6 @@ static struct fman_mac *memac_config(str
+ memac->event_cb = params->event_cb;
+ memac->dev_id = mac_dev;
+ memac->fm = params->fm;
+- memac->basex_if = params->basex_if;
+
+ /* Save FMan revision */
+ fman_get_revision(memac->fm, &memac->fm_rev_info);
+@@ -1064,37 +1058,44 @@ static struct phylink_pcs *memac_pcs_cre
+ return pcs;
+ }
+
++static bool memac_supports(struct mac_device *mac_dev, phy_interface_t iface)
++{
++ /* If there's no serdes device, assume that it's been configured for
++ * whatever the default interface mode is.
++ */
++ if (!mac_dev->fman_mac->serdes)
++ return mac_dev->phy_if == iface;
++ /* Otherwise, ask the serdes */
++ return !phy_validate(mac_dev->fman_mac->serdes, PHY_MODE_ETHERNET,
++ iface, NULL);
++}
++
+ int memac_initialization(struct mac_device *mac_dev,
+ struct device_node *mac_node,
+ struct fman_mac_params *params)
+ {
+ int err;
++ struct device_node *fixed;
+ struct phylink_pcs *pcs;
+- struct fixed_phy_status *fixed_link;
+ struct fman_mac *memac;
++ unsigned long capabilities;
++ unsigned long *supported;
+
++ mac_dev->phylink_ops = &memac_mac_ops;
+ mac_dev->set_promisc = memac_set_promiscuous;
+ mac_dev->change_addr = memac_modify_mac_address;
+ mac_dev->add_hash_mac_addr = memac_add_hash_mac_address;
+ mac_dev->remove_hash_mac_addr = memac_del_hash_mac_address;
+- mac_dev->set_tx_pause = memac_set_tx_pause_frames;
+- mac_dev->set_rx_pause = memac_accept_rx_pause_frames;
+ mac_dev->set_exception = memac_set_exception;
+ mac_dev->set_allmulti = memac_set_allmulti;
+ mac_dev->set_tstamp = memac_set_tstamp;
+ mac_dev->set_multi = fman_set_multi;
+- mac_dev->adjust_link = adjust_link_memac;
+ mac_dev->enable = memac_enable;
+ mac_dev->disable = memac_disable;
+
+- if (params->max_speed == SPEED_10000)
+- mac_dev->phy_if = PHY_INTERFACE_MODE_XGMII;
+-
+ mac_dev->fman_mac = memac_config(mac_dev, params);
+- if (!mac_dev->fman_mac) {
+- err = -EINVAL;
+- goto _return;
+- }
++ if (!mac_dev->fman_mac)
++ return -EINVAL;
+
+ memac = mac_dev->fman_mac;
+ memac->memac_drv_param->max_frame_length = fman_get_max_frm();
+@@ -1136,9 +1137,9 @@ int memac_initialization(struct mac_devi
+ else
+ pcs = memac_pcs_create(mac_node, err);
+
+- if (!pcs) {
+- dev_err(mac_dev->dev, "missing pcs\n");
+- err = -ENOENT;
++ if (IS_ERR(pcs)) {
++ err = PTR_ERR(pcs);
++ dev_err_probe(mac_dev->dev, err, "missing pcs\n");
+ goto _return_fm_mac_free;
+ }
+
+@@ -1159,84 +1160,100 @@ int memac_initialization(struct mac_devi
+ } else if (IS_ERR(memac->serdes)) {
+ dev_err_probe(mac_dev->dev, err, "could not get serdes\n");
+ goto _return_fm_mac_free;
+- } else {
+- err = phy_init(memac->serdes);
+- if (err) {
+- dev_err_probe(mac_dev->dev, err,
+- "could not initialize serdes\n");
+- goto _return_fm_mac_free;
+- }
+-
+- err = phy_power_on(memac->serdes);
+- if (err) {
+- dev_err_probe(mac_dev->dev, err,
+- "could not power on serdes\n");
+- goto _return_phy_exit;
+- }
+-
+- if (memac->phy_if == PHY_INTERFACE_MODE_SGMII ||
+- memac->phy_if == PHY_INTERFACE_MODE_1000BASEX ||
+- memac->phy_if == PHY_INTERFACE_MODE_2500BASEX ||
+- memac->phy_if == PHY_INTERFACE_MODE_QSGMII ||
+- memac->phy_if == PHY_INTERFACE_MODE_XGMII) {
+- err = phy_set_mode_ext(memac->serdes, PHY_MODE_ETHERNET,
+- memac->phy_if);
+- if (err) {
+- dev_err_probe(mac_dev->dev, err,
+- "could not set serdes mode to %s\n",
+- phy_modes(memac->phy_if));
+- goto _return_phy_power_off;
+- }
+- }
+ }
+
+- if (!mac_dev->phy_node && of_phy_is_fixed_link(mac_node)) {
+- struct phy_device *phy;
+-
+- err = of_phy_register_fixed_link(mac_node);
+- if (err)
+- goto _return_phy_power_off;
+-
+- fixed_link = kzalloc(sizeof(*fixed_link), GFP_KERNEL);
+- if (!fixed_link) {
+- err = -ENOMEM;
+- goto _return_phy_power_off;
+- }
++ /* The internal connection to the serdes is XGMII, but this isn't
++ * really correct for the phy mode (which is the external connection).
++ * However, this is how all older device trees say that they want
++ * 10GBASE-R (aka XFI), so just convert it for them.
++ */
++ if (mac_dev->phy_if == PHY_INTERFACE_MODE_XGMII)
++ mac_dev->phy_if = PHY_INTERFACE_MODE_10GBASER;
+
+- mac_dev->phy_node = of_node_get(mac_node);
+- phy = of_phy_find_device(mac_dev->phy_node);
+- if (!phy) {
+- err = -EINVAL;
+- of_node_put(mac_dev->phy_node);
+- goto _return_fixed_link_free;
+- }
++ /* TODO: The following interface modes are supported by (some) hardware
++ * but not by this driver:
++ * - 1000BASE-KX
++ * - 10GBASE-KR
++ * - XAUI/HiGig
++ */
++ supported = mac_dev->phylink_config.supported_interfaces;
+
+- fixed_link->link = phy->link;
+- fixed_link->speed = phy->speed;
+- fixed_link->duplex = phy->duplex;
+- fixed_link->pause = phy->pause;
+- fixed_link->asym_pause = phy->asym_pause;
++ /* Note that half duplex is only supported on 10/100M interfaces. */
+
+- put_device(&phy->mdio.dev);
+- memac->memac_drv_param->fixed_link = fixed_link;
++ if (memac->sgmii_pcs &&
++ (memac_supports(mac_dev, PHY_INTERFACE_MODE_SGMII) ||
++ memac_supports(mac_dev, PHY_INTERFACE_MODE_1000BASEX))) {
++ __set_bit(PHY_INTERFACE_MODE_SGMII, supported);
++ __set_bit(PHY_INTERFACE_MODE_1000BASEX, supported);
++ }
++
++ if (memac->sgmii_pcs &&
++ memac_supports(mac_dev, PHY_INTERFACE_MODE_2500BASEX))
++ __set_bit(PHY_INTERFACE_MODE_2500BASEX, supported);
++
++ if (memac->qsgmii_pcs &&
++ memac_supports(mac_dev, PHY_INTERFACE_MODE_QSGMII))
++ __set_bit(PHY_INTERFACE_MODE_QSGMII, supported);
++ else if (mac_dev->phy_if == PHY_INTERFACE_MODE_QSGMII)
++ dev_warn(mac_dev->dev, "no QSGMII pcs specified\n");
++
++ if (memac->xfi_pcs &&
++ memac_supports(mac_dev, PHY_INTERFACE_MODE_10GBASER)) {
++ __set_bit(PHY_INTERFACE_MODE_10GBASER, supported);
++ } else {
++ /* From what I can tell, no 10g macs support RGMII. */
++ phy_interface_set_rgmii(supported);
++ __set_bit(PHY_INTERFACE_MODE_MII, supported);
+ }
+
++ capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE | MAC_10 | MAC_100;
++ capabilities |= MAC_1000FD | MAC_2500FD | MAC_10000FD;
++
++ /* These SoCs don't support half duplex at all; there's no different
++ * FMan version or compatible, so we just have to check the machine
++ * compatible instead
++ */
++ if (of_machine_is_compatible("fsl,ls1043a") ||
++ of_machine_is_compatible("fsl,ls1046a") ||
++ of_machine_is_compatible("fsl,B4QDS"))
++ capabilities &= ~(MAC_10HD | MAC_100HD);
++
++ mac_dev->phylink_config.mac_capabilities = capabilities;
++
++ /* The T2080 and T4240 don't support half duplex RGMII. There is no
++ * other way to identify these SoCs, so just use the machine
++ * compatible.
++ */
++ if (of_machine_is_compatible("fsl,T2080QDS") ||
++ of_machine_is_compatible("fsl,T2080RDB") ||
++ of_machine_is_compatible("fsl,T2081QDS") ||
++ of_machine_is_compatible("fsl,T4240QDS") ||
++ of_machine_is_compatible("fsl,T4240RDB"))
++ memac->rgmii_no_half_duplex = true;
++
++ /* Most boards should use MLO_AN_INBAND, but existing boards don't have
++ * a managed property. Default to MLO_AN_INBAND if nothing else is
++ * specified. We need to be careful and not enable this if we have a
++ * fixed link or if we are using MII or RGMII, since those
++ * configurations modes don't use in-band autonegotiation.
++ */
++ fixed = of_get_child_by_name(mac_node, "fixed-link");
++ if (!fixed && !of_property_read_bool(mac_node, "fixed-link") &&
++ !of_property_read_bool(mac_node, "managed") &&
++ mac_dev->phy_if != PHY_INTERFACE_MODE_MII &&
++ !phy_interface_mode_is_rgmii(mac_dev->phy_if))
++ mac_dev->phylink_config.ovr_an_inband = true;
++ of_node_put(fixed);
++
+ err = memac_init(mac_dev->fman_mac);
+ if (err < 0)
+- goto _return_fixed_link_free;
++ goto _return_fm_mac_free;
+
+ dev_info(mac_dev->dev, "FMan MEMAC\n");
+
+- goto _return;
++ return 0;
+
+-_return_phy_power_off:
+- phy_power_off(memac->serdes);
+-_return_phy_exit:
+- phy_exit(memac->serdes);
+-_return_fixed_link_free:
+- kfree(fixed_link);
+ _return_fm_mac_free:
+ memac_free(mac_dev->fman_mac);
+-_return:
+ return err;
+ }
+--- a/drivers/net/ethernet/freescale/fman/fman_tgec.c
++++ b/drivers/net/ethernet/freescale/fman/fman_tgec.c
+@@ -13,6 +13,7 @@
+ #include <linux/bitrev.h>
+ #include <linux/io.h>
+ #include <linux/crc32.h>
++#include <linux/netdevice.h>
+
+ /* Transmit Inter-Packet Gap Length Register (TX_IPG_LENGTH) */
+ #define TGEC_TX_IPG_LENGTH_MASK 0x000003ff
+@@ -243,10 +244,6 @@ static int init(struct tgec_regs __iomem
+
+ static int check_init_parameters(struct fman_mac *tgec)
+ {
+- if (tgec->max_speed < SPEED_10000) {
+- pr_err("10G MAC driver only support 10G speed\n");
+- return -EINVAL;
+- }
+ if (!tgec->exception_cb) {
+ pr_err("uninitialized exception_cb\n");
+ return -EINVAL;
+@@ -384,40 +381,13 @@ static void free_init_resources(struct f
+ tgec->unicast_addr_hash = NULL;
+ }
+
+-static bool is_init_done(struct tgec_cfg *cfg)
+-{
+- /* Checks if tGEC driver parameters were initialized */
+- if (!cfg)
+- return true;
+-
+- return false;
+-}
+-
+ static int tgec_enable(struct fman_mac *tgec)
+ {
+- struct tgec_regs __iomem *regs = tgec->regs;
+- u32 tmp;
+-
+- if (!is_init_done(tgec->cfg))
+- return -EINVAL;
+-
+- tmp = ioread32be(&regs->command_config);
+- tmp |= CMD_CFG_RX_EN | CMD_CFG_TX_EN;
+- iowrite32be(tmp, &regs->command_config);
+-
+ return 0;
+ }
+
+ static void tgec_disable(struct fman_mac *tgec)
+ {
+- struct tgec_regs __iomem *regs = tgec->regs;
+- u32 tmp;
+-
+- WARN_ON_ONCE(!is_init_done(tgec->cfg));
+-
+- tmp = ioread32be(&regs->command_config);
+- tmp &= ~(CMD_CFG_RX_EN | CMD_CFG_TX_EN);
+- iowrite32be(tmp, &regs->command_config);
+ }
+
+ static int tgec_set_promiscuous(struct fman_mac *tgec, bool new_val)
+@@ -425,9 +395,6 @@ static int tgec_set_promiscuous(struct f
+ struct tgec_regs __iomem *regs = tgec->regs;
+ u32 tmp;
+
+- if (!is_init_done(tgec->cfg))
+- return -EINVAL;
+-
+ tmp = ioread32be(&regs->command_config);
+ if (new_val)
+ tmp |= CMD_CFG_PROMIS_EN;
+@@ -444,9 +411,6 @@ static int tgec_set_tx_pause_frames(stru
+ {
+ struct tgec_regs __iomem *regs = tgec->regs;
+
+- if (!is_init_done(tgec->cfg))
+- return -EINVAL;
+-
+ iowrite32be((u32)pause_time, &regs->pause_quant);
+
+ return 0;
+@@ -457,9 +421,6 @@ static int tgec_accept_rx_pause_frames(s
+ struct tgec_regs __iomem *regs = tgec->regs;
+ u32 tmp;
+
+- if (!is_init_done(tgec->cfg))
+- return -EINVAL;
+-
+ tmp = ioread32be(&regs->command_config);
+ if (!en)
+ tmp |= CMD_CFG_PAUSE_IGNORE;
+@@ -470,12 +431,53 @@ static int tgec_accept_rx_pause_frames(s
+ return 0;
+ }
+
++static void tgec_mac_config(struct phylink_config *config, unsigned int mode,
++ const struct phylink_link_state *state)
++{
++}
++
++static void tgec_link_up(struct phylink_config *config, struct phy_device *phy,
++ unsigned int mode, phy_interface_t interface,
++ int speed, int duplex, bool tx_pause, bool rx_pause)
++{
++ struct mac_device *mac_dev = fman_config_to_mac(config);
++ struct fman_mac *tgec = mac_dev->fman_mac;
++ struct tgec_regs __iomem *regs = tgec->regs;
++ u16 pause_time = tx_pause ? FSL_FM_PAUSE_TIME_ENABLE :
++ FSL_FM_PAUSE_TIME_DISABLE;
++ u32 tmp;
++
++ tgec_set_tx_pause_frames(tgec, 0, pause_time, 0);
++ tgec_accept_rx_pause_frames(tgec, rx_pause);
++ mac_dev->update_speed(mac_dev, speed);
++
++ tmp = ioread32be(&regs->command_config);
++ tmp |= CMD_CFG_RX_EN | CMD_CFG_TX_EN;
++ iowrite32be(tmp, &regs->command_config);
++}
++
++static void tgec_link_down(struct phylink_config *config, unsigned int mode,
++ phy_interface_t interface)
++{
++ struct fman_mac *tgec = fman_config_to_mac(config)->fman_mac;
++ struct tgec_regs __iomem *regs = tgec->regs;
++ u32 tmp;
++
++ tmp = ioread32be(&regs->command_config);
++ tmp &= ~(CMD_CFG_RX_EN | CMD_CFG_TX_EN);
++ iowrite32be(tmp, &regs->command_config);
++}
++
++static const struct phylink_mac_ops tgec_mac_ops = {
++ .validate = phylink_generic_validate,
++ .mac_config = tgec_mac_config,
++ .mac_link_up = tgec_link_up,
++ .mac_link_down = tgec_link_down,
++};
++
+ static int tgec_modify_mac_address(struct fman_mac *tgec,
+ const enet_addr_t *p_enet_addr)
+ {
+- if (!is_init_done(tgec->cfg))
+- return -EINVAL;
+-
+ tgec->addr = ENET_ADDR_TO_UINT64(*p_enet_addr);
+ set_mac_address(tgec->regs, (const u8 *)(*p_enet_addr));
+
+@@ -490,9 +492,6 @@ static int tgec_add_hash_mac_address(str
+ u32 crc = 0xFFFFFFFF, hash;
+ u64 addr;
+
+- if (!is_init_done(tgec->cfg))
+- return -EINVAL;
+-
+ addr = ENET_ADDR_TO_UINT64(*eth_addr);
+
+ if (!(addr & GROUP_ADDRESS)) {
+@@ -525,9 +524,6 @@ static int tgec_set_allmulti(struct fman
+ u32 entry;
+ struct tgec_regs __iomem *regs = tgec->regs;
+
+- if (!is_init_done(tgec->cfg))
+- return -EINVAL;
+-
+ if (enable) {
+ for (entry = 0; entry < TGEC_HASH_TABLE_SIZE; entry++)
+ iowrite32be(entry | TGEC_HASH_MCAST_EN,
+@@ -548,9 +544,6 @@ static int tgec_set_tstamp(struct fman_m
+ struct tgec_regs __iomem *regs = tgec->regs;
+ u32 tmp;
+
+- if (!is_init_done(tgec->cfg))
+- return -EINVAL;
+-
+ tmp = ioread32be(&regs->command_config);
+
+ if (enable)
+@@ -572,9 +565,6 @@ static int tgec_del_hash_mac_address(str
+ u32 crc = 0xFFFFFFFF, hash;
+ u64 addr;
+
+- if (!is_init_done(tgec->cfg))
+- return -EINVAL;
+-
+ addr = ((*(u64 *)eth_addr) >> 16);
+
+ /* CRC calculation */
+@@ -601,22 +591,12 @@ static int tgec_del_hash_mac_address(str
+ return 0;
+ }
+
+-static void tgec_adjust_link(struct mac_device *mac_dev)
+-{
+- struct phy_device *phy_dev = mac_dev->phy_dev;
+-
+- mac_dev->update_speed(mac_dev, phy_dev->speed);
+-}
+-
+ static int tgec_set_exception(struct fman_mac *tgec,
+ enum fman_mac_exceptions exception, bool enable)
+ {
+ struct tgec_regs __iomem *regs = tgec->regs;
+ u32 bit_mask = 0;
+
+- if (!is_init_done(tgec->cfg))
+- return -EINVAL;
+-
+ bit_mask = get_exception_flag(exception);
+ if (bit_mask) {
+ if (enable)
+@@ -641,9 +621,6 @@ static int tgec_init(struct fman_mac *tg
+ enet_addr_t eth_addr;
+ int err;
+
+- if (is_init_done(tgec->cfg))
+- return -EINVAL;
+-
+ if (DEFAULT_RESET_ON_INIT &&
+ (fman_reset_mac(tgec->fm, tgec->mac_id) != 0)) {
+ pr_err("Can't reset MAC!\n");
+@@ -753,7 +730,6 @@ static struct fman_mac *tgec_config(stru
+
+ tgec->regs = mac_dev->vaddr;
+ tgec->addr = ENET_ADDR_TO_UINT64(mac_dev->addr);
+- tgec->max_speed = params->max_speed;
+ tgec->mac_id = params->mac_id;
+ tgec->exceptions = (TGEC_IMASK_MDIO_SCAN_EVENT |
+ TGEC_IMASK_REM_FAULT |
+@@ -788,17 +764,15 @@ int tgec_initialization(struct mac_devic
+ int err;
+ struct fman_mac *tgec;
+
++ mac_dev->phylink_ops = &tgec_mac_ops;
+ mac_dev->set_promisc = tgec_set_promiscuous;
+ mac_dev->change_addr = tgec_modify_mac_address;
+ mac_dev->add_hash_mac_addr = tgec_add_hash_mac_address;
+ mac_dev->remove_hash_mac_addr = tgec_del_hash_mac_address;
+- mac_dev->set_tx_pause = tgec_set_tx_pause_frames;
+- mac_dev->set_rx_pause = tgec_accept_rx_pause_frames;
+ mac_dev->set_exception = tgec_set_exception;
+ mac_dev->set_allmulti = tgec_set_allmulti;
+ mac_dev->set_tstamp = tgec_set_tstamp;
+ mac_dev->set_multi = fman_set_multi;
+- mac_dev->adjust_link = tgec_adjust_link;
+ mac_dev->enable = tgec_enable;
+ mac_dev->disable = tgec_disable;
+
+@@ -808,6 +782,19 @@ int tgec_initialization(struct mac_devic
+ goto _return;
+ }
+
++ /* The internal connection to the serdes is XGMII, but this isn't
++ * really correct for the phy mode (which is the external connection).
++ * However, this is how all older device trees say that they want
++ * XAUI, so just convert it for them.
++ */
++ if (mac_dev->phy_if == PHY_INTERFACE_MODE_XGMII)
++ mac_dev->phy_if = PHY_INTERFACE_MODE_XAUI;
++
++ __set_bit(PHY_INTERFACE_MODE_XAUI,
++ mac_dev->phylink_config.supported_interfaces);
++ mac_dev->phylink_config.mac_capabilities =
++ MAC_SYM_PAUSE | MAC_ASYM_PAUSE | MAC_10000FD;
++
+ tgec = mac_dev->fman_mac;
+ tgec->cfg->max_frame_length = fman_get_max_frm();
+ err = tgec_init(tgec);
+--- a/drivers/net/ethernet/freescale/fman/mac.c
++++ b/drivers/net/ethernet/freescale/fman/mac.c
+@@ -15,6 +15,7 @@
+ #include <linux/phy.h>
+ #include <linux/netdevice.h>
+ #include <linux/phy_fixed.h>
++#include <linux/phylink.h>
+ #include <linux/etherdevice.h>
+ #include <linux/libfdt_env.h>
+
+@@ -93,130 +94,8 @@ int fman_set_multi(struct net_device *ne
+ return 0;
+ }
+
+-/**
+- * fman_set_mac_active_pause
+- * @mac_dev: A pointer to the MAC device
+- * @rx: Pause frame setting for RX
+- * @tx: Pause frame setting for TX
+- *
+- * Set the MAC RX/TX PAUSE frames settings
+- *
+- * Avoid redundant calls to FMD, if the MAC driver already contains the desired
+- * active PAUSE settings. Otherwise, the new active settings should be reflected
+- * in FMan.
+- *
+- * Return: 0 on success; Error code otherwise.
+- */
+-int fman_set_mac_active_pause(struct mac_device *mac_dev, bool rx, bool tx)
+-{
+- struct fman_mac *fman_mac = mac_dev->fman_mac;
+- int err = 0;
+-
+- if (rx != mac_dev->rx_pause_active) {
+- err = mac_dev->set_rx_pause(fman_mac, rx);
+- if (likely(err == 0))
+- mac_dev->rx_pause_active = rx;
+- }
+-
+- if (tx != mac_dev->tx_pause_active) {
+- u16 pause_time = (tx ? FSL_FM_PAUSE_TIME_ENABLE :
+- FSL_FM_PAUSE_TIME_DISABLE);
+-
+- err = mac_dev->set_tx_pause(fman_mac, 0, pause_time, 0);
+-
+- if (likely(err == 0))
+- mac_dev->tx_pause_active = tx;
+- }
+-
+- return err;
+-}
+-EXPORT_SYMBOL(fman_set_mac_active_pause);
+-
+-/**
+- * fman_get_pause_cfg
+- * @mac_dev: A pointer to the MAC device
+- * @rx_pause: Return value for RX setting
+- * @tx_pause: Return value for TX setting
+- *
+- * Determine the MAC RX/TX PAUSE frames settings based on PHY
+- * autonegotiation or values set by eththool.
+- *
+- * Return: Pointer to FMan device.
+- */
+-void fman_get_pause_cfg(struct mac_device *mac_dev, bool *rx_pause,
+- bool *tx_pause)
+-{
+- struct phy_device *phy_dev = mac_dev->phy_dev;
+- u16 lcl_adv, rmt_adv;
+- u8 flowctrl;
+-
+- *rx_pause = *tx_pause = false;
+-
+- if (!phy_dev->duplex)
+- return;
+-
+- /* If PAUSE autonegotiation is disabled, the TX/RX PAUSE settings
+- * are those set by ethtool.
+- */
+- if (!mac_dev->autoneg_pause) {
+- *rx_pause = mac_dev->rx_pause_req;
+- *tx_pause = mac_dev->tx_pause_req;
+- return;
+- }
+-
+- /* Else if PAUSE autonegotiation is enabled, the TX/RX PAUSE
+- * settings depend on the result of the link negotiation.
+- */
+-
+- /* get local capabilities */
+- lcl_adv = linkmode_adv_to_lcl_adv_t(phy_dev->advertising);
+-
+- /* get link partner capabilities */
+- rmt_adv = 0;
+- if (phy_dev->pause)
+- rmt_adv |= LPA_PAUSE_CAP;
+- if (phy_dev->asym_pause)
+- rmt_adv |= LPA_PAUSE_ASYM;
+-
+- /* Calculate TX/RX settings based on local and peer advertised
+- * symmetric/asymmetric PAUSE capabilities.
+- */
+- flowctrl = mii_resolve_flowctrl_fdx(lcl_adv, rmt_adv);
+- if (flowctrl & FLOW_CTRL_RX)
+- *rx_pause = true;
+- if (flowctrl & FLOW_CTRL_TX)
+- *tx_pause = true;
+-}
+-EXPORT_SYMBOL(fman_get_pause_cfg);
+-
+-#define DTSEC_SUPPORTED \
+- (SUPPORTED_10baseT_Half \
+- | SUPPORTED_10baseT_Full \
+- | SUPPORTED_100baseT_Half \
+- | SUPPORTED_100baseT_Full \
+- | SUPPORTED_Autoneg \
+- | SUPPORTED_Pause \
+- | SUPPORTED_Asym_Pause \
+- | SUPPORTED_FIBRE \
+- | SUPPORTED_MII)
+-
+ static DEFINE_MUTEX(eth_lock);
+
+-static const u16 phy2speed[] = {
+- [PHY_INTERFACE_MODE_MII] = SPEED_100,
+- [PHY_INTERFACE_MODE_GMII] = SPEED_1000,
+- [PHY_INTERFACE_MODE_SGMII] = SPEED_1000,
+- [PHY_INTERFACE_MODE_TBI] = SPEED_1000,
+- [PHY_INTERFACE_MODE_RMII] = SPEED_100,
+- [PHY_INTERFACE_MODE_RGMII] = SPEED_1000,
+- [PHY_INTERFACE_MODE_RGMII_ID] = SPEED_1000,
+- [PHY_INTERFACE_MODE_RGMII_RXID] = SPEED_1000,
+- [PHY_INTERFACE_MODE_RGMII_TXID] = SPEED_1000,
+- [PHY_INTERFACE_MODE_RTBI] = SPEED_1000,
+- [PHY_INTERFACE_MODE_QSGMII] = SPEED_1000,
+- [PHY_INTERFACE_MODE_XGMII] = SPEED_10000
+-};
+-
+ static struct platform_device *dpaa_eth_add_device(int fman_id,
+ struct mac_device *mac_dev)
+ {
+@@ -263,8 +142,8 @@ no_mem:
+ }
+
+ static const struct of_device_id mac_match[] = {
+- { .compatible = "fsl,fman-dtsec", .data = dtsec_initialization },
+- { .compatible = "fsl,fman-xgec", .data = tgec_initialization },
++ { .compatible = "fsl,fman-dtsec", .data = dtsec_initialization },
++ { .compatible = "fsl,fman-xgec", .data = tgec_initialization },
+ { .compatible = "fsl,fman-memac", .data = memac_initialization },
+ {}
+ };
+@@ -295,6 +174,7 @@ static int mac_probe(struct platform_dev
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
++ platform_set_drvdata(_of_dev, mac_dev);
+
+ /* Save private information */
+ mac_dev->priv = priv;
+@@ -424,57 +304,21 @@ static int mac_probe(struct platform_dev
+ }
+ mac_dev->phy_if = phy_if;
+
+- priv->speed = phy2speed[mac_dev->phy_if];
+- params.max_speed = priv->speed;
+- mac_dev->if_support = DTSEC_SUPPORTED;
+- /* We don't support half-duplex in SGMII mode */
+- if (mac_dev->phy_if == PHY_INTERFACE_MODE_SGMII)
+- mac_dev->if_support &= ~(SUPPORTED_10baseT_Half |
+- SUPPORTED_100baseT_Half);
+-
+- /* Gigabit support (no half-duplex) */
+- if (params.max_speed == 1000)
+- mac_dev->if_support |= SUPPORTED_1000baseT_Full;
+-
+- /* The 10G interface only supports one mode */
+- if (mac_dev->phy_if == PHY_INTERFACE_MODE_XGMII)
+- mac_dev->if_support = SUPPORTED_10000baseT_Full;
+-
+- /* Get the rest of the PHY information */
+- mac_dev->phy_node = of_parse_phandle(mac_node, "phy-handle", 0);
+-
+- params.basex_if = false;
+ params.mac_id = priv->cell_index;
+ params.fm = (void *)priv->fman;
+ params.exception_cb = mac_exception;
+ params.event_cb = mac_exception;
+
+ err = init(mac_dev, mac_node, &params);
+- if (err < 0) {
+- dev_err(dev, "mac_dev->init() = %d\n", err);
+- of_node_put(mac_dev->phy_node);
+- return err;
+- }
+-
+- /* pause frame autonegotiation enabled */
+- mac_dev->autoneg_pause = true;
+-
+- /* By intializing the values to false, force FMD to enable PAUSE frames
+- * on RX and TX
+- */
+- mac_dev->rx_pause_req = true;
+- mac_dev->tx_pause_req = true;
+- mac_dev->rx_pause_active = false;
+- mac_dev->tx_pause_active = false;
+- err = fman_set_mac_active_pause(mac_dev, true, true);
+ if (err < 0)
+- dev_err(dev, "fman_set_mac_active_pause() = %d\n", err);
++ return err;
+
+ if (!is_zero_ether_addr(mac_dev->addr))
+ dev_info(dev, "FMan MAC address: %pM\n", mac_dev->addr);
+
+ priv->eth_dev = dpaa_eth_add_device(fman_id, mac_dev);
+ if (IS_ERR(priv->eth_dev)) {
++ err = PTR_ERR(priv->eth_dev);
+ dev_err(dev, "failed to add Ethernet platform device for MAC %d\n",
+ priv->cell_index);
+ priv->eth_dev = NULL;
+--- a/drivers/net/ethernet/freescale/fman/mac.h
++++ b/drivers/net/ethernet/freescale/fman/mac.h
+@@ -9,6 +9,7 @@
+ #include <linux/device.h>
+ #include <linux/if_ether.h>
+ #include <linux/phy.h>
++#include <linux/phylink.h>
+ #include <linux/list.h>
+
+ #include "fman_port.h"
+@@ -24,32 +25,22 @@ struct mac_device {
+ struct resource *res;
+ u8 addr[ETH_ALEN];
+ struct fman_port *port[2];
+- u32 if_support;
+- struct phy_device *phy_dev;
++ struct phylink *phylink;
++ struct phylink_config phylink_config;
+ phy_interface_t phy_if;
+- struct device_node *phy_node;
+- struct net_device *net_dev;
+
+- bool autoneg_pause;
+- bool rx_pause_req;
+- bool tx_pause_req;
+- bool rx_pause_active;
+- bool tx_pause_active;
+ bool promisc;
+ bool allmulti;
+
++ const struct phylink_mac_ops *phylink_ops;
+ int (*enable)(struct fman_mac *mac_dev);
+ void (*disable)(struct fman_mac *mac_dev);
+- void (*adjust_link)(struct mac_device *mac_dev);
+ int (*set_promisc)(struct fman_mac *mac_dev, bool enable);
+ int (*change_addr)(struct fman_mac *mac_dev, const enet_addr_t *enet_addr);
+ int (*set_allmulti)(struct fman_mac *mac_dev, bool enable);
+ int (*set_tstamp)(struct fman_mac *mac_dev, bool enable);
+ int (*set_multi)(struct net_device *net_dev,
+ struct mac_device *mac_dev);
+- int (*set_rx_pause)(struct fman_mac *mac_dev, bool en);
+- int (*set_tx_pause)(struct fman_mac *mac_dev, u8 priority,
+- u16 pause_time, u16 thresh_time);
+ int (*set_exception)(struct fman_mac *mac_dev,
+ enum fman_mac_exceptions exception, bool enable);
+ int (*add_hash_mac_addr)(struct fman_mac *mac_dev,
+@@ -63,6 +54,12 @@ struct mac_device {
+ struct mac_priv_s *priv;
+ };
+
++static inline struct mac_device
++*fman_config_to_mac(struct phylink_config *config)
++{
++ return container_of(config, struct mac_device, phylink_config);
++}
++
+ struct dpaa_eth_data {
+ struct mac_device *mac_dev;
+ int mac_hw_id;
diff --git a/target/linux/generic/backport-6.6/715-04-v6.2-net-phylink-provide-phylink_validate_mask_caps-helpe.patch b/target/linux/generic/backport-6.6/715-04-v6.2-net-phylink-provide-phylink_validate_mask_caps-helpe.patch
new file mode 100644
index 0000000000..06c348b1cd
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-04-v6.2-net-phylink-provide-phylink_validate_mask_caps-helpe.patch
@@ -0,0 +1,93 @@
+From bf4de031052fe7c5309e8956c342d4e5ce79038e Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Mon, 17 Oct 2022 16:22:35 -0400
+Subject: [PATCH 04/21] net: phylink: provide phylink_validate_mask_caps()
+ helper
+
+Provide a helper that restricts the link modes according to the
+phylink capabilities.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+[rebased on net-next/master and added documentation]
+Signed-off-by: Sean Anderson <sean.anderson@seco.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/phylink.c | 41 +++++++++++++++++++++++++++------------
+ include/linux/phylink.h | 3 +++
+ 2 files changed, 32 insertions(+), 12 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -564,31 +564,48 @@ unsigned long phylink_get_capabilities(p
+ EXPORT_SYMBOL_GPL(phylink_get_capabilities);
+
+ /**
+- * phylink_generic_validate() - generic validate() callback implementation
+- * @config: a pointer to a &struct phylink_config.
++ * phylink_validate_mask_caps() - Restrict link modes based on caps
+ * @supported: ethtool bitmask for supported link modes.
+- * @state: a pointer to a &struct phylink_link_state.
++ * @state: an (optional) pointer to a &struct phylink_link_state.
++ * @mac_capabilities: bitmask of MAC capabilities
+ *
+- * Generic implementation of the validate() callback that MAC drivers can
+- * use when they pass the range of supported interfaces and MAC capabilities.
+- * This makes use of phylink_get_linkmodes().
++ * Calculate the supported link modes based on @mac_capabilities, and restrict
++ * @supported and @state based on that. Use this function if your capabiliies
++ * aren't constant, such as if they vary depending on the interface.
+ */
+-void phylink_generic_validate(struct phylink_config *config,
+- unsigned long *supported,
+- struct phylink_link_state *state)
++void phylink_validate_mask_caps(unsigned long *supported,
++ struct phylink_link_state *state,
++ unsigned long mac_capabilities)
+ {
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
+ unsigned long caps;
+
+ phylink_set_port_modes(mask);
+ phylink_set(mask, Autoneg);
+- caps = phylink_get_capabilities(state->interface,
+- config->mac_capabilities,
++ caps = phylink_get_capabilities(state->interface, mac_capabilities,
+ state->rate_matching);
+ phylink_caps_to_linkmodes(mask, caps);
+
+ linkmode_and(supported, supported, mask);
+- linkmode_and(state->advertising, state->advertising, mask);
++ if (state)
++ linkmode_and(state->advertising, state->advertising, mask);
++}
++EXPORT_SYMBOL_GPL(phylink_validate_mask_caps);
++
++/**
++ * phylink_generic_validate() - generic validate() callback implementation
++ * @config: a pointer to a &struct phylink_config.
++ * @supported: ethtool bitmask for supported link modes.
++ * @state: a pointer to a &struct phylink_link_state.
++ *
++ * Generic implementation of the validate() callback that MAC drivers can
++ * use when they pass the range of supported interfaces and MAC capabilities.
++ */
++void phylink_generic_validate(struct phylink_config *config,
++ unsigned long *supported,
++ struct phylink_link_state *state)
++{
++ phylink_validate_mask_caps(supported, state, config->mac_capabilities);
+ }
+ EXPORT_SYMBOL_GPL(phylink_generic_validate);
+
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -558,6 +558,9 @@ void phylink_caps_to_linkmodes(unsigned
+ unsigned long phylink_get_capabilities(phy_interface_t interface,
+ unsigned long mac_capabilities,
+ int rate_matching);
++void phylink_validate_mask_caps(unsigned long *supported,
++ struct phylink_link_state *state,
++ unsigned long caps);
+ void phylink_generic_validate(struct phylink_config *config,
+ unsigned long *supported,
+ struct phylink_link_state *state);
diff --git a/target/linux/generic/backport-6.6/715-05-v6.2-phylink-require-valid-state-argument-to-phylink_vali.patch b/target/linux/generic/backport-6.6/715-05-v6.2-phylink-require-valid-state-argument-to-phylink_vali.patch
new file mode 100644
index 0000000000..e3a1dda688
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-05-v6.2-phylink-require-valid-state-argument-to-phylink_vali.patch
@@ -0,0 +1,39 @@
+From 2bf7e4a68c42eed909f3c29582e1fb85cb157e35 Mon Sep 17 00:00:00 2001
+From: Jakub Kicinski <kuba@kernel.org>
+Date: Tue, 25 Oct 2022 11:51:26 -0700
+Subject: [PATCH 05/21] phylink: require valid state argument to
+ phylink_validate_mask_caps()
+
+state is deferenced earlier in the function, the NULL check
+is pointless. Since we don't have any crash reports presumably
+it's safe to assume state is not NULL.
+
+Fixes: f392a1846489 ("net: phylink: provide phylink_validate_mask_caps() helper")
+Reviewed-by: Sean Anderson <sean.anderson@seco.com>
+Link: https://lore.kernel.org/r/20221025185126.1720553-1-kuba@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 5 ++---
+ 1 file changed, 2 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -566,7 +566,7 @@ EXPORT_SYMBOL_GPL(phylink_get_capabiliti
+ /**
+ * phylink_validate_mask_caps() - Restrict link modes based on caps
+ * @supported: ethtool bitmask for supported link modes.
+- * @state: an (optional) pointer to a &struct phylink_link_state.
++ * @state: pointer to a &struct phylink_link_state.
+ * @mac_capabilities: bitmask of MAC capabilities
+ *
+ * Calculate the supported link modes based on @mac_capabilities, and restrict
+@@ -587,8 +587,7 @@ void phylink_validate_mask_caps(unsigned
+ phylink_caps_to_linkmodes(mask, caps);
+
+ linkmode_and(supported, supported, mask);
+- if (state)
+- linkmode_and(state->advertising, state->advertising, mask);
++ linkmode_and(state->advertising, state->advertising, mask);
+ }
+ EXPORT_SYMBOL_GPL(phylink_validate_mask_caps);
+
diff --git a/target/linux/generic/backport-6.6/715-06-v6.2-net-phylink-add-phylink_get_link_timer_ns-helper.patch b/target/linux/generic/backport-6.6/715-06-v6.2-net-phylink-add-phylink_get_link_timer_ns-helper.patch
new file mode 100644
index 0000000000..c217ed87b5
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-06-v6.2-net-phylink-add-phylink_get_link_timer_ns-helper.patch
@@ -0,0 +1,48 @@
+From f8fc363bf0c023e4736a0328174b4a24b44ab23a Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 27 Oct 2022 14:10:37 +0100
+Subject: [PATCH 06/21] net: phylink: add phylink_get_link_timer_ns() helper
+
+Add a helper to convert the PHY interface mode to the required link
+timer setting as stated by the appropriate standard. Inappropriate
+interface modes return an error.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ include/linux/phylink.h | 24 ++++++++++++++++++++++++
+ 1 file changed, 24 insertions(+)
+
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -617,6 +617,30 @@ int phylink_speed_up(struct phylink *pl)
+
+ void phylink_set_port_modes(unsigned long *bits);
+
++/**
++ * phylink_get_link_timer_ns - return the PCS link timer value
++ * @interface: link &typedef phy_interface_t mode
++ *
++ * Return the PCS link timer setting in nanoseconds for the PHY @interface
++ * mode, or -EINVAL if not appropriate.
++ */
++static inline int phylink_get_link_timer_ns(phy_interface_t interface)
++{
++ switch (interface) {
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_QSGMII:
++ case PHY_INTERFACE_MODE_USXGMII:
++ return 1600000;
++
++ case PHY_INTERFACE_MODE_1000BASEX:
++ case PHY_INTERFACE_MODE_2500BASEX:
++ return 10000000;
++
++ default:
++ return -EINVAL;
++ }
++}
++
+ void phylink_mii_c22_pcs_decode_state(struct phylink_link_state *state,
+ u16 bmsr, u16 lpa);
+ void phylink_mii_c22_pcs_get_state(struct mdio_device *pcs,
diff --git a/target/linux/generic/backport-6.6/715-07-v6.2-net-remove-explicit-phylink_generic_validate-referen.patch b/target/linux/generic/backport-6.6/715-07-v6.2-net-remove-explicit-phylink_generic_validate-referen.patch
new file mode 100644
index 0000000000..28154af920
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-07-v6.2-net-remove-explicit-phylink_generic_validate-referen.patch
@@ -0,0 +1,250 @@
+From b45b773a96b0e9e8d51e5d005485f4e376d6ce9a Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 4 Nov 2022 17:13:01 +0000
+Subject: [PATCH 07/21] net: remove explicit phylink_generic_validate()
+ references
+
+Virtually all conventional network drivers are now converted to use
+phylink_generic_validate() - only DSA drivers and fman_memac remain,
+so lets remove the necessity for network drivers to explicitly set
+this member, and default to phylink_generic_validate() when unset.
+This is possible as .validate must currently be set.
+
+Any remaining instances that have not been addressed by this patch can
+be fixed up later.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Link: https://lore.kernel.org/r/E1or0FZ-001tRa-DI@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/altera/altera_tse_main.c | 1 -
+ drivers/net/ethernet/atheros/ag71xx.c | 1 -
+ drivers/net/ethernet/cadence/macb_main.c | 1 -
+ drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c | 1 -
+ drivers/net/ethernet/freescale/enetc/enetc_pf.c | 1 -
+ drivers/net/ethernet/freescale/fman/fman_dtsec.c | 1 -
+ drivers/net/ethernet/freescale/fman/fman_tgec.c | 1 -
+ drivers/net/ethernet/marvell/mvneta.c | 1 -
+ drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c | 1 -
+ drivers/net/ethernet/marvell/prestera/prestera_main.c | 1 -
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 1 -
+ drivers/net/ethernet/microchip/lan966x/lan966x_phylink.c | 1 -
+ drivers/net/ethernet/microchip/sparx5/sparx5_phylink.c | 1 -
+ drivers/net/ethernet/mscc/ocelot_net.c | 1 -
+ drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 1 -
+ drivers/net/ethernet/ti/am65-cpsw-nuss.c | 1 -
+ drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 1 -
+ drivers/net/phy/phylink.c | 5 ++++-
+ drivers/net/usb/asix_devices.c | 1 -
+ include/linux/phylink.h | 5 +++++
+ 20 files changed, 9 insertions(+), 19 deletions(-)
+
+--- a/drivers/net/ethernet/altera/altera_tse_main.c
++++ b/drivers/net/ethernet/altera/altera_tse_main.c
+@@ -1096,7 +1096,6 @@ static struct phylink_pcs *alt_tse_selec
+ }
+
+ static const struct phylink_mac_ops alt_tse_phylink_ops = {
+- .validate = phylink_generic_validate,
+ .mac_an_restart = alt_tse_mac_an_restart,
+ .mac_config = alt_tse_mac_config,
+ .mac_link_down = alt_tse_mac_link_down,
+--- a/drivers/net/ethernet/atheros/ag71xx.c
++++ b/drivers/net/ethernet/atheros/ag71xx.c
+@@ -1086,7 +1086,6 @@ static void ag71xx_mac_link_up(struct ph
+ }
+
+ static const struct phylink_mac_ops ag71xx_phylink_mac_ops = {
+- .validate = phylink_generic_validate,
+ .mac_config = ag71xx_mac_config,
+ .mac_link_down = ag71xx_mac_link_down,
+ .mac_link_up = ag71xx_mac_link_up,
+--- a/drivers/net/ethernet/cadence/macb_main.c
++++ b/drivers/net/ethernet/cadence/macb_main.c
+@@ -752,7 +752,6 @@ static struct phylink_pcs *macb_mac_sele
+ }
+
+ static const struct phylink_mac_ops macb_phylink_ops = {
+- .validate = phylink_generic_validate,
+ .mac_select_pcs = macb_mac_select_pcs,
+ .mac_config = macb_mac_config,
+ .mac_link_down = macb_mac_link_down,
+--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c
++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c
+@@ -235,7 +235,6 @@ static void dpaa2_mac_link_down(struct p
+ }
+
+ static const struct phylink_mac_ops dpaa2_mac_phylink_ops = {
+- .validate = phylink_generic_validate,
+ .mac_select_pcs = dpaa2_mac_select_pcs,
+ .mac_config = dpaa2_mac_config,
+ .mac_link_up = dpaa2_mac_link_up,
+--- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c
++++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c
+@@ -1111,7 +1111,6 @@ static void enetc_pl_mac_link_down(struc
+ }
+
+ static const struct phylink_mac_ops enetc_mac_phylink_ops = {
+- .validate = phylink_generic_validate,
+ .mac_select_pcs = enetc_pl_mac_select_pcs,
+ .mac_config = enetc_pl_mac_config,
+ .mac_link_up = enetc_pl_mac_link_up,
+--- a/drivers/net/ethernet/freescale/fman/fman_dtsec.c
++++ b/drivers/net/ethernet/freescale/fman/fman_dtsec.c
+@@ -986,7 +986,6 @@ static void dtsec_link_down(struct phyli
+ }
+
+ static const struct phylink_mac_ops dtsec_mac_ops = {
+- .validate = phylink_generic_validate,
+ .mac_select_pcs = dtsec_select_pcs,
+ .mac_config = dtsec_mac_config,
+ .mac_link_up = dtsec_link_up,
+--- a/drivers/net/ethernet/freescale/fman/fman_tgec.c
++++ b/drivers/net/ethernet/freescale/fman/fman_tgec.c
+@@ -469,7 +469,6 @@ static void tgec_link_down(struct phylin
+ }
+
+ static const struct phylink_mac_ops tgec_mac_ops = {
+- .validate = phylink_generic_validate,
+ .mac_config = tgec_mac_config,
+ .mac_link_up = tgec_link_up,
+ .mac_link_down = tgec_link_down,
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -4228,7 +4228,6 @@ static void mvneta_mac_link_up(struct ph
+ }
+
+ static const struct phylink_mac_ops mvneta_phylink_ops = {
+- .validate = phylink_generic_validate,
+ .mac_select_pcs = mvneta_mac_select_pcs,
+ .mac_prepare = mvneta_mac_prepare,
+ .mac_config = mvneta_mac_config,
+--- a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
++++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
+@@ -6633,7 +6633,6 @@ static void mvpp2_mac_link_down(struct p
+ }
+
+ static const struct phylink_mac_ops mvpp2_phylink_ops = {
+- .validate = phylink_generic_validate,
+ .mac_select_pcs = mvpp2_select_pcs,
+ .mac_prepare = mvpp2_mac_prepare,
+ .mac_config = mvpp2_mac_config,
+--- a/drivers/net/ethernet/marvell/prestera/prestera_main.c
++++ b/drivers/net/ethernet/marvell/prestera/prestera_main.c
+@@ -360,7 +360,6 @@ static void prestera_pcs_an_restart(stru
+ }
+
+ static const struct phylink_mac_ops prestera_mac_ops = {
+- .validate = phylink_generic_validate,
+ .mac_select_pcs = prestera_mac_select_pcs,
+ .mac_config = prestera_mac_config,
+ .mac_link_down = prestera_mac_link_down,
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -654,7 +654,6 @@ static void mtk_mac_link_up(struct phyli
+ }
+
+ static const struct phylink_mac_ops mtk_phylink_ops = {
+- .validate = phylink_generic_validate,
+ .mac_select_pcs = mtk_mac_select_pcs,
+ .mac_pcs_get_state = mtk_mac_pcs_get_state,
+ .mac_config = mtk_mac_config,
+--- a/drivers/net/ethernet/microchip/lan966x/lan966x_phylink.c
++++ b/drivers/net/ethernet/microchip/lan966x/lan966x_phylink.c
+@@ -125,7 +125,6 @@ static void lan966x_pcs_aneg_restart(str
+ }
+
+ const struct phylink_mac_ops lan966x_phylink_mac_ops = {
+- .validate = phylink_generic_validate,
+ .mac_select_pcs = lan966x_phylink_mac_select,
+ .mac_config = lan966x_phylink_mac_config,
+ .mac_prepare = lan966x_phylink_mac_prepare,
+--- a/drivers/net/ethernet/microchip/sparx5/sparx5_phylink.c
++++ b/drivers/net/ethernet/microchip/sparx5/sparx5_phylink.c
+@@ -138,7 +138,6 @@ const struct phylink_pcs_ops sparx5_phyl
+ };
+
+ const struct phylink_mac_ops sparx5_phylink_mac_ops = {
+- .validate = phylink_generic_validate,
+ .mac_select_pcs = sparx5_phylink_mac_select_pcs,
+ .mac_config = sparx5_phylink_mac_config,
+ .mac_link_down = sparx5_phylink_mac_link_down,
+--- a/drivers/net/ethernet/mscc/ocelot_net.c
++++ b/drivers/net/ethernet/mscc/ocelot_net.c
+@@ -1737,7 +1737,6 @@ static void vsc7514_phylink_mac_link_up(
+ }
+
+ static const struct phylink_mac_ops ocelot_phylink_ops = {
+- .validate = phylink_generic_validate,
+ .mac_config = vsc7514_phylink_mac_config,
+ .mac_link_down = vsc7514_phylink_mac_link_down,
+ .mac_link_up = vsc7514_phylink_mac_link_up,
+--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+@@ -1090,7 +1090,6 @@ static void stmmac_mac_link_up(struct ph
+ }
+
+ static const struct phylink_mac_ops stmmac_phylink_mac_ops = {
+- .validate = phylink_generic_validate,
+ .mac_select_pcs = stmmac_mac_select_pcs,
+ .mac_config = stmmac_mac_config,
+ .mac_link_down = stmmac_mac_link_down,
+--- a/drivers/net/ethernet/ti/am65-cpsw-nuss.c
++++ b/drivers/net/ethernet/ti/am65-cpsw-nuss.c
+@@ -1493,7 +1493,6 @@ static void am65_cpsw_nuss_mac_link_up(s
+ }
+
+ static const struct phylink_mac_ops am65_cpsw_phylink_mac_ops = {
+- .validate = phylink_generic_validate,
+ .mac_config = am65_cpsw_nuss_mac_config,
+ .mac_link_down = am65_cpsw_nuss_mac_link_down,
+ .mac_link_up = am65_cpsw_nuss_mac_link_up,
+--- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
++++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
+@@ -1736,7 +1736,6 @@ static void axienet_mac_link_up(struct p
+ }
+
+ static const struct phylink_mac_ops axienet_phylink_ops = {
+- .validate = phylink_generic_validate,
+ .mac_select_pcs = axienet_mac_select_pcs,
+ .mac_config = axienet_mac_config,
+ .mac_link_down = axienet_mac_link_down,
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -651,7 +651,10 @@ static int phylink_validate_mac_and_pcs(
+ }
+
+ /* Then validate the link parameters with the MAC */
+- pl->mac_ops->validate(pl->config, supported, state);
++ if (pl->mac_ops->validate)
++ pl->mac_ops->validate(pl->config, supported, state);
++ else
++ phylink_generic_validate(pl->config, supported, state);
+
+ return phylink_is_empty_linkmode(supported) ? -EINVAL : 0;
+ }
+--- a/drivers/net/usb/asix_devices.c
++++ b/drivers/net/usb/asix_devices.c
+@@ -787,7 +787,6 @@ static void ax88772_mac_link_up(struct p
+ }
+
+ static const struct phylink_mac_ops ax88772_phylink_mac_ops = {
+- .validate = phylink_generic_validate,
+ .mac_config = ax88772_mac_config,
+ .mac_link_down = ax88772_mac_link_down,
+ .mac_link_up = ax88772_mac_link_up,
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -207,6 +207,11 @@ struct phylink_mac_ops {
+ *
+ * If the @state->interface mode is not supported, then the @supported
+ * mask must be cleared.
++ *
++ * This member is optional; if not set, the generic validator will be
++ * used making use of @config->mac_capabilities and
++ * @config->supported_interfaces to determine which link modes are
++ * supported.
+ */
+ void validate(struct phylink_config *config, unsigned long *supported,
+ struct phylink_link_state *state);
diff --git a/target/linux/generic/backport-6.6/715-08-net-sfp-make-sfp_bus_find_fwnode-take-a-const-fwnode.patch b/target/linux/generic/backport-6.6/715-08-net-sfp-make-sfp_bus_find_fwnode-take-a-const-fwnode.patch
new file mode 100644
index 0000000000..37d82b2cd7
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-08-net-sfp-make-sfp_bus_find_fwnode-take-a-const-fwnode.patch
@@ -0,0 +1,48 @@
+From a90ac762d345890b40d88a1385a34a2449c2d75e Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 24 Mar 2023 09:23:42 +0000
+Subject: [PATCH] net: sfp: make sfp_bus_find_fwnode() take a const fwnode
+
+sfp_bus_find_fwnode() does not write to the fwnode, so let's make it
+const.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/sfp-bus.c | 2 +-
+ include/linux/sfp.h | 5 +++--
+ 2 files changed, 4 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/sfp-bus.c
++++ b/drivers/net/phy/sfp-bus.c
+@@ -603,7 +603,7 @@ static void sfp_upstream_clear(struct sf
+ * - %-ENOMEM if we failed to allocate the bus.
+ * - an error from the upstream's connect_phy() method.
+ */
+-struct sfp_bus *sfp_bus_find_fwnode(struct fwnode_handle *fwnode)
++struct sfp_bus *sfp_bus_find_fwnode(const struct fwnode_handle *fwnode)
+ {
+ struct fwnode_reference_args ref;
+ struct sfp_bus *bus;
+--- a/include/linux/sfp.h
++++ b/include/linux/sfp.h
+@@ -548,7 +548,7 @@ int sfp_get_module_eeprom_by_page(struct
+ void sfp_upstream_start(struct sfp_bus *bus);
+ void sfp_upstream_stop(struct sfp_bus *bus);
+ void sfp_bus_put(struct sfp_bus *bus);
+-struct sfp_bus *sfp_bus_find_fwnode(struct fwnode_handle *fwnode);
++struct sfp_bus *sfp_bus_find_fwnode(const struct fwnode_handle *fwnode);
+ int sfp_bus_add_upstream(struct sfp_bus *bus, void *upstream,
+ const struct sfp_upstream_ops *ops);
+ void sfp_bus_del_upstream(struct sfp_bus *bus);
+@@ -610,7 +610,8 @@ static inline void sfp_bus_put(struct sf
+ {
+ }
+
+-static inline struct sfp_bus *sfp_bus_find_fwnode(struct fwnode_handle *fwnode)
++static inline struct sfp_bus *
++sfp_bus_find_fwnode(const struct fwnode_handle *fwnode)
+ {
+ return NULL;
+ }
diff --git a/target/linux/generic/backport-6.6/715-09-v6.4-net-pcs-lynx-don-t-print-an_enabled-in-pcs_get_state.patch b/target/linux/generic/backport-6.6/715-09-v6.4-net-pcs-lynx-don-t-print-an_enabled-in-pcs_get_state.patch
new file mode 100644
index 0000000000..290cb8d161
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-09-v6.4-net-pcs-lynx-don-t-print-an_enabled-in-pcs_get_state.patch
@@ -0,0 +1,31 @@
+From ecec0ebbc6381a5a375f1cf10c4858f24e91e2ef Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Wed, 15 Mar 2023 14:46:49 +0000
+Subject: [PATCH] net: pcs: lynx: don't print an_enabled in pcs_get_state()
+
+an_enabled will be going away, and in any case, pcs_get_state() should
+not be updating this member. Remove the print.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/pcs/pcs-lynx.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/pcs/pcs-lynx.c
++++ b/drivers/net/pcs/pcs-lynx.c
+@@ -115,11 +115,11 @@ static void lynx_pcs_get_state(struct ph
+ }
+
+ dev_dbg(&lynx->mdio->dev,
+- "mode=%s/%s/%s link=%u an_enabled=%u an_complete=%u\n",
++ "mode=%s/%s/%s link=%u an_complete=%u\n",
+ phy_modes(state->interface),
+ phy_speed_to_str(state->speed),
+ phy_duplex_to_str(state->duplex),
+- state->link, state->an_enabled, state->an_complete);
++ state->link, state->an_complete);
+ }
+
+ static int lynx_pcs_config_giga(struct mdio_device *pcs, unsigned int mode,
diff --git a/target/linux/generic/backport-6.6/715-10-v6.4-net-dpaa2-mac-use-Autoneg-bit-rather-than-an_enabled.patch b/target/linux/generic/backport-6.6/715-10-v6.4-net-dpaa2-mac-use-Autoneg-bit-rather-than-an_enabled.patch
new file mode 100644
index 0000000000..38ea265476
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-10-v6.4-net-dpaa2-mac-use-Autoneg-bit-rather-than-an_enabled.patch
@@ -0,0 +1,32 @@
+From 99d0f3a1095f4c938b1665025c29411edafe8a01 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 21 Mar 2023 15:58:44 +0000
+Subject: [PATCH] net: dpaa2-mac: use Autoneg bit rather than an_enabled
+
+The Autoneg bit in the advertising bitmap and state->an_enabled are
+always identical. Thus, we will be removing state->an_enabled.
+
+Use the Autoneg bit in the advertising bitmap to indicate whether
+autonegotiation should be used, rather than using the an_enabled
+member which will be going away. This means we use the same condition
+as phylink_mii_c22_pcs_config().
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c
++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c
+@@ -158,7 +158,8 @@ static void dpaa2_mac_config(struct phyl
+ struct dpmac_link_state *dpmac_state = &mac->state;
+ int err;
+
+- if (state->an_enabled)
++ if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++ state->advertising))
+ dpmac_state->options |= DPMAC_LINK_OPT_AUTONEG;
+ else
+ dpmac_state->options &= ~DPMAC_LINK_OPT_AUTONEG;
diff --git a/target/linux/generic/backport-6.6/715-11-v6.4-net-phylink-support-validated-pause-and-autoneg-in-f.patch b/target/linux/generic/backport-6.6/715-11-v6.4-net-phylink-support-validated-pause-and-autoneg-in-f.patch
new file mode 100644
index 0000000000..cb9c411cfb
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-11-v6.4-net-phylink-support-validated-pause-and-autoneg-in-f.patch
@@ -0,0 +1,64 @@
+From 471c40bde606ec0b1ce8c616f7998739c7a783a6 Mon Sep 17 00:00:00 2001
+From: Ivan Bornyakov <i.bornyakov@metrotek.ru>
+Date: Fri, 10 Feb 2023 18:46:27 +0300
+Subject: [PATCH 10/21] net: phylink: support validated pause and autoneg in
+ fixed-link
+
+In fixed-link setup phylink_parse_fixedlink() unconditionally sets
+Pause, Asym_Pause and Autoneg bits to "supported" bitmap, while MAC may
+not support these.
+
+This leads to ethtool reporting:
+
+ > Supported pause frame use: Symmetric Receive-only
+ > Supports auto-negotiation: Yes
+
+regardless of what is actually supported.
+
+Instead of unconditionally set Pause, Asym_Pause and Autoneg it is
+sensible to set them according to validated "supported" bitmap, i.e. the
+result of phylink_validate().
+
+Signed-off-by: Ivan Bornyakov <i.bornyakov@metrotek.ru>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/phylink.c | 17 ++++++++++++++---
+ 1 file changed, 14 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -709,6 +709,7 @@ static int phylink_parse_fixedlink(struc
+ struct fwnode_handle *fwnode)
+ {
+ struct fwnode_handle *fixed_node;
++ bool pause, asym_pause, autoneg;
+ const struct phy_setting *s;
+ struct gpio_desc *desc;
+ u32 speed;
+@@ -781,13 +782,23 @@ static int phylink_parse_fixedlink(struc
+ linkmode_copy(pl->link_config.advertising, pl->supported);
+ phylink_validate(pl, pl->supported, &pl->link_config);
+
++ pause = phylink_test(pl->supported, Pause);
++ asym_pause = phylink_test(pl->supported, Asym_Pause);
++ autoneg = phylink_test(pl->supported, Autoneg);
+ s = phy_lookup_setting(pl->link_config.speed, pl->link_config.duplex,
+ pl->supported, true);
+ linkmode_zero(pl->supported);
+ phylink_set(pl->supported, MII);
+- phylink_set(pl->supported, Pause);
+- phylink_set(pl->supported, Asym_Pause);
+- phylink_set(pl->supported, Autoneg);
++
++ if (pause)
++ phylink_set(pl->supported, Pause);
++
++ if (asym_pause)
++ phylink_set(pl->supported, Asym_Pause);
++
++ if (autoneg)
++ phylink_set(pl->supported, Autoneg);
++
+ if (s) {
+ __set_bit(s->bit, pl->supported);
+ __set_bit(s->bit, pl->link_config.lp_advertising);
diff --git a/target/linux/generic/backport-6.6/715-12-v6.4-net-phylink-remove-an_enabled.patch b/target/linux/generic/backport-6.6/715-12-v6.4-net-phylink-remove-an_enabled.patch
new file mode 100644
index 0000000000..03b4f9d0c4
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-12-v6.4-net-phylink-remove-an_enabled.patch
@@ -0,0 +1,177 @@
+From 7211ffd70941933a7825a56cf480f07ee81c321c Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 21 Mar 2023 15:58:54 +0000
+Subject: [PATCH 11/21] net: phylink: remove an_enabled
+
+The Autoneg bit in the advertising bitmap and state->an_enabled are
+always identical. state->an_enabled is now no longer used by any
+drivers, so lets kill this duplication.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 37 +++++++++++++++++--------------------
+ include/linux/phylink.h | 2 --
+ 2 files changed, 17 insertions(+), 22 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -841,7 +841,6 @@ static int phylink_parse_mode(struct phy
+ phylink_set(pl->supported, Autoneg);
+ phylink_set(pl->supported, Asym_Pause);
+ phylink_set(pl->supported, Pause);
+- pl->link_config.an_enabled = true;
+ pl->cfg_link_an_mode = MLO_AN_INBAND;
+
+ switch (pl->link_config.interface) {
+@@ -944,9 +943,6 @@ static int phylink_parse_mode(struct phy
+ "failed to validate link configuration for in-band status\n");
+ return -EINVAL;
+ }
+-
+- /* Check if MAC/PCS also supports Autoneg. */
+- pl->link_config.an_enabled = phylink_test(pl->supported, Autoneg);
+ }
+
+ return 0;
+@@ -956,7 +952,8 @@ static void phylink_apply_manual_flow(st
+ struct phylink_link_state *state)
+ {
+ /* If autoneg is disabled, pause AN is also disabled */
+- if (!state->an_enabled)
++ if (!linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++ state->advertising))
+ state->pause &= ~MLO_PAUSE_AN;
+
+ /* Manual configuration of pause modes */
+@@ -996,21 +993,22 @@ static void phylink_mac_config(struct ph
+ const struct phylink_link_state *state)
+ {
+ phylink_dbg(pl,
+- "%s: mode=%s/%s/%s/%s/%s adv=%*pb pause=%02x link=%u an=%u\n",
++ "%s: mode=%s/%s/%s/%s/%s adv=%*pb pause=%02x link=%u\n",
+ __func__, phylink_an_mode_str(pl->cur_link_an_mode),
+ phy_modes(state->interface),
+ phy_speed_to_str(state->speed),
+ phy_duplex_to_str(state->duplex),
+ phy_rate_matching_to_str(state->rate_matching),
+ __ETHTOOL_LINK_MODE_MASK_NBITS, state->advertising,
+- state->pause, state->link, state->an_enabled);
++ state->pause, state->link);
+
+ pl->mac_ops->mac_config(pl->config, pl->cur_link_an_mode, state);
+ }
+
+ static void phylink_mac_pcs_an_restart(struct phylink *pl)
+ {
+- if (pl->link_config.an_enabled &&
++ if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++ pl->link_config.advertising) &&
+ phy_interface_mode_is_8023z(pl->link_config.interface) &&
+ phylink_autoneg_inband(pl->cur_link_an_mode)) {
+ if (pl->pcs)
+@@ -1137,9 +1135,9 @@ static void phylink_mac_pcs_get_state(st
+ linkmode_copy(state->advertising, pl->link_config.advertising);
+ linkmode_zero(state->lp_advertising);
+ state->interface = pl->link_config.interface;
+- state->an_enabled = pl->link_config.an_enabled;
+ state->rate_matching = pl->link_config.rate_matching;
+- if (state->an_enabled) {
++ if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++ state->advertising)) {
+ state->speed = SPEED_UNKNOWN;
+ state->duplex = DUPLEX_UNKNOWN;
+ state->pause = MLO_PAUSE_NONE;
+@@ -1531,7 +1529,6 @@ struct phylink *phylink_create(struct ph
+ pl->link_config.pause = MLO_PAUSE_AN;
+ pl->link_config.speed = SPEED_UNKNOWN;
+ pl->link_config.duplex = DUPLEX_UNKNOWN;
+- pl->link_config.an_enabled = true;
+ pl->mac_ops = mac_ops;
+ __set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
+ timer_setup(&pl->link_poll, phylink_fixed_poll, 0);
+@@ -2155,8 +2152,9 @@ static void phylink_get_ksettings(const
+ kset->base.speed = state->speed;
+ kset->base.duplex = state->duplex;
+ }
+- kset->base.autoneg = state->an_enabled ? AUTONEG_ENABLE :
+- AUTONEG_DISABLE;
++ kset->base.autoneg = linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++ state->advertising) ?
++ AUTONEG_ENABLE : AUTONEG_DISABLE;
+ }
+
+ /**
+@@ -2303,9 +2301,8 @@ int phylink_ethtool_ksettings_set(struct
+ /* We have ruled out the case with a PHY attached, and the
+ * fixed-link cases. All that is left are in-band links.
+ */
+- config.an_enabled = kset->base.autoneg == AUTONEG_ENABLE;
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, config.advertising,
+- config.an_enabled);
++ kset->base.autoneg == AUTONEG_ENABLE);
+
+ /* If this link is with an SFP, ensure that changes to advertised modes
+ * also cause the associated interface to be selected such that the
+@@ -2339,13 +2336,14 @@ int phylink_ethtool_ksettings_set(struct
+ }
+
+ /* If autonegotiation is enabled, we must have an advertisement */
+- if (config.an_enabled && phylink_is_empty_linkmode(config.advertising))
++ if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++ config.advertising) &&
++ phylink_is_empty_linkmode(config.advertising))
+ return -EINVAL;
+
+ mutex_lock(&pl->state_mutex);
+ pl->link_config.speed = config.speed;
+ pl->link_config.duplex = config.duplex;
+- pl->link_config.an_enabled = config.an_enabled;
+
+ if (pl->link_config.interface != config.interface) {
+ /* The interface changed, e.g. 1000base-X <-> 2500base-X */
+@@ -2951,7 +2949,6 @@ static int phylink_sfp_config_phy(struct
+ config.speed = SPEED_UNKNOWN;
+ config.duplex = DUPLEX_UNKNOWN;
+ config.pause = MLO_PAUSE_AN;
+- config.an_enabled = pl->link_config.an_enabled;
+
+ /* Ignore errors if we're expecting a PHY to attach later */
+ ret = phylink_validate(pl, support, &config);
+@@ -3020,7 +3017,6 @@ static int phylink_sfp_config_optical(st
+ config.speed = SPEED_UNKNOWN;
+ config.duplex = DUPLEX_UNKNOWN;
+ config.pause = MLO_PAUSE_AN;
+- config.an_enabled = true;
+
+ /* For all the interfaces that are supported, reduce the sfp_support
+ * mask to only those link modes that can be supported.
+@@ -3354,7 +3350,8 @@ void phylink_mii_c22_pcs_decode_state(st
+ /* If there is no link or autonegotiation is disabled, the LP advertisement
+ * data is not meaningful, so don't go any further.
+ */
+- if (!state->link || !state->an_enabled)
++ if (!state->link || !linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++ state->advertising))
+ return;
+
+ switch (state->interface) {
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -93,7 +93,6 @@ static inline bool phylink_autoneg_inban
+ * the medium link mode (@speed and @duplex) and the speed/duplex of the phy
+ * interface mode (@interface) are different.
+ * @link: true if the link is up.
+- * @an_enabled: true if autonegotiation is enabled/desired.
+ * @an_complete: true if autonegotiation has completed.
+ */
+ struct phylink_link_state {
+@@ -105,7 +104,6 @@ struct phylink_link_state {
+ int pause;
+ int rate_matching;
+ unsigned int link:1;
+- unsigned int an_enabled:1;
+ unsigned int an_complete:1;
+ };
+
diff --git a/target/linux/generic/backport-6.6/715-13-v6.5-net-phylink-constify-fwnode-arguments.patch b/target/linux/generic/backport-6.6/715-13-v6.5-net-phylink-constify-fwnode-arguments.patch
new file mode 100644
index 0000000000..c06a367b8b
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-13-v6.5-net-phylink-constify-fwnode-arguments.patch
@@ -0,0 +1,88 @@
+From a3555d1f5c208f0a63eafee77381f68d304a0512 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 12 May 2023 17:58:37 +0100
+Subject: [PATCH 12/21] net: phylink: constify fwnode arguments
+
+Both phylink_create() and phylink_fwnode_phy_connect() do not modify
+the fwnode argument that they are passed, so lets constify these.
+
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/phylink.c | 11 ++++++-----
+ include/linux/phylink.h | 9 +++++----
+ 2 files changed, 11 insertions(+), 9 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -706,7 +706,7 @@ static int phylink_validate(struct phyli
+ }
+
+ static int phylink_parse_fixedlink(struct phylink *pl,
+- struct fwnode_handle *fwnode)
++ const struct fwnode_handle *fwnode)
+ {
+ struct fwnode_handle *fixed_node;
+ bool pause, asym_pause, autoneg;
+@@ -817,7 +817,8 @@ static int phylink_parse_fixedlink(struc
+ return 0;
+ }
+
+-static int phylink_parse_mode(struct phylink *pl, struct fwnode_handle *fwnode)
++static int phylink_parse_mode(struct phylink *pl,
++ const struct fwnode_handle *fwnode)
+ {
+ struct fwnode_handle *dn;
+ const char *managed;
+@@ -1440,7 +1441,7 @@ static void phylink_fixed_poll(struct ti
+ static const struct sfp_upstream_ops sfp_phylink_ops;
+
+ static int phylink_register_sfp(struct phylink *pl,
+- struct fwnode_handle *fwnode)
++ const struct fwnode_handle *fwnode)
+ {
+ struct sfp_bus *bus;
+ int ret;
+@@ -1479,7 +1480,7 @@ static int phylink_register_sfp(struct p
+ * must use IS_ERR() to check for errors from this function.
+ */
+ struct phylink *phylink_create(struct phylink_config *config,
+- struct fwnode_handle *fwnode,
++ const struct fwnode_handle *fwnode,
+ phy_interface_t iface,
+ const struct phylink_mac_ops *mac_ops)
+ {
+@@ -1809,7 +1810,7 @@ EXPORT_SYMBOL_GPL(phylink_of_phy_connect
+ * Returns 0 on success or a negative errno.
+ */
+ int phylink_fwnode_phy_connect(struct phylink *pl,
+- struct fwnode_handle *fwnode,
++ const struct fwnode_handle *fwnode,
+ u32 flags)
+ {
+ struct fwnode_handle *phy_fwnode;
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -568,16 +568,17 @@ void phylink_generic_validate(struct phy
+ unsigned long *supported,
+ struct phylink_link_state *state);
+
+-struct phylink *phylink_create(struct phylink_config *, struct fwnode_handle *,
+- phy_interface_t iface,
+- const struct phylink_mac_ops *mac_ops);
++struct phylink *phylink_create(struct phylink_config *,
++ const struct fwnode_handle *,
++ phy_interface_t,
++ const struct phylink_mac_ops *);
+ void phylink_destroy(struct phylink *);
+ bool phylink_expects_phy(struct phylink *pl);
+
+ int phylink_connect_phy(struct phylink *, struct phy_device *);
+ int phylink_of_phy_connect(struct phylink *, struct device_node *, u32 flags);
+ int phylink_fwnode_phy_connect(struct phylink *pl,
+- struct fwnode_handle *fwnode,
++ const struct fwnode_handle *fwnode,
+ u32 flags);
+ void phylink_disconnect_phy(struct phylink *);
+
diff --git a/target/linux/generic/backport-6.6/715-14-v6.3-net-phy-constify-fwnode_get_phy_node-fwnode-argument.patch b/target/linux/generic/backport-6.6/715-14-v6.3-net-phy-constify-fwnode_get_phy_node-fwnode-argument.patch
new file mode 100644
index 0000000000..2649634dc7
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-14-v6.3-net-phy-constify-fwnode_get_phy_node-fwnode-argument.patch
@@ -0,0 +1,38 @@
+From 4a0faa02d419a6728abef0f1d8a32d8c35ef95e6 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 24 Mar 2023 09:23:53 +0000
+Subject: [PATCH] net: phy: constify fwnode_get_phy_node() fwnode argument
+
+fwnode_get_phy_node() does not motify the fwnode structure, so make
+the argument const,
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/phy_device.c | 2 +-
+ include/linux/phy.h | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -3003,7 +3003,7 @@ EXPORT_SYMBOL_GPL(device_phy_find_device
+ * and "phy-device" are not supported in ACPI. DT supports all the three
+ * named references to the phy node.
+ */
+-struct fwnode_handle *fwnode_get_phy_node(struct fwnode_handle *fwnode)
++struct fwnode_handle *fwnode_get_phy_node(const struct fwnode_handle *fwnode)
+ {
+ struct fwnode_handle *phy_node;
+
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -1473,7 +1473,7 @@ int fwnode_get_phy_id(struct fwnode_hand
+ struct mdio_device *fwnode_mdio_find_device(struct fwnode_handle *fwnode);
+ struct phy_device *fwnode_phy_find_device(struct fwnode_handle *phy_fwnode);
+ struct phy_device *device_phy_find_device(struct device *dev);
+-struct fwnode_handle *fwnode_get_phy_node(struct fwnode_handle *fwnode);
++struct fwnode_handle *fwnode_get_phy_node(const struct fwnode_handle *fwnode);
+ struct phy_device *get_phy_device(struct mii_bus *bus, int addr, bool is_c45);
+ int phy_device_register(struct phy_device *phy);
+ void phy_device_free(struct phy_device *phydev);
diff --git a/target/linux/generic/backport-6.6/715-15-v6.4-net-phylink-fix-ksettings_set-ethtool-call.patch b/target/linux/generic/backport-6.6/715-15-v6.4-net-phylink-fix-ksettings_set-ethtool-call.patch
new file mode 100644
index 0000000000..5eba18b026
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-15-v6.4-net-phylink-fix-ksettings_set-ethtool-call.patch
@@ -0,0 +1,44 @@
+From cc73de0411f7d3cdd157564a78f7a39058420ff8 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Sat, 13 May 2023 22:03:45 +0100
+Subject: [PATCH 13/21] net: phylink: fix ksettings_set() ethtool call
+
+While testing a Fiberstore SFP-10G-T module (which uses 10GBASE-R with
+rate adaption) in a Clearfog platform (which can't do that) it was
+found that the PHYs advertisement was not limited according to the
+hosts capabilities when using ethtool to change it.
+
+Fix this by ensuring that we mask the advertisement with the computed
+support mask as the very first thing we do.
+
+Fixes: cbc1bb1e4689 ("net: phylink: simplify phy case for ksettings_set method")
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/phylink.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -2226,6 +2226,10 @@ int phylink_ethtool_ksettings_set(struct
+
+ ASSERT_RTNL();
+
++ /* Mask out unsupported advertisements */
++ linkmode_and(config.advertising, kset->link_modes.advertising,
++ pl->supported);
++
+ if (pl->phydev) {
+ /* We can rely on phylib for this update; we also do not need
+ * to update the pl->link_config settings:
+@@ -2250,10 +2254,6 @@ int phylink_ethtool_ksettings_set(struct
+
+ config = pl->link_config;
+
+- /* Mask out unsupported advertisements */
+- linkmode_and(config.advertising, kset->link_modes.advertising,
+- pl->supported);
+-
+ /* FIXME: should we reject autoneg if phy/mac does not support it? */
+ switch (kset->base.autoneg) {
+ case AUTONEG_DISABLE:
diff --git a/target/linux/generic/backport-6.6/715-16-v6.5-net-sfp-add-support-for-setting-signalling-rate.patch b/target/linux/generic/backport-6.6/715-16-v6.5-net-sfp-add-support-for-setting-signalling-rate.patch
new file mode 100644
index 0000000000..79de6122b7
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-16-v6.5-net-sfp-add-support-for-setting-signalling-rate.patch
@@ -0,0 +1,149 @@
+From 0100d1c5789018ba77bf2f4fab3bd91ecece7b3b Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Wed, 17 May 2023 11:38:12 +0100
+Subject: [PATCH 14/21] net: sfp: add support for setting signalling rate
+
+Add support to the SFP layer to allow phylink to set the signalling
+rate for a SFP module. The rate given will be in units of kilo-baud
+(1000 baud).
+
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 24 ++++++++++++++++++++++++
+ drivers/net/phy/sfp-bus.c | 20 ++++++++++++++++++++
+ drivers/net/phy/sfp.c | 5 +++++
+ drivers/net/phy/sfp.h | 1 +
+ include/linux/sfp.h | 6 ++++++
+ 5 files changed, 56 insertions(+)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -156,6 +156,23 @@ static const char *phylink_an_mode_str(u
+ return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown";
+ }
+
++static unsigned int phylink_interface_signal_rate(phy_interface_t interface)
++{
++ switch (interface) {
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_1000BASEX: /* 1.25Mbd */
++ return 1250;
++ case PHY_INTERFACE_MODE_2500BASEX: /* 3.125Mbd */
++ return 3125;
++ case PHY_INTERFACE_MODE_5GBASER: /* 5.15625Mbd */
++ return 5156;
++ case PHY_INTERFACE_MODE_10GBASER: /* 10.3125Mbd */
++ return 10313;
++ default:
++ return 0;
++ }
++}
++
+ /**
+ * phylink_interface_max_speed() - get the maximum speed of a phy interface
+ * @interface: phy interface mode defined by &typedef phy_interface_t
+@@ -1024,6 +1041,7 @@ static void phylink_major_config(struct
+ {
+ struct phylink_pcs *pcs = NULL;
+ bool pcs_changed = false;
++ unsigned int rate_kbd;
+ int err;
+
+ phylink_dbg(pl, "major config %s\n", phy_modes(state->interface));
+@@ -1083,6 +1101,12 @@ static void phylink_major_config(struct
+ ERR_PTR(err));
+ }
+
++ if (pl->sfp_bus) {
++ rate_kbd = phylink_interface_signal_rate(state->interface);
++ if (rate_kbd)
++ sfp_upstream_set_signal_rate(pl->sfp_bus, rate_kbd);
++ }
++
+ phylink_pcs_poll_start(pl);
+ }
+
+--- a/drivers/net/phy/sfp-bus.c
++++ b/drivers/net/phy/sfp-bus.c
+@@ -586,6 +586,26 @@ static void sfp_upstream_clear(struct sf
+ }
+
+ /**
++ * sfp_upstream_set_signal_rate() - set data signalling rate
++ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
++ * @rate_kbd: signalling rate in units of 1000 baud
++ *
++ * Configure the rate select settings on the SFP module for the signalling
++ * rate (not the same as the data rate).
++ *
++ * Locks that may be held:
++ * Phylink's state_mutex
++ * rtnl lock
++ * SFP's sm_mutex
++ */
++void sfp_upstream_set_signal_rate(struct sfp_bus *bus, unsigned int rate_kbd)
++{
++ if (bus->registered)
++ bus->socket_ops->set_signal_rate(bus->sfp, rate_kbd);
++}
++EXPORT_SYMBOL_GPL(sfp_upstream_set_signal_rate);
++
++/**
+ * sfp_bus_find_fwnode() - parse and locate the SFP bus from fwnode
+ * @fwnode: firmware node for the parent device (MAC or PHY)
+ *
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -2474,6 +2474,10 @@ static void sfp_stop(struct sfp *sfp)
+ sfp_sm_event(sfp, SFP_E_DEV_DOWN);
+ }
+
++static void sfp_set_signal_rate(struct sfp *sfp, unsigned int rate_kbd)
++{
++}
++
+ static int sfp_module_info(struct sfp *sfp, struct ethtool_modinfo *modinfo)
+ {
+ /* locking... and check module is present */
+@@ -2552,6 +2556,7 @@ static const struct sfp_socket_ops sfp_m
+ .detach = sfp_detach,
+ .start = sfp_start,
+ .stop = sfp_stop,
++ .set_signal_rate = sfp_set_signal_rate,
+ .module_info = sfp_module_info,
+ .module_eeprom = sfp_module_eeprom,
+ .module_eeprom_by_page = sfp_module_eeprom_by_page,
+--- a/drivers/net/phy/sfp.h
++++ b/drivers/net/phy/sfp.h
+@@ -19,6 +19,7 @@ struct sfp_socket_ops {
+ void (*detach)(struct sfp *sfp);
+ void (*start)(struct sfp *sfp);
+ void (*stop)(struct sfp *sfp);
++ void (*set_signal_rate)(struct sfp *sfp, unsigned int rate_kbd);
+ int (*module_info)(struct sfp *sfp, struct ethtool_modinfo *modinfo);
+ int (*module_eeprom)(struct sfp *sfp, struct ethtool_eeprom *ee,
+ u8 *data);
+--- a/include/linux/sfp.h
++++ b/include/linux/sfp.h
+@@ -547,6 +547,7 @@ int sfp_get_module_eeprom_by_page(struct
+ struct netlink_ext_ack *extack);
+ void sfp_upstream_start(struct sfp_bus *bus);
+ void sfp_upstream_stop(struct sfp_bus *bus);
++void sfp_upstream_set_signal_rate(struct sfp_bus *bus, unsigned int rate_kbd);
+ void sfp_bus_put(struct sfp_bus *bus);
+ struct sfp_bus *sfp_bus_find_fwnode(const struct fwnode_handle *fwnode);
+ int sfp_bus_add_upstream(struct sfp_bus *bus, void *upstream,
+@@ -606,6 +607,11 @@ static inline void sfp_upstream_stop(str
+ {
+ }
+
++static inline void sfp_upstream_set_signal_rate(struct sfp_bus *bus,
++ unsigned int rate_kbd)
++{
++}
++
+ static inline void sfp_bus_put(struct sfp_bus *bus)
+ {
+ }
diff --git a/target/linux/generic/backport-6.6/715-17-v6.5-net-phy-add-helpers-for-comparing-phy-IDs.patch b/target/linux/generic/backport-6.6/715-17-v6.5-net-phy-add-helpers-for-comparing-phy-IDs.patch
new file mode 100644
index 0000000000..8a694c86da
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-17-v6.5-net-phy-add-helpers-for-comparing-phy-IDs.patch
@@ -0,0 +1,147 @@
+From b84acdb07222a701bfc6403b374249c86f97d18d Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@armlinux.org.uk>
+Date: Fri, 19 May 2023 14:03:59 +0100
+Subject: [PATCH 15/21] net: phy: add helpers for comparing phy IDs
+
+There are several places which open code comparing PHY IDs. Provide a
+couple of helpers to assist with this, using a slightly simpler test
+than the original:
+
+- phy_id_compare() compares two arbitary PHY IDs and a mask of the
+ significant bits in the ID.
+- phydev_id_compare() compares the bound phydev with the specified
+ PHY ID, using the bound driver's mask.
+
+Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/micrel.c | 6 +++---
+ drivers/net/phy/phy_device.c | 16 +++++++---------
+ drivers/net/phy/phylink.c | 4 ++--
+ include/linux/phy.h | 28 ++++++++++++++++++++++++++++
+ 4 files changed, 40 insertions(+), 14 deletions(-)
+
+--- a/drivers/net/phy/micrel.c
++++ b/drivers/net/phy/micrel.c
+@@ -620,7 +620,7 @@ static int ksz8051_ksz8795_match_phy_dev
+ {
+ int ret;
+
+- if ((phydev->phy_id & MICREL_PHY_ID_MASK) != PHY_ID_KSZ8051)
++ if (!phy_id_compare(phydev->phy_id, PHY_ID_KSZ8051, MICREL_PHY_ID_MASK))
+ return 0;
+
+ ret = phy_read(phydev, MII_BMSR);
+@@ -1455,7 +1455,7 @@ static int ksz9x31_cable_test_fault_leng
+ *
+ * distance to fault = (VCT_DATA - 22) * 4 / cable propagation velocity
+ */
+- if ((phydev->phy_id & MICREL_PHY_ID_MASK) == PHY_ID_KSZ9131)
++ if (phydev_id_compare(phydev, PHY_ID_KSZ9131))
+ dt = clamp(dt - 22, 0, 255);
+
+ return (dt * 400) / 10;
+@@ -1887,7 +1887,7 @@ static __always_inline int ksz886x_cable
+ */
+ dt = FIELD_GET(data_mask, status);
+
+- if ((phydev->phy_id & MICREL_PHY_ID_MASK) == PHY_ID_LAN8814)
++ if (phydev_id_compare(phydev, PHY_ID_LAN8814))
+ return ((dt - 22) * 800) / 10;
+ else
+ return (dt * 400) / 10;
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -422,8 +422,7 @@ int phy_unregister_fixup(const char *bus
+ fixup = list_entry(pos, struct phy_fixup, list);
+
+ if ((!strcmp(fixup->bus_id, bus_id)) &&
+- ((fixup->phy_uid & phy_uid_mask) ==
+- (phy_uid & phy_uid_mask))) {
++ phy_id_compare(fixup->phy_uid, phy_uid, phy_uid_mask)) {
+ list_del(&fixup->list);
+ kfree(fixup);
+ ret = 0;
+@@ -459,8 +458,8 @@ static int phy_needs_fixup(struct phy_de
+ if (strcmp(fixup->bus_id, PHY_ANY_ID) != 0)
+ return 0;
+
+- if ((fixup->phy_uid & fixup->phy_uid_mask) !=
+- (phydev->phy_id & fixup->phy_uid_mask))
++ if (!phy_id_compare(phydev->phy_id, fixup->phy_uid,
++ fixup->phy_uid_mask))
+ if (fixup->phy_uid != PHY_ANY_UID)
+ return 0;
+
+@@ -507,15 +506,14 @@ static int phy_bus_match(struct device *
+ if (phydev->c45_ids.device_ids[i] == 0xffffffff)
+ continue;
+
+- if ((phydrv->phy_id & phydrv->phy_id_mask) ==
+- (phydev->c45_ids.device_ids[i] &
+- phydrv->phy_id_mask))
++ if (phy_id_compare(phydev->c45_ids.device_ids[i],
++ phydrv->phy_id, phydrv->phy_id_mask))
+ return 1;
+ }
+ return 0;
+ } else {
+- return (phydrv->phy_id & phydrv->phy_id_mask) ==
+- (phydev->phy_id & phydrv->phy_id_mask);
++ return phy_id_compare(phydev->phy_id, phydrv->phy_id,
++ phydrv->phy_id_mask);
+ }
+ }
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -3151,8 +3151,8 @@ static void phylink_sfp_link_up(void *up
+ */
+ static bool phylink_phy_no_inband(struct phy_device *phy)
+ {
+- return phy->is_c45 &&
+- (phy->c45_ids.device_ids[1] & 0xfffffff0) == 0xae025150;
++ return phy->is_c45 && phy_id_compare(phy->c45_ids.device_ids[1],
++ 0xae025150, 0xfffffff0);
+ }
+
+ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -993,6 +993,34 @@ struct phy_driver {
+ #define PHY_ID_MATCH_MODEL(id) .phy_id = (id), .phy_id_mask = GENMASK(31, 4)
+ #define PHY_ID_MATCH_VENDOR(id) .phy_id = (id), .phy_id_mask = GENMASK(31, 10)
+
++/**
++ * phy_id_compare - compare @id1 with @id2 taking account of @mask
++ * @id1: first PHY ID
++ * @id2: second PHY ID
++ * @mask: the PHY ID mask, set bits are significant in matching
++ *
++ * Return true if the bits from @id1 and @id2 specified by @mask match.
++ * This uses an equivalent test to (@id & @mask) == (@phy_id & @mask).
++ */
++static inline bool phy_id_compare(u32 id1, u32 id2, u32 mask)
++{
++ return !((id1 ^ id2) & mask);
++}
++
++/**
++ * phydev_id_compare - compare @id with the PHY's Clause 22 ID
++ * @phydev: the PHY device
++ * @id: the PHY ID to be matched
++ *
++ * Compare the @phydev clause 22 ID with the provided @id and return true or
++ * false depending whether it matches, using the bound driver mask. The
++ * @phydev must be bound to a driver.
++ */
++static inline bool phydev_id_compare(struct phy_device *phydev, u32 id)
++{
++ return phy_id_compare(id, phydev->phy_id, phydev->drv->phy_id_mask);
++}
++
+ /* A Structure for boards to register fixups with the PHY Lib */
+ struct phy_fixup {
+ struct list_head list;
diff --git a/target/linux/generic/backport-6.6/715-18-v6.5-net-phylink-require-supported_interfaces-to-be-fille.patch b/target/linux/generic/backport-6.6/715-18-v6.5-net-phylink-require-supported_interfaces-to-be-fille.patch
new file mode 100644
index 0000000000..5970355a6c
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-18-v6.5-net-phylink-require-supported_interfaces-to-be-fille.patch
@@ -0,0 +1,71 @@
+From 441e1e44301fc5762a06737f8ec04bf1ce3fb039 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Sat, 20 May 2023 11:41:42 +0100
+Subject: [PATCH 16/21] net: phylink: require supported_interfaces to be filled
+
+We have been requiring the supported_interfaces bitmap to be filled in
+by MAC drivers that have a mac_select_pcs() method. Now that all MAC
+drivers fill in the supported_interfaces bitmap, it is time to enforce
+this. We have already required supported_interfaces to be set in order
+for optical SFPs to be configured in commit f81fa96d8a6c ("net: phylink:
+use phy_interface_t bitmaps for optical modules").
+
+Refuse phylink creation if supported_interfaces is empty, and remove
+code to deal with cases where this mask is empty.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/E1q0K1u-006EIP-ET@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 26 +++++++++++---------------
+ 1 file changed, 11 insertions(+), 15 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -710,14 +710,11 @@ static int phylink_validate(struct phyli
+ {
+ const unsigned long *interfaces = pl->config->supported_interfaces;
+
+- if (!phy_interface_empty(interfaces)) {
+- if (state->interface == PHY_INTERFACE_MODE_NA)
+- return phylink_validate_mask(pl, supported, state,
+- interfaces);
++ if (state->interface == PHY_INTERFACE_MODE_NA)
++ return phylink_validate_mask(pl, supported, state, interfaces);
+
+- if (!test_bit(state->interface, interfaces))
+- return -EINVAL;
+- }
++ if (!test_bit(state->interface, interfaces))
++ return -EINVAL;
+
+ return phylink_validate_mac_and_pcs(pl, supported, state);
+ }
+@@ -1512,19 +1509,18 @@ struct phylink *phylink_create(struct ph
+ struct phylink *pl;
+ int ret;
+
+- if (mac_ops->mac_select_pcs &&
+- mac_ops->mac_select_pcs(config, PHY_INTERFACE_MODE_NA) !=
+- ERR_PTR(-EOPNOTSUPP))
+- using_mac_select_pcs = true;
+-
+ /* Validate the supplied configuration */
+- if (using_mac_select_pcs &&
+- phy_interface_empty(config->supported_interfaces)) {
++ if (phy_interface_empty(config->supported_interfaces)) {
+ dev_err(config->dev,
+- "phylink: error: empty supported_interfaces but mac_select_pcs() method present\n");
++ "phylink: error: empty supported_interfaces\n");
+ return ERR_PTR(-EINVAL);
+ }
+
++ if (mac_ops->mac_select_pcs &&
++ mac_ops->mac_select_pcs(config, PHY_INTERFACE_MODE_NA) !=
++ ERR_PTR(-EOPNOTSUPP))
++ using_mac_select_pcs = true;
++
+ pl = kzalloc(sizeof(*pl), GFP_KERNEL);
+ if (!pl)
+ return ERR_PTR(-ENOMEM);
diff --git a/target/linux/generic/backport-6.6/715-19-v6.5-net-phylink-remove-duplicated-linkmode-pause-resolut.patch b/target/linux/generic/backport-6.6/715-19-v6.5-net-phylink-remove-duplicated-linkmode-pause-resolut.patch
new file mode 100644
index 0000000000..3a26b4b600
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-19-v6.5-net-phylink-remove-duplicated-linkmode-pause-resolut.patch
@@ -0,0 +1,64 @@
+From 4b624a39f2ab523ca6a6ad9448fab1deb7b101e2 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 23 May 2023 11:15:53 +0100
+Subject: [PATCH 17/21] net: phylink: remove duplicated linkmode pause
+ resolution
+
+Phylink had two chunks of code virtually the same for resolving the
+negotiated pause modes. Factor this down to one function.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 15 ++++-----------
+ 1 file changed, 4 insertions(+), 11 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -976,11 +976,10 @@ static void phylink_apply_manual_flow(st
+ state->pause = pl->link_config.pause;
+ }
+
+-static void phylink_resolve_flow(struct phylink_link_state *state)
++static void phylink_resolve_an_pause(struct phylink_link_state *state)
+ {
+ bool tx_pause, rx_pause;
+
+- state->pause = MLO_PAUSE_NONE;
+ if (state->duplex == DUPLEX_FULL) {
+ linkmode_resolve_pause(state->advertising,
+ state->lp_advertising,
+@@ -1192,7 +1191,8 @@ static void phylink_get_fixed_state(stru
+ else if (pl->link_gpio)
+ state->link = !!gpiod_get_value_cansleep(pl->link_gpio);
+
+- phylink_resolve_flow(state);
++ state->pause = MLO_PAUSE_NONE;
++ phylink_resolve_an_pause(state);
+ }
+
+ static void phylink_mac_initial_config(struct phylink *pl, bool force_restart)
+@@ -3215,7 +3215,6 @@ static const struct sfp_upstream_ops sfp
+ static void phylink_decode_c37_word(struct phylink_link_state *state,
+ uint16_t config_reg, int speed)
+ {
+- bool tx_pause, rx_pause;
+ int fd_bit;
+
+ if (speed == SPEED_2500)
+@@ -3234,13 +3233,7 @@ static void phylink_decode_c37_word(stru
+ state->link = false;
+ }
+
+- linkmode_resolve_pause(state->advertising, state->lp_advertising,
+- &tx_pause, &rx_pause);
+-
+- if (tx_pause)
+- state->pause |= MLO_PAUSE_TX;
+- if (rx_pause)
+- state->pause |= MLO_PAUSE_RX;
++ phylink_resolve_an_pause(state);
+ }
+
+ static void phylink_decode_sgmii_word(struct phylink_link_state *state,
diff --git a/target/linux/generic/backport-6.6/715-20-v6.5-net-phylink-add-function-to-resolve-clause-73-negoti.patch b/target/linux/generic/backport-6.6/715-20-v6.5-net-phylink-add-function-to-resolve-clause-73-negoti.patch
new file mode 100644
index 0000000000..2b2634f80c
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-20-v6.5-net-phylink-add-function-to-resolve-clause-73-negoti.patch
@@ -0,0 +1,76 @@
+From aa8b6bd2b1f235b262bd27f317a0516f196c2c6a Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 23 May 2023 11:15:58 +0100
+Subject: [PATCH 18/21] net: phylink: add function to resolve clause 73
+ negotiation
+
+Add a function to resolve clause 73 negotiation according to the
+priority resolution function described in clause 73.3.6.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 39 +++++++++++++++++++++++++++++++++++++++
+ include/linux/phylink.h | 2 ++
+ 2 files changed, 41 insertions(+)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -3212,6 +3212,45 @@ static const struct sfp_upstream_ops sfp
+
+ /* Helpers for MAC drivers */
+
++static struct {
++ int bit;
++ int speed;
++} phylink_c73_priority_resolution[] = {
++ { ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT, SPEED_100000 },
++ { ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT, SPEED_100000 },
++ /* 100GBASE-KP4 and 100GBASE-CR10 not supported */
++ { ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT, SPEED_40000 },
++ { ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT, SPEED_40000 },
++ { ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, SPEED_10000 },
++ { ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, SPEED_10000 },
++ /* 5GBASE-KR not supported */
++ { ETHTOOL_LINK_MODE_2500baseX_Full_BIT, SPEED_2500 },
++ { ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, SPEED_1000 },
++};
++
++void phylink_resolve_c73(struct phylink_link_state *state)
++{
++ int i;
++
++ for (i = 0; i < ARRAY_SIZE(phylink_c73_priority_resolution); i++) {
++ int bit = phylink_c73_priority_resolution[i].bit;
++ if (linkmode_test_bit(bit, state->advertising) &&
++ linkmode_test_bit(bit, state->lp_advertising))
++ break;
++ }
++
++ if (i < ARRAY_SIZE(phylink_c73_priority_resolution)) {
++ state->speed = phylink_c73_priority_resolution[i].speed;
++ state->duplex = DUPLEX_FULL;
++ } else {
++ /* negotiation failure */
++ state->link = false;
++ }
++
++ phylink_resolve_an_pause(state);
++}
++EXPORT_SYMBOL_GPL(phylink_resolve_c73);
++
+ static void phylink_decode_c37_word(struct phylink_link_state *state,
+ uint16_t config_reg, int speed)
+ {
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -656,6 +656,8 @@ int phylink_mii_c22_pcs_config(struct md
+ const unsigned long *advertising);
+ void phylink_mii_c22_pcs_an_restart(struct mdio_device *pcs);
+
++void phylink_resolve_c73(struct phylink_link_state *state);
++
+ void phylink_mii_c45_pcs_get_state(struct mdio_device *pcs,
+ struct phylink_link_state *state);
+
diff --git a/target/linux/generic/backport-6.6/715-21-v6.5-net-phylink-provide-phylink_pcs_config-and-phylink_p.patch b/target/linux/generic/backport-6.6/715-21-v6.5-net-phylink-provide-phylink_pcs_config-and-phylink_p.patch
new file mode 100644
index 0000000000..eea99a5d78
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-21-v6.5-net-phylink-provide-phylink_pcs_config-and-phylink_p.patch
@@ -0,0 +1,100 @@
+From 796d709363135a6bd6a8ccc07b509c939e5b855f Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 23 May 2023 16:31:50 +0100
+Subject: [PATCH 19/21] net: phylink: provide phylink_pcs_config() and
+ phylink_pcs_link_up()
+
+Add two helper functions for calling PCS methods. phylink_pcs_config()
+allows us to handle PCS configuration specifics in one location, rather
+than the two call sites. phylink_pcs_link_up() gives us consistency.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://lore.kernel.org/r/E1q1TzK-007Exd-Rs@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 53 ++++++++++++++++++++++++---------------
+ 1 file changed, 33 insertions(+), 20 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -991,6 +991,25 @@ static void phylink_resolve_an_pause(str
+ }
+ }
+
++static int phylink_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
++ const struct phylink_link_state *state,
++ bool permit_pause_to_mac)
++{
++ if (!pcs)
++ return 0;
++
++ return pcs->ops->pcs_config(pcs, mode, state->interface,
++ state->advertising, permit_pause_to_mac);
++}
++
++static void phylink_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode,
++ phy_interface_t interface, int speed,
++ int duplex)
++{
++ if (pcs && pcs->ops->pcs_link_up)
++ pcs->ops->pcs_link_up(pcs, mode, interface, speed, duplex);
++}
++
+ static void phylink_pcs_poll_stop(struct phylink *pl)
+ {
+ if (pl->cfg_link_an_mode == MLO_AN_INBAND)
+@@ -1074,18 +1093,15 @@ static void phylink_major_config(struct
+
+ phylink_mac_config(pl, state);
+
+- if (pl->pcs) {
+- err = pl->pcs->ops->pcs_config(pl->pcs, pl->cur_link_an_mode,
+- state->interface,
+- state->advertising,
+- !!(pl->link_config.pause &
+- MLO_PAUSE_AN));
+- if (err < 0)
+- phylink_err(pl, "pcs_config failed: %pe\n",
+- ERR_PTR(err));
+- if (err > 0)
+- restart = true;
+- }
++ err = phylink_pcs_config(pl->pcs, pl->cur_link_an_mode, state,
++ !!(pl->link_config.pause &
++ MLO_PAUSE_AN));
++ if (err < 0)
++ phylink_err(pl, "pcs_config failed: %pe\n",
++ ERR_PTR(err));
++ else if (err > 0)
++ restart = true;
++
+ if (restart)
+ phylink_mac_pcs_an_restart(pl);
+
+@@ -1136,11 +1152,9 @@ static int phylink_change_inband_advert(
+ * restart negotiation if the pcs_config() helper indicates that
+ * the programmed advertisement has changed.
+ */
+- ret = pl->pcs->ops->pcs_config(pl->pcs, pl->cur_link_an_mode,
+- pl->link_config.interface,
+- pl->link_config.advertising,
+- !!(pl->link_config.pause &
+- MLO_PAUSE_AN));
++ ret = phylink_pcs_config(pl->pcs, pl->cur_link_an_mode,
++ &pl->link_config,
++ !!(pl->link_config.pause & MLO_PAUSE_AN));
+ if (ret < 0)
+ return ret;
+
+@@ -1272,9 +1286,8 @@ static void phylink_link_up(struct phyli
+
+ pl->cur_interface = link_state.interface;
+
+- if (pl->pcs && pl->pcs->ops->pcs_link_up)
+- pl->pcs->ops->pcs_link_up(pl->pcs, pl->cur_link_an_mode,
+- pl->cur_interface, speed, duplex);
++ phylink_pcs_link_up(pl->pcs, pl->cur_link_an_mode, pl->cur_interface,
++ speed, duplex);
+
+ pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->cur_link_an_mode,
+ pl->cur_interface, speed, duplex,
diff --git a/target/linux/generic/backport-6.6/715-23-v6.4-net-phylink-actually-fix-ksettings_set-ethtool-call.patch b/target/linux/generic/backport-6.6/715-23-v6.4-net-phylink-actually-fix-ksettings_set-ethtool-call.patch
new file mode 100644
index 0000000000..2f7f7a5737
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-23-v6.4-net-phylink-actually-fix-ksettings_set-ethtool-call.patch
@@ -0,0 +1,55 @@
+From 11933aa76865621d8e82553c8f3bc07796a5aaa2 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 1 Jun 2023 10:12:06 +0100
+Subject: [PATCH 20/21] net: phylink: actually fix ksettings_set() ethtool call
+
+Raju Lakkaraju reported that the below commit caused a regression
+with Lan743x drivers and a 2.5G SFP. Sadly, this is because the commit
+was utterly wrong. Let's fix this properly by not moving the
+linkmode_and(), but instead copying the link ksettings and then
+modifying the advertising mask before passing the modified link
+ksettings to phylib.
+
+Fixes: df0acdc59b09 ("net: phylink: fix ksettings_set() ethtool call")
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://lore.kernel.org/r/E1q4eLm-00Ayxk-GZ@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 15 ++++++++++-----
+ 1 file changed, 10 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -2259,11 +2259,13 @@ int phylink_ethtool_ksettings_set(struct
+
+ ASSERT_RTNL();
+
+- /* Mask out unsupported advertisements */
+- linkmode_and(config.advertising, kset->link_modes.advertising,
+- pl->supported);
+-
+ if (pl->phydev) {
++ struct ethtool_link_ksettings phy_kset = *kset;
++
++ linkmode_and(phy_kset.link_modes.advertising,
++ phy_kset.link_modes.advertising,
++ pl->supported);
++
+ /* We can rely on phylib for this update; we also do not need
+ * to update the pl->link_config settings:
+ * - the configuration returned via ksettings_get() will come
+@@ -2282,10 +2284,13 @@ int phylink_ethtool_ksettings_set(struct
+ * the presence of a PHY, this should not be changed as that
+ * should be determined from the media side advertisement.
+ */
+- return phy_ethtool_ksettings_set(pl->phydev, kset);
++ return phy_ethtool_ksettings_set(pl->phydev, &phy_kset);
+ }
+
+ config = pl->link_config;
++ /* Mask out unsupported advertisements */
++ linkmode_and(config.advertising, kset->link_modes.advertising,
++ pl->supported);
+
+ /* FIXME: should we reject autoneg if phy/mac does not support it? */
+ switch (kset->base.autoneg) {
diff --git a/target/linux/generic/backport-6.6/715-24-v6.5-net-phylink-add-PCS-negotiation-mode.patch b/target/linux/generic/backport-6.6/715-24-v6.5-net-phylink-add-PCS-negotiation-mode.patch
new file mode 100644
index 0000000000..e1a8539aae
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-24-v6.5-net-phylink-add-PCS-negotiation-mode.patch
@@ -0,0 +1,324 @@
+From 79b07c3e9c4a2272927be8848c26b372516e1958 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 16 Jun 2023 13:06:22 +0100
+Subject: [PATCH 21/21] net: phylink: add PCS negotiation mode
+
+PCS have to work out whether they should enable PCS negotiation by
+looking at the "mode" and "interface" arguments, and the Autoneg bit
+in the advertising mask.
+
+This leads to some complex logic, so lets pull that out into phylink
+and instead pass a "neg_mode" argument to the PCS configuration and
+link up methods, instead of the "mode" argument.
+
+In order to transition drivers, add a "neg_mode" flag to the phylink
+PCS structure to PCS can indicate whether they want to be passed the
+neg_mode or the old mode argument.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://lore.kernel.org/r/E1qA8De-00EaFA-Ht@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 45 +++++++++++++----
+ include/linux/phylink.h | 104 +++++++++++++++++++++++++++++++++++---
+ 2 files changed, 132 insertions(+), 17 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -71,6 +71,7 @@ struct phylink {
+ struct mutex state_mutex;
+ struct phylink_link_state phy_state;
+ struct work_struct resolve;
++ unsigned int pcs_neg_mode;
+
+ bool mac_link_dropped;
+ bool using_mac_select_pcs;
+@@ -991,23 +992,23 @@ static void phylink_resolve_an_pause(str
+ }
+ }
+
+-static int phylink_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
++static int phylink_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ const struct phylink_link_state *state,
+ bool permit_pause_to_mac)
+ {
+ if (!pcs)
+ return 0;
+
+- return pcs->ops->pcs_config(pcs, mode, state->interface,
++ return pcs->ops->pcs_config(pcs, neg_mode, state->interface,
+ state->advertising, permit_pause_to_mac);
+ }
+
+-static void phylink_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode,
++static void phylink_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
+ phy_interface_t interface, int speed,
+ int duplex)
+ {
+ if (pcs && pcs->ops->pcs_link_up)
+- pcs->ops->pcs_link_up(pcs, mode, interface, speed, duplex);
++ pcs->ops->pcs_link_up(pcs, neg_mode, interface, speed, duplex);
+ }
+
+ static void phylink_pcs_poll_stop(struct phylink *pl)
+@@ -1057,10 +1058,15 @@ static void phylink_major_config(struct
+ struct phylink_pcs *pcs = NULL;
+ bool pcs_changed = false;
+ unsigned int rate_kbd;
++ unsigned int neg_mode;
+ int err;
+
+ phylink_dbg(pl, "major config %s\n", phy_modes(state->interface));
+
++ pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode,
++ state->interface,
++ state->advertising);
++
+ if (pl->using_mac_select_pcs) {
+ pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
+ if (IS_ERR(pcs)) {
+@@ -1093,9 +1099,12 @@ static void phylink_major_config(struct
+
+ phylink_mac_config(pl, state);
+
+- err = phylink_pcs_config(pl->pcs, pl->cur_link_an_mode, state,
+- !!(pl->link_config.pause &
+- MLO_PAUSE_AN));
++ neg_mode = pl->cur_link_an_mode;
++ if (pl->pcs && pl->pcs->neg_mode)
++ neg_mode = pl->pcs_neg_mode;
++
++ err = phylink_pcs_config(pl->pcs, neg_mode, state,
++ !!(pl->link_config.pause & MLO_PAUSE_AN));
+ if (err < 0)
+ phylink_err(pl, "pcs_config failed: %pe\n",
+ ERR_PTR(err));
+@@ -1130,6 +1139,7 @@ static void phylink_major_config(struct
+ */
+ static int phylink_change_inband_advert(struct phylink *pl)
+ {
++ unsigned int neg_mode;
+ int ret;
+
+ if (test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state))
+@@ -1148,12 +1158,20 @@ static int phylink_change_inband_advert(
+ __ETHTOOL_LINK_MODE_MASK_NBITS, pl->link_config.advertising,
+ pl->link_config.pause);
+
++ /* Recompute the PCS neg mode */
++ pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode,
++ pl->link_config.interface,
++ pl->link_config.advertising);
++
++ neg_mode = pl->cur_link_an_mode;
++ if (pl->pcs->neg_mode)
++ neg_mode = pl->pcs_neg_mode;
++
+ /* Modern PCS-based method; update the advert at the PCS, and
+ * restart negotiation if the pcs_config() helper indicates that
+ * the programmed advertisement has changed.
+ */
+- ret = phylink_pcs_config(pl->pcs, pl->cur_link_an_mode,
+- &pl->link_config,
++ ret = phylink_pcs_config(pl->pcs, neg_mode, &pl->link_config,
+ !!(pl->link_config.pause & MLO_PAUSE_AN));
+ if (ret < 0)
+ return ret;
+@@ -1256,6 +1274,7 @@ static void phylink_link_up(struct phyli
+ struct phylink_link_state link_state)
+ {
+ struct net_device *ndev = pl->netdev;
++ unsigned int neg_mode;
+ int speed, duplex;
+ bool rx_pause;
+
+@@ -1286,8 +1305,12 @@ static void phylink_link_up(struct phyli
+
+ pl->cur_interface = link_state.interface;
+
+- phylink_pcs_link_up(pl->pcs, pl->cur_link_an_mode, pl->cur_interface,
+- speed, duplex);
++ neg_mode = pl->cur_link_an_mode;
++ if (pl->pcs && pl->pcs->neg_mode)
++ neg_mode = pl->pcs_neg_mode;
++
++ phylink_pcs_link_up(pl->pcs, neg_mode, pl->cur_interface, speed,
++ duplex);
+
+ pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->cur_link_an_mode,
+ pl->cur_interface, speed, duplex,
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -21,6 +21,24 @@ enum {
+ MLO_AN_FIXED, /* Fixed-link mode */
+ MLO_AN_INBAND, /* In-band protocol */
+
++ /* PCS "negotiation" mode.
++ * PHYLINK_PCS_NEG_NONE - protocol has no inband capability
++ * PHYLINK_PCS_NEG_OUTBAND - some out of band or fixed link setting
++ * PHYLINK_PCS_NEG_INBAND_DISABLED - inband mode disabled, e.g.
++ * 1000base-X with autoneg off
++ * PHYLINK_PCS_NEG_INBAND_ENABLED - inband mode enabled
++ * Additionally, this can be tested using bitmasks:
++ * PHYLINK_PCS_NEG_INBAND - inband mode selected
++ * PHYLINK_PCS_NEG_ENABLED - negotiation mode enabled
++ */
++ PHYLINK_PCS_NEG_NONE = 0,
++ PHYLINK_PCS_NEG_ENABLED = BIT(4),
++ PHYLINK_PCS_NEG_OUTBAND = BIT(5),
++ PHYLINK_PCS_NEG_INBAND = BIT(6),
++ PHYLINK_PCS_NEG_INBAND_DISABLED = PHYLINK_PCS_NEG_INBAND,
++ PHYLINK_PCS_NEG_INBAND_ENABLED = PHYLINK_PCS_NEG_INBAND |
++ PHYLINK_PCS_NEG_ENABLED,
++
+ /* MAC_SYM_PAUSE and MAC_ASYM_PAUSE are used when configuring our
+ * autonegotiation advertisement. They correspond to the PAUSE and
+ * ASM_DIR bits defined by 802.3, respectively.
+@@ -80,6 +98,70 @@ static inline bool phylink_autoneg_inban
+ }
+
+ /**
++ * phylink_pcs_neg_mode() - helper to determine PCS inband mode
++ * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND.
++ * @interface: interface mode to be used
++ * @advertising: adertisement ethtool link mode mask
++ *
++ * Determines the negotiation mode to be used by the PCS, and returns
++ * one of:
++ * %PHYLINK_PCS_NEG_NONE: interface mode does not support inband
++ * %PHYLINK_PCS_NEG_OUTBAND: an out of band mode (e.g. reading the PHY)
++ * will be used.
++ * %PHYLINK_PCS_NEG_INBAND_DISABLED: inband mode selected but autoneg disabled
++ * %PHYLINK_PCS_NEG_INBAND_ENABLED: inband mode selected and autoneg enabled
++ *
++ * Note: this is for cases where the PCS itself is involved in negotiation
++ * (e.g. Clause 37, SGMII and similar) not Clause 73.
++ */
++static inline unsigned int phylink_pcs_neg_mode(unsigned int mode,
++ phy_interface_t interface,
++ const unsigned long *advertising)
++{
++ unsigned int neg_mode;
++
++ switch (interface) {
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_QSGMII:
++ case PHY_INTERFACE_MODE_QUSGMII:
++ case PHY_INTERFACE_MODE_USXGMII:
++ /* These protocols are designed for use with a PHY which
++ * communicates its negotiation result back to the MAC via
++ * inband communication. Note: there exist PHYs that run
++ * with SGMII but do not send the inband data.
++ */
++ if (!phylink_autoneg_inband(mode))
++ neg_mode = PHYLINK_PCS_NEG_OUTBAND;
++ else
++ neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
++ break;
++
++ case PHY_INTERFACE_MODE_1000BASEX:
++ case PHY_INTERFACE_MODE_2500BASEX:
++ /* 1000base-X is designed for use media-side for Fibre
++ * connections, and thus the Autoneg bit needs to be
++ * taken into account. We also do this for 2500base-X
++ * as well, but drivers may not support this, so may
++ * need to override this.
++ */
++ if (!phylink_autoneg_inband(mode))
++ neg_mode = PHYLINK_PCS_NEG_OUTBAND;
++ else if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++ advertising))
++ neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
++ else
++ neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED;
++ break;
++
++ default:
++ neg_mode = PHYLINK_PCS_NEG_NONE;
++ break;
++ }
++
++ return neg_mode;
++}
++
++/**
+ * struct phylink_link_state - link state structure
+ * @advertising: ethtool bitmask containing advertised link modes
+ * @lp_advertising: ethtool bitmask containing link partner advertised link
+@@ -436,6 +518,7 @@ struct phylink_pcs_ops;
+ /**
+ * struct phylink_pcs - PHYLINK PCS instance
+ * @ops: a pointer to the &struct phylink_pcs_ops structure
++ * @neg_mode: provide PCS neg mode via "mode" argument
+ * @poll: poll the PCS for link changes
+ *
+ * This structure is designed to be embedded within the PCS private data,
+@@ -443,6 +526,7 @@ struct phylink_pcs_ops;
+ */
+ struct phylink_pcs {
+ const struct phylink_pcs_ops *ops;
++ bool neg_mode;
+ bool poll;
+ };
+
+@@ -460,12 +544,12 @@ struct phylink_pcs_ops {
+ const struct phylink_link_state *state);
+ void (*pcs_get_state)(struct phylink_pcs *pcs,
+ struct phylink_link_state *state);
+- int (*pcs_config)(struct phylink_pcs *pcs, unsigned int mode,
++ int (*pcs_config)(struct phylink_pcs *pcs, unsigned int neg_mode,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+ bool permit_pause_to_mac);
+ void (*pcs_an_restart)(struct phylink_pcs *pcs);
+- void (*pcs_link_up)(struct phylink_pcs *pcs, unsigned int mode,
++ void (*pcs_link_up)(struct phylink_pcs *pcs, unsigned int neg_mode,
+ phy_interface_t interface, int speed, int duplex);
+ };
+
+@@ -508,7 +592,7 @@ void pcs_get_state(struct phylink_pcs *p
+ /**
+ * pcs_config() - Configure the PCS mode and advertisement
+ * @pcs: a pointer to a &struct phylink_pcs.
+- * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND.
++ * @neg_mode: link negotiation mode (see below)
+ * @interface: interface mode to be used
+ * @advertising: adertisement ethtool link mode mask
+ * @permit_pause_to_mac: permit forwarding pause resolution to MAC
+@@ -526,8 +610,12 @@ void pcs_get_state(struct phylink_pcs *p
+ * For 1000BASE-X, the advertisement should be programmed into the PCS.
+ *
+ * For most 10GBASE-R, there is no advertisement.
++ *
++ * The %neg_mode argument should be tested via the phylink_mode_*() family of
++ * functions, or for PCS that set pcs->neg_mode true, should be tested
++ * against the %PHYLINK_PCS_NEG_* definitions.
+ */
+-int pcs_config(struct phylink_pcs *pcs, unsigned int mode,
++int pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ phy_interface_t interface, const unsigned long *advertising,
+ bool permit_pause_to_mac);
+
+@@ -543,7 +631,7 @@ void pcs_an_restart(struct phylink_pcs *
+ /**
+ * pcs_link_up() - program the PCS for the resolved link configuration
+ * @pcs: a pointer to a &struct phylink_pcs.
+- * @mode: link autonegotiation mode
++ * @neg_mode: link negotiation mode (see below)
+ * @interface: link &typedef phy_interface_t mode
+ * @speed: link speed
+ * @duplex: link duplex
+@@ -552,8 +640,12 @@ void pcs_an_restart(struct phylink_pcs *
+ * the resolved link parameters. For example, a PCS operating in SGMII
+ * mode without in-band AN needs to be manually configured for the link
+ * and duplex setting. Otherwise, this should be a no-op.
++ *
++ * The %mode argument should be tested via the phylink_mode_*() family of
++ * functions, or for PCS that set pcs->neg_mode true, should be tested
++ * against the %PHYLINK_PCS_NEG_* definitions.
+ */
+-void pcs_link_up(struct phylink_pcs *pcs, unsigned int mode,
++void pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
+ phy_interface_t interface, int speed, int duplex);
+ #endif
+
diff --git a/target/linux/generic/backport-6.6/715-25-v6.5-net-phylink-convert-phylink_mii_c22_pcs_config-to-ne.patch b/target/linux/generic/backport-6.6/715-25-v6.5-net-phylink-convert-phylink_mii_c22_pcs_config-to-ne.patch
new file mode 100644
index 0000000000..473e9d5836
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-25-v6.5-net-phylink-convert-phylink_mii_c22_pcs_config-to-ne.patch
@@ -0,0 +1,45 @@
+From cdb08aa0473730315dbc088d5394e59622314034 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 16 Jun 2023 13:06:27 +0100
+Subject: [PATCH 1/2] net: phylink: convert phylink_mii_c22_pcs_config() to
+ neg_mode
+
+Use phylink_pcs_neg_mode() for phylink_mii_c22_pcs_config(). This
+results in no functional change.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://lore.kernel.org/r/E1qA8Dj-00EaFG-Mt@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 9 ++++-----
+ 1 file changed, 4 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -3558,6 +3558,7 @@ int phylink_mii_c22_pcs_config(struct md
+ phy_interface_t interface,
+ const unsigned long *advertising)
+ {
++ unsigned int neg_mode;
+ bool changed = 0;
+ u16 bmcr;
+ int ret, adv;
+@@ -3571,15 +3572,13 @@ int phylink_mii_c22_pcs_config(struct md
+ changed = ret;
+ }
+
+- /* Ensure ISOLATE bit is disabled */
+- if (mode == MLO_AN_INBAND &&
+- (interface == PHY_INTERFACE_MODE_SGMII ||
+- interface == PHY_INTERFACE_MODE_QSGMII ||
+- linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, advertising)))
++ neg_mode = phylink_pcs_neg_mode(mode, interface, advertising);
++ if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
+ bmcr = BMCR_ANENABLE;
+ else
+ bmcr = 0;
+
++ /* Configure the inband state. Ensure ISOLATE bit is disabled */
+ ret = mdiodev_modify(pcs, MII_BMCR, BMCR_ANENABLE | BMCR_ISOLATE, bmcr);
+ if (ret < 0)
+ return ret;
diff --git a/target/linux/generic/backport-6.6/715-26-v6.5-net-phylink-pass-neg_mode-into-phylink_mii_c22_pcs_c.patch b/target/linux/generic/backport-6.6/715-26-v6.5-net-phylink-pass-neg_mode-into-phylink_mii_c22_pcs_c.patch
new file mode 100644
index 0000000000..5572850e95
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-26-v6.5-net-phylink-pass-neg_mode-into-phylink_mii_c22_pcs_c.patch
@@ -0,0 +1,187 @@
+From febf2aaf05641f3258cc30e072aff65cffc7c82c Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 16 Jun 2023 13:06:32 +0100
+Subject: [PATCH 2/2] net: phylink: pass neg_mode into
+ phylink_mii_c22_pcs_config()
+
+Convert fman_dtsec, xilinx_axienet and pcs-lynx to pass the neg_mode
+into phylink_mii_c22_pcs_config(). Where appropriate, drivers are
+updated to have neg_mode passed into their pcs_config() and
+pcs_link_up() functions. For other drivers, we just hoist the call
+to phylink_pcs_neg_mode() to their pcs_config() method out of
+phylink_mii_c22_pcs_config().
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://lore.kernel.org/r/E1qA8Do-00EaFM-Ra@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ .../net/ethernet/freescale/fman/fman_dtsec.c | 7 ++++---
+ .../net/ethernet/xilinx/xilinx_axienet_main.c | 6 ++++--
+ drivers/net/pcs/pcs-lynx.c | 18 ++++++++++++------
+ drivers/net/phy/phylink.c | 9 ++++-----
+ include/linux/phylink.h | 5 +++--
+ 5 files changed, 27 insertions(+), 18 deletions(-)
+
+--- a/drivers/net/ethernet/freescale/fman/fman_dtsec.c
++++ b/drivers/net/ethernet/freescale/fman/fman_dtsec.c
+@@ -763,15 +763,15 @@ static void dtsec_pcs_get_state(struct p
+ phylink_mii_c22_pcs_get_state(dtsec->tbidev, state);
+ }
+
+-static int dtsec_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
++static int dtsec_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+ bool permit_pause_to_mac)
+ {
+ struct fman_mac *dtsec = pcs_to_dtsec(pcs);
+
+- return phylink_mii_c22_pcs_config(dtsec->tbidev, mode, interface,
+- advertising);
++ return phylink_mii_c22_pcs_config(dtsec->tbidev, interface,
++ advertising, neg_mode);
+ }
+
+ static void dtsec_pcs_an_restart(struct phylink_pcs *pcs)
+@@ -1447,6 +1447,7 @@ int dtsec_initialization(struct mac_devi
+ goto _return_fm_mac_free;
+ }
+ dtsec->pcs.ops = &dtsec_pcs_ops;
++ dtsec->pcs.neg_mode = true;
+ dtsec->pcs.poll = true;
+
+ supported = mac_dev->phylink_config.supported_interfaces;
+--- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
++++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
+@@ -1631,7 +1631,7 @@ static void axienet_pcs_an_restart(struc
+ phylink_mii_c22_pcs_an_restart(pcs_phy);
+ }
+
+-static int axienet_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
++static int axienet_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+ bool permit_pause_to_mac)
+@@ -1653,7 +1653,8 @@ static int axienet_pcs_config(struct phy
+ }
+ }
+
+- ret = phylink_mii_c22_pcs_config(pcs_phy, mode, interface, advertising);
++ ret = phylink_mii_c22_pcs_config(pcs_phy, interface, advertising,
++ neg_mode);
+ if (ret < 0)
+ netdev_warn(ndev, "Failed to configure PCS: %d\n", ret);
+
+@@ -2129,6 +2130,7 @@ static int axienet_probe(struct platform
+ }
+ of_node_put(np);
+ lp->pcs.ops = &axienet_pcs_ops;
++ lp->pcs.neg_mode = true;
+ lp->pcs.poll = true;
+ }
+
+--- a/drivers/net/pcs/pcs-lynx.c
++++ b/drivers/net/pcs/pcs-lynx.c
+@@ -122,9 +122,10 @@ static void lynx_pcs_get_state(struct ph
+ state->link, state->an_complete);
+ }
+
+-static int lynx_pcs_config_giga(struct mdio_device *pcs, unsigned int mode,
++static int lynx_pcs_config_giga(struct mdio_device *pcs,
+ phy_interface_t interface,
+- const unsigned long *advertising)
++ const unsigned long *advertising,
++ unsigned int neg_mode)
+ {
+ u32 link_timer;
+ u16 if_mode;
+@@ -137,8 +138,9 @@ static int lynx_pcs_config_giga(struct m
+
+ if_mode = 0;
+ } else {
++ /* SGMII and QSGMII */
+ if_mode = IF_MODE_SGMII_EN;
+- if (mode == MLO_AN_INBAND) {
++ if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
+ if_mode |= IF_MODE_USE_SGMII_AN;
+
+ /* Adjust link timer for SGMII */
+@@ -154,7 +156,8 @@ static int lynx_pcs_config_giga(struct m
+ if (err)
+ return err;
+
+- return phylink_mii_c22_pcs_config(pcs, mode, interface, advertising);
++ return phylink_mii_c22_pcs_config(pcs, interface, advertising,
++ neg_mode);
+ }
+
+ static int lynx_pcs_config_usxgmii(struct mdio_device *pcs, unsigned int mode,
+@@ -181,13 +184,16 @@ static int lynx_pcs_config(struct phylin
+ bool permit)
+ {
+ struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs);
++ unsigned int neg_mode;
++
++ neg_mode = phylink_pcs_neg_mode(mode, ifmode, advertising);
+
+ switch (ifmode) {
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
+- return lynx_pcs_config_giga(lynx->mdio, mode, ifmode,
+- advertising);
++ return lynx_pcs_config_giga(lynx->mdio, ifmode, advertising,
++ neg_mode);
+ case PHY_INTERFACE_MODE_2500BASEX:
+ if (phylink_autoneg_inband(mode)) {
+ dev_err(&lynx->mdio->dev,
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -3545,20 +3545,20 @@ EXPORT_SYMBOL_GPL(phylink_mii_c22_pcs_en
+ /**
+ * phylink_mii_c22_pcs_config() - configure clause 22 PCS
+ * @pcs: a pointer to a &struct mdio_device.
+- * @mode: link autonegotiation mode
+ * @interface: the PHY interface mode being configured
+ * @advertising: the ethtool advertisement mask
++ * @neg_mode: PCS negotiation mode
+ *
+ * Configure a Clause 22 PCS PHY with the appropriate negotiation
+ * parameters for the @mode, @interface and @advertising parameters.
+ * Returns negative error number on failure, zero if the advertisement
+ * has not changed, or positive if there is a change.
+ */
+-int phylink_mii_c22_pcs_config(struct mdio_device *pcs, unsigned int mode,
++int phylink_mii_c22_pcs_config(struct mdio_device *pcs,
+ phy_interface_t interface,
+- const unsigned long *advertising)
++ const unsigned long *advertising,
++ unsigned int neg_mode)
+ {
+- unsigned int neg_mode;
+ bool changed = 0;
+ u16 bmcr;
+ int ret, adv;
+@@ -3572,7 +3572,6 @@ int phylink_mii_c22_pcs_config(struct md
+ changed = ret;
+ }
+
+- neg_mode = phylink_pcs_neg_mode(mode, interface, advertising);
+ if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
+ bmcr = BMCR_ANENABLE;
+ else
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -743,9 +743,10 @@ void phylink_mii_c22_pcs_get_state(struc
+ struct phylink_link_state *state);
+ int phylink_mii_c22_pcs_encode_advertisement(phy_interface_t interface,
+ const unsigned long *advertising);
+-int phylink_mii_c22_pcs_config(struct mdio_device *pcs, unsigned int mode,
++int phylink_mii_c22_pcs_config(struct mdio_device *pcs,
+ phy_interface_t interface,
+- const unsigned long *advertising);
++ const unsigned long *advertising,
++ unsigned int neg_mode);
+ void phylink_mii_c22_pcs_an_restart(struct mdio_device *pcs);
+
+ void phylink_resolve_c73(struct phylink_link_state *state);
diff --git a/target/linux/generic/backport-6.6/715-27-v6.5-net-pcs-lynxi-update-PCS-driver-to-use-neg_mode.patch b/target/linux/generic/backport-6.6/715-27-v6.5-net-pcs-lynxi-update-PCS-driver-to-use-neg_mode.patch
new file mode 100644
index 0000000000..5e0128766c
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-27-v6.5-net-pcs-lynxi-update-PCS-driver-to-use-neg_mode.patch
@@ -0,0 +1,101 @@
+From 3b2de56a146f34e3f70a84cc3a1897064e445d16 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 16 Jun 2023 13:06:43 +0100
+Subject: [PATCH] net: pcs: lynxi: update PCS driver to use neg_mode
+
+Update the Lynxi PCS driver to use neg_mode rather than the mode
+argument. This ensures that the link_up() method will always program
+the speed and duplex when negotiation is disabled.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://lore.kernel.org/r/E1qA8Dz-00EaFY-5A@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/pcs/pcs-mtk-lynxi.c | 39 ++++++++++++++-------------------
+ 1 file changed, 16 insertions(+), 23 deletions(-)
+
+--- a/drivers/net/pcs/pcs-mtk-lynxi.c
++++ b/drivers/net/pcs/pcs-mtk-lynxi.c
+@@ -102,13 +102,13 @@ static void mtk_pcs_lynxi_get_state(stru
+ FIELD_GET(SGMII_LPA, adv));
+ }
+
+-static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int mode,
++static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+ bool permit_pause_to_mac)
+ {
+ struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
+- bool mode_changed = false, changed, use_an;
++ bool mode_changed = false, changed;
+ unsigned int rgc3, sgm_mode, bmcr;
+ int advertise, link_timer;
+
+@@ -121,30 +121,22 @@ static int mtk_pcs_lynxi_config(struct p
+ * we assume that fixes it's speed at bitrate = line rate (in
+ * other words, 1000Mbps or 2500Mbps).
+ */
+- if (interface == PHY_INTERFACE_MODE_SGMII) {
++ if (interface == PHY_INTERFACE_MODE_SGMII)
+ sgm_mode = SGMII_IF_MODE_SGMII;
+- if (phylink_autoneg_inband(mode)) {
+- sgm_mode |= SGMII_REMOTE_FAULT_DIS |
+- SGMII_SPEED_DUPLEX_AN;
+- use_an = true;
+- } else {
+- use_an = false;
+- }
+- } else if (phylink_autoneg_inband(mode)) {
+- /* 1000base-X or 2500base-X autoneg */
+- sgm_mode = SGMII_REMOTE_FAULT_DIS;
+- use_an = linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+- advertising);
+- } else {
++ else
+ /* 1000base-X or 2500base-X without autoneg */
+ sgm_mode = 0;
+- use_an = false;
+- }
+
+- if (use_an)
++ if (neg_mode & PHYLINK_PCS_NEG_INBAND)
++ sgm_mode |= SGMII_REMOTE_FAULT_DIS;
++
++ if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
++ if (interface == PHY_INTERFACE_MODE_SGMII)
++ sgm_mode |= SGMII_SPEED_DUPLEX_AN;
+ bmcr = BMCR_ANENABLE;
+- else
++ } else {
+ bmcr = 0;
++ }
+
+ if (mpcs->interface != interface) {
+ link_timer = phylink_get_link_timer_ns(interface);
+@@ -216,14 +208,15 @@ static void mtk_pcs_lynxi_restart_an(str
+ regmap_set_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1, BMCR_ANRESTART);
+ }
+
+-static void mtk_pcs_lynxi_link_up(struct phylink_pcs *pcs, unsigned int mode,
++static void mtk_pcs_lynxi_link_up(struct phylink_pcs *pcs,
++ unsigned int neg_mode,
+ phy_interface_t interface, int speed,
+ int duplex)
+ {
+ struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
+ unsigned int sgm_mode;
+
+- if (!phylink_autoneg_inband(mode)) {
++ if (neg_mode != PHYLINK_PCS_NEG_INBAND_ENABLED) {
+ /* Force the speed and duplex setting */
+ if (speed == SPEED_10)
+ sgm_mode = SGMII_SPEED_10;
+@@ -286,6 +279,7 @@ struct phylink_pcs *mtk_pcs_lynxi_create
+ mpcs->regmap = regmap;
+ mpcs->flags = flags;
+ mpcs->pcs.ops = &mtk_pcs_lynxi_ops;
++ mpcs->pcs.neg_mode = true;
+ mpcs->pcs.poll = true;
+ mpcs->interface = PHY_INTERFACE_MODE_NA;
+
diff --git a/target/linux/generic/backport-6.6/715-28-v6.4-net-pcs-xpcs-use-Autoneg-bit-rather-than-an_enabled.patch b/target/linux/generic/backport-6.6/715-28-v6.4-net-pcs-xpcs-use-Autoneg-bit-rather-than-an_enabled.patch
new file mode 100644
index 0000000000..3dd22d2916
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-28-v6.4-net-pcs-xpcs-use-Autoneg-bit-rather-than-an_enabled.patch
@@ -0,0 +1,55 @@
+From 459fd2f11204c962e3153020f4f56748e0e10afb Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 21 Mar 2023 15:58:49 +0000
+Subject: [PATCH] net: pcs: xpcs: use Autoneg bit rather than an_enabled
+
+The Autoneg bit in the advertising bitmap and state->an_enabled are
+always identical. Thus, we will be removing state->an_enabled.
+
+Use the Autoneg bit in the advertising bitmap to indicate whether
+autonegotiation should be used, rather than using the an_enabled
+member which will be going away.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/pcs/pcs-xpcs.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/pcs/pcs-xpcs.c
++++ b/drivers/net/pcs/pcs-xpcs.c
+@@ -931,6 +931,7 @@ static int xpcs_get_state_c73(struct dw_
+ struct phylink_link_state *state,
+ const struct xpcs_compat *compat)
+ {
++ bool an_enabled;
+ int ret;
+
+ /* Link needs to be read first ... */
+@@ -948,11 +949,13 @@ static int xpcs_get_state_c73(struct dw_
+ return xpcs_do_config(xpcs, state->interface, MLO_AN_INBAND, NULL);
+ }
+
+- if (state->an_enabled && xpcs_aneg_done_c73(xpcs, state, compat)) {
++ an_enabled = linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++ state->advertising);
++ if (an_enabled && xpcs_aneg_done_c73(xpcs, state, compat)) {
+ state->an_complete = true;
+ xpcs_read_lpa_c73(xpcs, state);
+ xpcs_resolve_lpa_c73(xpcs, state);
+- } else if (state->an_enabled) {
++ } else if (an_enabled) {
+ state->link = 0;
+ } else if (state->link) {
+ xpcs_resolve_pma(xpcs, state);
+@@ -1007,7 +1010,8 @@ static int xpcs_get_state_c37_1000basex(
+ {
+ int lpa, bmsr;
+
+- if (state->an_enabled) {
++ if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++ state->advertising)) {
+ /* Reset link state */
+ state->link = false;
+
diff --git a/target/linux/generic/backport-6.6/715-29-v6.4-net-pcs-xpcs-fix-incorrect-number-of-interfaces.patch b/target/linux/generic/backport-6.6/715-29-v6.4-net-pcs-xpcs-fix-incorrect-number-of-interfaces.patch
new file mode 100644
index 0000000000..7cae8515fa
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-29-v6.4-net-pcs-xpcs-fix-incorrect-number-of-interfaces.patch
@@ -0,0 +1,30 @@
+From 43fb622d91a9f408322735d2f736495c1009f575 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 9 May 2023 12:50:04 +0100
+Subject: [PATCH] net: pcs: xpcs: fix incorrect number of interfaces
+
+In synopsys_xpcs_compat[], the DW_XPCS_2500BASEX entry was setting
+the number of interfaces using the xpcs_2500basex_features array
+rather than xpcs_2500basex_interfaces. This causes us to overflow
+the array of interfaces. Fix this.
+
+Fixes: f27abde3042a ("net: pcs: add 2500BASEX support for Intel mGbE controller")
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Leon Romanovsky <leonro@nvidia.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/pcs/pcs-xpcs.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/pcs/pcs-xpcs.c
++++ b/drivers/net/pcs/pcs-xpcs.c
+@@ -1211,7 +1211,7 @@ static const struct xpcs_compat synopsys
+ [DW_XPCS_2500BASEX] = {
+ .supported = xpcs_2500basex_features,
+ .interface = xpcs_2500basex_interfaces,
+- .num_interfaces = ARRAY_SIZE(xpcs_2500basex_features),
++ .num_interfaces = ARRAY_SIZE(xpcs_2500basex_interfaces),
+ .an_mode = DW_2500BASEX,
+ },
+ };
diff --git a/target/linux/generic/backport-6.6/715-v6.9-01-net-phy-qcom-qca808x-fix-logic-error-in-LED-brightne.patch b/target/linux/generic/backport-6.6/715-v6.9-01-net-phy-qcom-qca808x-fix-logic-error-in-LED-brightne.patch
new file mode 100644
index 0000000000..0a7b5358f1
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-v6.9-01-net-phy-qcom-qca808x-fix-logic-error-in-LED-brightne.patch
@@ -0,0 +1,36 @@
+From f2ec98566775dd4341ec1dcf93aa5859c60de826 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 1 Feb 2024 14:46:00 +0100
+Subject: [PATCH 1/2] net: phy: qcom: qca808x: fix logic error in LED
+ brightness set
+
+In switching to using phy_modify_mmd and a more short version of the
+LED ON/OFF condition in later revision, it was made a logic error where
+
+value ? QCA808X_LED_FORCE_ON : QCA808X_LED_FORCE_OFF is always true as
+value is always OR with QCA808X_LED_FORCE_EN due to missing ()
+resulting in the testing condition being QCA808X_LED_FORCE_EN | value.
+
+Add the () to apply the correct condition and restore correct
+functionality of the brightness ON/OFF.
+
+Fixes: 7196062b64ee ("net: phy: at803x: add LED support for qca808x")
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/qcom/qca808x.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -820,8 +820,8 @@ static int qca808x_led_brightness_set(st
+
+ return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
+ QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
+- QCA808X_LED_FORCE_EN | value ? QCA808X_LED_FORCE_ON :
+- QCA808X_LED_FORCE_OFF);
++ QCA808X_LED_FORCE_EN | (value ? QCA808X_LED_FORCE_ON :
++ QCA808X_LED_FORCE_OFF));
+ }
+
+ static int qca808x_led_blink_set(struct phy_device *phydev, u8 index,
diff --git a/target/linux/generic/backport-6.6/715-v6.9-02-net-phy-qcom-qca808x-default-to-LED-active-High-if-n.patch b/target/linux/generic/backport-6.6/715-v6.9-02-net-phy-qcom-qca808x-default-to-LED-active-High-if-n.patch
new file mode 100644
index 0000000000..e32ed7f777
--- /dev/null
+++ b/target/linux/generic/backport-6.6/715-v6.9-02-net-phy-qcom-qca808x-default-to-LED-active-High-if-n.patch
@@ -0,0 +1,41 @@
+From f203c8c77c7616c099647636f4c67d59a45fe8a2 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 1 Feb 2024 14:46:01 +0100
+Subject: [PATCH 2/2] net: phy: qcom: qca808x: default to LED active High if
+ not set
+
+qca808x PHY provide support for the led_polarity_set OP to configure
+and apply the active-low property but on PHY reset, the Active High bit
+is not set resulting in the LED driven as active-low.
+
+To fix this, check if active-low is not set in DT and enable Active High
+polarity by default to restore correct funcionality of the LED.
+
+Fixes: 7196062b64ee ("net: phy: at803x: add LED support for qca808x")
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/qcom/qca808x.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -290,8 +290,18 @@ static int qca808x_probe(struct phy_devi
+
+ static int qca808x_config_init(struct phy_device *phydev)
+ {
++ struct qca808x_priv *priv = phydev->priv;
+ int ret;
+
++ /* Default to LED Active High if active-low not in DT */
++ if (priv->led_polarity_mode == -1) {
++ ret = phy_set_bits_mmd(phydev, MDIO_MMD_AN,
++ QCA808X_MMD7_LED_POLARITY_CTRL,
++ QCA808X_LED_ACTIVE_HIGH);
++ if (ret)
++ return ret;
++ }
++
+ /* Active adc&vga on 802.3az for the link 1000M and 100M */
+ ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_ADDR_CLD_CTRL7,
+ QCA808X_8023AZ_AFE_CTRL_MASK, QCA808X_8023AZ_AFE_EN);
diff --git a/target/linux/generic/backport-6.6/716-v6.9-02-net-phy-add-support-for-scanning-PHY-in-PHY-packages.patch b/target/linux/generic/backport-6.6/716-v6.9-02-net-phy-add-support-for-scanning-PHY-in-PHY-packages.patch
new file mode 100644
index 0000000000..b355031aa5
--- /dev/null
+++ b/target/linux/generic/backport-6.6/716-v6.9-02-net-phy-add-support-for-scanning-PHY-in-PHY-packages.patch
@@ -0,0 +1,211 @@
+From 385ef48f468696d6d172eb367656a3466fa0408d Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 6 Feb 2024 18:31:05 +0100
+Subject: [PATCH 02/10] net: phy: add support for scanning PHY in PHY packages
+ nodes
+
+Add support for scanning PHY in PHY package nodes. PHY packages nodes
+are just container for actual PHY on the MDIO bus.
+
+Their PHY address defined in the PHY package node are absolute and
+reflect the address on the MDIO bus.
+
+mdio_bus.c and of_mdio.c is updated to now support and parse also
+PHY package subnode by checking if the node name match
+"ethernet-phy-package".
+
+As PHY package reg is mandatory and each PHY in the PHY package must
+have a reg, every invalid PHY Package node is ignored and will be
+skipped by the autoscan fallback.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/mdio/of_mdio.c | 79 +++++++++++++++++++++++++++-----------
+ drivers/net/phy/mdio_bus.c | 44 +++++++++++++++++----
+ 2 files changed, 92 insertions(+), 31 deletions(-)
+
+--- a/drivers/net/mdio/of_mdio.c
++++ b/drivers/net/mdio/of_mdio.c
+@@ -138,6 +138,53 @@ bool of_mdiobus_child_is_phy(struct devi
+ }
+ EXPORT_SYMBOL(of_mdiobus_child_is_phy);
+
++static int __of_mdiobus_parse_phys(struct mii_bus *mdio, struct device_node *np,
++ bool *scanphys)
++{
++ struct device_node *child;
++ int addr, rc = 0;
++
++ /* Loop over the child nodes and register a phy_device for each phy */
++ for_each_available_child_of_node(np, child) {
++ if (of_node_name_eq(child, "ethernet-phy-package")) {
++ /* Ignore invalid ethernet-phy-package node */
++ if (!of_find_property(child, "reg", NULL))
++ continue;
++
++ rc = __of_mdiobus_parse_phys(mdio, child, NULL);
++ if (rc && rc != -ENODEV)
++ goto exit;
++
++ continue;
++ }
++
++ addr = of_mdio_parse_addr(&mdio->dev, child);
++ if (addr < 0) {
++ /* Skip scanning for invalid ethernet-phy-package node */
++ if (scanphys)
++ *scanphys = true;
++ continue;
++ }
++
++ if (of_mdiobus_child_is_phy(child))
++ rc = of_mdiobus_register_phy(mdio, child, addr);
++ else
++ rc = of_mdiobus_register_device(mdio, child, addr);
++
++ if (rc == -ENODEV)
++ dev_err(&mdio->dev,
++ "MDIO device at address %d is missing.\n",
++ addr);
++ else if (rc)
++ goto exit;
++ }
++
++ return 0;
++exit:
++ of_node_put(child);
++ return rc;
++}
++
+ /**
+ * __of_mdiobus_register - Register mii_bus and create PHYs from the device tree
+ * @mdio: pointer to mii_bus structure
+@@ -179,33 +226,18 @@ int __of_mdiobus_register(struct mii_bus
+ return rc;
+
+ /* Loop over the child nodes and register a phy_device for each phy */
+- for_each_available_child_of_node(np, child) {
+- addr = of_mdio_parse_addr(&mdio->dev, child);
+- if (addr < 0) {
+- scanphys = true;
+- continue;
+- }
+-
+- if (of_mdiobus_child_is_phy(child))
+- rc = of_mdiobus_register_phy(mdio, child, addr);
+- else
+- rc = of_mdiobus_register_device(mdio, child, addr);
+-
+- if (rc == -ENODEV)
+- dev_err(&mdio->dev,
+- "MDIO device at address %d is missing.\n",
+- addr);
+- else if (rc)
+- goto unregister;
+- }
++ rc = __of_mdiobus_parse_phys(mdio, np, &scanphys);
++ if (rc)
++ goto unregister;
+
+ if (!scanphys)
+ return 0;
+
+ /* auto scan for PHYs with empty reg property */
+ for_each_available_child_of_node(np, child) {
+- /* Skip PHYs with reg property set */
+- if (of_find_property(child, "reg", NULL))
++ /* Skip PHYs with reg property set or ethernet-phy-package node */
++ if (of_find_property(child, "reg", NULL) ||
++ of_node_name_eq(child, "ethernet-phy-package"))
+ continue;
+
+ for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
+@@ -226,15 +258,16 @@ int __of_mdiobus_register(struct mii_bus
+ if (!rc)
+ break;
+ if (rc != -ENODEV)
+- goto unregister;
++ goto put_unregister;
+ }
+ }
+ }
+
+ return 0;
+
+-unregister:
++put_unregister:
+ of_node_put(child);
++unregister:
+ mdiobus_unregister(mdio);
+ return rc;
+ }
+--- a/drivers/net/phy/mdio_bus.c
++++ b/drivers/net/phy/mdio_bus.c
+@@ -448,19 +448,34 @@ EXPORT_SYMBOL(of_mdio_find_bus);
+ * found, set the of_node pointer for the mdio device. This allows
+ * auto-probed phy devices to be supplied with information passed in
+ * via DT.
++ * If a PHY package is found, PHY is searched also there.
+ */
+-static void of_mdiobus_link_mdiodev(struct mii_bus *bus,
+- struct mdio_device *mdiodev)
++static int of_mdiobus_find_phy(struct device *dev, struct mdio_device *mdiodev,
++ struct device_node *np)
+ {
+- struct device *dev = &mdiodev->dev;
+ struct device_node *child;
+
+- if (dev->of_node || !bus->dev.of_node)
+- return;
+-
+- for_each_available_child_of_node(bus->dev.of_node, child) {
++ for_each_available_child_of_node(np, child) {
+ int addr;
+
++ if (of_node_name_eq(child, "ethernet-phy-package")) {
++ /* Validate PHY package reg presence */
++ if (!of_find_property(child, "reg", NULL)) {
++ of_node_put(child);
++ return -EINVAL;
++ }
++
++ if (!of_mdiobus_find_phy(dev, mdiodev, child)) {
++ /* The refcount for the PHY package will be
++ * incremented later when PHY join the Package.
++ */
++ of_node_put(child);
++ return 0;
++ }
++
++ continue;
++ }
++
+ addr = of_mdio_parse_addr(dev, child);
+ if (addr < 0)
+ continue;
+@@ -470,9 +485,22 @@ static void of_mdiobus_link_mdiodev(stru
+ /* The refcount on "child" is passed to the mdio
+ * device. Do _not_ use of_node_put(child) here.
+ */
+- return;
++ return 0;
+ }
+ }
++
++ return -ENODEV;
++}
++
++static void of_mdiobus_link_mdiodev(struct mii_bus *bus,
++ struct mdio_device *mdiodev)
++{
++ struct device *dev = &mdiodev->dev;
++
++ if (dev->of_node || !bus->dev.of_node)
++ return;
++
++ of_mdiobus_find_phy(dev, mdiodev, bus->dev.of_node);
+ }
+ #else /* !IS_ENABLED(CONFIG_OF_MDIO) */
+ static inline void of_mdiobus_link_mdiodev(struct mii_bus *mdio,
diff --git a/target/linux/generic/backport-6.6/716-v6.9-03-net-phy-add-devm-of_phy_package_join-helper.patch b/target/linux/generic/backport-6.6/716-v6.9-03-net-phy-add-devm-of_phy_package_join-helper.patch
new file mode 100644
index 0000000000..3c7bf6c132
--- /dev/null
+++ b/target/linux/generic/backport-6.6/716-v6.9-03-net-phy-add-devm-of_phy_package_join-helper.patch
@@ -0,0 +1,185 @@
+From 471e8fd3afcef5a9f9089f0bd21965ad9ba35c91 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 6 Feb 2024 18:31:06 +0100
+Subject: [PATCH 03/10] net: phy: add devm/of_phy_package_join helper
+
+Add devm/of_phy_package_join helper to join PHYs in a PHY package. These
+are variant of the manual phy_package_join with the difference that
+these will use DT nodes to derive the base_addr instead of manually
+passing an hardcoded value.
+
+An additional value is added in phy_package_shared, "np" to reference
+the PHY package node pointer in specific PHY driver probe_once and
+config_init_once functions to make use of additional specific properties
+defined in the PHY package node in DT.
+
+The np value is filled only with of_phy_package_join if a valid PHY
+package node is found. A valid PHY package node must have the node name
+set to "ethernet-phy-package".
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/phy_device.c | 96 ++++++++++++++++++++++++++++++++++++
+ include/linux/phy.h | 6 +++
+ 2 files changed, 102 insertions(+)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -1648,6 +1648,7 @@ int phy_package_join(struct phy_device *
+ shared->priv_size = priv_size;
+ }
+ shared->base_addr = base_addr;
++ shared->np = NULL;
+ refcount_set(&shared->refcnt, 1);
+ bus->shared[base_addr] = shared;
+ } else {
+@@ -1671,6 +1672,63 @@ err_unlock:
+ EXPORT_SYMBOL_GPL(phy_package_join);
+
+ /**
++ * of_phy_package_join - join a common PHY group in PHY package
++ * @phydev: target phy_device struct
++ * @priv_size: if non-zero allocate this amount of bytes for private data
++ *
++ * This is a variant of phy_package_join for PHY package defined in DT.
++ *
++ * The parent node of the @phydev is checked as a valid PHY package node
++ * structure (by matching the node name "ethernet-phy-package") and the
++ * base_addr for the PHY package is passed to phy_package_join.
++ *
++ * With this configuration the shared struct will also have the np value
++ * filled to use additional DT defined properties in PHY specific
++ * probe_once and config_init_once PHY package OPs.
++ *
++ * Returns < 0 on error, 0 on success. Esp. calling phy_package_join()
++ * with the same cookie but a different priv_size is an error. Or a parent
++ * node is not detected or is not valid or doesn't match the expected node
++ * name for PHY package.
++ */
++int of_phy_package_join(struct phy_device *phydev, size_t priv_size)
++{
++ struct device_node *node = phydev->mdio.dev.of_node;
++ struct device_node *package_node;
++ u32 base_addr;
++ int ret;
++
++ if (!node)
++ return -EINVAL;
++
++ package_node = of_get_parent(node);
++ if (!package_node)
++ return -EINVAL;
++
++ if (!of_node_name_eq(package_node, "ethernet-phy-package")) {
++ ret = -EINVAL;
++ goto exit;
++ }
++
++ if (of_property_read_u32(package_node, "reg", &base_addr)) {
++ ret = -EINVAL;
++ goto exit;
++ }
++
++ ret = phy_package_join(phydev, base_addr, priv_size);
++ if (ret)
++ goto exit;
++
++ phydev->shared->np = package_node;
++
++ return 0;
++exit:
++ of_node_put(package_node);
++ return ret;
++}
++EXPORT_SYMBOL_GPL(of_phy_package_join);
++
++/**
+ * phy_package_leave - leave a common PHY group
+ * @phydev: target phy_device struct
+ *
+@@ -1686,6 +1744,10 @@ void phy_package_leave(struct phy_device
+ if (!shared)
+ return;
+
++ /* Decrease the node refcount on leave if present */
++ if (shared->np)
++ of_node_put(shared->np);
++
+ if (refcount_dec_and_mutex_lock(&shared->refcnt, &bus->shared_lock)) {
+ bus->shared[shared->base_addr] = NULL;
+ mutex_unlock(&bus->shared_lock);
+@@ -1739,6 +1801,40 @@ int devm_phy_package_join(struct device
+ EXPORT_SYMBOL_GPL(devm_phy_package_join);
+
+ /**
++ * devm_of_phy_package_join - resource managed of_phy_package_join()
++ * @dev: device that is registering this PHY package
++ * @phydev: target phy_device struct
++ * @priv_size: if non-zero allocate this amount of bytes for private data
++ *
++ * Managed of_phy_package_join(). Shared storage fetched by this function,
++ * phy_package_leave() is automatically called on driver detach. See
++ * of_phy_package_join() for more information.
++ */
++int devm_of_phy_package_join(struct device *dev, struct phy_device *phydev,
++ size_t priv_size)
++{
++ struct phy_device **ptr;
++ int ret;
++
++ ptr = devres_alloc(devm_phy_package_leave, sizeof(*ptr),
++ GFP_KERNEL);
++ if (!ptr)
++ return -ENOMEM;
++
++ ret = of_phy_package_join(phydev, priv_size);
++
++ if (!ret) {
++ *ptr = phydev;
++ devres_add(dev, ptr);
++ } else {
++ devres_free(ptr);
++ }
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(devm_of_phy_package_join);
++
++/**
+ * phy_detach - detach a PHY device from its network device
+ * @phydev: target phy_device struct
+ *
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -321,6 +321,7 @@ struct mdio_bus_stats {
+ * struct phy_package_shared - Shared information in PHY packages
+ * @base_addr: Base PHY address of PHY package used to combine PHYs
+ * in one package and for offset calculation of phy_package_read/write
++ * @np: Pointer to the Device Node if PHY package defined in DT
+ * @refcnt: Number of PHYs connected to this shared data
+ * @flags: Initialization of PHY package
+ * @priv_size: Size of the shared private data @priv
+@@ -332,6 +333,8 @@ struct mdio_bus_stats {
+ */
+ struct phy_package_shared {
+ u8 base_addr;
++ /* With PHY package defined in DT this points to the PHY package node */
++ struct device_node *np;
+ refcount_t refcnt;
+ unsigned long flags;
+ size_t priv_size;
+@@ -1793,9 +1796,12 @@ int phy_ethtool_set_link_ksettings(struc
+ const struct ethtool_link_ksettings *cmd);
+ int phy_ethtool_nway_reset(struct net_device *ndev);
+ int phy_package_join(struct phy_device *phydev, int base_addr, size_t priv_size);
++int of_phy_package_join(struct phy_device *phydev, size_t priv_size);
+ void phy_package_leave(struct phy_device *phydev);
+ int devm_phy_package_join(struct device *dev, struct phy_device *phydev,
+ int base_addr, size_t priv_size);
++int devm_of_phy_package_join(struct device *dev, struct phy_device *phydev,
++ size_t priv_size);
+
+ #if IS_ENABLED(CONFIG_PHYLIB)
+ int __init mdio_bus_init(void);
diff --git a/target/linux/generic/backport-6.6/716-v6.9-04-net-phy-qcom-move-more-function-to-shared-library.patch b/target/linux/generic/backport-6.6/716-v6.9-04-net-phy-qcom-move-more-function-to-shared-library.patch
new file mode 100644
index 0000000000..e935725630
--- /dev/null
+++ b/target/linux/generic/backport-6.6/716-v6.9-04-net-phy-qcom-move-more-function-to-shared-library.patch
@@ -0,0 +1,583 @@
+From 737eb75a815f9c08dcbb6631db57f4f4b0540a5b Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 6 Feb 2024 18:31:07 +0100
+Subject: [PATCH 04/10] net: phy: qcom: move more function to shared library
+
+Move more function to shared library in preparation for introduction of
+new PHY Family qca807x that will make use of both functions from at803x
+and qca808x as it's a transition PHY with some implementation of at803x
+and some from the new qca808x.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/qcom/at803x.c | 35 -----
+ drivers/net/phy/qcom/qca808x.c | 205 ----------------------------
+ drivers/net/phy/qcom/qcom-phy-lib.c | 193 ++++++++++++++++++++++++++
+ drivers/net/phy/qcom/qcom.h | 51 +++++++
+ 4 files changed, 244 insertions(+), 240 deletions(-)
+
+--- a/drivers/net/phy/qcom/at803x.c
++++ b/drivers/net/phy/qcom/at803x.c
+@@ -504,41 +504,6 @@ static void at803x_link_change_notify(st
+ }
+ }
+
+-static int at803x_read_status(struct phy_device *phydev)
+-{
+- struct at803x_ss_mask ss_mask = { 0 };
+- int err, old_link = phydev->link;
+-
+- /* Update the link, but return if there was an error */
+- err = genphy_update_link(phydev);
+- if (err)
+- return err;
+-
+- /* why bother the PHY if nothing can have changed */
+- if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
+- return 0;
+-
+- phydev->speed = SPEED_UNKNOWN;
+- phydev->duplex = DUPLEX_UNKNOWN;
+- phydev->pause = 0;
+- phydev->asym_pause = 0;
+-
+- err = genphy_read_lpa(phydev);
+- if (err < 0)
+- return err;
+-
+- ss_mask.speed_mask = AT803X_SS_SPEED_MASK;
+- ss_mask.speed_shift = __bf_shf(AT803X_SS_SPEED_MASK);
+- err = at803x_read_specific_status(phydev, ss_mask);
+- if (err < 0)
+- return err;
+-
+- if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete)
+- phy_resolve_aneg_pause(phydev);
+-
+- return 0;
+-}
+-
+ static int at803x_config_aneg(struct phy_device *phydev)
+ {
+ struct at803x_priv *priv = phydev->priv;
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -2,7 +2,6 @@
+
+ #include <linux/phy.h>
+ #include <linux/module.h>
+-#include <linux/ethtool_netlink.h>
+
+ #include "qcom.h"
+
+@@ -63,55 +62,6 @@
+ #define QCA808X_DBG_AN_TEST 0xb
+ #define QCA808X_HIBERNATION_EN BIT(15)
+
+-#define QCA808X_CDT_ENABLE_TEST BIT(15)
+-#define QCA808X_CDT_INTER_CHECK_DIS BIT(13)
+-#define QCA808X_CDT_STATUS BIT(11)
+-#define QCA808X_CDT_LENGTH_UNIT BIT(10)
+-
+-#define QCA808X_MMD3_CDT_STATUS 0x8064
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_A 0x8065
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_B 0x8066
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_C 0x8067
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_D 0x8068
+-#define QCA808X_CDT_DIAG_LENGTH_SAME_SHORT GENMASK(15, 8)
+-#define QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT GENMASK(7, 0)
+-
+-#define QCA808X_CDT_CODE_PAIR_A GENMASK(15, 12)
+-#define QCA808X_CDT_CODE_PAIR_B GENMASK(11, 8)
+-#define QCA808X_CDT_CODE_PAIR_C GENMASK(7, 4)
+-#define QCA808X_CDT_CODE_PAIR_D GENMASK(3, 0)
+-
+-#define QCA808X_CDT_STATUS_STAT_TYPE GENMASK(1, 0)
+-#define QCA808X_CDT_STATUS_STAT_FAIL FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 0)
+-#define QCA808X_CDT_STATUS_STAT_NORMAL FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 1)
+-#define QCA808X_CDT_STATUS_STAT_SAME_OPEN FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 2)
+-#define QCA808X_CDT_STATUS_STAT_SAME_SHORT FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 3)
+-
+-#define QCA808X_CDT_STATUS_STAT_MDI GENMASK(3, 2)
+-#define QCA808X_CDT_STATUS_STAT_MDI1 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 1)
+-#define QCA808X_CDT_STATUS_STAT_MDI2 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 2)
+-#define QCA808X_CDT_STATUS_STAT_MDI3 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 3)
+-
+-/* NORMAL are MDI with type set to 0 */
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI1
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
+- QCA808X_CDT_STATUS_STAT_MDI1)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
+- QCA808X_CDT_STATUS_STAT_MDI1)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI2
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
+- QCA808X_CDT_STATUS_STAT_MDI2)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
+- QCA808X_CDT_STATUS_STAT_MDI2)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI3
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
+- QCA808X_CDT_STATUS_STAT_MDI3)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
+- QCA808X_CDT_STATUS_STAT_MDI3)
+-
+-/* Added for reference of existence but should be handled by wait_for_completion already */
+-#define QCA808X_CDT_STATUS_STAT_BUSY (BIT(1) | BIT(3))
+-
+ #define QCA808X_MMD7_LED_GLOBAL 0x8073
+ #define QCA808X_LED_BLINK_1 GENMASK(11, 6)
+ #define QCA808X_LED_BLINK_2 GENMASK(5, 0)
+@@ -406,86 +356,6 @@ static int qca808x_soft_reset(struct phy
+ return ret;
+ }
+
+-static bool qca808x_cdt_fault_length_valid(int cdt_code)
+-{
+- switch (cdt_code) {
+- case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
+- case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
+- return true;
+- default:
+- return false;
+- }
+-}
+-
+-static int qca808x_cable_test_result_trans(int cdt_code)
+-{
+- switch (cdt_code) {
+- case QCA808X_CDT_STATUS_STAT_NORMAL:
+- return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+- case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
+- return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+- case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
+- return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
+- case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
+- return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
+- case QCA808X_CDT_STATUS_STAT_FAIL:
+- default:
+- return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+- }
+-}
+-
+-static int qca808x_cdt_fault_length(struct phy_device *phydev, int pair,
+- int result)
+-{
+- int val;
+- u32 cdt_length_reg = 0;
+-
+- switch (pair) {
+- case ETHTOOL_A_CABLE_PAIR_A:
+- cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_A;
+- break;
+- case ETHTOOL_A_CABLE_PAIR_B:
+- cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_B;
+- break;
+- case ETHTOOL_A_CABLE_PAIR_C:
+- cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_C;
+- break;
+- case ETHTOOL_A_CABLE_PAIR_D:
+- cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_D;
+- break;
+- default:
+- return -EINVAL;
+- }
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_PCS, cdt_length_reg);
+- if (val < 0)
+- return val;
+-
+- if (result == ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT)
+- val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_SAME_SHORT, val);
+- else
+- val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT, val);
+-
+- return at803x_cdt_fault_length(val);
+-}
+-
+ static int qca808x_cable_test_start(struct phy_device *phydev)
+ {
+ int ret;
+@@ -526,81 +396,6 @@ static int qca808x_cable_test_start(stru
+
+ return 0;
+ }
+-
+-static int qca808x_cable_test_get_pair_status(struct phy_device *phydev, u8 pair,
+- u16 status)
+-{
+- int length, result;
+- u16 pair_code;
+-
+- switch (pair) {
+- case ETHTOOL_A_CABLE_PAIR_A:
+- pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, status);
+- break;
+- case ETHTOOL_A_CABLE_PAIR_B:
+- pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, status);
+- break;
+- case ETHTOOL_A_CABLE_PAIR_C:
+- pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, status);
+- break;
+- case ETHTOOL_A_CABLE_PAIR_D:
+- pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, status);
+- break;
+- default:
+- return -EINVAL;
+- }
+-
+- result = qca808x_cable_test_result_trans(pair_code);
+- ethnl_cable_test_result(phydev, pair, result);
+-
+- if (qca808x_cdt_fault_length_valid(pair_code)) {
+- length = qca808x_cdt_fault_length(phydev, pair, result);
+- ethnl_cable_test_fault_length(phydev, pair, length);
+- }
+-
+- return 0;
+-}
+-
+-static int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished)
+-{
+- int ret, val;
+-
+- *finished = false;
+-
+- val = QCA808X_CDT_ENABLE_TEST |
+- QCA808X_CDT_LENGTH_UNIT;
+- ret = at803x_cdt_start(phydev, val);
+- if (ret)
+- return ret;
+-
+- ret = at803x_cdt_wait_for_completion(phydev, QCA808X_CDT_ENABLE_TEST);
+- if (ret)
+- return ret;
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_PCS, QCA808X_MMD3_CDT_STATUS);
+- if (val < 0)
+- return val;
+-
+- ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_A, val);
+- if (ret)
+- return ret;
+-
+- ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_B, val);
+- if (ret)
+- return ret;
+-
+- ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_C, val);
+- if (ret)
+- return ret;
+-
+- ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_D, val);
+- if (ret)
+- return ret;
+-
+- *finished = true;
+-
+- return 0;
+-}
+
+ static int qca808x_get_features(struct phy_device *phydev)
+ {
+--- a/drivers/net/phy/qcom/qcom-phy-lib.c
++++ b/drivers/net/phy/qcom/qcom-phy-lib.c
+@@ -5,6 +5,7 @@
+
+ #include <linux/netdevice.h>
+ #include <linux/etherdevice.h>
++#include <linux/ethtool_netlink.h>
+
+ #include "qcom.h"
+
+@@ -311,6 +312,42 @@ int at803x_prepare_config_aneg(struct ph
+ }
+ EXPORT_SYMBOL_GPL(at803x_prepare_config_aneg);
+
++int at803x_read_status(struct phy_device *phydev)
++{
++ struct at803x_ss_mask ss_mask = { 0 };
++ int err, old_link = phydev->link;
++
++ /* Update the link, but return if there was an error */
++ err = genphy_update_link(phydev);
++ if (err)
++ return err;
++
++ /* why bother the PHY if nothing can have changed */
++ if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
++ return 0;
++
++ phydev->speed = SPEED_UNKNOWN;
++ phydev->duplex = DUPLEX_UNKNOWN;
++ phydev->pause = 0;
++ phydev->asym_pause = 0;
++
++ err = genphy_read_lpa(phydev);
++ if (err < 0)
++ return err;
++
++ ss_mask.speed_mask = AT803X_SS_SPEED_MASK;
++ ss_mask.speed_shift = __bf_shf(AT803X_SS_SPEED_MASK);
++ err = at803x_read_specific_status(phydev, ss_mask);
++ if (err < 0)
++ return err;
++
++ if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete)
++ phy_resolve_aneg_pause(phydev);
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(at803x_read_status);
++
+ static int at803x_get_downshift(struct phy_device *phydev, u8 *d)
+ {
+ int val;
+@@ -427,3 +464,159 @@ int at803x_cdt_wait_for_completion(struc
+ return ret < 0 ? ret : 0;
+ }
+ EXPORT_SYMBOL_GPL(at803x_cdt_wait_for_completion);
++
++static bool qca808x_cdt_fault_length_valid(int cdt_code)
++{
++ switch (cdt_code) {
++ case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
++ return true;
++ default:
++ return false;
++ }
++}
++
++static int qca808x_cable_test_result_trans(int cdt_code)
++{
++ switch (cdt_code) {
++ case QCA808X_CDT_STATUS_STAT_NORMAL:
++ return ETHTOOL_A_CABLE_RESULT_CODE_OK;
++ case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
++ return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
++ case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
++ return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
++ case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
++ return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
++ case QCA808X_CDT_STATUS_STAT_FAIL:
++ default:
++ return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
++ }
++}
++
++static int qca808x_cdt_fault_length(struct phy_device *phydev, int pair,
++ int result)
++{
++ int val;
++ u32 cdt_length_reg = 0;
++
++ switch (pair) {
++ case ETHTOOL_A_CABLE_PAIR_A:
++ cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_A;
++ break;
++ case ETHTOOL_A_CABLE_PAIR_B:
++ cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_B;
++ break;
++ case ETHTOOL_A_CABLE_PAIR_C:
++ cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_C;
++ break;
++ case ETHTOOL_A_CABLE_PAIR_D:
++ cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_D;
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ val = phy_read_mmd(phydev, MDIO_MMD_PCS, cdt_length_reg);
++ if (val < 0)
++ return val;
++
++ if (result == ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT)
++ val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_SAME_SHORT, val);
++ else
++ val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT, val);
++
++ return at803x_cdt_fault_length(val);
++}
++
++static int qca808x_cable_test_get_pair_status(struct phy_device *phydev, u8 pair,
++ u16 status)
++{
++ int length, result;
++ u16 pair_code;
++
++ switch (pair) {
++ case ETHTOOL_A_CABLE_PAIR_A:
++ pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, status);
++ break;
++ case ETHTOOL_A_CABLE_PAIR_B:
++ pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, status);
++ break;
++ case ETHTOOL_A_CABLE_PAIR_C:
++ pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, status);
++ break;
++ case ETHTOOL_A_CABLE_PAIR_D:
++ pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, status);
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ result = qca808x_cable_test_result_trans(pair_code);
++ ethnl_cable_test_result(phydev, pair, result);
++
++ if (qca808x_cdt_fault_length_valid(pair_code)) {
++ length = qca808x_cdt_fault_length(phydev, pair, result);
++ ethnl_cable_test_fault_length(phydev, pair, length);
++ }
++
++ return 0;
++}
++
++int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished)
++{
++ int ret, val;
++
++ *finished = false;
++
++ val = QCA808X_CDT_ENABLE_TEST |
++ QCA808X_CDT_LENGTH_UNIT;
++ ret = at803x_cdt_start(phydev, val);
++ if (ret)
++ return ret;
++
++ ret = at803x_cdt_wait_for_completion(phydev, QCA808X_CDT_ENABLE_TEST);
++ if (ret)
++ return ret;
++
++ val = phy_read_mmd(phydev, MDIO_MMD_PCS, QCA808X_MMD3_CDT_STATUS);
++ if (val < 0)
++ return val;
++
++ ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_A, val);
++ if (ret)
++ return ret;
++
++ ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_B, val);
++ if (ret)
++ return ret;
++
++ ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_C, val);
++ if (ret)
++ return ret;
++
++ ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_D, val);
++ if (ret)
++ return ret;
++
++ *finished = true;
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(qca808x_cable_test_get_status);
+--- a/drivers/net/phy/qcom/qcom.h
++++ b/drivers/net/phy/qcom/qcom.h
+@@ -54,6 +54,55 @@
+ #define AT803X_CDT_STATUS_STAT_MASK GENMASK(9, 8)
+ #define AT803X_CDT_STATUS_DELTA_TIME_MASK GENMASK(7, 0)
+
++#define QCA808X_CDT_ENABLE_TEST BIT(15)
++#define QCA808X_CDT_INTER_CHECK_DIS BIT(13)
++#define QCA808X_CDT_STATUS BIT(11)
++#define QCA808X_CDT_LENGTH_UNIT BIT(10)
++
++#define QCA808X_MMD3_CDT_STATUS 0x8064
++#define QCA808X_MMD3_CDT_DIAG_PAIR_A 0x8065
++#define QCA808X_MMD3_CDT_DIAG_PAIR_B 0x8066
++#define QCA808X_MMD3_CDT_DIAG_PAIR_C 0x8067
++#define QCA808X_MMD3_CDT_DIAG_PAIR_D 0x8068
++#define QCA808X_CDT_DIAG_LENGTH_SAME_SHORT GENMASK(15, 8)
++#define QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT GENMASK(7, 0)
++
++#define QCA808X_CDT_CODE_PAIR_A GENMASK(15, 12)
++#define QCA808X_CDT_CODE_PAIR_B GENMASK(11, 8)
++#define QCA808X_CDT_CODE_PAIR_C GENMASK(7, 4)
++#define QCA808X_CDT_CODE_PAIR_D GENMASK(3, 0)
++
++#define QCA808X_CDT_STATUS_STAT_TYPE GENMASK(1, 0)
++#define QCA808X_CDT_STATUS_STAT_FAIL FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 0)
++#define QCA808X_CDT_STATUS_STAT_NORMAL FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 1)
++#define QCA808X_CDT_STATUS_STAT_SAME_OPEN FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 2)
++#define QCA808X_CDT_STATUS_STAT_SAME_SHORT FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 3)
++
++#define QCA808X_CDT_STATUS_STAT_MDI GENMASK(3, 2)
++#define QCA808X_CDT_STATUS_STAT_MDI1 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 1)
++#define QCA808X_CDT_STATUS_STAT_MDI2 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 2)
++#define QCA808X_CDT_STATUS_STAT_MDI3 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 3)
++
++/* NORMAL are MDI with type set to 0 */
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI1
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++ QCA808X_CDT_STATUS_STAT_MDI1)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++ QCA808X_CDT_STATUS_STAT_MDI1)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI2
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++ QCA808X_CDT_STATUS_STAT_MDI2)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++ QCA808X_CDT_STATUS_STAT_MDI2)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI3
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++ QCA808X_CDT_STATUS_STAT_MDI3)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++ QCA808X_CDT_STATUS_STAT_MDI3)
++
++/* Added for reference of existence but should be handled by wait_for_completion already */
++#define QCA808X_CDT_STATUS_STAT_BUSY (BIT(1) | BIT(3))
++
+ #define AT803X_LOC_MAC_ADDR_0_15_OFFSET 0x804C
+ #define AT803X_LOC_MAC_ADDR_16_31_OFFSET 0x804B
+ #define AT803X_LOC_MAC_ADDR_32_47_OFFSET 0x804A
+@@ -110,6 +159,7 @@ int at803x_read_specific_status(struct p
+ struct at803x_ss_mask ss_mask);
+ int at803x_config_mdix(struct phy_device *phydev, u8 ctrl);
+ int at803x_prepare_config_aneg(struct phy_device *phydev);
++int at803x_read_status(struct phy_device *phydev);
+ int at803x_get_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, void *data);
+ int at803x_set_tunable(struct phy_device *phydev,
+@@ -118,3 +168,4 @@ int at803x_cdt_fault_length(int dt);
+ int at803x_cdt_start(struct phy_device *phydev, u32 cdt_start);
+ int at803x_cdt_wait_for_completion(struct phy_device *phydev,
+ u32 cdt_en);
++int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished);
diff --git a/target/linux/generic/backport-6.6/716-v6.9-06-net-phy-provide-whether-link-has-changed-in-c37_read.patch b/target/linux/generic/backport-6.6/716-v6.9-06-net-phy-provide-whether-link-has-changed-in-c37_read.patch
new file mode 100644
index 0000000000..acaa4a644e
--- /dev/null
+++ b/target/linux/generic/backport-6.6/716-v6.9-06-net-phy-provide-whether-link-has-changed-in-c37_read.patch
@@ -0,0 +1,100 @@
+From 9b1d5e055508393561e26bd1720f4c2639b03b1a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 6 Feb 2024 18:31:09 +0100
+Subject: [PATCH 06/10] net: phy: provide whether link has changed in
+ c37_read_status
+
+Some PHY driver might require additional regs call after
+genphy_c37_read_status() is called.
+
+Expand genphy_c37_read_status to provide a bool wheather the link has
+changed or not to permit PHY driver to skip additional regs call if
+nothing has changed.
+
+Every user of genphy_c37_read_status() is updated with the new
+additional bool.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/broadcom.c | 3 ++-
+ drivers/net/phy/phy_device.c | 11 +++++++++--
+ drivers/net/phy/qcom/at803x.c | 3 ++-
+ include/linux/phy.h | 2 +-
+ 4 files changed, 14 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/broadcom.c
++++ b/drivers/net/phy/broadcom.c
+@@ -609,10 +609,11 @@ static int bcm54616s_config_aneg(struct
+ static int bcm54616s_read_status(struct phy_device *phydev)
+ {
+ struct bcm54616s_phy_priv *priv = phydev->priv;
++ bool changed;
+ int err;
+
+ if (priv->mode_1000bx_en)
+- err = genphy_c37_read_status(phydev);
++ err = genphy_c37_read_status(phydev, &changed);
+ else
+ err = genphy_read_status(phydev);
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -2549,12 +2549,15 @@ EXPORT_SYMBOL(genphy_read_status);
+ /**
+ * genphy_c37_read_status - check the link status and update current link state
+ * @phydev: target phy_device struct
++ * @changed: pointer where to store if link changed
+ *
+ * Description: Check the link, then figure out the current state
+ * by comparing what we advertise with what the link partner
+ * advertises. This function is for Clause 37 1000Base-X mode.
++ *
++ * If link has changed, @changed is set to true, false otherwise.
+ */
+-int genphy_c37_read_status(struct phy_device *phydev)
++int genphy_c37_read_status(struct phy_device *phydev, bool *changed)
+ {
+ int lpa, err, old_link = phydev->link;
+
+@@ -2564,9 +2567,13 @@ int genphy_c37_read_status(struct phy_de
+ return err;
+
+ /* why bother the PHY if nothing can have changed */
+- if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
++ if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link) {
++ *changed = false;
+ return 0;
++ }
+
++ /* Signal link has changed */
++ *changed = true;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+--- a/drivers/net/phy/qcom/at803x.c
++++ b/drivers/net/phy/qcom/at803x.c
+@@ -912,9 +912,10 @@ static int at8031_config_intr(struct phy
+ static int at8031_read_status(struct phy_device *phydev)
+ {
+ struct at803x_priv *priv = phydev->priv;
++ bool changed;
+
+ if (priv->is_1000basex)
+- return genphy_c37_read_status(phydev);
++ return genphy_c37_read_status(phydev, &changed);
+
+ return at803x_read_status(phydev);
+ }
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -1688,7 +1688,7 @@ int genphy_write_mmd_unsupported(struct
+
+ /* Clause 37 */
+ int genphy_c37_config_aneg(struct phy_device *phydev);
+-int genphy_c37_read_status(struct phy_device *phydev);
++int genphy_c37_read_status(struct phy_device *phydev, bool *changed);
+
+ /* Clause 45 PHY */
+ int genphy_c45_restart_aneg(struct phy_device *phydev);
diff --git a/target/linux/generic/backport-6.6/716-v6.9-07-net-phy-qcom-add-support-for-QCA807x-PHY-Family.patch b/target/linux/generic/backport-6.6/716-v6.9-07-net-phy-qcom-add-support-for-QCA807x-PHY-Family.patch
new file mode 100644
index 0000000000..bbf0f76d68
--- /dev/null
+++ b/target/linux/generic/backport-6.6/716-v6.9-07-net-phy-qcom-add-support-for-QCA807x-PHY-Family.patch
@@ -0,0 +1,668 @@
+From d1cb613efbd3cd7d0c000167816beb3f248f5eb8 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robert.marko@sartura.hr>
+Date: Tue, 6 Feb 2024 18:31:10 +0100
+Subject: [PATCH 07/10] net: phy: qcom: add support for QCA807x PHY Family
+
+This adds driver for the Qualcomm QCA8072 and QCA8075 PHY-s.
+
+They are 2 or 5 port IEEE 802.3 clause 22 compliant 10BASE-Te,
+100BASE-TX and 1000BASE-T PHY-s.
+
+They feature 2 SerDes, one for PSGMII or QSGMII connection with
+MAC, while second one is SGMII for connection to MAC or fiber.
+
+Both models have a combo port that supports 1000BASE-X and
+100BASE-FX fiber.
+
+PHY package can be configured in 3 mode following this table:
+
+ First Serdes mode Second Serdes mode
+Option 1 PSGMII for copper Disabled
+ ports 0-4
+Option 2 PSGMII for copper 1000BASE-X / 100BASE-FX
+ ports 0-4
+Option 3 QSGMII for copper SGMII for
+ ports 0-3 copper port 4
+
+Each PHY inside of QCA807x series has 4 digitally controlled
+output only pins that natively drive LED-s.
+But some vendors used these to driver generic LED-s controlled
+by userspace, so lets enable registering each PHY as GPIO
+controller and add driver for it.
+
+These are commonly used in Qualcomm IPQ40xx, IPQ60xx and IPQ807x
+boards.
+
+Co-developed-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: Robert Marko <robert.marko@sartura.hr>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/qcom/Kconfig | 8 +
+ drivers/net/phy/qcom/Makefile | 1 +
+ drivers/net/phy/qcom/qca807x.c | 597 +++++++++++++++++++++++++++++++++
+ 3 files changed, 606 insertions(+)
+ create mode 100644 drivers/net/phy/qcom/qca807x.c
+
+--- a/drivers/net/phy/qcom/Kconfig
++++ b/drivers/net/phy/qcom/Kconfig
+@@ -20,3 +20,11 @@ config QCA808X_PHY
+ select QCOM_NET_PHYLIB
+ help
+ Currently supports the QCA8081 model
++
++config QCA807X_PHY
++ tristate "Qualcomm QCA807x PHYs"
++ select QCOM_NET_PHYLIB
++ depends on OF_MDIO
++ help
++ Currently supports the Qualcomm QCA8072, QCA8075 and the PSGMII
++ control PHY.
+--- a/drivers/net/phy/qcom/Makefile
++++ b/drivers/net/phy/qcom/Makefile
+@@ -3,3 +3,4 @@ obj-$(CONFIG_QCOM_NET_PHYLIB) += qcom-ph
+ obj-$(CONFIG_AT803X_PHY) += at803x.o
+ obj-$(CONFIG_QCA83XX_PHY) += qca83xx.o
+ obj-$(CONFIG_QCA808X_PHY) += qca808x.o
++obj-$(CONFIG_QCA807X_PHY) += qca807x.o
+--- /dev/null
++++ b/drivers/net/phy/qcom/qca807x.c
+@@ -0,0 +1,597 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Copyright (c) 2023 Sartura Ltd.
++ *
++ * Author: Robert Marko <robert.marko@sartura.hr>
++ * Christian Marangi <ansuelsmth@gmail.com>
++ *
++ * Qualcomm QCA8072 and QCA8075 PHY driver
++ */
++
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/phy.h>
++#include <linux/bitfield.h>
++#include <linux/gpio/driver.h>
++#include <linux/sfp.h>
++
++#include "qcom.h"
++
++#define QCA807X_CHIP_CONFIGURATION 0x1f
++#define QCA807X_BT_BX_REG_SEL BIT(15)
++#define QCA807X_BT_BX_REG_SEL_FIBER 0
++#define QCA807X_BT_BX_REG_SEL_COPPER 1
++#define QCA807X_CHIP_CONFIGURATION_MODE_CFG_MASK GENMASK(3, 0)
++#define QCA807X_CHIP_CONFIGURATION_MODE_QSGMII_SGMII 4
++#define QCA807X_CHIP_CONFIGURATION_MODE_PSGMII_FIBER 3
++#define QCA807X_CHIP_CONFIGURATION_MODE_PSGMII_ALL_COPPER 0
++
++#define QCA807X_MEDIA_SELECT_STATUS 0x1a
++#define QCA807X_MEDIA_DETECTED_COPPER BIT(5)
++#define QCA807X_MEDIA_DETECTED_1000_BASE_X BIT(4)
++#define QCA807X_MEDIA_DETECTED_100_BASE_FX BIT(3)
++
++#define QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION 0x807e
++#define QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION_EN BIT(0)
++
++#define QCA807X_MMD7_1000BASE_T_POWER_SAVE_PER_CABLE_LENGTH 0x801a
++#define QCA807X_CONTROL_DAC_MASK GENMASK(2, 0)
++/* List of tweaks enabled by this bit:
++ * - With both FULL amplitude and FULL bias current: bias current
++ * is set to half.
++ * - With only DSP amplitude: bias current is set to half and
++ * is set to 1/4 with cable < 10m.
++ * - With DSP bias current (included both DSP amplitude and
++ * DSP bias current): bias current is half the detected current
++ * with cable < 10m.
++ */
++#define QCA807X_CONTROL_DAC_BIAS_CURRENT_TWEAK BIT(2)
++#define QCA807X_CONTROL_DAC_DSP_BIAS_CURRENT BIT(1)
++#define QCA807X_CONTROL_DAC_DSP_AMPLITUDE BIT(0)
++
++#define QCA807X_MMD7_LED_100N_1 0x8074
++#define QCA807X_MMD7_LED_100N_2 0x8075
++#define QCA807X_MMD7_LED_1000N_1 0x8076
++#define QCA807X_MMD7_LED_1000N_2 0x8077
++
++#define QCA807X_MMD7_LED_CTRL(x) (0x8074 + ((x) * 2))
++#define QCA807X_MMD7_LED_FORCE_CTRL(x) (0x8075 + ((x) * 2))
++
++#define QCA807X_GPIO_FORCE_EN BIT(15)
++#define QCA807X_GPIO_FORCE_MODE_MASK GENMASK(14, 13)
++
++#define QCA807X_FUNCTION_CONTROL 0x10
++#define QCA807X_FC_MDI_CROSSOVER_MODE_MASK GENMASK(6, 5)
++#define QCA807X_FC_MDI_CROSSOVER_AUTO 3
++#define QCA807X_FC_MDI_CROSSOVER_MANUAL_MDIX 1
++#define QCA807X_FC_MDI_CROSSOVER_MANUAL_MDI 0
++
++/* PQSGMII Analog PHY specific */
++#define PQSGMII_CTRL_REG 0x0
++#define PQSGMII_ANALOG_SW_RESET BIT(6)
++#define PQSGMII_DRIVE_CONTROL_1 0xb
++#define PQSGMII_TX_DRIVER_MASK GENMASK(7, 4)
++#define PQSGMII_TX_DRIVER_140MV 0x0
++#define PQSGMII_TX_DRIVER_160MV 0x1
++#define PQSGMII_TX_DRIVER_180MV 0x2
++#define PQSGMII_TX_DRIVER_200MV 0x3
++#define PQSGMII_TX_DRIVER_220MV 0x4
++#define PQSGMII_TX_DRIVER_240MV 0x5
++#define PQSGMII_TX_DRIVER_260MV 0x6
++#define PQSGMII_TX_DRIVER_280MV 0x7
++#define PQSGMII_TX_DRIVER_300MV 0x8
++#define PQSGMII_TX_DRIVER_320MV 0x9
++#define PQSGMII_TX_DRIVER_400MV 0xa
++#define PQSGMII_TX_DRIVER_500MV 0xb
++#define PQSGMII_TX_DRIVER_600MV 0xc
++#define PQSGMII_MODE_CTRL 0x6d
++#define PQSGMII_MODE_CTRL_AZ_WORKAROUND_MASK BIT(0)
++#define PQSGMII_MMD3_SERDES_CONTROL 0x805a
++
++#define PHY_ID_QCA8072 0x004dd0b2
++#define PHY_ID_QCA8075 0x004dd0b1
++
++#define QCA807X_COMBO_ADDR_OFFSET 4
++#define QCA807X_PQSGMII_ADDR_OFFSET 5
++#define SERDES_RESET_SLEEP 100
++
++enum qca807x_global_phy {
++ QCA807X_COMBO_ADDR = 4,
++ QCA807X_PQSGMII_ADDR = 5,
++};
++
++struct qca807x_shared_priv {
++ unsigned int package_mode;
++ u32 tx_drive_strength;
++};
++
++struct qca807x_gpio_priv {
++ struct phy_device *phy;
++};
++
++struct qca807x_priv {
++ bool dac_full_amplitude;
++ bool dac_full_bias_current;
++ bool dac_disable_bias_current_tweak;
++};
++
++static int qca807x_cable_test_start(struct phy_device *phydev)
++{
++ /* we do all the (time consuming) work later */
++ return 0;
++}
++
++#ifdef CONFIG_GPIOLIB
++static int qca807x_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
++{
++ return GPIO_LINE_DIRECTION_OUT;
++}
++
++static int qca807x_gpio_get(struct gpio_chip *gc, unsigned int offset)
++{
++ struct qca807x_gpio_priv *priv = gpiochip_get_data(gc);
++ u16 reg;
++ int val;
++
++ reg = QCA807X_MMD7_LED_FORCE_CTRL(offset);
++ val = phy_read_mmd(priv->phy, MDIO_MMD_AN, reg);
++
++ return FIELD_GET(QCA807X_GPIO_FORCE_MODE_MASK, val);
++}
++
++static void qca807x_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
++{
++ struct qca807x_gpio_priv *priv = gpiochip_get_data(gc);
++ u16 reg;
++ int val;
++
++ reg = QCA807X_MMD7_LED_FORCE_CTRL(offset);
++
++ val = phy_read_mmd(priv->phy, MDIO_MMD_AN, reg);
++ val &= ~QCA807X_GPIO_FORCE_MODE_MASK;
++ val |= QCA807X_GPIO_FORCE_EN;
++ val |= FIELD_PREP(QCA807X_GPIO_FORCE_MODE_MASK, value);
++
++ phy_write_mmd(priv->phy, MDIO_MMD_AN, reg, val);
++}
++
++static int qca807x_gpio_dir_out(struct gpio_chip *gc, unsigned int offset, int value)
++{
++ qca807x_gpio_set(gc, offset, value);
++
++ return 0;
++}
++
++static int qca807x_gpio(struct phy_device *phydev)
++{
++ struct device *dev = &phydev->mdio.dev;
++ struct qca807x_gpio_priv *priv;
++ struct gpio_chip *gc;
++
++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
++
++ priv->phy = phydev;
++
++ gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL);
++ if (!gc)
++ return -ENOMEM;
++
++ gc->label = dev_name(dev);
++ gc->base = -1;
++ gc->ngpio = 2;
++ gc->parent = dev;
++ gc->owner = THIS_MODULE;
++ gc->can_sleep = true;
++ gc->get_direction = qca807x_gpio_get_direction;
++ gc->direction_output = qca807x_gpio_dir_out;
++ gc->get = qca807x_gpio_get;
++ gc->set = qca807x_gpio_set;
++
++ return devm_gpiochip_add_data(dev, gc, priv);
++}
++#endif
++
++static int qca807x_read_fiber_status(struct phy_device *phydev)
++{
++ bool changed;
++ int ss, err;
++
++ err = genphy_c37_read_status(phydev, &changed);
++ if (err || !changed)
++ return err;
++
++ /* Read the QCA807x PHY-Specific Status register fiber page,
++ * which indicates the speed and duplex that the PHY is actually
++ * using, irrespective of whether we are in autoneg mode or not.
++ */
++ ss = phy_read(phydev, AT803X_SPECIFIC_STATUS);
++ if (ss < 0)
++ return ss;
++
++ phydev->speed = SPEED_UNKNOWN;
++ phydev->duplex = DUPLEX_UNKNOWN;
++ if (ss & AT803X_SS_SPEED_DUPLEX_RESOLVED) {
++ switch (FIELD_GET(AT803X_SS_SPEED_MASK, ss)) {
++ case AT803X_SS_SPEED_100:
++ phydev->speed = SPEED_100;
++ break;
++ case AT803X_SS_SPEED_1000:
++ phydev->speed = SPEED_1000;
++ break;
++ }
++
++ if (ss & AT803X_SS_DUPLEX)
++ phydev->duplex = DUPLEX_FULL;
++ else
++ phydev->duplex = DUPLEX_HALF;
++ }
++
++ return 0;
++}
++
++static int qca807x_read_status(struct phy_device *phydev)
++{
++ if (linkmode_test_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->supported)) {
++ switch (phydev->port) {
++ case PORT_FIBRE:
++ return qca807x_read_fiber_status(phydev);
++ case PORT_TP:
++ return at803x_read_status(phydev);
++ default:
++ return -EINVAL;
++ }
++ }
++
++ return at803x_read_status(phydev);
++}
++
++static int qca807x_phy_package_probe_once(struct phy_device *phydev)
++{
++ struct phy_package_shared *shared = phydev->shared;
++ struct qca807x_shared_priv *priv = shared->priv;
++ unsigned int tx_drive_strength;
++ const char *package_mode_name;
++
++ /* Default to 600mw if not defined */
++ if (of_property_read_u32(shared->np, "qcom,tx-drive-strength-milliwatt",
++ &tx_drive_strength))
++ tx_drive_strength = 600;
++
++ switch (tx_drive_strength) {
++ case 140:
++ priv->tx_drive_strength = PQSGMII_TX_DRIVER_140MV;
++ break;
++ case 160:
++ priv->tx_drive_strength = PQSGMII_TX_DRIVER_160MV;
++ break;
++ case 180:
++ priv->tx_drive_strength = PQSGMII_TX_DRIVER_180MV;
++ break;
++ case 200:
++ priv->tx_drive_strength = PQSGMII_TX_DRIVER_200MV;
++ break;
++ case 220:
++ priv->tx_drive_strength = PQSGMII_TX_DRIVER_220MV;
++ break;
++ case 240:
++ priv->tx_drive_strength = PQSGMII_TX_DRIVER_240MV;
++ break;
++ case 260:
++ priv->tx_drive_strength = PQSGMII_TX_DRIVER_260MV;
++ break;
++ case 280:
++ priv->tx_drive_strength = PQSGMII_TX_DRIVER_280MV;
++ break;
++ case 300:
++ priv->tx_drive_strength = PQSGMII_TX_DRIVER_300MV;
++ break;
++ case 320:
++ priv->tx_drive_strength = PQSGMII_TX_DRIVER_320MV;
++ break;
++ case 400:
++ priv->tx_drive_strength = PQSGMII_TX_DRIVER_400MV;
++ break;
++ case 500:
++ priv->tx_drive_strength = PQSGMII_TX_DRIVER_500MV;
++ break;
++ case 600:
++ priv->tx_drive_strength = PQSGMII_TX_DRIVER_600MV;
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ priv->package_mode = PHY_INTERFACE_MODE_NA;
++ if (!of_property_read_string(shared->np, "qcom,package-mode",
++ &package_mode_name)) {
++ if (!strcasecmp(package_mode_name,
++ phy_modes(PHY_INTERFACE_MODE_PSGMII)))
++ priv->package_mode = PHY_INTERFACE_MODE_PSGMII;
++ else if (!strcasecmp(package_mode_name,
++ phy_modes(PHY_INTERFACE_MODE_QSGMII)))
++ priv->package_mode = PHY_INTERFACE_MODE_QSGMII;
++ else
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
++static int qca807x_phy_package_config_init_once(struct phy_device *phydev)
++{
++ struct phy_package_shared *shared = phydev->shared;
++ struct qca807x_shared_priv *priv = shared->priv;
++ int val, ret;
++
++ phy_lock_mdio_bus(phydev);
++
++ /* Set correct PHY package mode */
++ val = __phy_package_read(phydev, QCA807X_COMBO_ADDR,
++ QCA807X_CHIP_CONFIGURATION);
++ val &= ~QCA807X_CHIP_CONFIGURATION_MODE_CFG_MASK;
++ /* package_mode can be QSGMII or PSGMII and we validate
++ * this in probe_once.
++ * With package_mode to NA, we default to PSGMII.
++ */
++ switch (priv->package_mode) {
++ case PHY_INTERFACE_MODE_QSGMII:
++ val |= QCA807X_CHIP_CONFIGURATION_MODE_QSGMII_SGMII;
++ break;
++ case PHY_INTERFACE_MODE_PSGMII:
++ default:
++ val |= QCA807X_CHIP_CONFIGURATION_MODE_PSGMII_ALL_COPPER;
++ }
++ ret = __phy_package_write(phydev, QCA807X_COMBO_ADDR,
++ QCA807X_CHIP_CONFIGURATION, val);
++ if (ret)
++ goto exit;
++
++ /* After mode change Serdes reset is required */
++ val = __phy_package_read(phydev, QCA807X_PQSGMII_ADDR,
++ PQSGMII_CTRL_REG);
++ val &= ~PQSGMII_ANALOG_SW_RESET;
++ ret = __phy_package_write(phydev, QCA807X_PQSGMII_ADDR,
++ PQSGMII_CTRL_REG, val);
++ if (ret)
++ goto exit;
++
++ msleep(SERDES_RESET_SLEEP);
++
++ val = __phy_package_read(phydev, QCA807X_PQSGMII_ADDR,
++ PQSGMII_CTRL_REG);
++ val |= PQSGMII_ANALOG_SW_RESET;
++ ret = __phy_package_write(phydev, QCA807X_PQSGMII_ADDR,
++ PQSGMII_CTRL_REG, val);
++ if (ret)
++ goto exit;
++
++ /* Workaround to enable AZ transmitting ability */
++ val = __phy_package_read_mmd(phydev, QCA807X_PQSGMII_ADDR,
++ MDIO_MMD_PMAPMD, PQSGMII_MODE_CTRL);
++ val &= ~PQSGMII_MODE_CTRL_AZ_WORKAROUND_MASK;
++ ret = __phy_package_write_mmd(phydev, QCA807X_PQSGMII_ADDR,
++ MDIO_MMD_PMAPMD, PQSGMII_MODE_CTRL, val);
++ if (ret)
++ goto exit;
++
++ /* Set PQSGMII TX AMP strength */
++ val = __phy_package_read(phydev, QCA807X_PQSGMII_ADDR,
++ PQSGMII_DRIVE_CONTROL_1);
++ val &= ~PQSGMII_TX_DRIVER_MASK;
++ val |= FIELD_PREP(PQSGMII_TX_DRIVER_MASK, priv->tx_drive_strength);
++ ret = __phy_package_write(phydev, QCA807X_PQSGMII_ADDR,
++ PQSGMII_DRIVE_CONTROL_1, val);
++ if (ret)
++ goto exit;
++
++ /* Prevent PSGMII going into hibernation via PSGMII self test */
++ val = __phy_package_read_mmd(phydev, QCA807X_COMBO_ADDR,
++ MDIO_MMD_PCS, PQSGMII_MMD3_SERDES_CONTROL);
++ val &= ~BIT(1);
++ ret = __phy_package_write_mmd(phydev, QCA807X_COMBO_ADDR,
++ MDIO_MMD_PCS, PQSGMII_MMD3_SERDES_CONTROL, val);
++
++exit:
++ phy_unlock_mdio_bus(phydev);
++
++ return ret;
++}
++
++static int qca807x_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
++{
++ struct phy_device *phydev = upstream;
++ __ETHTOOL_DECLARE_LINK_MODE_MASK(support) = { 0, };
++ phy_interface_t iface;
++ int ret;
++ DECLARE_PHY_INTERFACE_MASK(interfaces);
++
++ sfp_parse_support(phydev->sfp_bus, id, support, interfaces);
++ iface = sfp_select_interface(phydev->sfp_bus, support);
++
++ dev_info(&phydev->mdio.dev, "%s SFP module inserted\n", phy_modes(iface));
++
++ switch (iface) {
++ case PHY_INTERFACE_MODE_1000BASEX:
++ case PHY_INTERFACE_MODE_100BASEX:
++ /* Set PHY mode to PSGMII combo (1/4 copper + combo ports) mode */
++ ret = phy_modify(phydev,
++ QCA807X_CHIP_CONFIGURATION,
++ QCA807X_CHIP_CONFIGURATION_MODE_CFG_MASK,
++ QCA807X_CHIP_CONFIGURATION_MODE_PSGMII_FIBER);
++ /* Enable fiber mode autodection (1000Base-X or 100Base-FX) */
++ ret = phy_set_bits_mmd(phydev,
++ MDIO_MMD_AN,
++ QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION,
++ QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION_EN);
++ /* Select fiber page */
++ ret = phy_clear_bits(phydev,
++ QCA807X_CHIP_CONFIGURATION,
++ QCA807X_BT_BX_REG_SEL);
++
++ phydev->port = PORT_FIBRE;
++ break;
++ default:
++ dev_err(&phydev->mdio.dev, "Incompatible SFP module inserted\n");
++ return -EINVAL;
++ }
++
++ return ret;
++}
++
++static void qca807x_sfp_remove(void *upstream)
++{
++ struct phy_device *phydev = upstream;
++
++ /* Select copper page */
++ phy_set_bits(phydev,
++ QCA807X_CHIP_CONFIGURATION,
++ QCA807X_BT_BX_REG_SEL);
++
++ phydev->port = PORT_TP;
++}
++
++static const struct sfp_upstream_ops qca807x_sfp_ops = {
++ .attach = phy_sfp_attach,
++ .detach = phy_sfp_detach,
++ .module_insert = qca807x_sfp_insert,
++ .module_remove = qca807x_sfp_remove,
++};
++
++static int qca807x_probe(struct phy_device *phydev)
++{
++ struct device_node *node = phydev->mdio.dev.of_node;
++ struct qca807x_shared_priv *shared_priv;
++ struct device *dev = &phydev->mdio.dev;
++ struct phy_package_shared *shared;
++ struct qca807x_priv *priv;
++ int ret;
++
++ ret = devm_of_phy_package_join(dev, phydev, sizeof(*shared_priv));
++ if (ret)
++ return ret;
++
++ if (phy_package_probe_once(phydev)) {
++ ret = qca807x_phy_package_probe_once(phydev);
++ if (ret)
++ return ret;
++ }
++
++ shared = phydev->shared;
++ shared_priv = shared->priv;
++
++ /* Make sure PHY follow PHY package mode if enforced */
++ if (shared_priv->package_mode != PHY_INTERFACE_MODE_NA &&
++ phydev->interface != shared_priv->package_mode)
++ return -EINVAL;
++
++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
++
++ priv->dac_full_amplitude = of_property_read_bool(node, "qcom,dac-full-amplitude");
++ priv->dac_full_bias_current = of_property_read_bool(node, "qcom,dac-full-bias-current");
++ priv->dac_disable_bias_current_tweak = of_property_read_bool(node,
++ "qcom,dac-disable-bias-current-tweak");
++
++ if (IS_ENABLED(CONFIG_GPIOLIB)) {
++ /* Do not register a GPIO controller unless flagged for it */
++ if (of_property_read_bool(node, "gpio-controller")) {
++ ret = qca807x_gpio(phydev);
++ if (ret)
++ return ret;
++ }
++ }
++
++ /* Attach SFP bus on combo port*/
++ if (phy_read(phydev, QCA807X_CHIP_CONFIGURATION)) {
++ ret = phy_sfp_probe(phydev, &qca807x_sfp_ops);
++ if (ret)
++ return ret;
++ linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->supported);
++ linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->advertising);
++ }
++
++ phydev->priv = priv;
++
++ return 0;
++}
++
++static int qca807x_config_init(struct phy_device *phydev)
++{
++ struct qca807x_priv *priv = phydev->priv;
++ u16 control_dac;
++ int ret;
++
++ if (phy_package_init_once(phydev)) {
++ ret = qca807x_phy_package_config_init_once(phydev);
++ if (ret)
++ return ret;
++ }
++
++ control_dac = phy_read_mmd(phydev, MDIO_MMD_AN,
++ QCA807X_MMD7_1000BASE_T_POWER_SAVE_PER_CABLE_LENGTH);
++ control_dac &= ~QCA807X_CONTROL_DAC_MASK;
++ if (!priv->dac_full_amplitude)
++ control_dac |= QCA807X_CONTROL_DAC_DSP_AMPLITUDE;
++ if (!priv->dac_full_amplitude)
++ control_dac |= QCA807X_CONTROL_DAC_DSP_BIAS_CURRENT;
++ if (!priv->dac_disable_bias_current_tweak)
++ control_dac |= QCA807X_CONTROL_DAC_BIAS_CURRENT_TWEAK;
++ return phy_write_mmd(phydev, MDIO_MMD_AN,
++ QCA807X_MMD7_1000BASE_T_POWER_SAVE_PER_CABLE_LENGTH,
++ control_dac);
++}
++
++static struct phy_driver qca807x_drivers[] = {
++ {
++ PHY_ID_MATCH_EXACT(PHY_ID_QCA8072),
++ .name = "Qualcomm QCA8072",
++ .flags = PHY_POLL_CABLE_TEST,
++ /* PHY_GBIT_FEATURES */
++ .probe = qca807x_probe,
++ .config_init = qca807x_config_init,
++ .read_status = qca807x_read_status,
++ .config_intr = at803x_config_intr,
++ .handle_interrupt = at803x_handle_interrupt,
++ .soft_reset = genphy_soft_reset,
++ .get_tunable = at803x_get_tunable,
++ .set_tunable = at803x_set_tunable,
++ .resume = genphy_resume,
++ .suspend = genphy_suspend,
++ .cable_test_start = qca807x_cable_test_start,
++ .cable_test_get_status = qca808x_cable_test_get_status,
++ },
++ {
++ PHY_ID_MATCH_EXACT(PHY_ID_QCA8075),
++ .name = "Qualcomm QCA8075",
++ .flags = PHY_POLL_CABLE_TEST,
++ /* PHY_GBIT_FEATURES */
++ .probe = qca807x_probe,
++ .config_init = qca807x_config_init,
++ .read_status = qca807x_read_status,
++ .config_intr = at803x_config_intr,
++ .handle_interrupt = at803x_handle_interrupt,
++ .soft_reset = genphy_soft_reset,
++ .get_tunable = at803x_get_tunable,
++ .set_tunable = at803x_set_tunable,
++ .resume = genphy_resume,
++ .suspend = genphy_suspend,
++ .cable_test_start = qca807x_cable_test_start,
++ .cable_test_get_status = qca808x_cable_test_get_status,
++ },
++};
++module_phy_driver(qca807x_drivers);
++
++static struct mdio_device_id __maybe_unused qca807x_tbl[] = {
++ { PHY_ID_MATCH_EXACT(PHY_ID_QCA8072) },
++ { PHY_ID_MATCH_EXACT(PHY_ID_QCA8075) },
++ { }
++};
++
++MODULE_AUTHOR("Robert Marko <robert.marko@sartura.hr>");
++MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
++MODULE_DESCRIPTION("Qualcomm QCA807x PHY driver");
++MODULE_DEVICE_TABLE(mdio, qca807x_tbl);
++MODULE_LICENSE("GPL");
diff --git a/target/linux/generic/backport-6.6/716-v6.9-08-net-phy-qcom-move-common-qca808x-LED-define-to-share.patch b/target/linux/generic/backport-6.6/716-v6.9-08-net-phy-qcom-move-common-qca808x-LED-define-to-share.patch
new file mode 100644
index 0000000000..cf4d74e8c5
--- /dev/null
+++ b/target/linux/generic/backport-6.6/716-v6.9-08-net-phy-qcom-move-common-qca808x-LED-define-to-share.patch
@@ -0,0 +1,179 @@
+From ee9d9807bee0e6af8ca2a4db6f0d1dc0e5b41f44 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 6 Feb 2024 18:31:11 +0100
+Subject: [PATCH 08/10] net: phy: qcom: move common qca808x LED define to
+ shared header
+
+The LED implementation of qca808x and qca807x is the same but qca807x
+supports also Fiber port and have different hw control bits for Fiber
+port.
+
+In preparation for qca807x introduction, move all the common define to
+shared header.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/qcom/qca808x.c | 65 ----------------------------------
+ drivers/net/phy/qcom/qcom.h | 65 ++++++++++++++++++++++++++++++++++
+ 2 files changed, 65 insertions(+), 65 deletions(-)
+
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -62,29 +62,6 @@
+ #define QCA808X_DBG_AN_TEST 0xb
+ #define QCA808X_HIBERNATION_EN BIT(15)
+
+-#define QCA808X_MMD7_LED_GLOBAL 0x8073
+-#define QCA808X_LED_BLINK_1 GENMASK(11, 6)
+-#define QCA808X_LED_BLINK_2 GENMASK(5, 0)
+-/* Values are the same for both BLINK_1 and BLINK_2 */
+-#define QCA808X_LED_BLINK_FREQ_MASK GENMASK(5, 3)
+-#define QCA808X_LED_BLINK_FREQ_2HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x0)
+-#define QCA808X_LED_BLINK_FREQ_4HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x1)
+-#define QCA808X_LED_BLINK_FREQ_8HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x2)
+-#define QCA808X_LED_BLINK_FREQ_16HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x3)
+-#define QCA808X_LED_BLINK_FREQ_32HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x4)
+-#define QCA808X_LED_BLINK_FREQ_64HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x5)
+-#define QCA808X_LED_BLINK_FREQ_128HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x6)
+-#define QCA808X_LED_BLINK_FREQ_256HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x7)
+-#define QCA808X_LED_BLINK_DUTY_MASK GENMASK(2, 0)
+-#define QCA808X_LED_BLINK_DUTY_50_50 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x0)
+-#define QCA808X_LED_BLINK_DUTY_75_25 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x1)
+-#define QCA808X_LED_BLINK_DUTY_25_75 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x2)
+-#define QCA808X_LED_BLINK_DUTY_33_67 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x3)
+-#define QCA808X_LED_BLINK_DUTY_67_33 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x4)
+-#define QCA808X_LED_BLINK_DUTY_17_83 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x5)
+-#define QCA808X_LED_BLINK_DUTY_83_17 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x6)
+-#define QCA808X_LED_BLINK_DUTY_8_92 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x7)
+-
+ #define QCA808X_MMD7_LED2_CTRL 0x8074
+ #define QCA808X_MMD7_LED2_FORCE_CTRL 0x8075
+ #define QCA808X_MMD7_LED1_CTRL 0x8076
+@@ -92,51 +69,9 @@
+ #define QCA808X_MMD7_LED0_CTRL 0x8078
+ #define QCA808X_MMD7_LED_CTRL(x) (0x8078 - ((x) * 2))
+
+-/* LED hw control pattern is the same for every LED */
+-#define QCA808X_LED_PATTERN_MASK GENMASK(15, 0)
+-#define QCA808X_LED_SPEED2500_ON BIT(15)
+-#define QCA808X_LED_SPEED2500_BLINK BIT(14)
+-/* Follow blink trigger even if duplex or speed condition doesn't match */
+-#define QCA808X_LED_BLINK_CHECK_BYPASS BIT(13)
+-#define QCA808X_LED_FULL_DUPLEX_ON BIT(12)
+-#define QCA808X_LED_HALF_DUPLEX_ON BIT(11)
+-#define QCA808X_LED_TX_BLINK BIT(10)
+-#define QCA808X_LED_RX_BLINK BIT(9)
+-#define QCA808X_LED_TX_ON_10MS BIT(8)
+-#define QCA808X_LED_RX_ON_10MS BIT(7)
+-#define QCA808X_LED_SPEED1000_ON BIT(6)
+-#define QCA808X_LED_SPEED100_ON BIT(5)
+-#define QCA808X_LED_SPEED10_ON BIT(4)
+-#define QCA808X_LED_COLLISION_BLINK BIT(3)
+-#define QCA808X_LED_SPEED1000_BLINK BIT(2)
+-#define QCA808X_LED_SPEED100_BLINK BIT(1)
+-#define QCA808X_LED_SPEED10_BLINK BIT(0)
+-
+ #define QCA808X_MMD7_LED0_FORCE_CTRL 0x8079
+ #define QCA808X_MMD7_LED_FORCE_CTRL(x) (0x8079 - ((x) * 2))
+
+-/* LED force ctrl is the same for every LED
+- * No documentation exist for this, not even internal one
+- * with NDA as QCOM gives only info about configuring
+- * hw control pattern rules and doesn't indicate any way
+- * to force the LED to specific mode.
+- * These define comes from reverse and testing and maybe
+- * lack of some info or some info are not entirely correct.
+- * For the basic LED control and hw control these finding
+- * are enough to support LED control in all the required APIs.
+- *
+- * On doing some comparison with implementation with qca807x,
+- * it was found that it's 1:1 equal to it and confirms all the
+- * reverse done. It was also found further specification with the
+- * force mode and the blink modes.
+- */
+-#define QCA808X_LED_FORCE_EN BIT(15)
+-#define QCA808X_LED_FORCE_MODE_MASK GENMASK(14, 13)
+-#define QCA808X_LED_FORCE_BLINK_1 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x3)
+-#define QCA808X_LED_FORCE_BLINK_2 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x2)
+-#define QCA808X_LED_FORCE_ON FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x1)
+-#define QCA808X_LED_FORCE_OFF FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x0)
+-
+ #define QCA808X_MMD7_LED_POLARITY_CTRL 0x901a
+ /* QSDK sets by default 0x46 to this reg that sets BIT 6 for
+ * LED to active high. It's not clear what BIT 3 and BIT 4 does.
+--- a/drivers/net/phy/qcom/qcom.h
++++ b/drivers/net/phy/qcom/qcom.h
+@@ -103,6 +103,71 @@
+ /* Added for reference of existence but should be handled by wait_for_completion already */
+ #define QCA808X_CDT_STATUS_STAT_BUSY (BIT(1) | BIT(3))
+
++#define QCA808X_MMD7_LED_GLOBAL 0x8073
++#define QCA808X_LED_BLINK_1 GENMASK(11, 6)
++#define QCA808X_LED_BLINK_2 GENMASK(5, 0)
++/* Values are the same for both BLINK_1 and BLINK_2 */
++#define QCA808X_LED_BLINK_FREQ_MASK GENMASK(5, 3)
++#define QCA808X_LED_BLINK_FREQ_2HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x0)
++#define QCA808X_LED_BLINK_FREQ_4HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x1)
++#define QCA808X_LED_BLINK_FREQ_8HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x2)
++#define QCA808X_LED_BLINK_FREQ_16HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x3)
++#define QCA808X_LED_BLINK_FREQ_32HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x4)
++#define QCA808X_LED_BLINK_FREQ_64HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x5)
++#define QCA808X_LED_BLINK_FREQ_128HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x6)
++#define QCA808X_LED_BLINK_FREQ_256HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x7)
++#define QCA808X_LED_BLINK_DUTY_MASK GENMASK(2, 0)
++#define QCA808X_LED_BLINK_DUTY_50_50 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x0)
++#define QCA808X_LED_BLINK_DUTY_75_25 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x1)
++#define QCA808X_LED_BLINK_DUTY_25_75 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x2)
++#define QCA808X_LED_BLINK_DUTY_33_67 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x3)
++#define QCA808X_LED_BLINK_DUTY_67_33 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x4)
++#define QCA808X_LED_BLINK_DUTY_17_83 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x5)
++#define QCA808X_LED_BLINK_DUTY_83_17 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x6)
++#define QCA808X_LED_BLINK_DUTY_8_92 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x7)
++
++/* LED hw control pattern is the same for every LED */
++#define QCA808X_LED_PATTERN_MASK GENMASK(15, 0)
++#define QCA808X_LED_SPEED2500_ON BIT(15)
++#define QCA808X_LED_SPEED2500_BLINK BIT(14)
++/* Follow blink trigger even if duplex or speed condition doesn't match */
++#define QCA808X_LED_BLINK_CHECK_BYPASS BIT(13)
++#define QCA808X_LED_FULL_DUPLEX_ON BIT(12)
++#define QCA808X_LED_HALF_DUPLEX_ON BIT(11)
++#define QCA808X_LED_TX_BLINK BIT(10)
++#define QCA808X_LED_RX_BLINK BIT(9)
++#define QCA808X_LED_TX_ON_10MS BIT(8)
++#define QCA808X_LED_RX_ON_10MS BIT(7)
++#define QCA808X_LED_SPEED1000_ON BIT(6)
++#define QCA808X_LED_SPEED100_ON BIT(5)
++#define QCA808X_LED_SPEED10_ON BIT(4)
++#define QCA808X_LED_COLLISION_BLINK BIT(3)
++#define QCA808X_LED_SPEED1000_BLINK BIT(2)
++#define QCA808X_LED_SPEED100_BLINK BIT(1)
++#define QCA808X_LED_SPEED10_BLINK BIT(0)
++
++/* LED force ctrl is the same for every LED
++ * No documentation exist for this, not even internal one
++ * with NDA as QCOM gives only info about configuring
++ * hw control pattern rules and doesn't indicate any way
++ * to force the LED to specific mode.
++ * These define comes from reverse and testing and maybe
++ * lack of some info or some info are not entirely correct.
++ * For the basic LED control and hw control these finding
++ * are enough to support LED control in all the required APIs.
++ *
++ * On doing some comparison with implementation with qca807x,
++ * it was found that it's 1:1 equal to it and confirms all the
++ * reverse done. It was also found further specification with the
++ * force mode and the blink modes.
++ */
++#define QCA808X_LED_FORCE_EN BIT(15)
++#define QCA808X_LED_FORCE_MODE_MASK GENMASK(14, 13)
++#define QCA808X_LED_FORCE_BLINK_1 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x3)
++#define QCA808X_LED_FORCE_BLINK_2 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x2)
++#define QCA808X_LED_FORCE_ON FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x1)
++#define QCA808X_LED_FORCE_OFF FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x0)
++
+ #define AT803X_LOC_MAC_ADDR_0_15_OFFSET 0x804C
+ #define AT803X_LOC_MAC_ADDR_16_31_OFFSET 0x804B
+ #define AT803X_LOC_MAC_ADDR_32_47_OFFSET 0x804A
diff --git a/target/linux/generic/backport-6.6/716-v6.9-09-net-phy-qcom-generalize-some-qca808x-LED-functions.patch b/target/linux/generic/backport-6.6/716-v6.9-09-net-phy-qcom-generalize-some-qca808x-LED-functions.patch
new file mode 100644
index 0000000000..da73c1d3b8
--- /dev/null
+++ b/target/linux/generic/backport-6.6/716-v6.9-09-net-phy-qcom-generalize-some-qca808x-LED-functions.patch
@@ -0,0 +1,172 @@
+From 47b930d0dd437af927145dba50a2e2ea1ba97c67 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 6 Feb 2024 18:31:12 +0100
+Subject: [PATCH 09/10] net: phy: qcom: generalize some qca808x LED functions
+
+Generalize some qca808x LED functions in preparation for qca807x LED
+support.
+
+The LED implementation of qca808x and qca807x is the same but qca807x
+supports also Fiber port and have different hw control bits for Fiber
+port. To limit code duplication introduce micro functions that takes reg
+instead of LED index to tweak all the supported LED modes.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/qcom/qca808x.c | 38 +++-----------------
+ drivers/net/phy/qcom/qcom-phy-lib.c | 54 +++++++++++++++++++++++++++++
+ drivers/net/phy/qcom/qcom.h | 7 ++++
+ 3 files changed, 65 insertions(+), 34 deletions(-)
+
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -437,9 +437,7 @@ static int qca808x_led_hw_control_enable
+ return -EINVAL;
+
+ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+- return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
+- QCA808X_LED_FORCE_EN);
++ return qca808x_led_reg_hw_control_enable(phydev, reg);
+ }
+
+ static int qca808x_led_hw_is_supported(struct phy_device *phydev, u8 index,
+@@ -480,16 +478,12 @@ static int qca808x_led_hw_control_set(st
+ static bool qca808x_led_hw_control_status(struct phy_device *phydev, u8 index)
+ {
+ u16 reg;
+- int val;
+
+ if (index > 2)
+ return false;
+
+ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+- val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
+-
+- return !(val & QCA808X_LED_FORCE_EN);
++ return qca808x_led_reg_hw_control_status(phydev, reg);
+ }
+
+ static int qca808x_led_hw_control_get(struct phy_device *phydev, u8 index,
+@@ -557,44 +551,20 @@ static int qca808x_led_brightness_set(st
+ }
+
+ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+- return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
+- QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
+- QCA808X_LED_FORCE_EN | (value ? QCA808X_LED_FORCE_ON :
+- QCA808X_LED_FORCE_OFF));
++ return qca808x_led_reg_brightness_set(phydev, reg, value);
+ }
+
+ static int qca808x_led_blink_set(struct phy_device *phydev, u8 index,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+ {
+- int ret;
+ u16 reg;
+
+ if (index > 2)
+ return -EINVAL;
+
+ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+- /* Set blink to 50% off, 50% on at 4Hz by default */
+- ret = phy_modify_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_LED_GLOBAL,
+- QCA808X_LED_BLINK_FREQ_MASK | QCA808X_LED_BLINK_DUTY_MASK,
+- QCA808X_LED_BLINK_FREQ_4HZ | QCA808X_LED_BLINK_DUTY_50_50);
+- if (ret)
+- return ret;
+-
+- /* We use BLINK_1 for normal blinking */
+- ret = phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
+- QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
+- QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_BLINK_1);
+- if (ret)
+- return ret;
+-
+- /* We set blink to 4Hz, aka 250ms */
+- *delay_on = 250 / 2;
+- *delay_off = 250 / 2;
+-
+- return 0;
++ return qca808x_led_reg_blink_set(phydev, reg, delay_on, delay_off);
+ }
+
+ static int qca808x_led_polarity_set(struct phy_device *phydev, int index,
+--- a/drivers/net/phy/qcom/qcom-phy-lib.c
++++ b/drivers/net/phy/qcom/qcom-phy-lib.c
+@@ -620,3 +620,57 @@ int qca808x_cable_test_get_status(struct
+ return 0;
+ }
+ EXPORT_SYMBOL_GPL(qca808x_cable_test_get_status);
++
++int qca808x_led_reg_hw_control_enable(struct phy_device *phydev, u16 reg)
++{
++ return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
++ QCA808X_LED_FORCE_EN);
++}
++EXPORT_SYMBOL_GPL(qca808x_led_reg_hw_control_enable);
++
++bool qca808x_led_reg_hw_control_status(struct phy_device *phydev, u16 reg)
++{
++ int val;
++
++ val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
++ return !(val & QCA808X_LED_FORCE_EN);
++}
++EXPORT_SYMBOL_GPL(qca808x_led_reg_hw_control_status);
++
++int qca808x_led_reg_brightness_set(struct phy_device *phydev,
++ u16 reg, enum led_brightness value)
++{
++ return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++ QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
++ QCA808X_LED_FORCE_EN | (value ? QCA808X_LED_FORCE_ON :
++ QCA808X_LED_FORCE_OFF));
++}
++EXPORT_SYMBOL_GPL(qca808x_led_reg_brightness_set);
++
++int qca808x_led_reg_blink_set(struct phy_device *phydev, u16 reg,
++ unsigned long *delay_on,
++ unsigned long *delay_off)
++{
++ int ret;
++
++ /* Set blink to 50% off, 50% on at 4Hz by default */
++ ret = phy_modify_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_LED_GLOBAL,
++ QCA808X_LED_BLINK_FREQ_MASK | QCA808X_LED_BLINK_DUTY_MASK,
++ QCA808X_LED_BLINK_FREQ_4HZ | QCA808X_LED_BLINK_DUTY_50_50);
++ if (ret)
++ return ret;
++
++ /* We use BLINK_1 for normal blinking */
++ ret = phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++ QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
++ QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_BLINK_1);
++ if (ret)
++ return ret;
++
++ /* We set blink to 4Hz, aka 250ms */
++ *delay_on = 250 / 2;
++ *delay_off = 250 / 2;
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(qca808x_led_reg_blink_set);
+--- a/drivers/net/phy/qcom/qcom.h
++++ b/drivers/net/phy/qcom/qcom.h
+@@ -234,3 +234,10 @@ int at803x_cdt_start(struct phy_device *
+ int at803x_cdt_wait_for_completion(struct phy_device *phydev,
+ u32 cdt_en);
+ int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished);
++int qca808x_led_reg_hw_control_enable(struct phy_device *phydev, u16 reg);
++bool qca808x_led_reg_hw_control_status(struct phy_device *phydev, u16 reg);
++int qca808x_led_reg_brightness_set(struct phy_device *phydev,
++ u16 reg, enum led_brightness value);
++int qca808x_led_reg_blink_set(struct phy_device *phydev, u16 reg,
++ unsigned long *delay_on,
++ unsigned long *delay_off);
diff --git a/target/linux/generic/backport-6.6/716-v6.9-10-net-phy-qca807x-add-support-for-configurable-LED.patch b/target/linux/generic/backport-6.6/716-v6.9-10-net-phy-qca807x-add-support-for-configurable-LED.patch
new file mode 100644
index 0000000000..3bd36f6ffe
--- /dev/null
+++ b/target/linux/generic/backport-6.6/716-v6.9-10-net-phy-qca807x-add-support-for-configurable-LED.patch
@@ -0,0 +1,326 @@
+From f508a226b517a6a8afd78a317de46bc83e3e3d51 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 6 Feb 2024 18:31:13 +0100
+Subject: [PATCH 10/10] net: phy: qca807x: add support for configurable LED
+
+QCA8072/5 have up to 2 LEDs attached for PHY.
+
+LEDs can be configured to be ON/hw blink or be set to HW control.
+
+Hw blink mode is set to blink at 4Hz or 250ms.
+
+PHY can support both copper (TP) or fiber (FIBRE) kind and supports
+different HW control modes based on the port type.
+
+HW control modes supported for netdev trigger for copper ports are:
+- LINK_10
+- LINK_100
+- LINK_1000
+- TX
+- RX
+- FULL_DUPLEX
+- HALF_DUPLEX
+
+HW control modes supported for netdev trigger for fiber ports are:
+- LINK_100
+- LINK_1000
+- TX
+- RX
+- FULL_DUPLEX
+- HALF_DUPLEX
+
+LED support conflicts with GPIO controller feature and must be disabled
+if gpio-controller is used for the PHY.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/qcom/qca807x.c | 256 ++++++++++++++++++++++++++++++++-
+ 1 file changed, 254 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/qcom/qca807x.c
++++ b/drivers/net/phy/qcom/qca807x.c
+@@ -57,8 +57,18 @@
+ #define QCA807X_MMD7_LED_CTRL(x) (0x8074 + ((x) * 2))
+ #define QCA807X_MMD7_LED_FORCE_CTRL(x) (0x8075 + ((x) * 2))
+
+-#define QCA807X_GPIO_FORCE_EN BIT(15)
+-#define QCA807X_GPIO_FORCE_MODE_MASK GENMASK(14, 13)
++/* LED hw control pattern for fiber port */
++#define QCA807X_LED_FIBER_PATTERN_MASK GENMASK(11, 1)
++#define QCA807X_LED_FIBER_TXACT_BLK_EN BIT(10)
++#define QCA807X_LED_FIBER_RXACT_BLK_EN BIT(9)
++#define QCA807X_LED_FIBER_FDX_ON_EN BIT(6)
++#define QCA807X_LED_FIBER_HDX_ON_EN BIT(5)
++#define QCA807X_LED_FIBER_1000BX_ON_EN BIT(2)
++#define QCA807X_LED_FIBER_100FX_ON_EN BIT(1)
++
++/* Some device repurpose the LED as GPIO out */
++#define QCA807X_GPIO_FORCE_EN QCA808X_LED_FORCE_EN
++#define QCA807X_GPIO_FORCE_MODE_MASK QCA808X_LED_FORCE_MODE_MASK
+
+ #define QCA807X_FUNCTION_CONTROL 0x10
+ #define QCA807X_FC_MDI_CROSSOVER_MODE_MASK GENMASK(6, 5)
+@@ -121,6 +131,233 @@ static int qca807x_cable_test_start(stru
+ return 0;
+ }
+
++static int qca807x_led_parse_netdev(struct phy_device *phydev, unsigned long rules,
++ u16 *offload_trigger)
++{
++ /* Parsing specific to netdev trigger */
++ switch (phydev->port) {
++ case PORT_TP:
++ if (test_bit(TRIGGER_NETDEV_TX, &rules))
++ *offload_trigger |= QCA808X_LED_TX_BLINK;
++ if (test_bit(TRIGGER_NETDEV_RX, &rules))
++ *offload_trigger |= QCA808X_LED_RX_BLINK;
++ if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
++ *offload_trigger |= QCA808X_LED_SPEED10_ON;
++ if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
++ *offload_trigger |= QCA808X_LED_SPEED100_ON;
++ if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
++ *offload_trigger |= QCA808X_LED_SPEED1000_ON;
++ if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
++ *offload_trigger |= QCA808X_LED_HALF_DUPLEX_ON;
++ if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
++ *offload_trigger |= QCA808X_LED_FULL_DUPLEX_ON;
++ break;
++ case PORT_FIBRE:
++ if (test_bit(TRIGGER_NETDEV_TX, &rules))
++ *offload_trigger |= QCA807X_LED_FIBER_TXACT_BLK_EN;
++ if (test_bit(TRIGGER_NETDEV_RX, &rules))
++ *offload_trigger |= QCA807X_LED_FIBER_RXACT_BLK_EN;
++ if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
++ *offload_trigger |= QCA807X_LED_FIBER_100FX_ON_EN;
++ if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
++ *offload_trigger |= QCA807X_LED_FIBER_1000BX_ON_EN;
++ if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
++ *offload_trigger |= QCA807X_LED_FIBER_HDX_ON_EN;
++ if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
++ *offload_trigger |= QCA807X_LED_FIBER_FDX_ON_EN;
++ break;
++ default:
++ return -EOPNOTSUPP;
++ }
++
++ if (rules && !*offload_trigger)
++ return -EOPNOTSUPP;
++
++ return 0;
++}
++
++static int qca807x_led_hw_control_enable(struct phy_device *phydev, u8 index)
++{
++ u16 reg;
++
++ if (index > 1)
++ return -EINVAL;
++
++ reg = QCA807X_MMD7_LED_FORCE_CTRL(index);
++ return qca808x_led_reg_hw_control_enable(phydev, reg);
++}
++
++static int qca807x_led_hw_is_supported(struct phy_device *phydev, u8 index,
++ unsigned long rules)
++{
++ u16 offload_trigger = 0;
++
++ if (index > 1)
++ return -EINVAL;
++
++ return qca807x_led_parse_netdev(phydev, rules, &offload_trigger);
++}
++
++static int qca807x_led_hw_control_set(struct phy_device *phydev, u8 index,
++ unsigned long rules)
++{
++ u16 reg, mask, offload_trigger = 0;
++ int ret;
++
++ if (index > 1)
++ return -EINVAL;
++
++ ret = qca807x_led_parse_netdev(phydev, rules, &offload_trigger);
++ if (ret)
++ return ret;
++
++ ret = qca807x_led_hw_control_enable(phydev, index);
++ if (ret)
++ return ret;
++
++ switch (phydev->port) {
++ case PORT_TP:
++ reg = QCA807X_MMD7_LED_CTRL(index);
++ mask = QCA808X_LED_PATTERN_MASK;
++ break;
++ case PORT_FIBRE:
++ /* HW control pattern bits are in LED FORCE reg */
++ reg = QCA807X_MMD7_LED_FORCE_CTRL(index);
++ mask = QCA807X_LED_FIBER_PATTERN_MASK;
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ return phy_modify_mmd(phydev, MDIO_MMD_AN, reg, mask,
++ offload_trigger);
++}
++
++static bool qca807x_led_hw_control_status(struct phy_device *phydev, u8 index)
++{
++ u16 reg;
++
++ if (index > 1)
++ return false;
++
++ reg = QCA807X_MMD7_LED_FORCE_CTRL(index);
++ return qca808x_led_reg_hw_control_status(phydev, reg);
++}
++
++static int qca807x_led_hw_control_get(struct phy_device *phydev, u8 index,
++ unsigned long *rules)
++{
++ u16 reg;
++ int val;
++
++ if (index > 1)
++ return -EINVAL;
++
++ /* Check if we have hw control enabled */
++ if (qca807x_led_hw_control_status(phydev, index))
++ return -EINVAL;
++
++ /* Parsing specific to netdev trigger */
++ switch (phydev->port) {
++ case PORT_TP:
++ reg = QCA807X_MMD7_LED_CTRL(index);
++ val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
++ if (val & QCA808X_LED_TX_BLINK)
++ set_bit(TRIGGER_NETDEV_TX, rules);
++ if (val & QCA808X_LED_RX_BLINK)
++ set_bit(TRIGGER_NETDEV_RX, rules);
++ if (val & QCA808X_LED_SPEED10_ON)
++ set_bit(TRIGGER_NETDEV_LINK_10, rules);
++ if (val & QCA808X_LED_SPEED100_ON)
++ set_bit(TRIGGER_NETDEV_LINK_100, rules);
++ if (val & QCA808X_LED_SPEED1000_ON)
++ set_bit(TRIGGER_NETDEV_LINK_1000, rules);
++ if (val & QCA808X_LED_HALF_DUPLEX_ON)
++ set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
++ if (val & QCA808X_LED_FULL_DUPLEX_ON)
++ set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
++ break;
++ case PORT_FIBRE:
++ /* HW control pattern bits are in LED FORCE reg */
++ reg = QCA807X_MMD7_LED_FORCE_CTRL(index);
++ val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
++ if (val & QCA807X_LED_FIBER_TXACT_BLK_EN)
++ set_bit(TRIGGER_NETDEV_TX, rules);
++ if (val & QCA807X_LED_FIBER_RXACT_BLK_EN)
++ set_bit(TRIGGER_NETDEV_RX, rules);
++ if (val & QCA807X_LED_FIBER_100FX_ON_EN)
++ set_bit(TRIGGER_NETDEV_LINK_100, rules);
++ if (val & QCA807X_LED_FIBER_1000BX_ON_EN)
++ set_bit(TRIGGER_NETDEV_LINK_1000, rules);
++ if (val & QCA807X_LED_FIBER_HDX_ON_EN)
++ set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
++ if (val & QCA807X_LED_FIBER_FDX_ON_EN)
++ set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
++static int qca807x_led_hw_control_reset(struct phy_device *phydev, u8 index)
++{
++ u16 reg, mask;
++
++ if (index > 1)
++ return -EINVAL;
++
++ switch (phydev->port) {
++ case PORT_TP:
++ reg = QCA807X_MMD7_LED_CTRL(index);
++ mask = QCA808X_LED_PATTERN_MASK;
++ break;
++ case PORT_FIBRE:
++ /* HW control pattern bits are in LED FORCE reg */
++ reg = QCA807X_MMD7_LED_FORCE_CTRL(index);
++ mask = QCA807X_LED_FIBER_PATTERN_MASK;
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg, mask);
++}
++
++static int qca807x_led_brightness_set(struct phy_device *phydev,
++ u8 index, enum led_brightness value)
++{
++ u16 reg;
++ int ret;
++
++ if (index > 1)
++ return -EINVAL;
++
++ /* If we are setting off the LED reset any hw control rule */
++ if (!value) {
++ ret = qca807x_led_hw_control_reset(phydev, index);
++ if (ret)
++ return ret;
++ }
++
++ reg = QCA807X_MMD7_LED_FORCE_CTRL(index);
++ return qca808x_led_reg_brightness_set(phydev, reg, value);
++}
++
++static int qca807x_led_blink_set(struct phy_device *phydev, u8 index,
++ unsigned long *delay_on,
++ unsigned long *delay_off)
++{
++ u16 reg;
++
++ if (index > 1)
++ return -EINVAL;
++
++ reg = QCA807X_MMD7_LED_FORCE_CTRL(index);
++ return qca808x_led_reg_blink_set(phydev, reg, delay_on, delay_off);
++}
++
+ #ifdef CONFIG_GPIOLIB
+ static int qca807x_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+ {
+@@ -496,6 +733,16 @@ static int qca807x_probe(struct phy_devi
+ "qcom,dac-disable-bias-current-tweak");
+
+ if (IS_ENABLED(CONFIG_GPIOLIB)) {
++ /* Make sure we don't have mixed leds node and gpio-controller
++ * to prevent registering leds and having gpio-controller usage
++ * conflicting with them.
++ */
++ if (of_find_property(node, "leds", NULL) &&
++ of_find_property(node, "gpio-controller", NULL)) {
++ phydev_err(phydev, "Invalid property detected. LEDs and gpio-controller are mutually exclusive.");
++ return -EINVAL;
++ }
++
+ /* Do not register a GPIO controller unless flagged for it */
+ if (of_property_read_bool(node, "gpio-controller")) {
+ ret = qca807x_gpio(phydev);
+@@ -580,6 +827,11 @@ static struct phy_driver qca807x_drivers
+ .suspend = genphy_suspend,
+ .cable_test_start = qca807x_cable_test_start,
+ .cable_test_get_status = qca808x_cable_test_get_status,
++ .led_brightness_set = qca807x_led_brightness_set,
++ .led_blink_set = qca807x_led_blink_set,
++ .led_hw_is_supported = qca807x_led_hw_is_supported,
++ .led_hw_control_set = qca807x_led_hw_control_set,
++ .led_hw_control_get = qca807x_led_hw_control_get,
+ },
+ };
+ module_phy_driver(qca807x_drivers);
diff --git a/target/linux/generic/backport-6.6/717-v6.9-net-phy-qca807x-move-interface-mode-check-to-.config.patch b/target/linux/generic/backport-6.6/717-v6.9-net-phy-qca807x-move-interface-mode-check-to-.config.patch
new file mode 100644
index 0000000000..53652c38fc
--- /dev/null
+++ b/target/linux/generic/backport-6.6/717-v6.9-net-phy-qca807x-move-interface-mode-check-to-.config.patch
@@ -0,0 +1,51 @@
+From 3be0d950b62852a693182cb678948f481de02825 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Mon, 12 Feb 2024 12:49:34 +0100
+Subject: [PATCH] net: phy: qca807x: move interface mode check to
+ .config_init_once
+
+Currently, we are checking whether the PHY package mode matches the
+individual PHY interface modes at PHY package probe time, but at that time
+we only know the PHY package mode and not the individual PHY interface
+modes as of_get_phy_mode() that populates it will only get called once the
+netdev to which PHY-s are attached to is being probed and thus this check
+will always fail and return -EINVAL.
+
+So, lets move this check to .config_init_once as at that point individual
+PHY interface modes should be populated.
+
+Fixes: d1cb613efbd3 ("net: phy: qcom: add support for QCA807x PHY Family")
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/20240212115043.1725918-1-robimarko@gmail.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/qcom/qca807x.c | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/qcom/qca807x.c
++++ b/drivers/net/phy/qcom/qca807x.c
+@@ -562,6 +562,11 @@ static int qca807x_phy_package_config_in
+ struct qca807x_shared_priv *priv = shared->priv;
+ int val, ret;
+
++ /* Make sure PHY follow PHY package mode if enforced */
++ if (priv->package_mode != PHY_INTERFACE_MODE_NA &&
++ phydev->interface != priv->package_mode)
++ return -EINVAL;
++
+ phy_lock_mdio_bus(phydev);
+
+ /* Set correct PHY package mode */
+@@ -718,11 +723,6 @@ static int qca807x_probe(struct phy_devi
+ shared = phydev->shared;
+ shared_priv = shared->priv;
+
+- /* Make sure PHY follow PHY package mode if enforced */
+- if (shared_priv->package_mode != PHY_INTERFACE_MODE_NA &&
+- phydev->interface != shared_priv->package_mode)
+- return -EINVAL;
+-
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
diff --git a/target/linux/generic/backport-6.6/720-v6.9-net-mdio-ipq4019-add-support-for-clock-frequency-pro.patch b/target/linux/generic/backport-6.6/720-v6.9-net-mdio-ipq4019-add-support-for-clock-frequency-pro.patch
new file mode 100644
index 0000000000..e6a240dbda
--- /dev/null
+++ b/target/linux/generic/backport-6.6/720-v6.9-net-mdio-ipq4019-add-support-for-clock-frequency-pro.patch
@@ -0,0 +1,205 @@
+From bdce82e960d1205d118662f575cec39379984e34 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 31 Jan 2024 03:26:04 +0100
+Subject: [PATCH] net: mdio: ipq4019: add support for clock-frequency property
+
+The IPQ4019 MDIO internally divide the clock feed by AHB based on the
+MDIO_MODE reg. On reset or power up, the default value for the
+divider is 0xff that reflect the divider set to /256.
+
+This makes the MDC run at a very low rate, that is, considering AHB is
+always fixed to 100Mhz, a value of 390KHz.
+
+This hasn't have been a problem as MDIO wasn't used for time sensitive
+operation, it is now that on IPQ807x is usually mounted with PHY that
+requires MDIO to load their firmware (example Aquantia PHY).
+
+To handle this problem and permit to set the correct designed MDC
+frequency for the SoC add support for the standard "clock-frequency"
+property for the MDIO node.
+
+The divider supports value from /1 to /256 and the common value are to
+set it to /16 to reflect 6.25Mhz or to /8 on newer platform to reflect
+12.5Mhz.
+
+To scan if the requested rate is supported by the divider, loop with
+each supported divider and stop when the requested rate match the final
+rate with the current divider. An error is returned if the rate doesn't
+match any value.
+
+On MDIO reset, the divider is restored to the requested value to prevent
+any kind of downclocking caused by the divider reverting to a default
+value.
+
+To follow 802.3 spec of 2.5MHz of default value, if divider is set at
+/256 and "clock-frequency" is not set in DT, assume nobody set the
+divider and try to find the closest MDC rate to 2.5MHz. (in the case of
+AHB set to 100MHz, it's 1.5625MHz)
+
+While at is also document other bits of the MDIO_MODE reg to have a
+clear idea of what is actually applied there.
+
+Documentation of some BITs is skipped as they are marked as reserved and
+their usage is not clear (RES 11:9 GENPHY 16:13 RES1 19:17)
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/mdio/mdio-ipq4019.c | 109 ++++++++++++++++++++++++++++++--
+ 1 file changed, 103 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/mdio/mdio-ipq4019.c
++++ b/drivers/net/mdio/mdio-ipq4019.c
+@@ -14,6 +14,20 @@
+ #include <linux/clk.h>
+
+ #define MDIO_MODE_REG 0x40
++#define MDIO_MODE_MDC_MODE BIT(12)
++/* 0 = Clause 22, 1 = Clause 45 */
++#define MDIO_MODE_C45 BIT(8)
++#define MDIO_MODE_DIV_MASK GENMASK(7, 0)
++#define MDIO_MODE_DIV(x) FIELD_PREP(MDIO_MODE_DIV_MASK, (x) - 1)
++#define MDIO_MODE_DIV_1 0x0
++#define MDIO_MODE_DIV_2 0x1
++#define MDIO_MODE_DIV_4 0x3
++#define MDIO_MODE_DIV_8 0x7
++#define MDIO_MODE_DIV_16 0xf
++#define MDIO_MODE_DIV_32 0x1f
++#define MDIO_MODE_DIV_64 0x3f
++#define MDIO_MODE_DIV_128 0x7f
++#define MDIO_MODE_DIV_256 0xff
+ #define MDIO_ADDR_REG 0x44
+ #define MDIO_DATA_WRITE_REG 0x48
+ #define MDIO_DATA_READ_REG 0x4c
+@@ -26,9 +40,6 @@
+ #define MDIO_CMD_ACCESS_CODE_C45_WRITE 1
+ #define MDIO_CMD_ACCESS_CODE_C45_READ 2
+
+-/* 0 = Clause 22, 1 = Clause 45 */
+-#define MDIO_MODE_C45 BIT(8)
+-
+ #define IPQ4019_MDIO_TIMEOUT 10000
+ #define IPQ4019_MDIO_SLEEP 10
+
+@@ -41,6 +52,7 @@ struct ipq4019_mdio_data {
+ void __iomem *membase;
+ void __iomem *eth_ldo_rdy;
+ struct clk *mdio_clk;
++ unsigned int mdc_rate;
+ };
+
+ static int ipq4019_mdio_wait_busy(struct mii_bus *bus)
+@@ -179,6 +191,38 @@ static int ipq4019_mdio_write(struct mii
+ return 0;
+ }
+
++static int ipq4019_mdio_set_div(struct ipq4019_mdio_data *priv)
++{
++ unsigned long ahb_rate;
++ int div;
++ u32 val;
++
++ /* If we don't have a clock for AHB use the fixed value */
++ ahb_rate = IPQ_MDIO_CLK_RATE;
++ if (priv->mdio_clk)
++ ahb_rate = clk_get_rate(priv->mdio_clk);
++
++ /* MDC rate is ahb_rate/(MDIO_MODE_DIV + 1)
++ * While supported, internal documentation doesn't
++ * assure correct functionality of the MDIO bus
++ * with divider of 1, 2 or 4.
++ */
++ for (div = 8; div <= 256; div *= 2) {
++ /* The requested rate is supported by the div */
++ if (priv->mdc_rate == DIV_ROUND_UP(ahb_rate, div)) {
++ val = readl(priv->membase + MDIO_MODE_REG);
++ val &= ~MDIO_MODE_DIV_MASK;
++ val |= MDIO_MODE_DIV(div);
++ writel(val, priv->membase + MDIO_MODE_REG);
++
++ return 0;
++ }
++ }
++
++ /* The requested rate is not supported */
++ return -EINVAL;
++}
++
+ static int ipq_mdio_reset(struct mii_bus *bus)
+ {
+ struct ipq4019_mdio_data *priv = bus->priv;
+@@ -201,10 +245,58 @@ static int ipq_mdio_reset(struct mii_bus
+ return ret;
+
+ ret = clk_prepare_enable(priv->mdio_clk);
+- if (ret == 0)
+- mdelay(10);
++ if (ret)
++ return ret;
++
++ mdelay(10);
+
+- return ret;
++ /* Restore MDC rate */
++ return ipq4019_mdio_set_div(priv);
++}
++
++static void ipq4019_mdio_select_mdc_rate(struct platform_device *pdev,
++ struct ipq4019_mdio_data *priv)
++{
++ unsigned long ahb_rate;
++ int div;
++ u32 val;
++
++ /* MDC rate defined in DT, we don't have to decide a default value */
++ if (!of_property_read_u32(pdev->dev.of_node, "clock-frequency",
++ &priv->mdc_rate))
++ return;
++
++ /* If we don't have a clock for AHB use the fixed value */
++ ahb_rate = IPQ_MDIO_CLK_RATE;
++ if (priv->mdio_clk)
++ ahb_rate = clk_get_rate(priv->mdio_clk);
++
++ /* Check what is the current div set */
++ val = readl(priv->membase + MDIO_MODE_REG);
++ div = FIELD_GET(MDIO_MODE_DIV_MASK, val);
++
++ /* div is not set to the default value of /256
++ * Probably someone changed that (bootloader, other drivers)
++ * Keep this and don't overwrite it.
++ */
++ if (div != MDIO_MODE_DIV_256) {
++ priv->mdc_rate = DIV_ROUND_UP(ahb_rate, div + 1);
++ return;
++ }
++
++ /* If div is /256 assume nobody have set this value and
++ * try to find one MDC rate that is close the 802.3 spec of
++ * 2.5MHz
++ */
++ for (div = 256; div >= 8; div /= 2) {
++ /* Stop as soon as we found a divider that
++ * reached the closest value to 2.5MHz
++ */
++ if (DIV_ROUND_UP(ahb_rate, div) > 2500000)
++ break;
++
++ priv->mdc_rate = DIV_ROUND_UP(ahb_rate, div);
++ }
+ }
+
+ static int ipq4019_mdio_probe(struct platform_device *pdev)
+@@ -228,6 +320,11 @@ static int ipq4019_mdio_probe(struct pla
+ if (IS_ERR(priv->mdio_clk))
+ return PTR_ERR(priv->mdio_clk);
+
++ ipq4019_mdio_select_mdc_rate(pdev, priv);
++ ret = ipq4019_mdio_set_div(priv);
++ if (ret)
++ return ret;
++
+ /* The platform resource is provided on the chipset IPQ5018 */
+ /* This resource is optional */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
diff --git a/target/linux/generic/backport-6.6/721-v6.7-net-phy-aquantia-drop-wrong-endianness-conversion-fo.patch b/target/linux/generic/backport-6.6/721-v6.7-net-phy-aquantia-drop-wrong-endianness-conversion-fo.patch
new file mode 100644
index 0000000000..d32a7edf93
--- /dev/null
+++ b/target/linux/generic/backport-6.6/721-v6.7-net-phy-aquantia-drop-wrong-endianness-conversion-fo.patch
@@ -0,0 +1,92 @@
+From 7edce370d87a23e8ed46af5b76a9fef1e341b67b Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 28 Nov 2023 14:59:28 +0100
+Subject: [PATCH] net: phy: aquantia: drop wrong endianness conversion for addr
+ and CRC
+
+On further testing on BE target with kernel test robot, it was notice
+that the endianness conversion for addr and CRC in fw_load_memory was
+wrong.
+
+Drop the cpu_to_le32 conversion for addr load as it's not needed.
+
+Use get_unaligned_le32 instead of get_unaligned for FW data word load to
+correctly convert data in the correct order to follow system endian.
+
+Also drop the cpu_to_be32 for CRC calculation as it's wrong and would
+cause different CRC on BE system.
+The loaded word is swapped internally and MAILBOX calculates the CRC on
+the swapped word. To correctly calculate the CRC to be later matched
+with the one from MAILBOX, use an u8 struct and swap the word there to
+keep the same order on both LE and BE for crc_ccitt_false function.
+Also add additional comments on how the CRC verification for the loaded
+section works.
+
+CRC is calculated as we load the section and verified with the MAILBOX
+only after the entire section is loaded to skip additional slowdown by
+loop the section data again.
+
+Reported-by: kernel test robot <lkp@intel.com>
+Closes: https://lore.kernel.org/oe-kbuild-all/202311210414.sEJZjlcD-lkp@intel.com/
+Fixes: e93984ebc1c8 ("net: phy: aquantia: add firmware load support")
+Tested-by: Robert Marko <robimarko@gmail.com> # ipq8072 LE device
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Link: https://lore.kernel.org/r/20231128135928.9841-1-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/aquantia/aquantia_firmware.c | 24 ++++++++++++--------
+ 1 file changed, 14 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/phy/aquantia/aquantia_firmware.c
++++ b/drivers/net/phy/aquantia/aquantia_firmware.c
+@@ -93,9 +93,6 @@ static int aqr_fw_load_memory(struct phy
+ u16 crc = 0, up_crc;
+ size_t pos;
+
+- /* PHY expect addr in LE */
+- addr = (__force u32)cpu_to_le32(addr);
+-
+ phy_write_mmd(phydev, MDIO_MMD_VEND1,
+ VEND1_GLOBAL_MAILBOX_INTERFACE1,
+ VEND1_GLOBAL_MAILBOX_INTERFACE1_CRC_RESET);
+@@ -110,10 +107,11 @@ static int aqr_fw_load_memory(struct phy
+ * If a firmware that is not word aligned is found, please report upstream.
+ */
+ for (pos = 0; pos < len; pos += sizeof(u32)) {
++ u8 crc_data[4];
+ u32 word;
+
+ /* FW data is always stored in little-endian */
+- word = get_unaligned((const u32 *)(data + pos));
++ word = get_unaligned_le32((const u32 *)(data + pos));
+
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_MAILBOX_INTERFACE5,
+ VEND1_GLOBAL_MAILBOX_INTERFACE5_MSW_DATA(word));
+@@ -124,15 +122,21 @@ static int aqr_fw_load_memory(struct phy
+ VEND1_GLOBAL_MAILBOX_INTERFACE1_EXECUTE |
+ VEND1_GLOBAL_MAILBOX_INTERFACE1_WRITE);
+
+- /* calculate CRC as we load data to the mailbox.
+- * We convert word to big-endian as PHY is BE and mailbox will
+- * return a BE CRC.
++ /* Word is swapped internally and MAILBOX CRC is calculated
++ * using big-endian order. Mimic what the PHY does to have a
++ * matching CRC...
+ */
+- word = (__force u32)cpu_to_be32(word);
+- crc = crc_ccitt_false(crc, (u8 *)&word, sizeof(word));
+- }
++ crc_data[0] = word >> 24;
++ crc_data[1] = word >> 16;
++ crc_data[2] = word >> 8;
++ crc_data[3] = word;
+
++ /* ...calculate CRC as we load data... */
++ crc = crc_ccitt_false(crc, crc_data, sizeof(crc_data));
++ }
++ /* ...gets CRC from MAILBOX after we have loaded the entire section... */
+ up_crc = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_MAILBOX_INTERFACE2);
++ /* ...and make sure it does match our calculated CRC */
+ if (crc != up_crc) {
+ phydev_err(phydev, "CRC mismatch: calculated 0x%04x PHY 0x%04x\n",
+ crc, up_crc);
diff --git a/target/linux/generic/backport-6.6/724-v6.2-net-ethernet-mtk_eth_soc-enable-flow-offloading-supp.patch b/target/linux/generic/backport-6.6/724-v6.2-net-ethernet-mtk_eth_soc-enable-flow-offloading-supp.patch
new file mode 100644
index 0000000000..816aa67787
--- /dev/null
+++ b/target/linux/generic/backport-6.6/724-v6.2-net-ethernet-mtk_eth_soc-enable-flow-offloading-supp.patch
@@ -0,0 +1,26 @@
+From 1b9827ceab08450308f7971d6fd700ec88b6ce67 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 3 Dec 2022 14:20:37 +0100
+Subject: [PATCH 098/250] net: mtk_eth_soc: enable flow offload support for
+ MT7986 SoC
+
+Since Wireless Ethernet Dispatcher is now available for mt7986 in mt76,
+enable hw flow support for MT7986 SoC.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://lore.kernel.org/r/fdcaacd827938e6a8c4aa1ac2c13e46d2c08c821.1670072898.git.lorenzo@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -4333,6 +4333,7 @@ static const struct mtk_soc_data mt7986_
+ .hw_features = MTK_HW_FEATURES,
+ .required_clks = MT7986_CLKS_BITMAP,
+ .required_pctl = false,
++ .offload_version = 2,
+ .hash_offset = 4,
+ .foe_entry_size = sizeof(struct mtk_foe_entry),
+ .txrx = {
diff --git a/target/linux/generic/backport-6.6/729-01-v6.1-net-ethernet-mtk_wed-introduce-wed-mcu-support.patch b/target/linux/generic/backport-6.6/729-01-v6.1-net-ethernet-mtk_wed-introduce-wed-mcu-support.patch
new file mode 100644
index 0000000000..c48613929d
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-01-v6.1-net-ethernet-mtk_wed-introduce-wed-mcu-support.patch
@@ -0,0 +1,591 @@
+From: Sujuan Chen <sujuan.chen@mediatek.com>
+Date: Sat, 5 Nov 2022 23:36:18 +0100
+Subject: [PATCH] net: ethernet: mtk_wed: introduce wed mcu support
+
+Introduce WED mcu support used to configure WED WO chip.
+This is a preliminary patch in order to add RX Wireless
+Ethernet Dispatch available on MT7986 SoC.
+
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Co-developed-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_mcu.c
+ create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_wo.h
+
+--- a/drivers/net/ethernet/mediatek/Makefile
++++ b/drivers/net/ethernet/mediatek/Makefile
+@@ -5,7 +5,7 @@
+
+ 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
++mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed.o mtk_wed_mcu.o
+ ifdef CONFIG_DEBUG_FS
+ mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed_debugfs.o
+ endif
+--- /dev/null
++++ b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
+@@ -0,0 +1,359 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* Copyright (C) 2022 MediaTek Inc.
++ *
++ * Author: Lorenzo Bianconi <lorenzo@kernel.org>
++ * Sujuan Chen <sujuan.chen@mediatek.com>
++ */
++
++#include <linux/firmware.h>
++#include <linux/of_address.h>
++#include <linux/of_reserved_mem.h>
++#include <linux/mfd/syscon.h>
++#include <linux/soc/mediatek/mtk_wed.h>
++
++#include "mtk_wed_regs.h"
++#include "mtk_wed_wo.h"
++#include "mtk_wed.h"
++
++static u32 wo_r32(struct mtk_wed_wo *wo, u32 reg)
++{
++ return readl(wo->boot.addr + reg);
++}
++
++static void wo_w32(struct mtk_wed_wo *wo, u32 reg, u32 val)
++{
++ writel(val, wo->boot.addr + reg);
++}
++
++static struct sk_buff *
++mtk_wed_mcu_msg_alloc(const void *data, int data_len)
++{
++ int length = sizeof(struct mtk_wed_mcu_hdr) + data_len;
++ struct sk_buff *skb;
++
++ skb = alloc_skb(length, GFP_KERNEL);
++ if (!skb)
++ return NULL;
++
++ memset(skb->head, 0, length);
++ skb_reserve(skb, sizeof(struct mtk_wed_mcu_hdr));
++ if (data && data_len)
++ skb_put_data(skb, data, data_len);
++
++ return skb;
++}
++
++static struct sk_buff *
++mtk_wed_mcu_get_response(struct mtk_wed_wo *wo, unsigned long expires)
++{
++ if (!time_is_after_jiffies(expires))
++ return NULL;
++
++ wait_event_timeout(wo->mcu.wait, !skb_queue_empty(&wo->mcu.res_q),
++ expires - jiffies);
++ return skb_dequeue(&wo->mcu.res_q);
++}
++
++void mtk_wed_mcu_rx_event(struct mtk_wed_wo *wo, struct sk_buff *skb)
++{
++ skb_queue_tail(&wo->mcu.res_q, skb);
++ wake_up(&wo->mcu.wait);
++}
++
++void mtk_wed_mcu_rx_unsolicited_event(struct mtk_wed_wo *wo,
++ struct sk_buff *skb)
++{
++ struct mtk_wed_mcu_hdr *hdr = (struct mtk_wed_mcu_hdr *)skb->data;
++
++ switch (hdr->cmd) {
++ case MTK_WED_WO_EVT_LOG_DUMP: {
++ const char *msg = (const char *)(skb->data + sizeof(*hdr));
++
++ dev_notice(wo->hw->dev, "%s\n", msg);
++ break;
++ }
++ case MTK_WED_WO_EVT_PROFILING: {
++ struct mtk_wed_wo_log_info *info;
++ u32 count = (skb->len - sizeof(*hdr)) / sizeof(*info);
++ int i;
++
++ info = (struct mtk_wed_wo_log_info *)(skb->data + sizeof(*hdr));
++ for (i = 0 ; i < count ; i++)
++ dev_notice(wo->hw->dev,
++ "SN:%u latency: total=%u, rro:%u, mod:%u\n",
++ le32_to_cpu(info[i].sn),
++ le32_to_cpu(info[i].total),
++ le32_to_cpu(info[i].rro),
++ le32_to_cpu(info[i].mod));
++ break;
++ }
++ case MTK_WED_WO_EVT_RXCNT_INFO:
++ break;
++ default:
++ break;
++ }
++
++ dev_kfree_skb(skb);
++}
++
++static int
++mtk_wed_mcu_skb_send_msg(struct mtk_wed_wo *wo, struct sk_buff *skb,
++ int id, int cmd, u16 *wait_seq, bool wait_resp)
++{
++ struct mtk_wed_mcu_hdr *hdr;
++
++ /* TODO: make it dynamic based on cmd */
++ wo->mcu.timeout = 20 * HZ;
++
++ hdr = (struct mtk_wed_mcu_hdr *)skb_push(skb, sizeof(*hdr));
++ hdr->cmd = cmd;
++ hdr->length = cpu_to_le16(skb->len);
++
++ if (wait_resp && wait_seq) {
++ u16 seq = ++wo->mcu.seq;
++
++ if (!seq)
++ seq = ++wo->mcu.seq;
++ *wait_seq = seq;
++
++ hdr->flag |= cpu_to_le16(MTK_WED_WARP_CMD_FLAG_NEED_RSP);
++ hdr->seq = cpu_to_le16(seq);
++ }
++ if (id == MTK_WED_MODULE_ID_WO)
++ hdr->flag |= cpu_to_le16(MTK_WED_WARP_CMD_FLAG_FROM_TO_WO);
++
++ dev_kfree_skb(skb);
++ return 0;
++}
++
++static int
++mtk_wed_mcu_parse_response(struct mtk_wed_wo *wo, struct sk_buff *skb,
++ int cmd, int seq)
++{
++ struct mtk_wed_mcu_hdr *hdr;
++
++ if (!skb) {
++ dev_err(wo->hw->dev, "Message %08x (seq %d) timeout\n",
++ cmd, seq);
++ return -ETIMEDOUT;
++ }
++
++ hdr = (struct mtk_wed_mcu_hdr *)skb->data;
++ if (le16_to_cpu(hdr->seq) != seq)
++ return -EAGAIN;
++
++ skb_pull(skb, sizeof(*hdr));
++ switch (cmd) {
++ case MTK_WED_WO_CMD_RXCNT_INFO:
++ default:
++ break;
++ }
++
++ return 0;
++}
++
++int mtk_wed_mcu_send_msg(struct mtk_wed_wo *wo, int id, int cmd,
++ const void *data, int len, bool wait_resp)
++{
++ unsigned long expires;
++ struct sk_buff *skb;
++ u16 seq;
++ int ret;
++
++ skb = mtk_wed_mcu_msg_alloc(data, len);
++ if (!skb)
++ return -ENOMEM;
++
++ mutex_lock(&wo->mcu.mutex);
++
++ ret = mtk_wed_mcu_skb_send_msg(wo, skb, id, cmd, &seq, wait_resp);
++ if (ret || !wait_resp)
++ goto unlock;
++
++ expires = jiffies + wo->mcu.timeout;
++ do {
++ skb = mtk_wed_mcu_get_response(wo, expires);
++ ret = mtk_wed_mcu_parse_response(wo, skb, cmd, seq);
++ dev_kfree_skb(skb);
++ } while (ret == -EAGAIN);
++
++unlock:
++ mutex_unlock(&wo->mcu.mutex);
++
++ return ret;
++}
++
++static int
++mtk_wed_get_memory_region(struct mtk_wed_wo *wo,
++ struct mtk_wed_wo_memory_region *region)
++{
++ struct reserved_mem *rmem;
++ struct device_node *np;
++ int index;
++
++ index = of_property_match_string(wo->hw->node, "memory-region-names",
++ region->name);
++ if (index < 0)
++ return index;
++
++ np = of_parse_phandle(wo->hw->node, "memory-region", index);
++ if (!np)
++ return -ENODEV;
++
++ rmem = of_reserved_mem_lookup(np);
++ of_node_put(np);
++
++ if (!rmem)
++ return -ENODEV;
++
++ region->phy_addr = rmem->base;
++ region->size = rmem->size;
++ region->addr = devm_ioremap(wo->hw->dev, region->phy_addr, region->size);
++
++ return !region->addr ? -EINVAL : 0;
++}
++
++static int
++mtk_wed_mcu_run_firmware(struct mtk_wed_wo *wo, const struct firmware *fw,
++ struct mtk_wed_wo_memory_region *region)
++{
++ const u8 *first_region_ptr, *region_ptr, *trailer_ptr, *ptr = fw->data;
++ const struct mtk_wed_fw_trailer *trailer;
++ const struct mtk_wed_fw_region *fw_region;
++
++ trailer_ptr = fw->data + fw->size - sizeof(*trailer);
++ trailer = (const struct mtk_wed_fw_trailer *)trailer_ptr;
++ region_ptr = trailer_ptr - trailer->num_region * sizeof(*fw_region);
++ first_region_ptr = region_ptr;
++
++ while (region_ptr < trailer_ptr) {
++ u32 length;
++
++ fw_region = (const struct mtk_wed_fw_region *)region_ptr;
++ length = le32_to_cpu(fw_region->len);
++
++ if (region->phy_addr != le32_to_cpu(fw_region->addr))
++ goto next;
++
++ if (region->size < length)
++ goto next;
++
++ if (first_region_ptr < ptr + length)
++ goto next;
++
++ if (region->shared && region->consumed)
++ return 0;
++
++ if (!region->shared || !region->consumed) {
++ memcpy_toio(region->addr, ptr, length);
++ region->consumed = true;
++ return 0;
++ }
++next:
++ region_ptr += sizeof(*fw_region);
++ ptr += length;
++ }
++
++ return -EINVAL;
++}
++
++static int
++mtk_wed_mcu_load_firmware(struct mtk_wed_wo *wo)
++{
++ static struct mtk_wed_wo_memory_region mem_region[] = {
++ [MTK_WED_WO_REGION_EMI] = {
++ .name = "wo-emi",
++ },
++ [MTK_WED_WO_REGION_ILM] = {
++ .name = "wo-ilm",
++ },
++ [MTK_WED_WO_REGION_DATA] = {
++ .name = "wo-data",
++ .shared = true,
++ },
++ };
++ const struct mtk_wed_fw_trailer *trailer;
++ const struct firmware *fw;
++ const char *fw_name;
++ u32 val, boot_cr;
++ int ret, i;
++
++ /* load firmware region metadata */
++ for (i = 0; i < ARRAY_SIZE(mem_region); i++) {
++ ret = mtk_wed_get_memory_region(wo, &mem_region[i]);
++ if (ret)
++ return ret;
++ }
++
++ wo->boot.name = "wo-boot";
++ ret = mtk_wed_get_memory_region(wo, &wo->boot);
++ if (ret)
++ return ret;
++
++ /* set dummy cr */
++ wed_w32(wo->hw->wed_dev, MTK_WED_SCR0 + 4 * MTK_WED_DUMMY_CR_FWDL,
++ wo->hw->index + 1);
++
++ /* load firmware */
++ fw_name = wo->hw->index ? MT7986_FIRMWARE_WO1 : MT7986_FIRMWARE_WO0;
++ ret = request_firmware(&fw, fw_name, wo->hw->dev);
++ if (ret)
++ return ret;
++
++ trailer = (void *)(fw->data + fw->size -
++ sizeof(struct mtk_wed_fw_trailer));
++ dev_info(wo->hw->dev,
++ "MTK WED WO Firmware Version: %.10s, Build Time: %.15s\n",
++ trailer->fw_ver, trailer->build_date);
++ dev_info(wo->hw->dev, "MTK WED WO Chip ID %02x Region %d\n",
++ trailer->chip_id, trailer->num_region);
++
++ for (i = 0; i < ARRAY_SIZE(mem_region); i++) {
++ ret = mtk_wed_mcu_run_firmware(wo, fw, &mem_region[i]);
++ if (ret)
++ goto out;
++ }
++
++ /* set the start address */
++ boot_cr = wo->hw->index ? MTK_WO_MCU_CFG_LS_WA_BOOT_ADDR_ADDR
++ : MTK_WO_MCU_CFG_LS_WM_BOOT_ADDR_ADDR;
++ wo_w32(wo, boot_cr, mem_region[MTK_WED_WO_REGION_EMI].phy_addr >> 16);
++ /* wo firmware reset */
++ wo_w32(wo, MTK_WO_MCU_CFG_LS_WF_MCCR_CLR_ADDR, 0xc00);
++
++ val = wo_r32(wo, MTK_WO_MCU_CFG_LS_WF_MCU_CFG_WM_WA_ADDR);
++ val |= wo->hw->index ? MTK_WO_MCU_CFG_LS_WF_WM_WA_WA_CPU_RSTB_MASK
++ : MTK_WO_MCU_CFG_LS_WF_WM_WA_WM_CPU_RSTB_MASK;
++ wo_w32(wo, MTK_WO_MCU_CFG_LS_WF_MCU_CFG_WM_WA_ADDR, val);
++out:
++ release_firmware(fw);
++
++ return ret;
++}
++
++static u32
++mtk_wed_mcu_read_fw_dl(struct mtk_wed_wo *wo)
++{
++ return wed_r32(wo->hw->wed_dev,
++ MTK_WED_SCR0 + 4 * MTK_WED_DUMMY_CR_FWDL);
++}
++
++int mtk_wed_mcu_init(struct mtk_wed_wo *wo)
++{
++ u32 val;
++ int ret;
++
++ skb_queue_head_init(&wo->mcu.res_q);
++ init_waitqueue_head(&wo->mcu.wait);
++ mutex_init(&wo->mcu.mutex);
++
++ ret = mtk_wed_mcu_load_firmware(wo);
++ if (ret)
++ return ret;
++
++ return readx_poll_timeout(mtk_wed_mcu_read_fw_dl, wo, val, !val,
++ 100, MTK_FW_DL_TIMEOUT);
++}
++
++MODULE_FIRMWARE(MT7986_FIRMWARE_WO0);
++MODULE_FIRMWARE(MT7986_FIRMWARE_WO1);
+--- a/drivers/net/ethernet/mediatek/mtk_wed_regs.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
+@@ -152,6 +152,7 @@ struct mtk_wdma_desc {
+
+ #define MTK_WED_RING_RX(_n) (0x400 + (_n) * 0x10)
+
++#define MTK_WED_SCR0 0x3c0
+ #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)
+--- /dev/null
++++ b/drivers/net/ethernet/mediatek/mtk_wed_wo.h
+@@ -0,0 +1,150 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* Copyright (C) 2022 Lorenzo Bianconi <lorenzo@kernel.org> */
++
++#ifndef __MTK_WED_WO_H
++#define __MTK_WED_WO_H
++
++#include <linux/skbuff.h>
++#include <linux/netdevice.h>
++
++struct mtk_wed_hw;
++
++struct mtk_wed_mcu_hdr {
++ /* DW0 */
++ u8 version;
++ u8 cmd;
++ __le16 length;
++
++ /* DW1 */
++ __le16 seq;
++ __le16 flag;
++
++ /* DW2 */
++ __le32 status;
++
++ /* DW3 */
++ u8 rsv[20];
++};
++
++struct mtk_wed_wo_log_info {
++ __le32 sn;
++ __le32 total;
++ __le32 rro;
++ __le32 mod;
++};
++
++enum mtk_wed_wo_event {
++ MTK_WED_WO_EVT_LOG_DUMP = 0x1,
++ MTK_WED_WO_EVT_PROFILING = 0x2,
++ MTK_WED_WO_EVT_RXCNT_INFO = 0x3,
++};
++
++#define MTK_WED_MODULE_ID_WO 1
++#define MTK_FW_DL_TIMEOUT 4000000 /* us */
++#define MTK_WOCPU_TIMEOUT 2000000 /* us */
++
++enum {
++ MTK_WED_WARP_CMD_FLAG_RSP = BIT(0),
++ MTK_WED_WARP_CMD_FLAG_NEED_RSP = BIT(1),
++ MTK_WED_WARP_CMD_FLAG_FROM_TO_WO = BIT(2),
++};
++
++enum {
++ MTK_WED_WO_REGION_EMI,
++ MTK_WED_WO_REGION_ILM,
++ MTK_WED_WO_REGION_DATA,
++ MTK_WED_WO_REGION_BOOT,
++ __MTK_WED_WO_REGION_MAX,
++};
++
++enum mtk_wed_dummy_cr_idx {
++ MTK_WED_DUMMY_CR_FWDL,
++ MTK_WED_DUMMY_CR_WO_STATUS,
++};
++
++#define MT7986_FIRMWARE_WO0 "mediatek/mt7986_wo_0.bin"
++#define MT7986_FIRMWARE_WO1 "mediatek/mt7986_wo_1.bin"
++
++#define MTK_WO_MCU_CFG_LS_BASE 0
++#define MTK_WO_MCU_CFG_LS_HW_VER_ADDR (MTK_WO_MCU_CFG_LS_BASE + 0x000)
++#define MTK_WO_MCU_CFG_LS_FW_VER_ADDR (MTK_WO_MCU_CFG_LS_BASE + 0x004)
++#define MTK_WO_MCU_CFG_LS_CFG_DBG1_ADDR (MTK_WO_MCU_CFG_LS_BASE + 0x00c)
++#define MTK_WO_MCU_CFG_LS_CFG_DBG2_ADDR (MTK_WO_MCU_CFG_LS_BASE + 0x010)
++#define MTK_WO_MCU_CFG_LS_WF_MCCR_ADDR (MTK_WO_MCU_CFG_LS_BASE + 0x014)
++#define MTK_WO_MCU_CFG_LS_WF_MCCR_SET_ADDR (MTK_WO_MCU_CFG_LS_BASE + 0x018)
++#define MTK_WO_MCU_CFG_LS_WF_MCCR_CLR_ADDR (MTK_WO_MCU_CFG_LS_BASE + 0x01c)
++#define MTK_WO_MCU_CFG_LS_WF_MCU_CFG_WM_WA_ADDR (MTK_WO_MCU_CFG_LS_BASE + 0x050)
++#define MTK_WO_MCU_CFG_LS_WM_BOOT_ADDR_ADDR (MTK_WO_MCU_CFG_LS_BASE + 0x060)
++#define MTK_WO_MCU_CFG_LS_WA_BOOT_ADDR_ADDR (MTK_WO_MCU_CFG_LS_BASE + 0x064)
++
++#define MTK_WO_MCU_CFG_LS_WF_WM_WA_WM_CPU_RSTB_MASK BIT(5)
++#define MTK_WO_MCU_CFG_LS_WF_WM_WA_WA_CPU_RSTB_MASK BIT(0)
++
++struct mtk_wed_wo_memory_region {
++ const char *name;
++ void __iomem *addr;
++ phys_addr_t phy_addr;
++ u32 size;
++ bool shared:1;
++ bool consumed:1;
++};
++
++struct mtk_wed_fw_region {
++ __le32 decomp_crc;
++ __le32 decomp_len;
++ __le32 decomp_blk_sz;
++ u8 rsv0[4];
++ __le32 addr;
++ __le32 len;
++ u8 feature_set;
++ u8 rsv1[15];
++} __packed;
++
++struct mtk_wed_fw_trailer {
++ u8 chip_id;
++ u8 eco_code;
++ u8 num_region;
++ u8 format_ver;
++ u8 format_flag;
++ u8 rsv[2];
++ char fw_ver[10];
++ char build_date[15];
++ u32 crc;
++};
++
++struct mtk_wed_wo {
++ struct mtk_wed_hw *hw;
++ struct mtk_wed_wo_memory_region boot;
++
++ struct {
++ struct mutex mutex;
++ int timeout;
++ u16 seq;
++
++ struct sk_buff_head res_q;
++ wait_queue_head_t wait;
++ } mcu;
++};
++
++static inline int
++mtk_wed_mcu_check_msg(struct mtk_wed_wo *wo, struct sk_buff *skb)
++{
++ struct mtk_wed_mcu_hdr *hdr = (struct mtk_wed_mcu_hdr *)skb->data;
++
++ if (hdr->version)
++ return -EINVAL;
++
++ if (skb->len < sizeof(*hdr) || skb->len != le16_to_cpu(hdr->length))
++ return -EINVAL;
++
++ return 0;
++}
++
++void mtk_wed_mcu_rx_event(struct mtk_wed_wo *wo, struct sk_buff *skb);
++void mtk_wed_mcu_rx_unsolicited_event(struct mtk_wed_wo *wo,
++ struct sk_buff *skb);
++int mtk_wed_mcu_send_msg(struct mtk_wed_wo *wo, int id, int cmd,
++ const void *data, int len, bool wait_resp);
++int mtk_wed_mcu_init(struct mtk_wed_wo *wo);
++
++#endif /* __MTK_WED_WO_H */
+--- a/include/linux/soc/mediatek/mtk_wed.h
++++ b/include/linux/soc/mediatek/mtk_wed.h
+@@ -11,6 +11,35 @@
+ struct mtk_wed_hw;
+ struct mtk_wdma_desc;
+
++enum mtk_wed_wo_cmd {
++ MTK_WED_WO_CMD_WED_CFG,
++ MTK_WED_WO_CMD_WED_RX_STAT,
++ MTK_WED_WO_CMD_RRO_SER,
++ MTK_WED_WO_CMD_DBG_INFO,
++ MTK_WED_WO_CMD_DEV_INFO,
++ MTK_WED_WO_CMD_BSS_INFO,
++ MTK_WED_WO_CMD_STA_REC,
++ MTK_WED_WO_CMD_DEV_INFO_DUMP,
++ MTK_WED_WO_CMD_BSS_INFO_DUMP,
++ MTK_WED_WO_CMD_STA_REC_DUMP,
++ MTK_WED_WO_CMD_BA_INFO_DUMP,
++ MTK_WED_WO_CMD_FBCMD_Q_DUMP,
++ MTK_WED_WO_CMD_FW_LOG_CTRL,
++ MTK_WED_WO_CMD_LOG_FLUSH,
++ MTK_WED_WO_CMD_CHANGE_STATE,
++ MTK_WED_WO_CMD_CPU_STATS_ENABLE,
++ MTK_WED_WO_CMD_CPU_STATS_DUMP,
++ MTK_WED_WO_CMD_EXCEPTION_INIT,
++ MTK_WED_WO_CMD_PROF_CTRL,
++ MTK_WED_WO_CMD_STA_BA_DUMP,
++ MTK_WED_WO_CMD_BA_CTRL_DUMP,
++ MTK_WED_WO_CMD_RXCNT_CTRL,
++ MTK_WED_WO_CMD_RXCNT_INFO,
++ MTK_WED_WO_CMD_SET_CAP,
++ MTK_WED_WO_CMD_CCIF_RING_DUMP,
++ MTK_WED_WO_CMD_WED_END
++};
++
+ enum mtk_wed_bus_tye {
+ MTK_WED_BUS_PCIE,
+ MTK_WED_BUS_AXI,
diff --git a/target/linux/generic/backport-6.6/729-02-v6.1-net-ethernet-mtk_wed-introduce-wed-wo-support.patch b/target/linux/generic/backport-6.6/729-02-v6.1-net-ethernet-mtk_wed-introduce-wed-wo-support.patch
new file mode 100644
index 0000000000..fd5f45df2a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-02-v6.1-net-ethernet-mtk_wed-introduce-wed-wo-support.patch
@@ -0,0 +1,737 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 5 Nov 2022 23:36:19 +0100
+Subject: [PATCH] net: ethernet: mtk_wed: introduce wed wo support
+
+Introduce WO chip support to mtk wed driver. MTK WED WO is used to
+implement RX Wireless Ethernet Dispatch and offload traffic received by
+wlan nic to the wired interface.
+
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_wo.c
+
+--- a/drivers/net/ethernet/mediatek/Makefile
++++ b/drivers/net/ethernet/mediatek/Makefile
+@@ -5,7 +5,7 @@
+
+ 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 mtk_wed_mcu.o
++mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed.o mtk_wed_mcu.o mtk_wed_wo.o
+ ifdef CONFIG_DEBUG_FS
+ mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed_debugfs.o
+ endif
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -16,6 +16,7 @@
+ #include "mtk_wed_regs.h"
+ #include "mtk_wed.h"
+ #include "mtk_ppe.h"
++#include "mtk_wed_wo.h"
+
+ #define MTK_PCIE_BASE(n) (0x1a143000 + (n) * 0x2000)
+
+@@ -355,6 +356,8 @@ mtk_wed_detach(struct mtk_wed_device *de
+
+ mtk_wed_free_buffer(dev);
+ mtk_wed_free_tx_rings(dev);
++ if (hw->version != 1)
++ mtk_wed_wo_deinit(hw);
+
+ if (dev->wlan.bus_type == MTK_WED_BUS_PCIE) {
+ struct device_node *wlan_node;
+@@ -878,9 +881,11 @@ mtk_wed_attach(struct mtk_wed_device *de
+ }
+
+ mtk_wed_hw_init_early(dev);
+- if (hw->hifsys)
++ if (hw->version == 1)
+ regmap_update_bits(hw->hifsys, HIFSYS_DMA_AG_MAP,
+ BIT(hw->index), 0);
++ else
++ ret = mtk_wed_wo_init(hw);
+
+ out:
+ mutex_unlock(&hw_lock);
+--- a/drivers/net/ethernet/mediatek/mtk_wed.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed.h
+@@ -10,6 +10,7 @@
+ #include <linux/netdevice.h>
+
+ struct mtk_eth;
++struct mtk_wed_wo;
+
+ struct mtk_wed_hw {
+ struct device_node *node;
+@@ -22,6 +23,7 @@ struct mtk_wed_hw {
+ struct regmap *mirror;
+ struct dentry *debugfs_dir;
+ struct mtk_wed_device *wed_dev;
++ struct mtk_wed_wo *wed_wo;
+ u32 debugfs_reg;
+ u32 num_flows;
+ u8 version;
+--- a/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
+@@ -122,8 +122,7 @@ mtk_wed_mcu_skb_send_msg(struct mtk_wed_
+ if (id == MTK_WED_MODULE_ID_WO)
+ hdr->flag |= cpu_to_le16(MTK_WED_WARP_CMD_FLAG_FROM_TO_WO);
+
+- dev_kfree_skb(skb);
+- return 0;
++ return mtk_wed_wo_queue_tx_skb(wo, &wo->q_tx, skb);
+ }
+
+ static int
+--- /dev/null
++++ b/drivers/net/ethernet/mediatek/mtk_wed_wo.c
+@@ -0,0 +1,508 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* Copyright (C) 2022 MediaTek Inc.
++ *
++ * Author: Lorenzo Bianconi <lorenzo@kernel.org>
++ * Sujuan Chen <sujuan.chen@mediatek.com>
++ */
++
++#include <linux/kernel.h>
++#include <linux/dma-mapping.h>
++#include <linux/of_platform.h>
++#include <linux/interrupt.h>
++#include <linux/of_address.h>
++#include <linux/mfd/syscon.h>
++#include <linux/of_irq.h>
++#include <linux/bitfield.h>
++
++#include "mtk_wed.h"
++#include "mtk_wed_regs.h"
++#include "mtk_wed_wo.h"
++
++static u32
++mtk_wed_mmio_r32(struct mtk_wed_wo *wo, u32 reg)
++{
++ u32 val;
++
++ if (regmap_read(wo->mmio.regs, reg, &val))
++ val = ~0;
++
++ return val;
++}
++
++static void
++mtk_wed_mmio_w32(struct mtk_wed_wo *wo, u32 reg, u32 val)
++{
++ regmap_write(wo->mmio.regs, reg, val);
++}
++
++static u32
++mtk_wed_wo_get_isr(struct mtk_wed_wo *wo)
++{
++ u32 val = mtk_wed_mmio_r32(wo, MTK_WED_WO_CCIF_RCHNUM);
++
++ return val & MTK_WED_WO_CCIF_RCHNUM_MASK;
++}
++
++static void
++mtk_wed_wo_set_isr(struct mtk_wed_wo *wo, u32 mask)
++{
++ mtk_wed_mmio_w32(wo, MTK_WED_WO_CCIF_IRQ0_MASK, mask);
++}
++
++static void
++mtk_wed_wo_set_ack(struct mtk_wed_wo *wo, u32 mask)
++{
++ mtk_wed_mmio_w32(wo, MTK_WED_WO_CCIF_ACK, mask);
++}
++
++static void
++mtk_wed_wo_set_isr_mask(struct mtk_wed_wo *wo, u32 mask, u32 val, bool set)
++{
++ unsigned long flags;
++
++ spin_lock_irqsave(&wo->mmio.lock, flags);
++ wo->mmio.irq_mask &= ~mask;
++ wo->mmio.irq_mask |= val;
++ if (set)
++ mtk_wed_wo_set_isr(wo, wo->mmio.irq_mask);
++ spin_unlock_irqrestore(&wo->mmio.lock, flags);
++}
++
++static void
++mtk_wed_wo_irq_enable(struct mtk_wed_wo *wo, u32 mask)
++{
++ mtk_wed_wo_set_isr_mask(wo, 0, mask, false);
++ tasklet_schedule(&wo->mmio.irq_tasklet);
++}
++
++static void
++mtk_wed_wo_irq_disable(struct mtk_wed_wo *wo, u32 mask)
++{
++ mtk_wed_wo_set_isr_mask(wo, mask, 0, true);
++}
++
++static void
++mtk_wed_wo_kickout(struct mtk_wed_wo *wo)
++{
++ mtk_wed_mmio_w32(wo, MTK_WED_WO_CCIF_BUSY, 1 << MTK_WED_WO_TXCH_NUM);
++ mtk_wed_mmio_w32(wo, MTK_WED_WO_CCIF_TCHNUM, MTK_WED_WO_TXCH_NUM);
++}
++
++static void
++mtk_wed_wo_queue_kick(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q,
++ u32 val)
++{
++ wmb();
++ mtk_wed_mmio_w32(wo, q->regs.cpu_idx, val);
++}
++
++static void *
++mtk_wed_wo_dequeue(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q, u32 *len,
++ bool flush)
++{
++ int buf_len = SKB_WITH_OVERHEAD(q->buf_size);
++ int index = (q->tail + 1) % q->n_desc;
++ struct mtk_wed_wo_queue_entry *entry;
++ struct mtk_wed_wo_queue_desc *desc;
++ void *buf;
++
++ if (!q->queued)
++ return NULL;
++
++ if (flush)
++ q->desc[index].ctrl |= cpu_to_le32(MTK_WED_WO_CTL_DMA_DONE);
++ else if (!(q->desc[index].ctrl & cpu_to_le32(MTK_WED_WO_CTL_DMA_DONE)))
++ return NULL;
++
++ q->tail = index;
++ q->queued--;
++
++ desc = &q->desc[index];
++ entry = &q->entry[index];
++ buf = entry->buf;
++ if (len)
++ *len = FIELD_GET(MTK_WED_WO_CTL_SD_LEN0,
++ le32_to_cpu(READ_ONCE(desc->ctrl)));
++ if (buf)
++ dma_unmap_single(wo->hw->dev, entry->addr, buf_len,
++ DMA_FROM_DEVICE);
++ entry->buf = NULL;
++
++ return buf;
++}
++
++static int
++mtk_wed_wo_queue_refill(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q,
++ gfp_t gfp, bool rx)
++{
++ enum dma_data_direction dir = rx ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
++ int n_buf = 0;
++
++ spin_lock_bh(&q->lock);
++ while (q->queued < q->n_desc) {
++ void *buf = page_frag_alloc(&q->cache, q->buf_size, gfp);
++ struct mtk_wed_wo_queue_entry *entry;
++ dma_addr_t addr;
++
++ if (!buf)
++ break;
++
++ addr = dma_map_single(wo->hw->dev, buf, q->buf_size, dir);
++ if (unlikely(dma_mapping_error(wo->hw->dev, addr))) {
++ skb_free_frag(buf);
++ break;
++ }
++
++ q->head = (q->head + 1) % q->n_desc;
++ entry = &q->entry[q->head];
++ entry->addr = addr;
++ entry->len = q->buf_size;
++ q->entry[q->head].buf = buf;
++
++ if (rx) {
++ struct mtk_wed_wo_queue_desc *desc = &q->desc[q->head];
++ u32 ctrl = MTK_WED_WO_CTL_LAST_SEC0 |
++ FIELD_PREP(MTK_WED_WO_CTL_SD_LEN0,
++ entry->len);
++
++ WRITE_ONCE(desc->buf0, cpu_to_le32(addr));
++ WRITE_ONCE(desc->ctrl, cpu_to_le32(ctrl));
++ }
++ q->queued++;
++ n_buf++;
++ }
++ spin_unlock_bh(&q->lock);
++
++ return n_buf;
++}
++
++static void
++mtk_wed_wo_rx_complete(struct mtk_wed_wo *wo)
++{
++ mtk_wed_wo_set_ack(wo, MTK_WED_WO_RXCH_INT_MASK);
++ mtk_wed_wo_irq_enable(wo, MTK_WED_WO_RXCH_INT_MASK);
++}
++
++static void
++mtk_wed_wo_rx_run_queue(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q)
++{
++ for (;;) {
++ struct mtk_wed_mcu_hdr *hdr;
++ struct sk_buff *skb;
++ void *data;
++ u32 len;
++
++ data = mtk_wed_wo_dequeue(wo, q, &len, false);
++ if (!data)
++ break;
++
++ skb = build_skb(data, q->buf_size);
++ if (!skb) {
++ skb_free_frag(data);
++ continue;
++ }
++
++ __skb_put(skb, len);
++ if (mtk_wed_mcu_check_msg(wo, skb)) {
++ dev_kfree_skb(skb);
++ continue;
++ }
++
++ hdr = (struct mtk_wed_mcu_hdr *)skb->data;
++ if (hdr->flag & cpu_to_le16(MTK_WED_WARP_CMD_FLAG_RSP))
++ mtk_wed_mcu_rx_event(wo, skb);
++ else
++ mtk_wed_mcu_rx_unsolicited_event(wo, skb);
++ }
++
++ if (mtk_wed_wo_queue_refill(wo, q, GFP_ATOMIC, true)) {
++ u32 index = (q->head - 1) % q->n_desc;
++
++ mtk_wed_wo_queue_kick(wo, q, index);
++ }
++}
++
++static irqreturn_t
++mtk_wed_wo_irq_handler(int irq, void *data)
++{
++ struct mtk_wed_wo *wo = data;
++
++ mtk_wed_wo_set_isr(wo, 0);
++ tasklet_schedule(&wo->mmio.irq_tasklet);
++
++ return IRQ_HANDLED;
++}
++
++static void mtk_wed_wo_irq_tasklet(struct tasklet_struct *t)
++{
++ struct mtk_wed_wo *wo = from_tasklet(wo, t, mmio.irq_tasklet);
++ u32 intr, mask;
++
++ /* disable interrupts */
++ mtk_wed_wo_set_isr(wo, 0);
++
++ intr = mtk_wed_wo_get_isr(wo);
++ intr &= wo->mmio.irq_mask;
++ mask = intr & (MTK_WED_WO_RXCH_INT_MASK | MTK_WED_WO_EXCEPTION_INT_MASK);
++ mtk_wed_wo_irq_disable(wo, mask);
++
++ if (intr & MTK_WED_WO_RXCH_INT_MASK) {
++ mtk_wed_wo_rx_run_queue(wo, &wo->q_rx);
++ mtk_wed_wo_rx_complete(wo);
++ }
++}
++
++/* mtk wed wo hw queues */
++
++static int
++mtk_wed_wo_queue_alloc(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q,
++ int n_desc, int buf_size, int index,
++ struct mtk_wed_wo_queue_regs *regs)
++{
++ spin_lock_init(&q->lock);
++ q->regs = *regs;
++ q->n_desc = n_desc;
++ q->buf_size = buf_size;
++
++ q->desc = dmam_alloc_coherent(wo->hw->dev, n_desc * sizeof(*q->desc),
++ &q->desc_dma, GFP_KERNEL);
++ if (!q->desc)
++ return -ENOMEM;
++
++ q->entry = devm_kzalloc(wo->hw->dev, n_desc * sizeof(*q->entry),
++ GFP_KERNEL);
++ if (!q->entry)
++ return -ENOMEM;
++
++ return 0;
++}
++
++static void
++mtk_wed_wo_queue_free(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q)
++{
++ mtk_wed_mmio_w32(wo, q->regs.cpu_idx, 0);
++ dma_free_coherent(wo->hw->dev, q->n_desc * sizeof(*q->desc), q->desc,
++ q->desc_dma);
++}
++
++static void
++mtk_wed_wo_queue_tx_clean(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q)
++{
++ struct page *page;
++ int i;
++
++ spin_lock_bh(&q->lock);
++ for (i = 0; i < q->n_desc; i++) {
++ struct mtk_wed_wo_queue_entry *entry = &q->entry[i];
++
++ dma_unmap_single(wo->hw->dev, entry->addr, entry->len,
++ DMA_TO_DEVICE);
++ skb_free_frag(entry->buf);
++ entry->buf = NULL;
++ }
++ spin_unlock_bh(&q->lock);
++
++ if (!q->cache.va)
++ return;
++
++ page = virt_to_page(q->cache.va);
++ __page_frag_cache_drain(page, q->cache.pagecnt_bias);
++ memset(&q->cache, 0, sizeof(q->cache));
++}
++
++static void
++mtk_wed_wo_queue_rx_clean(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q)
++{
++ struct page *page;
++
++ spin_lock_bh(&q->lock);
++ for (;;) {
++ void *buf = mtk_wed_wo_dequeue(wo, q, NULL, true);
++
++ if (!buf)
++ break;
++
++ skb_free_frag(buf);
++ }
++ spin_unlock_bh(&q->lock);
++
++ if (!q->cache.va)
++ return;
++
++ page = virt_to_page(q->cache.va);
++ __page_frag_cache_drain(page, q->cache.pagecnt_bias);
++ memset(&q->cache, 0, sizeof(q->cache));
++}
++
++static void
++mtk_wed_wo_queue_reset(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q)
++{
++ mtk_wed_mmio_w32(wo, q->regs.cpu_idx, 0);
++ mtk_wed_mmio_w32(wo, q->regs.desc_base, q->desc_dma);
++ mtk_wed_mmio_w32(wo, q->regs.ring_size, q->n_desc);
++}
++
++int mtk_wed_wo_queue_tx_skb(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q,
++ struct sk_buff *skb)
++{
++ struct mtk_wed_wo_queue_entry *entry;
++ struct mtk_wed_wo_queue_desc *desc;
++ int ret = 0, index;
++ u32 ctrl;
++
++ spin_lock_bh(&q->lock);
++
++ q->tail = mtk_wed_mmio_r32(wo, q->regs.dma_idx);
++ index = (q->head + 1) % q->n_desc;
++ if (q->tail == index) {
++ ret = -ENOMEM;
++ goto out;
++ }
++
++ entry = &q->entry[index];
++ if (skb->len > entry->len) {
++ ret = -ENOMEM;
++ goto out;
++ }
++
++ desc = &q->desc[index];
++ q->head = index;
++
++ dma_sync_single_for_cpu(wo->hw->dev, entry->addr, skb->len,
++ DMA_TO_DEVICE);
++ memcpy(entry->buf, skb->data, skb->len);
++ dma_sync_single_for_device(wo->hw->dev, entry->addr, skb->len,
++ DMA_TO_DEVICE);
++
++ ctrl = FIELD_PREP(MTK_WED_WO_CTL_SD_LEN0, skb->len) |
++ MTK_WED_WO_CTL_LAST_SEC0 | MTK_WED_WO_CTL_DMA_DONE;
++ WRITE_ONCE(desc->buf0, cpu_to_le32(entry->addr));
++ WRITE_ONCE(desc->ctrl, cpu_to_le32(ctrl));
++
++ mtk_wed_wo_queue_kick(wo, q, q->head);
++ mtk_wed_wo_kickout(wo);
++out:
++ spin_unlock_bh(&q->lock);
++
++ dev_kfree_skb(skb);
++
++ return ret;
++}
++
++static int
++mtk_wed_wo_exception_init(struct mtk_wed_wo *wo)
++{
++ return 0;
++}
++
++static int
++mtk_wed_wo_hardware_init(struct mtk_wed_wo *wo)
++{
++ struct mtk_wed_wo_queue_regs regs;
++ struct device_node *np;
++ int ret;
++
++ np = of_parse_phandle(wo->hw->node, "mediatek,wo-ccif", 0);
++ if (!np)
++ return -ENODEV;
++
++ wo->mmio.regs = syscon_regmap_lookup_by_phandle(np, NULL);
++ if (IS_ERR_OR_NULL(wo->mmio.regs))
++ return PTR_ERR(wo->mmio.regs);
++
++ wo->mmio.irq = irq_of_parse_and_map(np, 0);
++ wo->mmio.irq_mask = MTK_WED_WO_ALL_INT_MASK;
++ spin_lock_init(&wo->mmio.lock);
++ tasklet_setup(&wo->mmio.irq_tasklet, mtk_wed_wo_irq_tasklet);
++
++ ret = devm_request_irq(wo->hw->dev, wo->mmio.irq,
++ mtk_wed_wo_irq_handler, IRQF_TRIGGER_HIGH,
++ KBUILD_MODNAME, wo);
++ if (ret)
++ goto error;
++
++ regs.desc_base = MTK_WED_WO_CCIF_DUMMY1;
++ regs.ring_size = MTK_WED_WO_CCIF_DUMMY2;
++ regs.dma_idx = MTK_WED_WO_CCIF_SHADOW4;
++ regs.cpu_idx = MTK_WED_WO_CCIF_DUMMY3;
++
++ ret = mtk_wed_wo_queue_alloc(wo, &wo->q_tx, MTK_WED_WO_RING_SIZE,
++ MTK_WED_WO_CMD_LEN, MTK_WED_WO_TXCH_NUM,
++ &regs);
++ if (ret)
++ goto error;
++
++ mtk_wed_wo_queue_refill(wo, &wo->q_tx, GFP_KERNEL, false);
++ mtk_wed_wo_queue_reset(wo, &wo->q_tx);
++
++ regs.desc_base = MTK_WED_WO_CCIF_DUMMY5;
++ regs.ring_size = MTK_WED_WO_CCIF_DUMMY6;
++ regs.dma_idx = MTK_WED_WO_CCIF_SHADOW8;
++ regs.cpu_idx = MTK_WED_WO_CCIF_DUMMY7;
++
++ ret = mtk_wed_wo_queue_alloc(wo, &wo->q_rx, MTK_WED_WO_RING_SIZE,
++ MTK_WED_WO_CMD_LEN, MTK_WED_WO_RXCH_NUM,
++ &regs);
++ if (ret)
++ goto error;
++
++ mtk_wed_wo_queue_refill(wo, &wo->q_rx, GFP_KERNEL, true);
++ mtk_wed_wo_queue_reset(wo, &wo->q_rx);
++
++ /* rx queue irqmask */
++ mtk_wed_wo_set_isr(wo, wo->mmio.irq_mask);
++
++ return 0;
++
++error:
++ devm_free_irq(wo->hw->dev, wo->mmio.irq, wo);
++
++ return ret;
++}
++
++static void
++mtk_wed_wo_hw_deinit(struct mtk_wed_wo *wo)
++{
++ /* disable interrupts */
++ mtk_wed_wo_set_isr(wo, 0);
++
++ tasklet_disable(&wo->mmio.irq_tasklet);
++
++ disable_irq(wo->mmio.irq);
++ devm_free_irq(wo->hw->dev, wo->mmio.irq, wo);
++
++ mtk_wed_wo_queue_tx_clean(wo, &wo->q_tx);
++ mtk_wed_wo_queue_rx_clean(wo, &wo->q_rx);
++ mtk_wed_wo_queue_free(wo, &wo->q_tx);
++ mtk_wed_wo_queue_free(wo, &wo->q_rx);
++}
++
++int mtk_wed_wo_init(struct mtk_wed_hw *hw)
++{
++ struct mtk_wed_wo *wo;
++ int ret;
++
++ wo = devm_kzalloc(hw->dev, sizeof(*wo), GFP_KERNEL);
++ if (!wo)
++ return -ENOMEM;
++
++ hw->wed_wo = wo;
++ wo->hw = hw;
++
++ ret = mtk_wed_wo_hardware_init(wo);
++ if (ret)
++ return ret;
++
++ ret = mtk_wed_mcu_init(wo);
++ if (ret)
++ return ret;
++
++ return mtk_wed_wo_exception_init(wo);
++}
++
++void mtk_wed_wo_deinit(struct mtk_wed_hw *hw)
++{
++ struct mtk_wed_wo *wo = hw->wed_wo;
++
++ mtk_wed_wo_hw_deinit(wo);
++}
+--- a/drivers/net/ethernet/mediatek/mtk_wed_wo.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed_wo.h
+@@ -80,6 +80,54 @@ enum mtk_wed_dummy_cr_idx {
+ #define MTK_WO_MCU_CFG_LS_WF_WM_WA_WM_CPU_RSTB_MASK BIT(5)
+ #define MTK_WO_MCU_CFG_LS_WF_WM_WA_WA_CPU_RSTB_MASK BIT(0)
+
++#define MTK_WED_WO_RING_SIZE 256
++#define MTK_WED_WO_CMD_LEN 1504
++
++#define MTK_WED_WO_TXCH_NUM 0
++#define MTK_WED_WO_RXCH_NUM 1
++#define MTK_WED_WO_RXCH_WO_EXCEPTION 7
++
++#define MTK_WED_WO_TXCH_INT_MASK BIT(0)
++#define MTK_WED_WO_RXCH_INT_MASK BIT(1)
++#define MTK_WED_WO_EXCEPTION_INT_MASK BIT(7)
++#define MTK_WED_WO_ALL_INT_MASK (MTK_WED_WO_RXCH_INT_MASK | \
++ MTK_WED_WO_EXCEPTION_INT_MASK)
++
++#define MTK_WED_WO_CCIF_BUSY 0x004
++#define MTK_WED_WO_CCIF_START 0x008
++#define MTK_WED_WO_CCIF_TCHNUM 0x00c
++#define MTK_WED_WO_CCIF_RCHNUM 0x010
++#define MTK_WED_WO_CCIF_RCHNUM_MASK GENMASK(7, 0)
++
++#define MTK_WED_WO_CCIF_ACK 0x014
++#define MTK_WED_WO_CCIF_IRQ0_MASK 0x018
++#define MTK_WED_WO_CCIF_IRQ1_MASK 0x01c
++#define MTK_WED_WO_CCIF_DUMMY1 0x020
++#define MTK_WED_WO_CCIF_DUMMY2 0x024
++#define MTK_WED_WO_CCIF_DUMMY3 0x028
++#define MTK_WED_WO_CCIF_DUMMY4 0x02c
++#define MTK_WED_WO_CCIF_SHADOW1 0x030
++#define MTK_WED_WO_CCIF_SHADOW2 0x034
++#define MTK_WED_WO_CCIF_SHADOW3 0x038
++#define MTK_WED_WO_CCIF_SHADOW4 0x03c
++#define MTK_WED_WO_CCIF_DUMMY5 0x050
++#define MTK_WED_WO_CCIF_DUMMY6 0x054
++#define MTK_WED_WO_CCIF_DUMMY7 0x058
++#define MTK_WED_WO_CCIF_DUMMY8 0x05c
++#define MTK_WED_WO_CCIF_SHADOW5 0x060
++#define MTK_WED_WO_CCIF_SHADOW6 0x064
++#define MTK_WED_WO_CCIF_SHADOW7 0x068
++#define MTK_WED_WO_CCIF_SHADOW8 0x06c
++
++#define MTK_WED_WO_CTL_SD_LEN1 GENMASK(13, 0)
++#define MTK_WED_WO_CTL_LAST_SEC1 BIT(14)
++#define MTK_WED_WO_CTL_BURST BIT(15)
++#define MTK_WED_WO_CTL_SD_LEN0_SHIFT 16
++#define MTK_WED_WO_CTL_SD_LEN0 GENMASK(29, 16)
++#define MTK_WED_WO_CTL_LAST_SEC0 BIT(30)
++#define MTK_WED_WO_CTL_DMA_DONE BIT(31)
++#define MTK_WED_WO_INFO_WINFO GENMASK(15, 0)
++
+ struct mtk_wed_wo_memory_region {
+ const char *name;
+ void __iomem *addr;
+@@ -112,10 +160,53 @@ struct mtk_wed_fw_trailer {
+ u32 crc;
+ };
+
++struct mtk_wed_wo_queue_regs {
++ u32 desc_base;
++ u32 ring_size;
++ u32 cpu_idx;
++ u32 dma_idx;
++};
++
++struct mtk_wed_wo_queue_desc {
++ __le32 buf0;
++ __le32 ctrl;
++ __le32 buf1;
++ __le32 info;
++ __le32 reserved[4];
++} __packed __aligned(32);
++
++struct mtk_wed_wo_queue_entry {
++ dma_addr_t addr;
++ void *buf;
++ u32 len;
++};
++
++struct mtk_wed_wo_queue {
++ struct mtk_wed_wo_queue_regs regs;
++
++ struct page_frag_cache cache;
++ spinlock_t lock;
++
++ struct mtk_wed_wo_queue_desc *desc;
++ dma_addr_t desc_dma;
++
++ struct mtk_wed_wo_queue_entry *entry;
++
++ u16 head;
++ u16 tail;
++ int n_desc;
++ int queued;
++ int buf_size;
++
++};
++
+ struct mtk_wed_wo {
+ struct mtk_wed_hw *hw;
+ struct mtk_wed_wo_memory_region boot;
+
++ struct mtk_wed_wo_queue q_tx;
++ struct mtk_wed_wo_queue q_rx;
++
+ struct {
+ struct mutex mutex;
+ int timeout;
+@@ -124,6 +215,15 @@ struct mtk_wed_wo {
+ struct sk_buff_head res_q;
+ wait_queue_head_t wait;
+ } mcu;
++
++ struct {
++ struct regmap *regs;
++
++ spinlock_t lock;
++ struct tasklet_struct irq_tasklet;
++ int irq;
++ u32 irq_mask;
++ } mmio;
+ };
+
+ static inline int
+@@ -146,5 +246,9 @@ void mtk_wed_mcu_rx_unsolicited_event(st
+ int mtk_wed_mcu_send_msg(struct mtk_wed_wo *wo, int id, int cmd,
+ const void *data, int len, bool wait_resp);
+ int mtk_wed_mcu_init(struct mtk_wed_wo *wo);
++int mtk_wed_wo_init(struct mtk_wed_hw *hw);
++void mtk_wed_wo_deinit(struct mtk_wed_hw *hw);
++int mtk_wed_wo_queue_tx_skb(struct mtk_wed_wo *dev, struct mtk_wed_wo_queue *q,
++ struct sk_buff *skb);
+
+ #endif /* __MTK_WED_WO_H */
diff --git a/target/linux/generic/backport-6.6/729-03-v6.1-net-ethernet-mtk_wed-rename-tx_wdma-array-in-rx_wdma.patch b/target/linux/generic/backport-6.6/729-03-v6.1-net-ethernet-mtk_wed-rename-tx_wdma-array-in-rx_wdma.patch
new file mode 100644
index 0000000000..a002a5f851
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-03-v6.1-net-ethernet-mtk_wed-rename-tx_wdma-array-in-rx_wdma.patch
@@ -0,0 +1,79 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 5 Nov 2022 23:36:20 +0100
+Subject: [PATCH] net: ethernet: mtk_wed: rename tx_wdma array in rx_wdma
+
+Rename tx_wdma queue array in rx_wdma since this is rx side of wdma soc.
+Moreover rename mtk_wed_wdma_ring_setup routine in
+mtk_wed_wdma_rx_ring_setup()
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+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
+@@ -253,8 +253,8 @@ mtk_wed_free_tx_rings(struct mtk_wed_dev
+
+ 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]);
++ for (i = 0; i < ARRAY_SIZE(dev->rx_wdma); i++)
++ mtk_wed_free_ring(dev, &dev->rx_wdma[i]);
+ }
+
+ static void
+@@ -688,10 +688,10 @@ mtk_wed_ring_alloc(struct mtk_wed_device
+ }
+
+ static int
+-mtk_wed_wdma_ring_setup(struct mtk_wed_device *dev, int idx, int size)
++mtk_wed_wdma_rx_ring_setup(struct mtk_wed_device *dev, int idx, int size)
+ {
+ u32 desc_size = sizeof(struct mtk_wdma_desc) * dev->hw->version;
+- struct mtk_wed_ring *wdma = &dev->tx_wdma[idx];
++ struct mtk_wed_ring *wdma = &dev->rx_wdma[idx];
+
+ if (mtk_wed_ring_alloc(dev, wdma, MTK_WED_WDMA_RING_SIZE, desc_size))
+ return -ENOMEM;
+@@ -805,9 +805,9 @@ mtk_wed_start(struct mtk_wed_device *dev
+ {
+ 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);
++ for (i = 0; i < ARRAY_SIZE(dev->rx_wdma); i++)
++ if (!dev->rx_wdma[i].desc)
++ mtk_wed_wdma_rx_ring_setup(dev, i, 16);
+
+ mtk_wed_hw_init(dev);
+ mtk_wed_configure_irq(dev, irq_mask);
+@@ -916,7 +916,7 @@ mtk_wed_tx_ring_setup(struct mtk_wed_dev
+ sizeof(*ring->desc)))
+ return -ENOMEM;
+
+- if (mtk_wed_wdma_ring_setup(dev, idx, MTK_WED_WDMA_RING_SIZE))
++ if (mtk_wed_wdma_rx_ring_setup(dev, idx, MTK_WED_WDMA_RING_SIZE))
+ return -ENOMEM;
+
+ ring->reg_base = MTK_WED_RING_TX(idx);
+--- a/include/linux/soc/mediatek/mtk_wed.h
++++ b/include/linux/soc/mediatek/mtk_wed.h
+@@ -7,6 +7,7 @@
+ #include <linux/pci.h>
+
+ #define MTK_WED_TX_QUEUES 2
++#define MTK_WED_RX_QUEUES 2
+
+ struct mtk_wed_hw;
+ struct mtk_wdma_desc;
+@@ -66,7 +67,7 @@ struct mtk_wed_device {
+
+ 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 mtk_wed_ring rx_wdma[MTK_WED_RX_QUEUES];
+
+ struct {
+ int size;
diff --git a/target/linux/generic/backport-6.6/729-04-v6.1-net-ethernet-mtk_wed-add-configure-wed-wo-support.patch b/target/linux/generic/backport-6.6/729-04-v6.1-net-ethernet-mtk_wed-add-configure-wed-wo-support.patch
new file mode 100644
index 0000000000..eca29739b4
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-04-v6.1-net-ethernet-mtk_wed-add-configure-wed-wo-support.patch
@@ -0,0 +1,1521 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 5 Nov 2022 23:36:21 +0100
+Subject: [PATCH] net: ethernet: mtk_wed: add configure wed wo support
+
+Enable RX Wireless Ethernet Dispatch available on MT7986 Soc.
+
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+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
+@@ -9,6 +9,7 @@
+ #include <linux/skbuff.h>
+ #include <linux/of_platform.h>
+ #include <linux/of_address.h>
++#include <linux/of_reserved_mem.h>
+ #include <linux/mfd/syscon.h>
+ #include <linux/debugfs.h>
+ #include <linux/soc/mediatek/mtk_wed.h>
+@@ -23,6 +24,7 @@
+ #define MTK_WED_PKT_SIZE 1900
+ #define MTK_WED_BUF_SIZE 2048
+ #define MTK_WED_BUF_PER_PAGE (PAGE_SIZE / 2048)
++#define MTK_WED_RX_RING_SIZE 1536
+
+ #define MTK_WED_TX_RING_SIZE 2048
+ #define MTK_WED_WDMA_RING_SIZE 1024
+@@ -31,6 +33,10 @@
+ #define MTK_WED_PER_GROUP_PKT 128
+
+ #define MTK_WED_FBUF_SIZE 128
++#define MTK_WED_MIOD_CNT 16
++#define MTK_WED_FB_CMD_CNT 1024
++#define MTK_WED_RRO_QUE_CNT 8192
++#define MTK_WED_MIOD_ENTRY_CNT 128
+
+ static struct mtk_wed_hw *hw_list[2];
+ static DEFINE_MUTEX(hw_lock);
+@@ -65,12 +71,76 @@ wdma_set(struct mtk_wed_device *dev, u32
+ wdma_m32(dev, reg, 0, mask);
+ }
+
++static void
++wdma_clr(struct mtk_wed_device *dev, u32 reg, u32 mask)
++{
++ wdma_m32(dev, reg, mask, 0);
++}
++
++static u32
++wifi_r32(struct mtk_wed_device *dev, u32 reg)
++{
++ return readl(dev->wlan.base + reg);
++}
++
++static void
++wifi_w32(struct mtk_wed_device *dev, u32 reg, u32 val)
++{
++ writel(val, dev->wlan.base + reg);
++}
++
+ static u32
+ mtk_wed_read_reset(struct mtk_wed_device *dev)
+ {
+ return wed_r32(dev, MTK_WED_RESET);
+ }
+
++static u32
++mtk_wdma_read_reset(struct mtk_wed_device *dev)
++{
++ return wdma_r32(dev, MTK_WDMA_GLO_CFG);
++}
++
++static void
++mtk_wdma_rx_reset(struct mtk_wed_device *dev)
++{
++ u32 status, mask = MTK_WDMA_GLO_CFG_RX_DMA_BUSY;
++ int i;
++
++ wdma_clr(dev, MTK_WDMA_GLO_CFG, MTK_WDMA_GLO_CFG_RX_DMA_EN);
++ if (readx_poll_timeout(mtk_wdma_read_reset, dev, status,
++ !(status & mask), 0, 1000))
++ dev_err(dev->hw->dev, "rx reset failed\n");
++
++ for (i = 0; i < ARRAY_SIZE(dev->rx_wdma); i++) {
++ if (dev->rx_wdma[i].desc)
++ continue;
++
++ wdma_w32(dev,
++ MTK_WDMA_RING_RX(i) + MTK_WED_RING_OFS_CPU_IDX, 0);
++ }
++}
++
++static void
++mtk_wdma_tx_reset(struct mtk_wed_device *dev)
++{
++ u32 status, mask = MTK_WDMA_GLO_CFG_TX_DMA_BUSY;
++ int i;
++
++ wdma_clr(dev, MTK_WDMA_GLO_CFG, MTK_WDMA_GLO_CFG_TX_DMA_EN);
++ if (readx_poll_timeout(mtk_wdma_read_reset, dev, status,
++ !(status & mask), 0, 1000))
++ dev_err(dev->hw->dev, "tx reset failed\n");
++
++ for (i = 0; i < ARRAY_SIZE(dev->tx_wdma); i++) {
++ if (dev->tx_wdma[i].desc)
++ continue;
++
++ wdma_w32(dev,
++ MTK_WDMA_RING_TX(i) + MTK_WED_RING_OFS_CPU_IDX, 0);
++ }
++}
++
+ static void
+ mtk_wed_reset(struct mtk_wed_device *dev, u32 mask)
+ {
+@@ -82,6 +152,54 @@ mtk_wed_reset(struct mtk_wed_device *dev
+ WARN_ON_ONCE(1);
+ }
+
++static u32
++mtk_wed_wo_read_status(struct mtk_wed_device *dev)
++{
++ return wed_r32(dev, MTK_WED_SCR0 + 4 * MTK_WED_DUMMY_CR_WO_STATUS);
++}
++
++static void
++mtk_wed_wo_reset(struct mtk_wed_device *dev)
++{
++ struct mtk_wed_wo *wo = dev->hw->wed_wo;
++ u8 state = MTK_WED_WO_STATE_DISABLE;
++ void __iomem *reg;
++ u32 val;
++
++ mtk_wdma_tx_reset(dev);
++ mtk_wed_reset(dev, MTK_WED_RESET_WED);
++
++ mtk_wed_mcu_send_msg(wo, MTK_WED_MODULE_ID_WO,
++ MTK_WED_WO_CMD_CHANGE_STATE, &state,
++ sizeof(state), false);
++
++ if (readx_poll_timeout(mtk_wed_wo_read_status, dev, val,
++ val == MTK_WED_WOIF_DISABLE_DONE,
++ 100, MTK_WOCPU_TIMEOUT))
++ dev_err(dev->hw->dev, "failed to disable wed-wo\n");
++
++ reg = ioremap(MTK_WED_WO_CPU_MCUSYS_RESET_ADDR, 4);
++
++ val = readl(reg);
++ switch (dev->hw->index) {
++ case 0:
++ val |= MTK_WED_WO_CPU_WO0_MCUSYS_RESET_MASK;
++ writel(val, reg);
++ val &= ~MTK_WED_WO_CPU_WO0_MCUSYS_RESET_MASK;
++ writel(val, reg);
++ break;
++ case 1:
++ val |= MTK_WED_WO_CPU_WO1_MCUSYS_RESET_MASK;
++ writel(val, reg);
++ val &= ~MTK_WED_WO_CPU_WO1_MCUSYS_RESET_MASK;
++ writel(val, reg);
++ break;
++ default:
++ break;
++ }
++ iounmap(reg);
++}
++
+ static struct mtk_wed_hw *
+ mtk_wed_assign(struct mtk_wed_device *dev)
+ {
+@@ -116,7 +234,7 @@ out:
+ }
+
+ static int
+-mtk_wed_buffer_alloc(struct mtk_wed_device *dev)
++mtk_wed_tx_buffer_alloc(struct mtk_wed_device *dev)
+ {
+ struct mtk_wdma_desc *desc;
+ dma_addr_t desc_phys;
+@@ -133,16 +251,16 @@ mtk_wed_buffer_alloc(struct mtk_wed_devi
+ if (!page_list)
+ return -ENOMEM;
+
+- dev->buf_ring.size = ring_size;
+- dev->buf_ring.pages = page_list;
++ dev->tx_buf_ring.size = ring_size;
++ dev->tx_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;
++ dev->tx_buf_ring.desc = desc;
++ dev->tx_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;
+@@ -203,10 +321,10 @@ mtk_wed_buffer_alloc(struct mtk_wed_devi
+ }
+
+ static void
+-mtk_wed_free_buffer(struct mtk_wed_device *dev)
++mtk_wed_free_tx_buffer(struct mtk_wed_device *dev)
+ {
+- struct mtk_wdma_desc *desc = dev->buf_ring.desc;
+- void **page_list = dev->buf_ring.pages;
++ struct mtk_wdma_desc *desc = dev->tx_buf_ring.desc;
++ void **page_list = dev->tx_buf_ring.pages;
+ int page_idx;
+ int i;
+
+@@ -216,7 +334,8 @@ mtk_wed_free_buffer(struct mtk_wed_devic
+ if (!desc)
+ goto free_pagelist;
+
+- for (i = 0, page_idx = 0; i < dev->buf_ring.size; i += MTK_WED_BUF_PER_PAGE) {
++ for (i = 0, page_idx = 0; i < dev->tx_buf_ring.size;
++ i += MTK_WED_BUF_PER_PAGE) {
+ void *page = page_list[page_idx++];
+ dma_addr_t buf_addr;
+
+@@ -229,13 +348,59 @@ mtk_wed_free_buffer(struct mtk_wed_devic
+ __free_page(page);
+ }
+
+- dma_free_coherent(dev->hw->dev, dev->buf_ring.size * sizeof(*desc),
+- desc, dev->buf_ring.desc_phys);
++ dma_free_coherent(dev->hw->dev, dev->tx_buf_ring.size * sizeof(*desc),
++ desc, dev->tx_buf_ring.desc_phys);
+
+ free_pagelist:
+ kfree(page_list);
+ }
+
++static int
++mtk_wed_rx_buffer_alloc(struct mtk_wed_device *dev)
++{
++ struct mtk_rxbm_desc *desc;
++ dma_addr_t desc_phys;
++
++ dev->rx_buf_ring.size = dev->wlan.rx_nbuf;
++ desc = dma_alloc_coherent(dev->hw->dev,
++ dev->wlan.rx_nbuf * sizeof(*desc),
++ &desc_phys, GFP_KERNEL);
++ if (!desc)
++ return -ENOMEM;
++
++ dev->rx_buf_ring.desc = desc;
++ dev->rx_buf_ring.desc_phys = desc_phys;
++ dev->wlan.init_rx_buf(dev, dev->wlan.rx_npkt);
++
++ return 0;
++}
++
++static void
++mtk_wed_free_rx_buffer(struct mtk_wed_device *dev)
++{
++ struct mtk_rxbm_desc *desc = dev->rx_buf_ring.desc;
++
++ if (!desc)
++ return;
++
++ dev->wlan.release_rx_buf(dev);
++ dma_free_coherent(dev->hw->dev, dev->rx_buf_ring.size * sizeof(*desc),
++ desc, dev->rx_buf_ring.desc_phys);
++}
++
++static void
++mtk_wed_rx_buffer_hw_init(struct mtk_wed_device *dev)
++{
++ wed_w32(dev, MTK_WED_RX_BM_RX_DMAD,
++ FIELD_PREP(MTK_WED_RX_BM_RX_DMAD_SDL0, dev->wlan.rx_size));
++ wed_w32(dev, MTK_WED_RX_BM_BASE, dev->rx_buf_ring.desc_phys);
++ wed_w32(dev, MTK_WED_RX_BM_INIT_PTR, MTK_WED_RX_BM_INIT_SW_TAIL |
++ FIELD_PREP(MTK_WED_RX_BM_SW_TAIL, dev->wlan.rx_npkt));
++ wed_w32(dev, MTK_WED_RX_BM_DYN_ALLOC_TH,
++ FIELD_PREP(MTK_WED_RX_BM_DYN_ALLOC_TH_H, 0xffff));
++ wed_set(dev, MTK_WED_CTRL, MTK_WED_CTRL_WED_RX_BM_EN);
++}
++
+ static void
+ mtk_wed_free_ring(struct mtk_wed_device *dev, struct mtk_wed_ring *ring)
+ {
+@@ -247,6 +412,13 @@ mtk_wed_free_ring(struct mtk_wed_device
+ }
+
+ static void
++mtk_wed_free_rx_rings(struct mtk_wed_device *dev)
++{
++ mtk_wed_free_rx_buffer(dev);
++ mtk_wed_free_ring(dev, &dev->rro.ring);
++}
++
++static void
+ mtk_wed_free_tx_rings(struct mtk_wed_device *dev)
+ {
+ int i;
+@@ -291,6 +463,38 @@ mtk_wed_set_512_support(struct mtk_wed_d
+ }
+ }
+
++#define MTK_WFMDA_RX_DMA_EN BIT(2)
++static void
++mtk_wed_check_wfdma_rx_fill(struct mtk_wed_device *dev, int idx)
++{
++ u32 val;
++ int i;
++
++ if (!(dev->rx_ring[idx].flags & MTK_WED_RING_CONFIGURED))
++ return; /* queue is not configured by mt76 */
++
++ for (i = 0; i < 3; i++) {
++ u32 cur_idx;
++
++ cur_idx = wed_r32(dev,
++ MTK_WED_WPDMA_RING_RX_DATA(idx) +
++ MTK_WED_RING_OFS_CPU_IDX);
++ if (cur_idx == MTK_WED_RX_RING_SIZE - 1)
++ break;
++
++ usleep_range(100000, 200000);
++ }
++
++ if (i == 3) {
++ dev_err(dev->hw->dev, "rx dma enable failed\n");
++ return;
++ }
++
++ val = wifi_r32(dev, dev->wlan.wpdma_rx_glo - dev->wlan.phy_base) |
++ MTK_WFMDA_RX_DMA_EN;
++ wifi_w32(dev, dev->wlan.wpdma_rx_glo - dev->wlan.phy_base, val);
++}
++
+ static void
+ mtk_wed_dma_disable(struct mtk_wed_device *dev)
+ {
+@@ -304,20 +508,25 @@ mtk_wed_dma_disable(struct mtk_wed_devic
+ MTK_WED_GLO_CFG_TX_DMA_EN |
+ MTK_WED_GLO_CFG_RX_DMA_EN);
+
+- wdma_m32(dev, MTK_WDMA_GLO_CFG,
++ wdma_clr(dev, MTK_WDMA_GLO_CFG,
+ MTK_WDMA_GLO_CFG_TX_DMA_EN |
+ MTK_WDMA_GLO_CFG_RX_INFO1_PRERES |
+- MTK_WDMA_GLO_CFG_RX_INFO2_PRERES, 0);
++ MTK_WDMA_GLO_CFG_RX_INFO2_PRERES);
+
+ if (dev->hw->version == 1) {
+ regmap_write(dev->hw->mirror, dev->hw->index * 4, 0);
+- wdma_m32(dev, MTK_WDMA_GLO_CFG,
+- MTK_WDMA_GLO_CFG_RX_INFO3_PRERES, 0);
++ wdma_clr(dev, MTK_WDMA_GLO_CFG,
++ MTK_WDMA_GLO_CFG_RX_INFO3_PRERES);
+ } else {
+ wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
+ MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_PKT_PROC |
+ MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_CRX_SYNC);
+
++ wed_clr(dev, MTK_WED_WPDMA_RX_D_GLO_CFG,
++ MTK_WED_WPDMA_RX_D_RX_DRV_EN);
++ wed_clr(dev, MTK_WED_WDMA_GLO_CFG,
++ MTK_WED_WDMA_GLO_CFG_TX_DDONE_CHK);
++
+ mtk_wed_set_512_support(dev, false);
+ }
+ }
+@@ -338,6 +547,13 @@ mtk_wed_stop(struct mtk_wed_device *dev)
+ wdma_w32(dev, MTK_WDMA_INT_MASK, 0);
+ wdma_w32(dev, MTK_WDMA_INT_GRP2, 0);
+ wed_w32(dev, MTK_WED_WPDMA_INT_MASK, 0);
++
++ if (dev->hw->version == 1)
++ return;
++
++ wed_w32(dev, MTK_WED_EXT_INT_MASK1, 0);
++ wed_w32(dev, MTK_WED_EXT_INT_MASK2, 0);
++ wed_clr(dev, MTK_WED_CTRL, MTK_WED_CTRL_WED_RX_BM_EN);
+ }
+
+ static void
+@@ -353,11 +569,21 @@ mtk_wed_detach(struct mtk_wed_device *de
+ wdma_w32(dev, MTK_WDMA_RESET_IDX, 0);
+
+ mtk_wed_reset(dev, MTK_WED_RESET_WED);
++ if (mtk_wed_get_rx_capa(dev)) {
++ wdma_clr(dev, MTK_WDMA_GLO_CFG, MTK_WDMA_GLO_CFG_TX_DMA_EN);
++ wdma_w32(dev, MTK_WDMA_RESET_IDX, MTK_WDMA_RESET_IDX_TX);
++ wdma_w32(dev, MTK_WDMA_RESET_IDX, 0);
++ }
+
+- mtk_wed_free_buffer(dev);
++ mtk_wed_free_tx_buffer(dev);
+ mtk_wed_free_tx_rings(dev);
+- if (hw->version != 1)
++
++ if (mtk_wed_get_rx_capa(dev)) {
++ mtk_wed_wo_reset(dev);
++ mtk_wed_free_rx_rings(dev);
+ mtk_wed_wo_deinit(hw);
++ mtk_wdma_rx_reset(dev);
++ }
+
+ if (dev->wlan.bus_type == MTK_WED_BUS_PCIE) {
+ struct device_node *wlan_node;
+@@ -434,10 +660,12 @@ mtk_wed_set_wpdma(struct mtk_wed_device
+ } else {
+ mtk_wed_bus_init(dev);
+
+- wed_w32(dev, MTK_WED_WPDMA_CFG_BASE, dev->wlan.wpdma_int);
+- wed_w32(dev, MTK_WED_WPDMA_CFG_INT_MASK, dev->wlan.wpdma_mask);
+- wed_w32(dev, MTK_WED_WPDMA_CFG_TX, dev->wlan.wpdma_tx);
+- wed_w32(dev, MTK_WED_WPDMA_CFG_TX_FREE, dev->wlan.wpdma_txfree);
++ wed_w32(dev, MTK_WED_WPDMA_CFG_BASE, dev->wlan.wpdma_int);
++ wed_w32(dev, MTK_WED_WPDMA_CFG_INT_MASK, dev->wlan.wpdma_mask);
++ wed_w32(dev, MTK_WED_WPDMA_CFG_TX, dev->wlan.wpdma_tx);
++ wed_w32(dev, MTK_WED_WPDMA_CFG_TX_FREE, dev->wlan.wpdma_txfree);
++ wed_w32(dev, MTK_WED_WPDMA_RX_GLO_CFG, dev->wlan.wpdma_rx_glo);
++ wed_w32(dev, MTK_WED_WPDMA_RX_RING, dev->wlan.wpdma_rx);
+ }
+ }
+
+@@ -487,6 +715,132 @@ mtk_wed_hw_init_early(struct mtk_wed_dev
+ }
+ }
+
++static int
++mtk_wed_rro_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->desc_size = sizeof(*ring->desc);
++ ring->size = size;
++ memset(ring->desc, 0, size);
++
++ return 0;
++}
++
++#define MTK_WED_MIOD_COUNT (MTK_WED_MIOD_ENTRY_CNT * MTK_WED_MIOD_CNT)
++static int
++mtk_wed_rro_alloc(struct mtk_wed_device *dev)
++{
++ struct reserved_mem *rmem;
++ struct device_node *np;
++ int index;
++
++ index = of_property_match_string(dev->hw->node, "memory-region-names",
++ "wo-dlm");
++ if (index < 0)
++ return index;
++
++ np = of_parse_phandle(dev->hw->node, "memory-region", index);
++ if (!np)
++ return -ENODEV;
++
++ rmem = of_reserved_mem_lookup(np);
++ of_node_put(np);
++
++ if (!rmem)
++ return -ENODEV;
++
++ dev->rro.miod_phys = rmem->base;
++ dev->rro.fdbk_phys = MTK_WED_MIOD_COUNT + dev->rro.miod_phys;
++
++ return mtk_wed_rro_ring_alloc(dev, &dev->rro.ring,
++ MTK_WED_RRO_QUE_CNT);
++}
++
++static int
++mtk_wed_rro_cfg(struct mtk_wed_device *dev)
++{
++ struct mtk_wed_wo *wo = dev->hw->wed_wo;
++ struct {
++ struct {
++ __le32 base;
++ __le32 cnt;
++ __le32 unit;
++ } ring[2];
++ __le32 wed;
++ u8 version;
++ } req = {
++ .ring[0] = {
++ .base = cpu_to_le32(MTK_WED_WOCPU_VIEW_MIOD_BASE),
++ .cnt = cpu_to_le32(MTK_WED_MIOD_CNT),
++ .unit = cpu_to_le32(MTK_WED_MIOD_ENTRY_CNT),
++ },
++ .ring[1] = {
++ .base = cpu_to_le32(MTK_WED_WOCPU_VIEW_MIOD_BASE +
++ MTK_WED_MIOD_COUNT),
++ .cnt = cpu_to_le32(MTK_WED_FB_CMD_CNT),
++ .unit = cpu_to_le32(4),
++ },
++ };
++
++ return mtk_wed_mcu_send_msg(wo, MTK_WED_MODULE_ID_WO,
++ MTK_WED_WO_CMD_WED_CFG,
++ &req, sizeof(req), true);
++}
++
++static void
++mtk_wed_rro_hw_init(struct mtk_wed_device *dev)
++{
++ wed_w32(dev, MTK_WED_RROQM_MIOD_CFG,
++ FIELD_PREP(MTK_WED_RROQM_MIOD_MID_DW, 0x70 >> 2) |
++ FIELD_PREP(MTK_WED_RROQM_MIOD_MOD_DW, 0x10 >> 2) |
++ FIELD_PREP(MTK_WED_RROQM_MIOD_ENTRY_DW,
++ MTK_WED_MIOD_ENTRY_CNT >> 2));
++
++ wed_w32(dev, MTK_WED_RROQM_MIOD_CTRL0, dev->rro.miod_phys);
++ wed_w32(dev, MTK_WED_RROQM_MIOD_CTRL1,
++ FIELD_PREP(MTK_WED_RROQM_MIOD_CNT, MTK_WED_MIOD_CNT));
++ wed_w32(dev, MTK_WED_RROQM_FDBK_CTRL0, dev->rro.fdbk_phys);
++ wed_w32(dev, MTK_WED_RROQM_FDBK_CTRL1,
++ FIELD_PREP(MTK_WED_RROQM_FDBK_CNT, MTK_WED_FB_CMD_CNT));
++ wed_w32(dev, MTK_WED_RROQM_FDBK_CTRL2, 0);
++ wed_w32(dev, MTK_WED_RROQ_BASE_L, dev->rro.ring.desc_phys);
++
++ wed_set(dev, MTK_WED_RROQM_RST_IDX,
++ MTK_WED_RROQM_RST_IDX_MIOD |
++ MTK_WED_RROQM_RST_IDX_FDBK);
++
++ wed_w32(dev, MTK_WED_RROQM_RST_IDX, 0);
++ wed_w32(dev, MTK_WED_RROQM_MIOD_CTRL2, MTK_WED_MIOD_CNT - 1);
++ wed_set(dev, MTK_WED_CTRL, MTK_WED_CTRL_RX_RRO_QM_EN);
++}
++
++static void
++mtk_wed_route_qm_hw_init(struct mtk_wed_device *dev)
++{
++ wed_w32(dev, MTK_WED_RESET, MTK_WED_RESET_RX_ROUTE_QM);
++
++ for (;;) {
++ usleep_range(100, 200);
++ if (!(wed_r32(dev, MTK_WED_RESET) & MTK_WED_RESET_RX_ROUTE_QM))
++ break;
++ }
++
++ /* configure RX_ROUTE_QM */
++ wed_clr(dev, MTK_WED_RTQM_GLO_CFG, MTK_WED_RTQM_Q_RST);
++ wed_clr(dev, MTK_WED_RTQM_GLO_CFG, MTK_WED_RTQM_TXDMAD_FPORT);
++ wed_set(dev, MTK_WED_RTQM_GLO_CFG,
++ FIELD_PREP(MTK_WED_RTQM_TXDMAD_FPORT, 0x3 + dev->hw->index));
++ wed_clr(dev, MTK_WED_RTQM_GLO_CFG, MTK_WED_RTQM_Q_RST);
++ /* enable RX_ROUTE_QM */
++ wed_set(dev, MTK_WED_CTRL, MTK_WED_CTRL_RX_ROUTE_QM_EN);
++}
++
+ static void
+ mtk_wed_hw_init(struct mtk_wed_device *dev)
+ {
+@@ -498,11 +852,11 @@ mtk_wed_hw_init(struct mtk_wed_device *d
+ 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) |
++ dev->tx_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_BASE, dev->tx_buf_ring.desc_phys);
+
+ wed_w32(dev, MTK_WED_TX_BM_BUF_LEN, MTK_WED_PKT_SIZE);
+
+@@ -529,9 +883,9 @@ mtk_wed_hw_init(struct mtk_wed_device *d
+ wed_w32(dev, MTK_WED_TX_TKID_CTRL,
+ MTK_WED_TX_TKID_CTRL_PAUSE |
+ FIELD_PREP(MTK_WED_TX_TKID_CTRL_VLD_GRP_NUM,
+- dev->buf_ring.size / 128) |
++ dev->tx_buf_ring.size / 128) |
+ FIELD_PREP(MTK_WED_TX_TKID_CTRL_RSV_GRP_NUM,
+- dev->buf_ring.size / 128));
++ dev->tx_buf_ring.size / 128));
+ wed_w32(dev, MTK_WED_TX_TKID_DYN_THR,
+ FIELD_PREP(MTK_WED_TX_TKID_DYN_THR_LO, 0) |
+ MTK_WED_TX_TKID_DYN_THR_HI);
+@@ -539,18 +893,28 @@ mtk_wed_hw_init(struct mtk_wed_device *d
+
+ mtk_wed_reset(dev, MTK_WED_RESET_TX_BM);
+
+- if (dev->hw->version == 1)
++ if (dev->hw->version == 1) {
+ wed_set(dev, MTK_WED_CTRL,
+ MTK_WED_CTRL_WED_TX_BM_EN |
+ MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
+- else
++ } else {
+ wed_clr(dev, MTK_WED_TX_TKID_CTRL, MTK_WED_TX_TKID_CTRL_PAUSE);
++ /* rx hw init */
++ wed_w32(dev, MTK_WED_WPDMA_RX_D_RST_IDX,
++ MTK_WED_WPDMA_RX_D_RST_CRX_IDX |
++ MTK_WED_WPDMA_RX_D_RST_DRV_IDX);
++ wed_w32(dev, MTK_WED_WPDMA_RX_D_RST_IDX, 0);
++
++ mtk_wed_rx_buffer_hw_init(dev);
++ mtk_wed_rro_hw_init(dev);
++ mtk_wed_route_qm_hw_init(dev);
++ }
+
+ wed_clr(dev, MTK_WED_TX_BM_CTRL, MTK_WED_TX_BM_CTRL_PAUSE);
+ }
+
+ static void
+-mtk_wed_ring_reset(struct mtk_wed_ring *ring, int size)
++mtk_wed_ring_reset(struct mtk_wed_ring *ring, int size, bool tx)
+ {
+ void *head = (void *)ring->desc;
+ int i;
+@@ -560,7 +924,10 @@ mtk_wed_ring_reset(struct mtk_wed_ring *
+
+ desc = (struct mtk_wdma_desc *)(head + i * ring->desc_size);
+ desc->buf0 = 0;
+- desc->ctrl = cpu_to_le32(MTK_WDMA_DESC_CTRL_DMA_DONE);
++ if (tx)
++ desc->ctrl = cpu_to_le32(MTK_WDMA_DESC_CTRL_DMA_DONE);
++ else
++ desc->ctrl = cpu_to_le32(MTK_WFDMA_DESC_CTRL_TO_HOST);
+ desc->buf1 = 0;
+ desc->info = 0;
+ }
+@@ -616,7 +983,8 @@ mtk_wed_reset_dma(struct mtk_wed_device
+ if (!dev->tx_ring[i].desc)
+ continue;
+
+- mtk_wed_ring_reset(&dev->tx_ring[i], MTK_WED_TX_RING_SIZE);
++ mtk_wed_ring_reset(&dev->tx_ring[i], MTK_WED_TX_RING_SIZE,
++ true);
+ }
+
+ if (mtk_wed_poll_busy(dev))
+@@ -634,6 +1002,9 @@ mtk_wed_reset_dma(struct mtk_wed_device
+ wdma_w32(dev, MTK_WDMA_RESET_IDX, MTK_WDMA_RESET_IDX_RX);
+ wdma_w32(dev, MTK_WDMA_RESET_IDX, 0);
+
++ if (mtk_wed_get_rx_capa(dev))
++ mtk_wdma_rx_reset(dev);
++
+ if (busy) {
+ mtk_wed_reset(dev, MTK_WED_RESET_WDMA_INT_AGENT);
+ mtk_wed_reset(dev, MTK_WED_RESET_WDMA_RX_DRV);
+@@ -668,12 +1039,11 @@ mtk_wed_reset_dma(struct mtk_wed_device
+ 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, u32 desc_size)
++ int size, u32 desc_size, bool tx)
+ {
+ ring->desc = dma_alloc_coherent(dev->hw->dev, size * desc_size,
+ &ring->desc_phys, GFP_KERNEL);
+@@ -682,7 +1052,7 @@ mtk_wed_ring_alloc(struct mtk_wed_device
+
+ ring->desc_size = desc_size;
+ ring->size = size;
+- mtk_wed_ring_reset(ring, size);
++ mtk_wed_ring_reset(ring, size, tx);
+
+ return 0;
+ }
+@@ -691,9 +1061,14 @@ static int
+ mtk_wed_wdma_rx_ring_setup(struct mtk_wed_device *dev, int idx, int size)
+ {
+ u32 desc_size = sizeof(struct mtk_wdma_desc) * dev->hw->version;
+- struct mtk_wed_ring *wdma = &dev->rx_wdma[idx];
++ struct mtk_wed_ring *wdma;
+
+- if (mtk_wed_ring_alloc(dev, wdma, MTK_WED_WDMA_RING_SIZE, desc_size))
++ if (idx >= ARRAY_SIZE(dev->rx_wdma))
++ return -EINVAL;
++
++ wdma = &dev->rx_wdma[idx];
++ if (mtk_wed_ring_alloc(dev, wdma, MTK_WED_WDMA_RING_SIZE, desc_size,
++ true))
+ return -ENOMEM;
+
+ wdma_w32(dev, MTK_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_BASE,
+@@ -710,6 +1085,60 @@ mtk_wed_wdma_rx_ring_setup(struct mtk_we
+ return 0;
+ }
+
++static int
++mtk_wed_wdma_tx_ring_setup(struct mtk_wed_device *dev, int idx, int size)
++{
++ u32 desc_size = sizeof(struct mtk_wdma_desc) * dev->hw->version;
++ struct mtk_wed_ring *wdma;
++
++ if (idx >= ARRAY_SIZE(dev->tx_wdma))
++ return -EINVAL;
++
++ wdma = &dev->tx_wdma[idx];
++ if (mtk_wed_ring_alloc(dev, wdma, MTK_WED_WDMA_RING_SIZE, desc_size,
++ true))
++ return -ENOMEM;
++
++ wdma_w32(dev, MTK_WDMA_RING_TX(idx) + MTK_WED_RING_OFS_BASE,
++ wdma->desc_phys);
++ wdma_w32(dev, MTK_WDMA_RING_TX(idx) + MTK_WED_RING_OFS_COUNT,
++ size);
++ wdma_w32(dev, MTK_WDMA_RING_TX(idx) + MTK_WED_RING_OFS_CPU_IDX, 0);
++ wdma_w32(dev, MTK_WDMA_RING_TX(idx) + MTK_WED_RING_OFS_DMA_IDX, 0);
++
++ if (!idx) {
++ wed_w32(dev, MTK_WED_WDMA_RING_TX + MTK_WED_RING_OFS_BASE,
++ wdma->desc_phys);
++ wed_w32(dev, MTK_WED_WDMA_RING_TX + MTK_WED_RING_OFS_COUNT,
++ size);
++ wed_w32(dev, MTK_WED_WDMA_RING_TX + MTK_WED_RING_OFS_CPU_IDX,
++ 0);
++ wed_w32(dev, MTK_WED_WDMA_RING_TX + MTK_WED_RING_OFS_DMA_IDX,
++ 0);
++ }
++
++ return 0;
++}
++
++static void
++mtk_wed_ppe_check(struct mtk_wed_device *dev, struct sk_buff *skb,
++ u32 reason, u32 hash)
++{
++ struct mtk_eth *eth = dev->hw->eth;
++ struct ethhdr *eh;
++
++ if (!skb)
++ return;
++
++ if (reason != MTK_PPE_CPU_REASON_HIT_UNBIND_RATE_REACHED)
++ return;
++
++ skb_set_mac_header(skb, 0);
++ eh = eth_hdr(skb);
++ skb->protocol = eh->h_proto;
++ mtk_ppe_check_skb(eth->ppe[dev->hw->index], skb, hash);
++}
++
+ static void
+ mtk_wed_configure_irq(struct mtk_wed_device *dev, u32 irq_mask)
+ {
+@@ -732,6 +1161,8 @@ mtk_wed_configure_irq(struct mtk_wed_dev
+
+ wed_clr(dev, MTK_WED_WDMA_INT_CTRL, wdma_mask);
+ } else {
++ wdma_mask |= FIELD_PREP(MTK_WDMA_INT_MASK_TX_DONE,
++ GENMASK(1, 0));
+ /* initail tx interrupt trigger */
+ wed_w32(dev, MTK_WED_WPDMA_INT_CTRL_TX,
+ MTK_WED_WPDMA_INT_CTRL_TX0_DONE_EN |
+@@ -750,6 +1181,16 @@ mtk_wed_configure_irq(struct mtk_wed_dev
+ FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_TX_FREE_DONE_TRIG,
+ dev->wlan.txfree_tbit));
+
++ wed_w32(dev, MTK_WED_WPDMA_INT_CTRL_RX,
++ MTK_WED_WPDMA_INT_CTRL_RX0_EN |
++ MTK_WED_WPDMA_INT_CTRL_RX0_CLR |
++ MTK_WED_WPDMA_INT_CTRL_RX1_EN |
++ MTK_WED_WPDMA_INT_CTRL_RX1_CLR |
++ FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_RX0_DONE_TRIG,
++ dev->wlan.rx_tbit[0]) |
++ FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_RX1_DONE_TRIG,
++ dev->wlan.rx_tbit[1]));
++
+ wed_w32(dev, MTK_WED_WDMA_INT_CLR, wdma_mask);
+ wed_set(dev, MTK_WED_WDMA_INT_CTRL,
+ FIELD_PREP(MTK_WED_WDMA_INT_CTRL_POLL_SRC_SEL,
+@@ -787,9 +1228,15 @@ mtk_wed_dma_enable(struct mtk_wed_device
+ wdma_set(dev, MTK_WDMA_GLO_CFG,
+ MTK_WDMA_GLO_CFG_RX_INFO3_PRERES);
+ } else {
++ int i;
++
+ wed_set(dev, MTK_WED_WPDMA_CTRL,
+ MTK_WED_WPDMA_CTRL_SDL1_FIXED);
+
++ wed_set(dev, MTK_WED_WDMA_GLO_CFG,
++ MTK_WED_WDMA_GLO_CFG_TX_DRV_EN |
++ MTK_WED_WDMA_GLO_CFG_TX_DDONE_CHK);
++
+ wed_set(dev, MTK_WED_WPDMA_GLO_CFG,
+ MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_PKT_PROC |
+ MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_CRX_SYNC);
+@@ -797,6 +1244,15 @@ mtk_wed_dma_enable(struct mtk_wed_device
+ wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
+ MTK_WED_WPDMA_GLO_CFG_TX_TKID_KEEP |
+ MTK_WED_WPDMA_GLO_CFG_TX_DMAD_DW3_PREV);
++
++ wed_set(dev, MTK_WED_WPDMA_RX_D_GLO_CFG,
++ MTK_WED_WPDMA_RX_D_RX_DRV_EN |
++ FIELD_PREP(MTK_WED_WPDMA_RX_D_RXD_READ_LEN, 0x18) |
++ FIELD_PREP(MTK_WED_WPDMA_RX_D_INIT_PHASE_RXEN_SEL,
++ 0x2));
++
++ for (i = 0; i < MTK_WED_RX_QUEUES; i++)
++ mtk_wed_check_wfdma_rx_fill(dev, i);
+ }
+ }
+
+@@ -822,7 +1278,19 @@ mtk_wed_start(struct mtk_wed_device *dev
+ val |= BIT(0) | (BIT(1) * !!dev->hw->index);
+ regmap_write(dev->hw->mirror, dev->hw->index * 4, val);
+ } else {
+- mtk_wed_set_512_support(dev, true);
++ /* driver set mid ready and only once */
++ wed_w32(dev, MTK_WED_EXT_INT_MASK1,
++ MTK_WED_EXT_INT_STATUS_WPDMA_MID_RDY);
++ wed_w32(dev, MTK_WED_EXT_INT_MASK2,
++ MTK_WED_EXT_INT_STATUS_WPDMA_MID_RDY);
++
++ wed_r32(dev, MTK_WED_EXT_INT_MASK1);
++ wed_r32(dev, MTK_WED_EXT_INT_MASK2);
++
++ if (mtk_wed_rro_cfg(dev))
++ return;
++
++ mtk_wed_set_512_support(dev, dev->wlan.wcid_512);
+ }
+
+ mtk_wed_dma_enable(dev);
+@@ -856,7 +1324,7 @@ mtk_wed_attach(struct mtk_wed_device *de
+ if (!hw) {
+ module_put(THIS_MODULE);
+ ret = -ENODEV;
+- goto out;
++ goto unlock;
+ }
+
+ device = dev->wlan.bus_type == MTK_WED_BUS_PCIE
+@@ -869,15 +1337,24 @@ mtk_wed_attach(struct mtk_wed_device *de
+ dev->dev = hw->dev;
+ dev->irq = hw->irq;
+ dev->wdma_idx = hw->index;
++ dev->version = hw->version;
+
+ 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);
++ ret = mtk_wed_tx_buffer_alloc(dev);
++ if (ret)
+ goto out;
++
++ if (mtk_wed_get_rx_capa(dev)) {
++ ret = mtk_wed_rx_buffer_alloc(dev);
++ if (ret)
++ goto out;
++
++ ret = mtk_wed_rro_alloc(dev);
++ if (ret)
++ goto out;
+ }
+
+ mtk_wed_hw_init_early(dev);
+@@ -886,8 +1363,10 @@ mtk_wed_attach(struct mtk_wed_device *de
+ BIT(hw->index), 0);
+ else
+ ret = mtk_wed_wo_init(hw);
+-
+ out:
++ if (ret)
++ mtk_wed_detach(dev);
++unlock:
+ mutex_unlock(&hw_lock);
+
+ return ret;
+@@ -910,10 +1389,11 @@ mtk_wed_tx_ring_setup(struct mtk_wed_dev
+ * WDMA RX.
+ */
+
+- BUG_ON(idx >= ARRAY_SIZE(dev->tx_ring));
++ if (WARN_ON(idx >= ARRAY_SIZE(dev->tx_ring)))
++ return -EINVAL;
+
+ if (mtk_wed_ring_alloc(dev, ring, MTK_WED_TX_RING_SIZE,
+- sizeof(*ring->desc)))
++ sizeof(*ring->desc), true))
+ return -ENOMEM;
+
+ if (mtk_wed_wdma_rx_ring_setup(dev, idx, MTK_WED_WDMA_RING_SIZE))
+@@ -960,6 +1440,37 @@ mtk_wed_txfree_ring_setup(struct mtk_wed
+ return 0;
+ }
+
++static int
++mtk_wed_rx_ring_setup(struct mtk_wed_device *dev, int idx, void __iomem *regs)
++{
++ struct mtk_wed_ring *ring = &dev->rx_ring[idx];
++
++ if (WARN_ON(idx >= ARRAY_SIZE(dev->rx_ring)))
++ return -EINVAL;
++
++ if (mtk_wed_ring_alloc(dev, ring, MTK_WED_RX_RING_SIZE,
++ sizeof(*ring->desc), false))
++ return -ENOMEM;
++
++ if (mtk_wed_wdma_tx_ring_setup(dev, idx, MTK_WED_WDMA_RING_SIZE))
++ return -ENOMEM;
++
++ ring->reg_base = MTK_WED_RING_RX_DATA(idx);
++ ring->wpdma = regs;
++ ring->flags |= MTK_WED_RING_CONFIGURED;
++
++ /* WPDMA -> WED */
++ wpdma_rx_w32(dev, idx, MTK_WED_RING_OFS_BASE, ring->desc_phys);
++ wpdma_rx_w32(dev, idx, MTK_WED_RING_OFS_COUNT, MTK_WED_RX_RING_SIZE);
++
++ wed_w32(dev, MTK_WED_WPDMA_RING_RX_DATA(idx) + MTK_WED_RING_OFS_BASE,
++ ring->desc_phys);
++ wed_w32(dev, MTK_WED_WPDMA_RING_RX_DATA(idx) + MTK_WED_RING_OFS_COUNT,
++ MTK_WED_RX_RING_SIZE);
++
++ return 0;
++}
++
+ static u32
+ mtk_wed_irq_get(struct mtk_wed_device *dev, u32 mask)
+ {
+@@ -1056,7 +1567,9 @@ void mtk_wed_add_hw(struct device_node *
+ static const struct mtk_wed_ops wed_ops = {
+ .attach = mtk_wed_attach,
+ .tx_ring_setup = mtk_wed_tx_ring_setup,
++ .rx_ring_setup = mtk_wed_rx_ring_setup,
+ .txfree_ring_setup = mtk_wed_txfree_ring_setup,
++ .msg_update = mtk_wed_mcu_msg_update,
+ .start = mtk_wed_start,
+ .stop = mtk_wed_stop,
+ .reset_dma = mtk_wed_reset_dma,
+@@ -1065,6 +1578,7 @@ void mtk_wed_add_hw(struct device_node *
+ .irq_get = mtk_wed_irq_get,
+ .irq_set_mask = mtk_wed_irq_set_mask,
+ .detach = mtk_wed_detach,
++ .ppe_check = mtk_wed_ppe_check,
+ };
+ struct device_node *eth_np = eth->dev->of_node;
+ struct platform_device *pdev;
+--- a/drivers/net/ethernet/mediatek/mtk_wed.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed.h
+@@ -87,6 +87,24 @@ wpdma_tx_w32(struct mtk_wed_device *dev,
+ }
+
+ static inline u32
++wpdma_rx_r32(struct mtk_wed_device *dev, int ring, u32 reg)
++{
++ if (!dev->rx_ring[ring].wpdma)
++ return 0;
++
++ return readl(dev->rx_ring[ring].wpdma + reg);
++}
++
++static inline void
++wpdma_rx_w32(struct mtk_wed_device *dev, int ring, u32 reg, u32 val)
++{
++ if (!dev->rx_ring[ring].wpdma)
++ return;
++
++ writel(val, dev->rx_ring[ring].wpdma + reg);
++}
++
++static inline u32
+ wpdma_txfree_r32(struct mtk_wed_device *dev, u32 reg)
+ {
+ if (!dev->txfree_ring.wpdma)
+@@ -128,6 +146,7 @@ static inline int mtk_wed_flow_add(int i
+ static inline void mtk_wed_flow_remove(int index)
+ {
+ }
++
+ #endif
+
+ #ifdef CONFIG_DEBUG_FS
+--- a/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
+@@ -10,6 +10,7 @@
+ #include <linux/of_reserved_mem.h>
+ #include <linux/mfd/syscon.h>
+ #include <linux/soc/mediatek/mtk_wed.h>
++#include <asm/unaligned.h>
+
+ #include "mtk_wed_regs.h"
+ #include "mtk_wed_wo.h"
+@@ -60,24 +61,37 @@ void mtk_wed_mcu_rx_event(struct mtk_wed
+ wake_up(&wo->mcu.wait);
+ }
+
++static void
++mtk_wed_update_rx_stats(struct mtk_wed_device *wed, struct sk_buff *skb)
++{
++ u32 count = get_unaligned_le32(skb->data);
++ struct mtk_wed_wo_rx_stats *stats;
++ int i;
++
++ if (count * sizeof(*stats) > skb->len - sizeof(u32))
++ return;
++
++ stats = (struct mtk_wed_wo_rx_stats *)(skb->data + sizeof(u32));
++ for (i = 0 ; i < count ; i++)
++ wed->wlan.update_wo_rx_stats(wed, &stats[i]);
++}
++
+ void mtk_wed_mcu_rx_unsolicited_event(struct mtk_wed_wo *wo,
+ struct sk_buff *skb)
+ {
+ struct mtk_wed_mcu_hdr *hdr = (struct mtk_wed_mcu_hdr *)skb->data;
+
+- switch (hdr->cmd) {
+- case MTK_WED_WO_EVT_LOG_DUMP: {
+- const char *msg = (const char *)(skb->data + sizeof(*hdr));
++ skb_pull(skb, sizeof(*hdr));
+
+- dev_notice(wo->hw->dev, "%s\n", msg);
++ switch (hdr->cmd) {
++ case MTK_WED_WO_EVT_LOG_DUMP:
++ dev_notice(wo->hw->dev, "%s\n", skb->data);
+ break;
+- }
+ case MTK_WED_WO_EVT_PROFILING: {
+- struct mtk_wed_wo_log_info *info;
+- u32 count = (skb->len - sizeof(*hdr)) / sizeof(*info);
++ struct mtk_wed_wo_log_info *info = (void *)skb->data;
++ u32 count = skb->len / sizeof(*info);
+ int i;
+
+- info = (struct mtk_wed_wo_log_info *)(skb->data + sizeof(*hdr));
+ for (i = 0 ; i < count ; i++)
+ dev_notice(wo->hw->dev,
+ "SN:%u latency: total=%u, rro:%u, mod:%u\n",
+@@ -88,6 +102,7 @@ void mtk_wed_mcu_rx_unsolicited_event(st
+ break;
+ }
+ case MTK_WED_WO_EVT_RXCNT_INFO:
++ mtk_wed_update_rx_stats(wo->hw->wed_dev, skb);
+ break;
+ default:
+ break;
+@@ -144,6 +159,8 @@ mtk_wed_mcu_parse_response(struct mtk_we
+ skb_pull(skb, sizeof(*hdr));
+ switch (cmd) {
+ case MTK_WED_WO_CMD_RXCNT_INFO:
++ mtk_wed_update_rx_stats(wo->hw->wed_dev, skb);
++ break;
+ default:
+ break;
+ }
+@@ -182,6 +199,18 @@ unlock:
+ return ret;
+ }
+
++int mtk_wed_mcu_msg_update(struct mtk_wed_device *dev, int id, void *data,
++ int len)
++{
++ struct mtk_wed_wo *wo = dev->hw->wed_wo;
++
++ if (dev->hw->version == 1)
++ return 0;
++
++ return mtk_wed_mcu_send_msg(wo, MTK_WED_MODULE_ID_WO, id, data, len,
++ true);
++}
++
+ static int
+ mtk_wed_get_memory_region(struct mtk_wed_wo *wo,
+ struct mtk_wed_wo_memory_region *region)
+--- a/drivers/net/ethernet/mediatek/mtk_wed_regs.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
+@@ -4,6 +4,7 @@
+ #ifndef __MTK_WED_REGS_H
+ #define __MTK_WED_REGS_H
+
++#define MTK_WFDMA_DESC_CTRL_TO_HOST BIT(8)
+ #define MTK_WDMA_DESC_CTRL_LEN1 GENMASK(14, 0)
+ #define MTK_WDMA_DESC_CTRL_LEN1_V2 GENMASK(13, 0)
+ #define MTK_WDMA_DESC_CTRL_LAST_SEG1 BIT(15)
+@@ -28,6 +29,8 @@ struct mtk_wdma_desc {
+ #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_RX_RRO_QM BIT(20)
++#define MTK_WED_RESET_RX_ROUTE_QM BIT(21)
+ #define MTK_WED_RESET_WED BIT(31)
+
+ #define MTK_WED_CTRL 0x00c
+@@ -39,8 +42,12 @@ struct mtk_wdma_desc {
+ #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_WED_RX_BM_EN BIT(12)
++#define MTK_WED_CTRL_WED_RX_BM_BUSY BIT(13)
++#define MTK_WED_CTRL_RX_RRO_QM_EN BIT(14)
++#define MTK_WED_CTRL_RX_RRO_QM_BUSY BIT(15)
++#define MTK_WED_CTRL_RX_ROUTE_QM_EN BIT(16)
++#define MTK_WED_CTRL_RX_ROUTE_QM_BUSY BIT(17)
+ #define MTK_WED_CTRL_FINAL_DIDX_READ BIT(24)
+ #define MTK_WED_CTRL_ETH_DMAD_FMT BIT(25)
+ #define MTK_WED_CTRL_MIB_READ_CLEAR BIT(28)
+@@ -62,6 +69,9 @@ struct mtk_wdma_desc {
+ #define MTK_WED_EXT_INT_STATUS_TX_DMA_R_RESP_ERR BIT(22)
+ #define MTK_WED_EXT_INT_STATUS_TX_DMA_W_RESP_ERR BIT(23)
+ #define MTK_WED_EXT_INT_STATUS_RX_DRV_DMA_RECYCLE BIT(24)
++#define MTK_WED_EXT_INT_STATUS_RX_DRV_GET_BM_DMAD_SKIP BIT(25)
++#define MTK_WED_EXT_INT_STATUS_WPDMA_RX_D_DRV_ERR BIT(26)
++#define MTK_WED_EXT_INT_STATUS_WPDMA_MID_RDY BIT(27)
+ #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 | \
+@@ -71,6 +81,8 @@ struct mtk_wdma_desc {
+ MTK_WED_EXT_INT_STATUS_TX_DMA_R_RESP_ERR)
+
+ #define MTK_WED_EXT_INT_MASK 0x028
++#define MTK_WED_EXT_INT_MASK1 0x02c
++#define MTK_WED_EXT_INT_MASK2 0x030
+
+ #define MTK_WED_STATUS 0x060
+ #define MTK_WED_STATUS_TX GENMASK(15, 8)
+@@ -151,6 +163,7 @@ struct mtk_wdma_desc {
+ #define MTK_WED_RING_TX(_n) (0x300 + (_n) * 0x10)
+
+ #define MTK_WED_RING_RX(_n) (0x400 + (_n) * 0x10)
++#define MTK_WED_RING_RX_DATA(_n) (0x420 + (_n) * 0x10)
+
+ #define MTK_WED_SCR0 0x3c0
+ #define MTK_WED_WPDMA_INT_TRIGGER 0x504
+@@ -213,6 +226,12 @@ struct mtk_wdma_desc {
+ #define MTK_WED_WPDMA_INT_CTRL_TX1_DONE_TRIG GENMASK(14, 10)
+
+ #define MTK_WED_WPDMA_INT_CTRL_RX 0x534
++#define MTK_WED_WPDMA_INT_CTRL_RX0_EN BIT(0)
++#define MTK_WED_WPDMA_INT_CTRL_RX0_CLR BIT(1)
++#define MTK_WED_WPDMA_INT_CTRL_RX0_DONE_TRIG GENMASK(6, 2)
++#define MTK_WED_WPDMA_INT_CTRL_RX1_EN BIT(8)
++#define MTK_WED_WPDMA_INT_CTRL_RX1_CLR BIT(9)
++#define MTK_WED_WPDMA_INT_CTRL_RX1_DONE_TRIG GENMASK(14, 10)
+
+ #define MTK_WED_WPDMA_INT_CTRL_TX_FREE 0x538
+ #define MTK_WED_WPDMA_INT_CTRL_TX_FREE_DONE_EN BIT(0)
+@@ -242,11 +261,34 @@ struct mtk_wdma_desc {
+
+ #define MTK_WED_WPDMA_RING_TX(_n) (0x600 + (_n) * 0x10)
+ #define MTK_WED_WPDMA_RING_RX(_n) (0x700 + (_n) * 0x10)
++#define MTK_WED_WPDMA_RING_RX_DATA(_n) (0x730 + (_n) * 0x10)
++
++#define MTK_WED_WPDMA_RX_D_GLO_CFG 0x75c
++#define MTK_WED_WPDMA_RX_D_RX_DRV_EN BIT(0)
++#define MTK_WED_WPDMA_RX_D_INIT_PHASE_RXEN_SEL GENMASK(11, 7)
++#define MTK_WED_WPDMA_RX_D_RXD_READ_LEN GENMASK(31, 24)
++
++#define MTK_WED_WPDMA_RX_D_RST_IDX 0x760
++#define MTK_WED_WPDMA_RX_D_RST_CRX_IDX GENMASK(17, 16)
++#define MTK_WED_WPDMA_RX_D_RST_DRV_IDX GENMASK(25, 24)
++
++#define MTK_WED_WPDMA_RX_GLO_CFG 0x76c
++#define MTK_WED_WPDMA_RX_RING 0x770
++
++#define MTK_WED_WPDMA_RX_D_MIB(_n) (0x774 + (_n) * 4)
++#define MTK_WED_WPDMA_RX_D_PROCESSED_MIB(_n) (0x784 + (_n) * 4)
++#define MTK_WED_WPDMA_RX_D_COHERENT_MIB 0x78c
++
++#define MTK_WED_WDMA_RING_TX 0x800
++
++#define MTK_WED_WDMA_TX_MIB 0x810
++
+ #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_TX_DDONE_CHK BIT(1)
+ #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)
+@@ -291,6 +333,20 @@ struct mtk_wdma_desc {
+ #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_RX_BM_RX_DMAD 0xd80
++#define MTK_WED_RX_BM_RX_DMAD_SDL0 GENMASK(13, 0)
++
++#define MTK_WED_RX_BM_BASE 0xd84
++#define MTK_WED_RX_BM_INIT_PTR 0xd88
++#define MTK_WED_RX_BM_SW_TAIL GENMASK(15, 0)
++#define MTK_WED_RX_BM_INIT_SW_TAIL BIT(16)
++
++#define MTK_WED_RX_PTR 0xd8c
++
++#define MTK_WED_RX_BM_DYN_ALLOC_TH 0xdb4
++#define MTK_WED_RX_BM_DYN_ALLOC_TH_H GENMASK(31, 16)
++#define MTK_WED_RX_BM_DYN_ALLOC_TH_L GENMASK(15, 0)
++
+ #define MTK_WED_RING_OFS_BASE 0x00
+ #define MTK_WED_RING_OFS_COUNT 0x04
+ #define MTK_WED_RING_OFS_CPU_IDX 0x08
+@@ -301,7 +357,9 @@ struct mtk_wdma_desc {
+
+ #define MTK_WDMA_GLO_CFG 0x204
+ #define MTK_WDMA_GLO_CFG_TX_DMA_EN BIT(0)
++#define MTK_WDMA_GLO_CFG_TX_DMA_BUSY BIT(1)
+ #define MTK_WDMA_GLO_CFG_RX_DMA_EN BIT(2)
++#define MTK_WDMA_GLO_CFG_RX_DMA_BUSY BIT(3)
+ #define MTK_WDMA_GLO_CFG_RX_INFO3_PRERES BIT(26)
+ #define MTK_WDMA_GLO_CFG_RX_INFO2_PRERES BIT(27)
+ #define MTK_WDMA_GLO_CFG_RX_INFO1_PRERES BIT(28)
+@@ -330,4 +388,70 @@ struct mtk_wdma_desc {
+ /* DMA channel mapping */
+ #define HIFSYS_DMA_AG_MAP 0x008
+
++#define MTK_WED_RTQM_GLO_CFG 0xb00
++#define MTK_WED_RTQM_BUSY BIT(1)
++#define MTK_WED_RTQM_Q_RST BIT(2)
++#define MTK_WED_RTQM_Q_DBG_BYPASS BIT(5)
++#define MTK_WED_RTQM_TXDMAD_FPORT GENMASK(23, 20)
++
++#define MTK_WED_RTQM_R2H_MIB(_n) (0xb70 + (_n) * 0x4)
++#define MTK_WED_RTQM_R2Q_MIB(_n) (0xb78 + (_n) * 0x4)
++#define MTK_WED_RTQM_Q2N_MIB 0xb80
++#define MTK_WED_RTQM_Q2H_MIB(_n) (0xb84 + (_n) * 0x4)
++
++#define MTK_WED_RTQM_Q2B_MIB 0xb8c
++#define MTK_WED_RTQM_PFDBK_MIB 0xb90
++
++#define MTK_WED_RROQM_GLO_CFG 0xc04
++#define MTK_WED_RROQM_RST_IDX 0xc08
++#define MTK_WED_RROQM_RST_IDX_MIOD BIT(0)
++#define MTK_WED_RROQM_RST_IDX_FDBK BIT(4)
++
++#define MTK_WED_RROQM_MIOD_CTRL0 0xc40
++#define MTK_WED_RROQM_MIOD_CTRL1 0xc44
++#define MTK_WED_RROQM_MIOD_CNT GENMASK(11, 0)
++
++#define MTK_WED_RROQM_MIOD_CTRL2 0xc48
++#define MTK_WED_RROQM_MIOD_CTRL3 0xc4c
++
++#define MTK_WED_RROQM_FDBK_CTRL0 0xc50
++#define MTK_WED_RROQM_FDBK_CTRL1 0xc54
++#define MTK_WED_RROQM_FDBK_CNT GENMASK(11, 0)
++
++#define MTK_WED_RROQM_FDBK_CTRL2 0xc58
++
++#define MTK_WED_RROQ_BASE_L 0xc80
++#define MTK_WED_RROQ_BASE_H 0xc84
++
++#define MTK_WED_RROQM_MIOD_CFG 0xc8c
++#define MTK_WED_RROQM_MIOD_MID_DW GENMASK(5, 0)
++#define MTK_WED_RROQM_MIOD_MOD_DW GENMASK(13, 8)
++#define MTK_WED_RROQM_MIOD_ENTRY_DW GENMASK(22, 16)
++
++#define MTK_WED_RROQM_MID_MIB 0xcc0
++#define MTK_WED_RROQM_MOD_MIB 0xcc4
++#define MTK_WED_RROQM_MOD_COHERENT_MIB 0xcc8
++#define MTK_WED_RROQM_FDBK_MIB 0xcd0
++#define MTK_WED_RROQM_FDBK_COHERENT_MIB 0xcd4
++#define MTK_WED_RROQM_FDBK_IND_MIB 0xce0
++#define MTK_WED_RROQM_FDBK_ENQ_MIB 0xce4
++#define MTK_WED_RROQM_FDBK_ANC_MIB 0xce8
++#define MTK_WED_RROQM_FDBK_ANC2H_MIB 0xcec
++
++#define MTK_WED_RX_BM_RX_DMAD 0xd80
++#define MTK_WED_RX_BM_BASE 0xd84
++#define MTK_WED_RX_BM_INIT_PTR 0xd88
++#define MTK_WED_RX_BM_PTR 0xd8c
++#define MTK_WED_RX_BM_PTR_HEAD GENMASK(32, 16)
++#define MTK_WED_RX_BM_PTR_TAIL GENMASK(15, 0)
++
++#define MTK_WED_RX_BM_BLEN 0xd90
++#define MTK_WED_RX_BM_STS 0xd94
++#define MTK_WED_RX_BM_INTF2 0xd98
++#define MTK_WED_RX_BM_INTF 0xd9c
++#define MTK_WED_RX_BM_ERR_STS 0xda8
++
++#define MTK_WED_WOCPU_VIEW_MIOD_BASE 0x8000
++#define MTK_WED_PCIE_INT_MASK 0x0
++
+ #endif
+--- a/drivers/net/ethernet/mediatek/mtk_wed_wo.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed_wo.h
+@@ -49,6 +49,10 @@ enum {
+ MTK_WED_WARP_CMD_FLAG_FROM_TO_WO = BIT(2),
+ };
+
++#define MTK_WED_WO_CPU_MCUSYS_RESET_ADDR 0x15194050
++#define MTK_WED_WO_CPU_WO0_MCUSYS_RESET_MASK 0x20
++#define MTK_WED_WO_CPU_WO1_MCUSYS_RESET_MASK 0x1
++
+ enum {
+ MTK_WED_WO_REGION_EMI,
+ MTK_WED_WO_REGION_ILM,
+@@ -57,6 +61,28 @@ enum {
+ __MTK_WED_WO_REGION_MAX,
+ };
+
++enum mtk_wed_wo_state {
++ MTK_WED_WO_STATE_UNDEFINED,
++ MTK_WED_WO_STATE_INIT,
++ MTK_WED_WO_STATE_ENABLE,
++ MTK_WED_WO_STATE_DISABLE,
++ MTK_WED_WO_STATE_HALT,
++ MTK_WED_WO_STATE_GATING,
++ MTK_WED_WO_STATE_SER_RESET,
++ MTK_WED_WO_STATE_WF_RESET,
++};
++
++enum mtk_wed_wo_done_state {
++ MTK_WED_WOIF_UNDEFINED,
++ MTK_WED_WOIF_DISABLE_DONE,
++ MTK_WED_WOIF_TRIGGER_ENABLE,
++ MTK_WED_WOIF_ENABLE_DONE,
++ MTK_WED_WOIF_TRIGGER_GATING,
++ MTK_WED_WOIF_GATING_DONE,
++ MTK_WED_WOIF_TRIGGER_HALT,
++ MTK_WED_WOIF_HALT_DONE,
++};
++
+ enum mtk_wed_dummy_cr_idx {
+ MTK_WED_DUMMY_CR_FWDL,
+ MTK_WED_DUMMY_CR_WO_STATUS,
+@@ -245,6 +271,8 @@ void mtk_wed_mcu_rx_unsolicited_event(st
+ struct sk_buff *skb);
+ int mtk_wed_mcu_send_msg(struct mtk_wed_wo *wo, int id, int cmd,
+ const void *data, int len, bool wait_resp);
++int mtk_wed_mcu_msg_update(struct mtk_wed_device *dev, int id, void *data,
++ int len);
+ int mtk_wed_mcu_init(struct mtk_wed_wo *wo);
+ int mtk_wed_wo_init(struct mtk_wed_hw *hw);
+ void mtk_wed_wo_deinit(struct mtk_wed_hw *hw);
+--- a/include/linux/soc/mediatek/mtk_wed.h
++++ b/include/linux/soc/mediatek/mtk_wed.h
+@@ -5,10 +5,13 @@
+ #include <linux/rcupdate.h>
+ #include <linux/regmap.h>
+ #include <linux/pci.h>
++#include <linux/skbuff.h>
+
+ #define MTK_WED_TX_QUEUES 2
+ #define MTK_WED_RX_QUEUES 2
+
++#define WED_WO_STA_REC 0x6
++
+ struct mtk_wed_hw;
+ struct mtk_wdma_desc;
+
+@@ -41,21 +44,37 @@ enum mtk_wed_wo_cmd {
+ MTK_WED_WO_CMD_WED_END
+ };
+
++struct mtk_rxbm_desc {
++ __le32 buf0;
++ __le32 token;
++} __packed __aligned(4);
++
+ enum mtk_wed_bus_tye {
+ MTK_WED_BUS_PCIE,
+ MTK_WED_BUS_AXI,
+ };
+
++#define MTK_WED_RING_CONFIGURED BIT(0)
+ struct mtk_wed_ring {
+ struct mtk_wdma_desc *desc;
+ dma_addr_t desc_phys;
+ u32 desc_size;
+ int size;
++ u32 flags;
+
+ u32 reg_base;
+ void __iomem *wpdma;
+ };
+
++struct mtk_wed_wo_rx_stats {
++ __le16 wlan_idx;
++ __le16 tid;
++ __le32 rx_pkt_cnt;
++ __le32 rx_byte_cnt;
++ __le32 rx_err_cnt;
++ __le32 rx_drop_cnt;
++};
++
+ struct mtk_wed_device {
+ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
+ const struct mtk_wed_ops *ops;
+@@ -64,9 +83,12 @@ struct mtk_wed_device {
+ bool init_done, running;
+ int wdma_idx;
+ int irq;
++ u8 version;
+
+ struct mtk_wed_ring tx_ring[MTK_WED_TX_QUEUES];
++ struct mtk_wed_ring rx_ring[MTK_WED_RX_QUEUES];
+ struct mtk_wed_ring txfree_ring;
++ struct mtk_wed_ring tx_wdma[MTK_WED_TX_QUEUES];
+ struct mtk_wed_ring rx_wdma[MTK_WED_RX_QUEUES];
+
+ struct {
+@@ -74,7 +96,20 @@ struct mtk_wed_device {
+ void **pages;
+ struct mtk_wdma_desc *desc;
+ dma_addr_t desc_phys;
+- } buf_ring;
++ } tx_buf_ring;
++
++ struct {
++ int size;
++ struct page_frag_cache rx_page;
++ struct mtk_rxbm_desc *desc;
++ dma_addr_t desc_phys;
++ } rx_buf_ring;
++
++ struct {
++ struct mtk_wed_ring ring;
++ dma_addr_t miod_phys;
++ dma_addr_t fdbk_phys;
++ } rro;
+
+ /* filled by driver: */
+ struct {
+@@ -83,22 +118,36 @@ struct mtk_wed_device {
+ struct pci_dev *pci_dev;
+ };
+ enum mtk_wed_bus_tye bus_type;
++ void __iomem *base;
++ u32 phy_base;
+
+ u32 wpdma_phys;
+ u32 wpdma_int;
+ u32 wpdma_mask;
+ u32 wpdma_tx;
+ u32 wpdma_txfree;
++ u32 wpdma_rx_glo;
++ u32 wpdma_rx;
++
++ bool wcid_512;
+
+ u16 token_start;
+ unsigned int nbuf;
++ unsigned int rx_nbuf;
++ unsigned int rx_npkt;
++ unsigned int rx_size;
+
+ u8 tx_tbit[MTK_WED_TX_QUEUES];
++ u8 rx_tbit[MTK_WED_RX_QUEUES];
+ u8 txfree_tbit;
+
+ u32 (*init_buf)(void *ptr, dma_addr_t phys, int token_id);
+ int (*offload_enable)(struct mtk_wed_device *wed);
+ void (*offload_disable)(struct mtk_wed_device *wed);
++ u32 (*init_rx_buf)(struct mtk_wed_device *wed, int size);
++ void (*release_rx_buf)(struct mtk_wed_device *wed);
++ void (*update_wo_rx_stats)(struct mtk_wed_device *wed,
++ struct mtk_wed_wo_rx_stats *stats);
+ } wlan;
+ #endif
+ };
+@@ -107,9 +156,15 @@ 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 (*rx_ring_setup)(struct mtk_wed_device *dev, int ring,
++ void __iomem *regs);
+ int (*txfree_ring_setup)(struct mtk_wed_device *dev,
+ void __iomem *regs);
++ int (*msg_update)(struct mtk_wed_device *dev, int cmd_id,
++ void *data, int len);
+ void (*detach)(struct mtk_wed_device *dev);
++ void (*ppe_check)(struct mtk_wed_device *dev, struct sk_buff *skb,
++ u32 reason, u32 hash);
+
+ void (*stop)(struct mtk_wed_device *dev);
+ void (*start)(struct mtk_wed_device *dev, u32 irq_mask);
+@@ -144,6 +199,16 @@ mtk_wed_device_attach(struct mtk_wed_dev
+ return ret;
+ }
+
++static inline bool
++mtk_wed_get_rx_capa(struct mtk_wed_device *dev)
++{
++#ifdef CONFIG_NET_MEDIATEK_SOC_WED
++ return dev->version != 1;
++#else
++ return false;
++#endif
++}
++
+ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
+ #define mtk_wed_device_active(_dev) !!(_dev)->ops
+ #define mtk_wed_device_detach(_dev) (_dev)->ops->detach(_dev)
+@@ -160,6 +225,12 @@ mtk_wed_device_attach(struct mtk_wed_dev
+ (_dev)->ops->irq_get(_dev, _mask)
+ #define mtk_wed_device_irq_set_mask(_dev, _mask) \
+ (_dev)->ops->irq_set_mask(_dev, _mask)
++#define mtk_wed_device_rx_ring_setup(_dev, _ring, _regs) \
++ (_dev)->ops->rx_ring_setup(_dev, _ring, _regs)
++#define mtk_wed_device_ppe_check(_dev, _skb, _reason, _hash) \
++ (_dev)->ops->ppe_check(_dev, _skb, _reason, _hash)
++#define mtk_wed_device_update_msg(_dev, _id, _msg, _len) \
++ (_dev)->ops->msg_update(_dev, _id, _msg, _len)
+ #else
+ static inline bool mtk_wed_device_active(struct mtk_wed_device *dev)
+ {
+@@ -173,6 +244,9 @@ static inline bool mtk_wed_device_active
+ #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)
++#define mtk_wed_device_rx_ring_setup(_dev, _ring, _regs) -ENODEV
++#define mtk_wed_device_ppe_check(_dev, _skb, _reason, _hash) do {} while (0)
++#define mtk_wed_device_update_msg(_dev, _id, _msg, _len) -ENODEV
+ #endif
+
+ #endif
diff --git a/target/linux/generic/backport-6.6/729-05-v6.1-net-ethernet-mtk_wed-add-rx-mib-counters.patch b/target/linux/generic/backport-6.6/729-05-v6.1-net-ethernet-mtk_wed-add-rx-mib-counters.patch
new file mode 100644
index 0000000000..bb1066dece
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-05-v6.1-net-ethernet-mtk_wed-add-rx-mib-counters.patch
@@ -0,0 +1,149 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 5 Nov 2022 23:36:22 +0100
+Subject: [PATCH] net: ethernet: mtk_wed: add rx mib counters
+
+Introduce WED RX MIB counters support available on MT7986a SoC.
+
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
+@@ -2,6 +2,7 @@
+ /* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name> */
+
+ #include <linux/seq_file.h>
++#include <linux/soc/mediatek/mtk_wed.h>
+ #include "mtk_wed.h"
+ #include "mtk_wed_regs.h"
+
+@@ -18,6 +19,8 @@ enum {
+ DUMP_TYPE_WDMA,
+ DUMP_TYPE_WPDMA_TX,
+ DUMP_TYPE_WPDMA_TXFREE,
++ DUMP_TYPE_WPDMA_RX,
++ DUMP_TYPE_WED_RRO,
+ };
+
+ #define DUMP_STR(_str) { _str, 0, DUMP_TYPE_STRING }
+@@ -36,6 +39,9 @@ enum {
+
+ #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)
++#define DUMP_WPDMA_RX_RING(_n) DUMP_RING("WPDMA_RX" #_n, 0, DUMP_TYPE_WPDMA_RX, _n)
++#define DUMP_WED_RRO_RING(_base)DUMP_RING("WED_RRO_MIOD", MTK_##_base, DUMP_TYPE_WED_RRO)
++#define DUMP_WED_RRO_FDBK(_base)DUMP_RING("WED_RRO_FDBK", MTK_##_base, DUMP_TYPE_WED_RRO)
+
+ static void
+ print_reg_val(struct seq_file *s, const char *name, u32 val)
+@@ -57,6 +63,7 @@ dump_wed_regs(struct seq_file *s, struct
+ cur > regs ? "\n" : "",
+ cur->name);
+ continue;
++ case DUMP_TYPE_WED_RRO:
+ case DUMP_TYPE_WED:
+ val = wed_r32(dev, cur->offset);
+ break;
+@@ -69,6 +76,9 @@ dump_wed_regs(struct seq_file *s, struct
+ case DUMP_TYPE_WPDMA_TXFREE:
+ val = wpdma_txfree_r32(dev, cur->offset);
+ break;
++ case DUMP_TYPE_WPDMA_RX:
++ val = wpdma_rx_r32(dev, cur->base, cur->offset);
++ break;
+ }
+ print_reg_val(s, cur->name, val);
+ }
+@@ -132,6 +142,80 @@ wed_txinfo_show(struct seq_file *s, void
+ }
+ DEFINE_SHOW_ATTRIBUTE(wed_txinfo);
+
++static int
++wed_rxinfo_show(struct seq_file *s, void *data)
++{
++ static const struct reg_dump regs[] = {
++ DUMP_STR("WPDMA RX"),
++ DUMP_WPDMA_RX_RING(0),
++ DUMP_WPDMA_RX_RING(1),
++
++ DUMP_STR("WPDMA RX"),
++ DUMP_WED(WED_WPDMA_RX_D_MIB(0)),
++ DUMP_WED_RING(WED_WPDMA_RING_RX_DATA(0)),
++ DUMP_WED(WED_WPDMA_RX_D_PROCESSED_MIB(0)),
++ DUMP_WED(WED_WPDMA_RX_D_MIB(1)),
++ DUMP_WED_RING(WED_WPDMA_RING_RX_DATA(1)),
++ DUMP_WED(WED_WPDMA_RX_D_PROCESSED_MIB(1)),
++ DUMP_WED(WED_WPDMA_RX_D_COHERENT_MIB),
++
++ DUMP_STR("WED RX"),
++ DUMP_WED_RING(WED_RING_RX_DATA(0)),
++ DUMP_WED_RING(WED_RING_RX_DATA(1)),
++
++ DUMP_STR("WED RRO"),
++ DUMP_WED_RRO_RING(WED_RROQM_MIOD_CTRL0),
++ DUMP_WED(WED_RROQM_MID_MIB),
++ DUMP_WED(WED_RROQM_MOD_MIB),
++ DUMP_WED(WED_RROQM_MOD_COHERENT_MIB),
++ DUMP_WED_RRO_FDBK(WED_RROQM_FDBK_CTRL0),
++ DUMP_WED(WED_RROQM_FDBK_IND_MIB),
++ DUMP_WED(WED_RROQM_FDBK_ENQ_MIB),
++ DUMP_WED(WED_RROQM_FDBK_ANC_MIB),
++ DUMP_WED(WED_RROQM_FDBK_ANC2H_MIB),
++
++ DUMP_STR("WED Route QM"),
++ DUMP_WED(WED_RTQM_R2H_MIB(0)),
++ DUMP_WED(WED_RTQM_R2Q_MIB(0)),
++ DUMP_WED(WED_RTQM_Q2H_MIB(0)),
++ DUMP_WED(WED_RTQM_R2H_MIB(1)),
++ DUMP_WED(WED_RTQM_R2Q_MIB(1)),
++ DUMP_WED(WED_RTQM_Q2H_MIB(1)),
++ DUMP_WED(WED_RTQM_Q2N_MIB),
++ DUMP_WED(WED_RTQM_Q2B_MIB),
++ DUMP_WED(WED_RTQM_PFDBK_MIB),
++
++ DUMP_STR("WED WDMA TX"),
++ DUMP_WED(WED_WDMA_TX_MIB),
++ DUMP_WED_RING(WED_WDMA_RING_TX),
++
++ DUMP_STR("WDMA TX"),
++ DUMP_WDMA(WDMA_GLO_CFG),
++ DUMP_WDMA_RING(WDMA_RING_TX(0)),
++ DUMP_WDMA_RING(WDMA_RING_TX(1)),
++
++ DUMP_STR("WED RX BM"),
++ DUMP_WED(WED_RX_BM_BASE),
++ DUMP_WED(WED_RX_BM_RX_DMAD),
++ DUMP_WED(WED_RX_BM_PTR),
++ DUMP_WED(WED_RX_BM_TKID_MIB),
++ DUMP_WED(WED_RX_BM_BLEN),
++ DUMP_WED(WED_RX_BM_STS),
++ DUMP_WED(WED_RX_BM_INTF2),
++ DUMP_WED(WED_RX_BM_INTF),
++ DUMP_WED(WED_RX_BM_ERR_STS),
++ };
++ 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_rxinfo);
+
+ static int
+ mtk_wed_reg_set(void *data, u64 val)
+@@ -175,4 +259,7 @@ void mtk_wed_hw_add_debugfs(struct mtk_w
+ 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);
++ if (hw->version != 1)
++ debugfs_create_file_unsafe("rxinfo", 0400, dir, hw,
++ &wed_rxinfo_fops);
+ }
diff --git a/target/linux/generic/backport-6.6/729-07-v6.1-net-ethernet-mtk_eth_soc-remove-cpu_relax-in-mtk_pen.patch b/target/linux/generic/backport-6.6/729-07-v6.1-net-ethernet-mtk_eth_soc-remove-cpu_relax-in-mtk_pen.patch
new file mode 100644
index 0000000000..cefe1eefff
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-07-v6.1-net-ethernet-mtk_eth_soc-remove-cpu_relax-in-mtk_pen.patch
@@ -0,0 +1,36 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Thu, 17 Nov 2022 00:58:46 +0100
+Subject: [PATCH] net: ethernet: mtk_eth_soc: remove cpu_relax in
+ mtk_pending_work
+
+Get rid of cpu_relax in mtk_pending_work routine since MTK_RESETTING is
+set only in mtk_pending_work() and it runs holding rtnl lock
+
+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
+@@ -3480,11 +3480,8 @@ static void mtk_pending_work(struct work
+ rtnl_lock();
+
+ dev_dbg(eth->dev, "[%s][%d] reset\n", __func__, __LINE__);
++ set_bit(MTK_RESETTING, &eth->state);
+
+- while (test_and_set_bit_lock(MTK_RESETTING, &eth->state))
+- cpu_relax();
+-
+- dev_dbg(eth->dev, "[%s][%d] mtk_stop starts\n", __func__, __LINE__);
+ /* stop all devices to make sure that dma is properly shut down */
+ for (i = 0; i < MTK_MAC_COUNT; i++) {
+ if (!eth->netdev[i])
+@@ -3518,7 +3515,7 @@ static void mtk_pending_work(struct work
+
+ dev_dbg(eth->dev, "[%s][%d] reset done\n", __func__, __LINE__);
+
+- clear_bit_unlock(MTK_RESETTING, &eth->state);
++ clear_bit(MTK_RESETTING, &eth->state);
+
+ rtnl_unlock();
+ }
diff --git a/target/linux/generic/backport-6.6/729-09-v6.2-net-ethernet-mtk_wed-add-wcid-overwritten-support-fo.patch b/target/linux/generic/backport-6.6/729-09-v6.2-net-ethernet-mtk_wed-add-wcid-overwritten-support-fo.patch
new file mode 100644
index 0000000000..117ccc0902
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-09-v6.2-net-ethernet-mtk_wed-add-wcid-overwritten-support-fo.patch
@@ -0,0 +1,80 @@
+From: Sujuan Chen <sujuan.chen@mediatek.com>
+Date: Thu, 24 Nov 2022 11:18:14 +0800
+Subject: [PATCH] net: ethernet: mtk_wed: add wcid overwritten support for wed
+ v1
+
+All wed versions should enable the wcid overwritten feature,
+since the wcid size is controlled by the wlan driver.
+
+Tested-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Co-developed-by: Bo Jiao <bo.jiao@mediatek.com>
+Signed-off-by: Bo Jiao <bo.jiao@mediatek.com>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.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
+@@ -526,9 +526,9 @@ mtk_wed_dma_disable(struct mtk_wed_devic
+ MTK_WED_WPDMA_RX_D_RX_DRV_EN);
+ wed_clr(dev, MTK_WED_WDMA_GLO_CFG,
+ MTK_WED_WDMA_GLO_CFG_TX_DDONE_CHK);
+-
+- mtk_wed_set_512_support(dev, false);
+ }
++
++ mtk_wed_set_512_support(dev, false);
+ }
+
+ static void
+@@ -1290,9 +1290,10 @@ mtk_wed_start(struct mtk_wed_device *dev
+ if (mtk_wed_rro_cfg(dev))
+ return;
+
+- mtk_wed_set_512_support(dev, dev->wlan.wcid_512);
+ }
+
++ mtk_wed_set_512_support(dev, dev->wlan.wcid_512);
++
+ mtk_wed_dma_enable(dev);
+ dev->running = true;
+ }
+@@ -1358,11 +1359,13 @@ mtk_wed_attach(struct mtk_wed_device *de
+ }
+
+ mtk_wed_hw_init_early(dev);
+- if (hw->version == 1)
++ if (hw->version == 1) {
+ regmap_update_bits(hw->hifsys, HIFSYS_DMA_AG_MAP,
+ BIT(hw->index), 0);
+- else
++ } else {
++ dev->rev_id = wed_r32(dev, MTK_WED_REV_ID);
+ ret = mtk_wed_wo_init(hw);
++ }
+ out:
+ if (ret)
+ mtk_wed_detach(dev);
+--- a/drivers/net/ethernet/mediatek/mtk_wed_regs.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
+@@ -20,6 +20,8 @@ struct mtk_wdma_desc {
+ __le32 info;
+ } __packed __aligned(4);
+
++#define MTK_WED_REV_ID 0x004
++
+ #define MTK_WED_RESET 0x008
+ #define MTK_WED_RESET_TX_BM BIT(0)
+ #define MTK_WED_RESET_TX_FREE_AGENT BIT(4)
+--- a/include/linux/soc/mediatek/mtk_wed.h
++++ b/include/linux/soc/mediatek/mtk_wed.h
+@@ -85,6 +85,9 @@ struct mtk_wed_device {
+ int irq;
+ u8 version;
+
++ /* used by wlan driver */
++ u32 rev_id;
++
+ struct mtk_wed_ring tx_ring[MTK_WED_TX_QUEUES];
+ struct mtk_wed_ring rx_ring[MTK_WED_RX_QUEUES];
+ struct mtk_wed_ring txfree_ring;
diff --git a/target/linux/generic/backport-6.6/729-10-v6.2-net-ethernet-mtk_wed-return-status-value-in-mtk_wdma.patch b/target/linux/generic/backport-6.6/729-10-v6.2-net-ethernet-mtk_wed-return-status-value-in-mtk_wdma.patch
new file mode 100644
index 0000000000..ec58c3fc57
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-10-v6.2-net-ethernet-mtk_wed-return-status-value-in-mtk_wdma.patch
@@ -0,0 +1,85 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Thu, 24 Nov 2022 16:22:51 +0100
+Subject: [PATCH] net: ethernet: mtk_wed: return status value in
+ mtk_wdma_rx_reset
+
+Move MTK_WDMA_RESET_IDX configuration in mtk_wdma_rx_reset routine.
+Increase poll timeout to 10ms in order to be aligned with vendor sdk.
+This is a preliminary patch to add Wireless Ethernet Dispatcher reset
+support.
+
+Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -101,17 +101,21 @@ mtk_wdma_read_reset(struct mtk_wed_devic
+ return wdma_r32(dev, MTK_WDMA_GLO_CFG);
+ }
+
+-static void
++static int
+ mtk_wdma_rx_reset(struct mtk_wed_device *dev)
+ {
+ u32 status, mask = MTK_WDMA_GLO_CFG_RX_DMA_BUSY;
+- int i;
++ int i, ret;
+
+ wdma_clr(dev, MTK_WDMA_GLO_CFG, MTK_WDMA_GLO_CFG_RX_DMA_EN);
+- if (readx_poll_timeout(mtk_wdma_read_reset, dev, status,
+- !(status & mask), 0, 1000))
++ ret = readx_poll_timeout(mtk_wdma_read_reset, dev, status,
++ !(status & mask), 0, 10000);
++ if (ret)
+ dev_err(dev->hw->dev, "rx reset failed\n");
+
++ wdma_w32(dev, MTK_WDMA_RESET_IDX, MTK_WDMA_RESET_IDX_RX);
++ wdma_w32(dev, MTK_WDMA_RESET_IDX, 0);
++
+ for (i = 0; i < ARRAY_SIZE(dev->rx_wdma); i++) {
+ if (dev->rx_wdma[i].desc)
+ continue;
+@@ -119,6 +123,8 @@ mtk_wdma_rx_reset(struct mtk_wed_device
+ wdma_w32(dev,
+ MTK_WDMA_RING_RX(i) + MTK_WED_RING_OFS_CPU_IDX, 0);
+ }
++
++ return ret;
+ }
+
+ static void
+@@ -565,9 +571,7 @@ mtk_wed_detach(struct mtk_wed_device *de
+
+ 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_wdma_rx_reset(dev);
+ mtk_wed_reset(dev, MTK_WED_RESET_WED);
+ if (mtk_wed_get_rx_capa(dev)) {
+ wdma_clr(dev, MTK_WDMA_GLO_CFG, MTK_WDMA_GLO_CFG_TX_DMA_EN);
+@@ -582,7 +586,6 @@ mtk_wed_detach(struct mtk_wed_device *de
+ mtk_wed_wo_reset(dev);
+ mtk_wed_free_rx_rings(dev);
+ mtk_wed_wo_deinit(hw);
+- mtk_wdma_rx_reset(dev);
+ }
+
+ if (dev->wlan.bus_type == MTK_WED_BUS_PCIE) {
+@@ -999,11 +1002,7 @@ mtk_wed_reset_dma(struct mtk_wed_device
+ 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 (mtk_wed_get_rx_capa(dev))
+- mtk_wdma_rx_reset(dev);
++ mtk_wdma_rx_reset(dev);
+
+ if (busy) {
+ mtk_wed_reset(dev, MTK_WED_RESET_WDMA_INT_AGENT);
diff --git a/target/linux/generic/backport-6.6/729-11-v6.2-net-ethernet-mtk_wed-move-MTK_WDMA_RESET_IDX_TX-conf.patch b/target/linux/generic/backport-6.6/729-11-v6.2-net-ethernet-mtk_wed-move-MTK_WDMA_RESET_IDX_TX-conf.patch
new file mode 100644
index 0000000000..10c1732c96
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-11-v6.2-net-ethernet-mtk_wed-move-MTK_WDMA_RESET_IDX_TX-conf.patch
@@ -0,0 +1,52 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Thu, 24 Nov 2022 16:22:52 +0100
+Subject: [PATCH] net: ethernet: mtk_wed: move MTK_WDMA_RESET_IDX_TX
+ configuration in mtk_wdma_tx_reset
+
+Remove duplicated code. Increase poll timeout to 10ms in order to be
+aligned with vendor sdk.
+This is a preliminary patch to add Wireless Ethernet Dispatcher reset
+support.
+
+Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -135,16 +135,15 @@ mtk_wdma_tx_reset(struct mtk_wed_device
+
+ wdma_clr(dev, MTK_WDMA_GLO_CFG, MTK_WDMA_GLO_CFG_TX_DMA_EN);
+ if (readx_poll_timeout(mtk_wdma_read_reset, dev, status,
+- !(status & mask), 0, 1000))
++ !(status & mask), 0, 10000))
+ dev_err(dev->hw->dev, "tx reset failed\n");
+
+- for (i = 0; i < ARRAY_SIZE(dev->tx_wdma); i++) {
+- if (dev->tx_wdma[i].desc)
+- continue;
++ wdma_w32(dev, MTK_WDMA_RESET_IDX, MTK_WDMA_RESET_IDX_TX);
++ wdma_w32(dev, MTK_WDMA_RESET_IDX, 0);
+
++ for (i = 0; i < ARRAY_SIZE(dev->tx_wdma); i++)
+ wdma_w32(dev,
+ MTK_WDMA_RING_TX(i) + MTK_WED_RING_OFS_CPU_IDX, 0);
+- }
+ }
+
+ static void
+@@ -573,12 +572,6 @@ mtk_wed_detach(struct mtk_wed_device *de
+
+ mtk_wdma_rx_reset(dev);
+ mtk_wed_reset(dev, MTK_WED_RESET_WED);
+- if (mtk_wed_get_rx_capa(dev)) {
+- wdma_clr(dev, MTK_WDMA_GLO_CFG, MTK_WDMA_GLO_CFG_TX_DMA_EN);
+- wdma_w32(dev, MTK_WDMA_RESET_IDX, MTK_WDMA_RESET_IDX_TX);
+- wdma_w32(dev, MTK_WDMA_RESET_IDX, 0);
+- }
+-
+ mtk_wed_free_tx_buffer(dev);
+ mtk_wed_free_tx_rings(dev);
+
diff --git a/target/linux/generic/backport-6.6/729-12-v6.2-net-ethernet-mtk_wed-update-mtk_wed_stop.patch b/target/linux/generic/backport-6.6/729-12-v6.2-net-ethernet-mtk_wed-update-mtk_wed_stop.patch
new file mode 100644
index 0000000000..f4e842d515
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-12-v6.2-net-ethernet-mtk_wed-update-mtk_wed_stop.patch
@@ -0,0 +1,98 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Thu, 24 Nov 2022 16:22:53 +0100
+Subject: [PATCH] net: ethernet: mtk_wed: update mtk_wed_stop
+
+Update mtk_wed_stop routine and rename old mtk_wed_stop() to
+mtk_wed_deinit(). This is a preliminary patch to add Wireless Ethernet
+Dispatcher reset support.
+
+Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -539,14 +539,8 @@ mtk_wed_dma_disable(struct mtk_wed_devic
+ static void
+ mtk_wed_stop(struct mtk_wed_device *dev)
+ {
+- mtk_wed_dma_disable(dev);
+ mtk_wed_set_ext_int(dev, false);
+
+- wed_clr(dev, MTK_WED_CTRL,
+- 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);
+@@ -558,7 +552,27 @@ mtk_wed_stop(struct mtk_wed_device *dev)
+
+ wed_w32(dev, MTK_WED_EXT_INT_MASK1, 0);
+ wed_w32(dev, MTK_WED_EXT_INT_MASK2, 0);
+- wed_clr(dev, MTK_WED_CTRL, MTK_WED_CTRL_WED_RX_BM_EN);
++}
++
++static void
++mtk_wed_deinit(struct mtk_wed_device *dev)
++{
++ mtk_wed_stop(dev);
++ mtk_wed_dma_disable(dev);
++
++ 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);
++
++ if (dev->hw->version == 1)
++ return;
++
++ wed_clr(dev, MTK_WED_CTRL,
++ MTK_WED_CTRL_RX_ROUTE_QM_EN |
++ MTK_WED_CTRL_WED_RX_BM_EN |
++ MTK_WED_CTRL_RX_RRO_QM_EN);
+ }
+
+ static void
+@@ -568,7 +582,7 @@ mtk_wed_detach(struct mtk_wed_device *de
+
+ mutex_lock(&hw_lock);
+
+- mtk_wed_stop(dev);
++ mtk_wed_deinit(dev);
+
+ mtk_wdma_rx_reset(dev);
+ mtk_wed_reset(dev, MTK_WED_RESET_WED);
+@@ -670,7 +684,7 @@ mtk_wed_hw_init_early(struct mtk_wed_dev
+ {
+ u32 mask, set;
+
+- mtk_wed_stop(dev);
++ mtk_wed_deinit(dev);
+ mtk_wed_reset(dev, MTK_WED_RESET_WED);
+ mtk_wed_set_wpdma(dev);
+
+--- a/include/linux/soc/mediatek/mtk_wed.h
++++ b/include/linux/soc/mediatek/mtk_wed.h
+@@ -234,6 +234,8 @@ mtk_wed_get_rx_capa(struct mtk_wed_devic
+ (_dev)->ops->ppe_check(_dev, _skb, _reason, _hash)
+ #define mtk_wed_device_update_msg(_dev, _id, _msg, _len) \
+ (_dev)->ops->msg_update(_dev, _id, _msg, _len)
++#define mtk_wed_device_stop(_dev) (_dev)->ops->stop(_dev)
++#define mtk_wed_device_dma_reset(_dev) (_dev)->ops->reset_dma(_dev)
+ #else
+ static inline bool mtk_wed_device_active(struct mtk_wed_device *dev)
+ {
+@@ -250,6 +252,8 @@ static inline bool mtk_wed_device_active
+ #define mtk_wed_device_rx_ring_setup(_dev, _ring, _regs) -ENODEV
+ #define mtk_wed_device_ppe_check(_dev, _skb, _reason, _hash) do {} while (0)
+ #define mtk_wed_device_update_msg(_dev, _id, _msg, _len) -ENODEV
++#define mtk_wed_device_stop(_dev) do {} while (0)
++#define mtk_wed_device_dma_reset(_dev) do {} while (0)
+ #endif
+
+ #endif
diff --git a/target/linux/generic/backport-6.6/729-13-v6.2-net-ethernet-mtk_wed-add-mtk_wed_rx_reset-routine.patch b/target/linux/generic/backport-6.6/729-13-v6.2-net-ethernet-mtk_wed-add-mtk_wed_rx_reset-routine.patch
new file mode 100644
index 0000000000..a0fc9da99e
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-13-v6.2-net-ethernet-mtk_wed-add-mtk_wed_rx_reset-routine.patch
@@ -0,0 +1,309 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Thu, 24 Nov 2022 16:22:54 +0100
+Subject: [PATCH] net: ethernet: mtk_wed: add mtk_wed_rx_reset routine
+
+Introduce mtk_wed_rx_reset routine in order to reset rx DMA for Wireless
+Ethernet Dispatcher available on MT7986 SoC.
+
+Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -944,42 +944,130 @@ mtk_wed_ring_reset(struct mtk_wed_ring *
+ }
+
+ static u32
+-mtk_wed_check_busy(struct mtk_wed_device *dev)
++mtk_wed_check_busy(struct mtk_wed_device *dev, u32 reg, u32 mask)
+ {
+- 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;
++ return !!(wed_r32(dev, reg) & mask);
+ }
+
+ static int
+-mtk_wed_poll_busy(struct mtk_wed_device *dev)
++mtk_wed_poll_busy(struct mtk_wed_device *dev, u32 reg, u32 mask)
+ {
+ int sleep = 15000;
+ int timeout = 100 * sleep;
+ u32 val;
+
+ return read_poll_timeout(mtk_wed_check_busy, val, !val, sleep,
+- timeout, false, dev);
++ timeout, false, dev, reg, mask);
++}
++
++static int
++mtk_wed_rx_reset(struct mtk_wed_device *dev)
++{
++ struct mtk_wed_wo *wo = dev->hw->wed_wo;
++ u8 val = MTK_WED_WO_STATE_SER_RESET;
++ int i, ret;
++
++ ret = mtk_wed_mcu_send_msg(wo, MTK_WED_MODULE_ID_WO,
++ MTK_WED_WO_CMD_CHANGE_STATE, &val,
++ sizeof(val), true);
++ if (ret)
++ return ret;
++
++ wed_clr(dev, MTK_WED_WPDMA_RX_D_GLO_CFG, MTK_WED_WPDMA_RX_D_RX_DRV_EN);
++ ret = mtk_wed_poll_busy(dev, MTK_WED_WPDMA_RX_D_GLO_CFG,
++ MTK_WED_WPDMA_RX_D_RX_DRV_BUSY);
++ if (ret) {
++ mtk_wed_reset(dev, MTK_WED_RESET_WPDMA_INT_AGENT);
++ mtk_wed_reset(dev, MTK_WED_RESET_WPDMA_RX_D_DRV);
++ } else {
++ wed_w32(dev, MTK_WED_WPDMA_RX_D_RST_IDX,
++ MTK_WED_WPDMA_RX_D_RST_CRX_IDX |
++ MTK_WED_WPDMA_RX_D_RST_DRV_IDX);
++
++ wed_set(dev, MTK_WED_WPDMA_RX_D_GLO_CFG,
++ MTK_WED_WPDMA_RX_D_RST_INIT_COMPLETE |
++ MTK_WED_WPDMA_RX_D_FSM_RETURN_IDLE);
++ wed_clr(dev, MTK_WED_WPDMA_RX_D_GLO_CFG,
++ MTK_WED_WPDMA_RX_D_RST_INIT_COMPLETE |
++ MTK_WED_WPDMA_RX_D_FSM_RETURN_IDLE);
++
++ wed_w32(dev, MTK_WED_WPDMA_RX_D_RST_IDX, 0);
++ }
++
++ /* reset rro qm */
++ wed_clr(dev, MTK_WED_CTRL, MTK_WED_CTRL_RX_RRO_QM_EN);
++ ret = mtk_wed_poll_busy(dev, MTK_WED_CTRL,
++ MTK_WED_CTRL_RX_RRO_QM_BUSY);
++ if (ret) {
++ mtk_wed_reset(dev, MTK_WED_RESET_RX_RRO_QM);
++ } else {
++ wed_set(dev, MTK_WED_RROQM_RST_IDX,
++ MTK_WED_RROQM_RST_IDX_MIOD |
++ MTK_WED_RROQM_RST_IDX_FDBK);
++ wed_w32(dev, MTK_WED_RROQM_RST_IDX, 0);
++ }
++
++ /* reset route qm */
++ wed_clr(dev, MTK_WED_CTRL, MTK_WED_CTRL_RX_ROUTE_QM_EN);
++ ret = mtk_wed_poll_busy(dev, MTK_WED_CTRL,
++ MTK_WED_CTRL_RX_ROUTE_QM_BUSY);
++ if (ret)
++ mtk_wed_reset(dev, MTK_WED_RESET_RX_ROUTE_QM);
++ else
++ wed_set(dev, MTK_WED_RTQM_GLO_CFG,
++ MTK_WED_RTQM_Q_RST);
++
++ /* reset tx wdma */
++ mtk_wdma_tx_reset(dev);
++
++ /* reset tx wdma drv */
++ wed_clr(dev, MTK_WED_WDMA_GLO_CFG, MTK_WED_WDMA_GLO_CFG_TX_DRV_EN);
++ mtk_wed_poll_busy(dev, MTK_WED_CTRL,
++ MTK_WED_CTRL_WDMA_INT_AGENT_BUSY);
++ mtk_wed_reset(dev, MTK_WED_RESET_WDMA_TX_DRV);
++
++ /* reset wed rx dma */
++ ret = mtk_wed_poll_busy(dev, MTK_WED_GLO_CFG,
++ MTK_WED_GLO_CFG_RX_DMA_BUSY);
++ wed_clr(dev, MTK_WED_GLO_CFG, MTK_WED_GLO_CFG_RX_DMA_EN);
++ if (ret) {
++ mtk_wed_reset(dev, MTK_WED_RESET_WED_RX_DMA);
++ } else {
++ struct mtk_eth *eth = dev->hw->eth;
++
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ wed_set(dev, MTK_WED_RESET_IDX,
++ MTK_WED_RESET_IDX_RX_V2);
++ else
++ wed_set(dev, MTK_WED_RESET_IDX, MTK_WED_RESET_IDX_RX);
++ wed_w32(dev, MTK_WED_RESET_IDX, 0);
++ }
++
++ /* reset rx bm */
++ wed_clr(dev, MTK_WED_CTRL, MTK_WED_CTRL_WED_RX_BM_EN);
++ mtk_wed_poll_busy(dev, MTK_WED_CTRL,
++ MTK_WED_CTRL_WED_RX_BM_BUSY);
++ mtk_wed_reset(dev, MTK_WED_RESET_RX_BM);
++
++ /* wo change to enable state */
++ val = MTK_WED_WO_STATE_ENABLE;
++ ret = mtk_wed_mcu_send_msg(wo, MTK_WED_MODULE_ID_WO,
++ MTK_WED_WO_CMD_CHANGE_STATE, &val,
++ sizeof(val), true);
++ if (ret)
++ return ret;
++
++ /* wed_rx_ring_reset */
++ for (i = 0; i < ARRAY_SIZE(dev->rx_ring); i++) {
++ if (!dev->rx_ring[i].desc)
++ continue;
++
++ mtk_wed_ring_reset(&dev->rx_ring[i], MTK_WED_RX_RING_SIZE,
++ false);
++ }
++ mtk_wed_free_rx_buffer(dev);
++
++ return 0;
+ }
+
+ static void
+@@ -997,19 +1085,23 @@ mtk_wed_reset_dma(struct mtk_wed_device
+ true);
+ }
+
+- if (mtk_wed_poll_busy(dev))
+- busy = mtk_wed_check_busy(dev);
+-
++ /* 1. reset WED tx DMA */
++ wed_clr(dev, MTK_WED_GLO_CFG, MTK_WED_GLO_CFG_TX_DMA_EN);
++ busy = mtk_wed_poll_busy(dev, MTK_WED_GLO_CFG,
++ MTK_WED_GLO_CFG_TX_DMA_BUSY);
+ 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, MTK_WED_RESET_IDX_TX);
+ wed_w32(dev, MTK_WED_RESET_IDX, 0);
+ }
+
+- mtk_wdma_rx_reset(dev);
++ /* 2. reset WDMA rx DMA */
++ busy = !!mtk_wdma_rx_reset(dev);
++ wed_clr(dev, MTK_WED_WDMA_GLO_CFG, MTK_WED_WDMA_GLO_CFG_RX_DRV_EN);
++ if (!busy)
++ busy = mtk_wed_poll_busy(dev, MTK_WED_WDMA_GLO_CFG,
++ MTK_WED_WDMA_GLO_CFG_RX_DRV_BUSY);
+
+ if (busy) {
+ mtk_wed_reset(dev, MTK_WED_RESET_WDMA_INT_AGENT);
+@@ -1026,6 +1118,9 @@ mtk_wed_reset_dma(struct mtk_wed_device
+ MTK_WED_WDMA_GLO_CFG_RST_INIT_COMPLETE);
+ }
+
++ /* 3. reset WED WPDMA tx */
++ wed_clr(dev, MTK_WED_CTRL, MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
++
+ 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)
+@@ -1033,8 +1128,19 @@ mtk_wed_reset_dma(struct mtk_wed_device
+ }
+
+ mtk_wed_reset(dev, MTK_WED_RESET_TX_FREE_AGENT);
++ wed_clr(dev, MTK_WED_CTRL, MTK_WED_CTRL_WED_TX_BM_EN);
+ mtk_wed_reset(dev, MTK_WED_RESET_TX_BM);
+
++ /* 4. reset WED WPDMA tx */
++ busy = mtk_wed_poll_busy(dev, MTK_WED_WPDMA_GLO_CFG,
++ MTK_WED_WPDMA_GLO_CFG_TX_DRV_BUSY);
++ wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
++ MTK_WED_WPDMA_GLO_CFG_TX_DRV_EN |
++ MTK_WED_WPDMA_GLO_CFG_RX_DRV_EN);
++ if (!busy)
++ busy = mtk_wed_poll_busy(dev, MTK_WED_WPDMA_GLO_CFG,
++ MTK_WED_WPDMA_GLO_CFG_RX_DRV_BUSY);
++
+ if (busy) {
+ mtk_wed_reset(dev, MTK_WED_RESET_WPDMA_INT_AGENT);
+ mtk_wed_reset(dev, MTK_WED_RESET_WPDMA_TX_DRV);
+@@ -1045,6 +1151,17 @@ mtk_wed_reset_dma(struct mtk_wed_device
+ MTK_WED_WPDMA_RESET_IDX_RX);
+ wed_w32(dev, MTK_WED_WPDMA_RESET_IDX, 0);
+ }
++
++ dev->init_done = false;
++ if (dev->hw->version == 1)
++ return;
++
++ if (!busy) {
++ wed_w32(dev, MTK_WED_RESET_IDX, MTK_WED_RESET_WPDMA_IDX_RX);
++ wed_w32(dev, MTK_WED_RESET_IDX, 0);
++ }
++
++ mtk_wed_rx_reset(dev);
+ }
+
+ static int
+@@ -1267,6 +1384,9 @@ mtk_wed_start(struct mtk_wed_device *dev
+ {
+ int i;
+
++ if (mtk_wed_get_rx_capa(dev) && mtk_wed_rx_buffer_alloc(dev))
++ return;
++
+ for (i = 0; i < ARRAY_SIZE(dev->rx_wdma); i++)
+ if (!dev->rx_wdma[i].desc)
+ mtk_wed_wdma_rx_ring_setup(dev, i, 16);
+@@ -1355,10 +1475,6 @@ mtk_wed_attach(struct mtk_wed_device *de
+ goto out;
+
+ if (mtk_wed_get_rx_capa(dev)) {
+- ret = mtk_wed_rx_buffer_alloc(dev);
+- if (ret)
+- goto out;
+-
+ ret = mtk_wed_rro_alloc(dev);
+ if (ret)
+ goto out;
+--- a/drivers/net/ethernet/mediatek/mtk_wed_regs.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
+@@ -24,11 +24,15 @@ struct mtk_wdma_desc {
+
+ #define MTK_WED_RESET 0x008
+ #define MTK_WED_RESET_TX_BM BIT(0)
++#define MTK_WED_RESET_RX_BM BIT(1)
+ #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_RX_D_DRV BIT(10)
+ #define MTK_WED_RESET_WPDMA_INT_AGENT BIT(11)
+ #define MTK_WED_RESET_WED_TX_DMA BIT(12)
++#define MTK_WED_RESET_WED_RX_DMA BIT(13)
++#define MTK_WED_RESET_WDMA_TX_DRV BIT(16)
+ #define MTK_WED_RESET_WDMA_RX_DRV BIT(17)
+ #define MTK_WED_RESET_WDMA_INT_AGENT BIT(19)
+ #define MTK_WED_RESET_RX_RRO_QM BIT(20)
+@@ -158,6 +162,8 @@ struct mtk_wdma_desc {
+ #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_RESET_IDX_RX_V2 GENMASK(7, 6)
++#define MTK_WED_RESET_WPDMA_IDX_RX GENMASK(31, 30)
+
+ #define MTK_WED_TX_MIB(_n) (0x2a0 + (_n) * 4)
+ #define MTK_WED_RX_MIB(_n) (0x2e0 + (_n) * 4)
+@@ -267,6 +273,9 @@ struct mtk_wdma_desc {
+
+ #define MTK_WED_WPDMA_RX_D_GLO_CFG 0x75c
+ #define MTK_WED_WPDMA_RX_D_RX_DRV_EN BIT(0)
++#define MTK_WED_WPDMA_RX_D_RX_DRV_BUSY BIT(1)
++#define MTK_WED_WPDMA_RX_D_FSM_RETURN_IDLE BIT(3)
++#define MTK_WED_WPDMA_RX_D_RST_INIT_COMPLETE BIT(4)
+ #define MTK_WED_WPDMA_RX_D_INIT_PHASE_RXEN_SEL GENMASK(11, 7)
+ #define MTK_WED_WPDMA_RX_D_RXD_READ_LEN GENMASK(31, 24)
+
diff --git a/target/linux/generic/backport-6.6/729-14-v6.2-net-ethernet-mtk_wed-add-reset-to-tx_ring_setup-call.patch b/target/linux/generic/backport-6.6/729-14-v6.2-net-ethernet-mtk_wed-add-reset-to-tx_ring_setup-call.patch
new file mode 100644
index 0000000000..4404971cc7
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-14-v6.2-net-ethernet-mtk_wed-add-reset-to-tx_ring_setup-call.patch
@@ -0,0 +1,103 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Thu, 24 Nov 2022 16:22:55 +0100
+Subject: [PATCH] net: ethernet: mtk_wed: add reset to tx_ring_setup callback
+
+Introduce reset parameter to mtk_wed_tx_ring_setup signature.
+This is a preliminary patch to add Wireless Ethernet Dispatcher reset
+support.
+
+Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -1181,7 +1181,8 @@ mtk_wed_ring_alloc(struct mtk_wed_device
+ }
+
+ static int
+-mtk_wed_wdma_rx_ring_setup(struct mtk_wed_device *dev, int idx, int size)
++mtk_wed_wdma_rx_ring_setup(struct mtk_wed_device *dev, int idx, int size,
++ bool reset)
+ {
+ u32 desc_size = sizeof(struct mtk_wdma_desc) * dev->hw->version;
+ struct mtk_wed_ring *wdma;
+@@ -1190,8 +1191,8 @@ mtk_wed_wdma_rx_ring_setup(struct mtk_we
+ return -EINVAL;
+
+ wdma = &dev->rx_wdma[idx];
+- if (mtk_wed_ring_alloc(dev, wdma, MTK_WED_WDMA_RING_SIZE, desc_size,
+- true))
++ if (!reset && mtk_wed_ring_alloc(dev, wdma, MTK_WED_WDMA_RING_SIZE,
++ desc_size, true))
+ return -ENOMEM;
+
+ wdma_w32(dev, MTK_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_BASE,
+@@ -1389,7 +1390,7 @@ mtk_wed_start(struct mtk_wed_device *dev
+
+ for (i = 0; i < ARRAY_SIZE(dev->rx_wdma); i++)
+ if (!dev->rx_wdma[i].desc)
+- mtk_wed_wdma_rx_ring_setup(dev, i, 16);
++ mtk_wed_wdma_rx_ring_setup(dev, i, 16, false);
+
+ mtk_wed_hw_init(dev);
+ mtk_wed_configure_irq(dev, irq_mask);
+@@ -1498,7 +1499,8 @@ unlock:
+ }
+
+ static int
+-mtk_wed_tx_ring_setup(struct mtk_wed_device *dev, int idx, void __iomem *regs)
++mtk_wed_tx_ring_setup(struct mtk_wed_device *dev, int idx, void __iomem *regs,
++ bool reset)
+ {
+ struct mtk_wed_ring *ring = &dev->tx_ring[idx];
+
+@@ -1517,11 +1519,12 @@ mtk_wed_tx_ring_setup(struct mtk_wed_dev
+ if (WARN_ON(idx >= ARRAY_SIZE(dev->tx_ring)))
+ return -EINVAL;
+
+- if (mtk_wed_ring_alloc(dev, ring, MTK_WED_TX_RING_SIZE,
+- sizeof(*ring->desc), true))
++ if (!reset && mtk_wed_ring_alloc(dev, ring, MTK_WED_TX_RING_SIZE,
++ sizeof(*ring->desc), true))
+ return -ENOMEM;
+
+- if (mtk_wed_wdma_rx_ring_setup(dev, idx, MTK_WED_WDMA_RING_SIZE))
++ if (mtk_wed_wdma_rx_ring_setup(dev, idx, MTK_WED_WDMA_RING_SIZE,
++ reset))
+ return -ENOMEM;
+
+ ring->reg_base = MTK_WED_RING_TX(idx);
+--- a/include/linux/soc/mediatek/mtk_wed.h
++++ b/include/linux/soc/mediatek/mtk_wed.h
+@@ -158,7 +158,7 @@ struct mtk_wed_device {
+ 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);
++ void __iomem *regs, bool reset);
+ int (*rx_ring_setup)(struct mtk_wed_device *dev, int ring,
+ void __iomem *regs);
+ int (*txfree_ring_setup)(struct mtk_wed_device *dev,
+@@ -216,8 +216,8 @@ mtk_wed_get_rx_capa(struct mtk_wed_devic
+ #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_tx_ring_setup(_dev, _ring, _regs, _reset) \
++ (_dev)->ops->tx_ring_setup(_dev, _ring, _regs, _reset)
+ #define mtk_wed_device_txfree_ring_setup(_dev, _regs) \
+ (_dev)->ops->txfree_ring_setup(_dev, _regs)
+ #define mtk_wed_device_reg_read(_dev, _reg) \
+@@ -243,7 +243,7 @@ static inline bool mtk_wed_device_active
+ }
+ #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_tx_ring_setup(_dev, _ring, _regs, _reset) -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)
diff --git a/target/linux/generic/backport-6.6/729-15-v6.2-net-ethernet-mtk_wed-fix-sleep-while-atomic-in-mtk_w.patch b/target/linux/generic/backport-6.6/729-15-v6.2-net-ethernet-mtk_wed-fix-sleep-while-atomic-in-mtk_w.patch
new file mode 100644
index 0000000000..f9b11326b1
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-15-v6.2-net-ethernet-mtk_wed-fix-sleep-while-atomic-in-mtk_w.patch
@@ -0,0 +1,103 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Thu, 1 Dec 2022 16:26:53 +0100
+Subject: [PATCH] net: ethernet: mtk_wed: fix sleep while atomic in
+ mtk_wed_wo_queue_refill
+
+In order to fix the following sleep while atomic bug always alloc pages
+with GFP_ATOMIC in mtk_wed_wo_queue_refill since page_frag_alloc runs in
+spin_lock critical section.
+
+[ 9.049719] Hardware name: MediaTek MT7986a RFB (DT)
+[ 9.054665] Call trace:
+[ 9.057096] dump_backtrace+0x0/0x154
+[ 9.060751] show_stack+0x14/0x1c
+[ 9.064052] dump_stack_lvl+0x64/0x7c
+[ 9.067702] dump_stack+0x14/0x2c
+[ 9.071001] ___might_sleep+0xec/0x120
+[ 9.074736] __might_sleep+0x4c/0x9c
+[ 9.078296] __alloc_pages+0x184/0x2e4
+[ 9.082030] page_frag_alloc_align+0x98/0x1ac
+[ 9.086369] mtk_wed_wo_queue_refill+0x134/0x234
+[ 9.090974] mtk_wed_wo_init+0x174/0x2c0
+[ 9.094881] mtk_wed_attach+0x7c8/0x7e0
+[ 9.098701] mt7915_mmio_wed_init+0x1f0/0x3a0 [mt7915e]
+[ 9.103940] mt7915_pci_probe+0xec/0x3bc [mt7915e]
+[ 9.108727] pci_device_probe+0xac/0x13c
+[ 9.112638] really_probe.part.0+0x98/0x2f4
+[ 9.116807] __driver_probe_device+0x94/0x13c
+[ 9.121147] driver_probe_device+0x40/0x114
+[ 9.125314] __driver_attach+0x7c/0x180
+[ 9.129133] bus_for_each_dev+0x5c/0x90
+[ 9.132953] driver_attach+0x20/0x2c
+[ 9.136513] bus_add_driver+0x104/0x1fc
+[ 9.140333] driver_register+0x74/0x120
+[ 9.144153] __pci_register_driver+0x40/0x50
+[ 9.148407] mt7915_init+0x5c/0x1000 [mt7915e]
+[ 9.152848] do_one_initcall+0x40/0x25c
+[ 9.156669] do_init_module+0x44/0x230
+[ 9.160403] load_module+0x1f30/0x2750
+[ 9.164135] __do_sys_init_module+0x150/0x200
+[ 9.168475] __arm64_sys_init_module+0x18/0x20
+[ 9.172901] invoke_syscall.constprop.0+0x4c/0xe0
+[ 9.177589] do_el0_svc+0x48/0xe0
+[ 9.180889] el0_svc+0x14/0x50
+[ 9.183929] el0t_64_sync_handler+0x9c/0x120
+[ 9.188183] el0t_64_sync+0x158/0x15c
+
+Fixes: 799684448e3e ("net: ethernet: mtk_wed: introduce wed wo support")
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Reviewed-by: Pavan Chebbi <pavan.chebbi@broadcom.com>
+Link: https://lore.kernel.org/r/67ca94bdd3d9eaeb86e52b3050fbca0bcf7bb02f.1669908312.git.lorenzo@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed_wo.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_wo.c
+@@ -133,17 +133,18 @@ mtk_wed_wo_dequeue(struct mtk_wed_wo *wo
+
+ static int
+ mtk_wed_wo_queue_refill(struct mtk_wed_wo *wo, struct mtk_wed_wo_queue *q,
+- gfp_t gfp, bool rx)
++ bool rx)
+ {
+ enum dma_data_direction dir = rx ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
+ int n_buf = 0;
+
+ spin_lock_bh(&q->lock);
+ while (q->queued < q->n_desc) {
+- void *buf = page_frag_alloc(&q->cache, q->buf_size, gfp);
+ struct mtk_wed_wo_queue_entry *entry;
+ dma_addr_t addr;
++ void *buf;
+
++ buf = page_frag_alloc(&q->cache, q->buf_size, GFP_ATOMIC);
+ if (!buf)
+ break;
+
+@@ -215,7 +216,7 @@ mtk_wed_wo_rx_run_queue(struct mtk_wed_w
+ mtk_wed_mcu_rx_unsolicited_event(wo, skb);
+ }
+
+- if (mtk_wed_wo_queue_refill(wo, q, GFP_ATOMIC, true)) {
++ if (mtk_wed_wo_queue_refill(wo, q, true)) {
+ u32 index = (q->head - 1) % q->n_desc;
+
+ mtk_wed_wo_queue_kick(wo, q, index);
+@@ -432,7 +433,7 @@ mtk_wed_wo_hardware_init(struct mtk_wed_
+ if (ret)
+ goto error;
+
+- mtk_wed_wo_queue_refill(wo, &wo->q_tx, GFP_KERNEL, false);
++ mtk_wed_wo_queue_refill(wo, &wo->q_tx, false);
+ mtk_wed_wo_queue_reset(wo, &wo->q_tx);
+
+ regs.desc_base = MTK_WED_WO_CCIF_DUMMY5;
+@@ -446,7 +447,7 @@ mtk_wed_wo_hardware_init(struct mtk_wed_
+ if (ret)
+ goto error;
+
+- mtk_wed_wo_queue_refill(wo, &wo->q_rx, GFP_KERNEL, true);
++ mtk_wed_wo_queue_refill(wo, &wo->q_rx, true);
+ mtk_wed_wo_queue_reset(wo, &wo->q_rx);
+
+ /* rx queue irqmask */
diff --git a/target/linux/generic/backport-6.6/729-16-v6.3-net-ethernet-mtk_wed-get-rid-of-queue-lock-for-rx-qu.patch b/target/linux/generic/backport-6.6/729-16-v6.3-net-ethernet-mtk_wed-get-rid-of-queue-lock-for-rx-qu.patch
new file mode 100644
index 0000000000..fa6f56dbe7
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-16-v6.3-net-ethernet-mtk_wed-get-rid-of-queue-lock-for-rx-qu.patch
@@ -0,0 +1,52 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Tue, 10 Jan 2023 10:31:26 +0100
+Subject: [PATCH] net: ethernet: mtk_wed: get rid of queue lock for rx queue
+
+Queue spinlock is currently held in mtk_wed_wo_queue_rx_clean and
+mtk_wed_wo_queue_refill routines for MTK Wireless Ethernet Dispatcher
+MCU rx queue. mtk_wed_wo_queue_refill() is running during initialization
+and in rx tasklet while mtk_wed_wo_queue_rx_clean() is running in
+mtk_wed_wo_hw_deinit() during hw de-init phase after rx tasklet has been
+disabled. Since mtk_wed_wo_queue_rx_clean and mtk_wed_wo_queue_refill
+routines can't run concurrently get rid of spinlock for mcu rx queue.
+
+Reviewed-by: Alexander Duyck <alexanderduyck@fb.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://lore.kernel.org/r/36ec3b729542ea60898471d890796f745479ba32.1673342990.git.lorenzo@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed_wo.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_wo.c
+@@ -138,7 +138,6 @@ mtk_wed_wo_queue_refill(struct mtk_wed_w
+ enum dma_data_direction dir = rx ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
+ int n_buf = 0;
+
+- spin_lock_bh(&q->lock);
+ while (q->queued < q->n_desc) {
+ struct mtk_wed_wo_queue_entry *entry;
+ dma_addr_t addr;
+@@ -172,7 +171,6 @@ mtk_wed_wo_queue_refill(struct mtk_wed_w
+ q->queued++;
+ n_buf++;
+ }
+- spin_unlock_bh(&q->lock);
+
+ return n_buf;
+ }
+@@ -316,7 +314,6 @@ mtk_wed_wo_queue_rx_clean(struct mtk_wed
+ {
+ struct page *page;
+
+- spin_lock_bh(&q->lock);
+ for (;;) {
+ void *buf = mtk_wed_wo_dequeue(wo, q, NULL, true);
+
+@@ -325,7 +322,6 @@ mtk_wed_wo_queue_rx_clean(struct mtk_wed
+
+ skb_free_frag(buf);
+ }
+- spin_unlock_bh(&q->lock);
+
+ if (!q->cache.va)
+ return;
diff --git a/target/linux/generic/backport-6.6/729-17-v6.3-net-ethernet-mtk_wed-get-rid-of-queue-lock-for-tx-qu.patch b/target/linux/generic/backport-6.6/729-17-v6.3-net-ethernet-mtk_wed-get-rid-of-queue-lock-for-tx-qu.patch
new file mode 100644
index 0000000000..9b1e4c3250
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-17-v6.3-net-ethernet-mtk_wed-get-rid-of-queue-lock-for-tx-qu.patch
@@ -0,0 +1,75 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Thu, 12 Jan 2023 10:21:29 +0100
+Subject: [PATCH] net: ethernet: mtk_wed: get rid of queue lock for tx queue
+
+Similar to MTK Wireless Ethernet Dispatcher (WED) MCU rx queue,
+we do not need to protect WED MCU tx queue with a spin lock since
+the tx queue is accessed in the two following routines:
+- mtk_wed_wo_queue_tx_skb():
+ it is run at initialization and during mt7915 normal operation.
+ Moreover MCU messages are serialized through MCU mutex.
+- mtk_wed_wo_queue_tx_clean():
+ it runs just at mt7915 driver module unload when no more messages
+ are sent to the MCU.
+
+Remove tx queue spinlock.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://lore.kernel.org/r/7bd0337b2a13ab1a63673b7c03fd35206b3b284e.1673515140.git.lorenzo@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed_wo.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_wo.c
+@@ -258,7 +258,6 @@ mtk_wed_wo_queue_alloc(struct mtk_wed_wo
+ int n_desc, int buf_size, int index,
+ struct mtk_wed_wo_queue_regs *regs)
+ {
+- spin_lock_init(&q->lock);
+ q->regs = *regs;
+ q->n_desc = n_desc;
+ q->buf_size = buf_size;
+@@ -290,7 +289,6 @@ mtk_wed_wo_queue_tx_clean(struct mtk_wed
+ struct page *page;
+ int i;
+
+- spin_lock_bh(&q->lock);
+ for (i = 0; i < q->n_desc; i++) {
+ struct mtk_wed_wo_queue_entry *entry = &q->entry[i];
+
+@@ -299,7 +297,6 @@ mtk_wed_wo_queue_tx_clean(struct mtk_wed
+ skb_free_frag(entry->buf);
+ entry->buf = NULL;
+ }
+- spin_unlock_bh(&q->lock);
+
+ if (!q->cache.va)
+ return;
+@@ -347,8 +344,6 @@ int mtk_wed_wo_queue_tx_skb(struct mtk_w
+ int ret = 0, index;
+ u32 ctrl;
+
+- spin_lock_bh(&q->lock);
+-
+ q->tail = mtk_wed_mmio_r32(wo, q->regs.dma_idx);
+ index = (q->head + 1) % q->n_desc;
+ if (q->tail == index) {
+@@ -379,8 +374,6 @@ int mtk_wed_wo_queue_tx_skb(struct mtk_w
+ mtk_wed_wo_queue_kick(wo, q, q->head);
+ mtk_wed_wo_kickout(wo);
+ out:
+- spin_unlock_bh(&q->lock);
+-
+ dev_kfree_skb(skb);
+
+ return ret;
+--- a/drivers/net/ethernet/mediatek/mtk_wed_wo.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed_wo.h
+@@ -211,7 +211,6 @@ struct mtk_wed_wo_queue {
+ struct mtk_wed_wo_queue_regs regs;
+
+ struct page_frag_cache cache;
+- spinlock_t lock;
+
+ struct mtk_wed_wo_queue_desc *desc;
+ dma_addr_t desc_dma;
diff --git a/target/linux/generic/backport-6.6/729-18-v6.3-net-ethernet-mtk_eth_soc-introduce-mtk_hw_reset-util.patch b/target/linux/generic/backport-6.6/729-18-v6.3-net-ethernet-mtk_eth_soc-introduce-mtk_hw_reset-util.patch
new file mode 100644
index 0000000000..c91861a8f1
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-18-v6.3-net-ethernet-mtk_eth_soc-introduce-mtk_hw_reset-util.patch
@@ -0,0 +1,70 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 14 Jan 2023 18:01:28 +0100
+Subject: [PATCH] net: ethernet: mtk_eth_soc: introduce mtk_hw_reset utility
+ routine
+
+This is a preliminary patch to add Wireless Ethernet Dispatcher reset
+support.
+
+Reviewed-by: Leon Romanovsky <leonro@nvidia.com>
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -3256,6 +3256,27 @@ static void mtk_set_mcr_max_rx(struct mt
+ mtk_w32(mac->hw, mcr_new, MTK_MAC_MCR(mac->id));
+ }
+
++static void mtk_hw_reset(struct mtk_eth *eth)
++{
++ u32 val;
++
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
++ regmap_write(eth->ethsys, ETHSYS_FE_RST_CHK_IDLE_EN, 0);
++ val = RSTCTRL_PPE0_V2;
++ } else {
++ val = RSTCTRL_PPE0;
++ }
++
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
++ val |= RSTCTRL_PPE1;
++
++ ethsys_reset(eth, RSTCTRL_ETH | RSTCTRL_FE | val);
++
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ regmap_write(eth->ethsys, ETHSYS_FE_RST_CHK_IDLE_EN,
++ 0x3ffffff);
++}
++
+ static int mtk_hw_init(struct mtk_eth *eth)
+ {
+ u32 dma_mask = ETHSYS_DMA_AG_MAP_PDMA | ETHSYS_DMA_AG_MAP_QDMA |
+@@ -3295,22 +3316,9 @@ static int mtk_hw_init(struct mtk_eth *e
+ return 0;
+ }
+
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+- regmap_write(eth->ethsys, ETHSYS_FE_RST_CHK_IDLE_EN, 0);
+- val = RSTCTRL_PPE0_V2;
+- } else {
+- val = RSTCTRL_PPE0;
+- }
+-
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
+- val |= RSTCTRL_PPE1;
+-
+- ethsys_reset(eth, RSTCTRL_ETH | RSTCTRL_FE | val);
++ mtk_hw_reset(eth);
+
+ 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);
diff --git a/target/linux/generic/backport-6.6/729-19-v6.3-net-ethernet-mtk_eth_soc-introduce-mtk_hw_warm_reset.patch b/target/linux/generic/backport-6.6/729-19-v6.3-net-ethernet-mtk_eth_soc-introduce-mtk_hw_warm_reset.patch
new file mode 100644
index 0000000000..6597eb5b74
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-19-v6.3-net-ethernet-mtk_eth_soc-introduce-mtk_hw_warm_reset.patch
@@ -0,0 +1,107 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 14 Jan 2023 18:01:29 +0100
+Subject: [PATCH] net: ethernet: mtk_eth_soc: introduce mtk_hw_warm_reset
+ support
+
+Introduce mtk_hw_warm_reset utility routine. This is a preliminary patch
+to align reset procedure to vendor sdk and avoid to power down the chip
+during hw reset.
+
+Reviewed-by: Leon Romanovsky <leonro@nvidia.com>
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -3277,7 +3277,54 @@ static void mtk_hw_reset(struct mtk_eth
+ 0x3ffffff);
+ }
+
+-static int mtk_hw_init(struct mtk_eth *eth)
++static u32 mtk_hw_reset_read(struct mtk_eth *eth)
++{
++ u32 val;
++
++ regmap_read(eth->ethsys, ETHSYS_RSTCTRL, &val);
++ return val;
++}
++
++static void mtk_hw_warm_reset(struct mtk_eth *eth)
++{
++ u32 rst_mask, val;
++
++ regmap_update_bits(eth->ethsys, ETHSYS_RSTCTRL, RSTCTRL_FE,
++ RSTCTRL_FE);
++ if (readx_poll_timeout_atomic(mtk_hw_reset_read, eth, val,
++ val & RSTCTRL_FE, 1, 1000)) {
++ dev_err(eth->dev, "warm reset failed\n");
++ mtk_hw_reset(eth);
++ return;
++ }
++
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ rst_mask = RSTCTRL_ETH | RSTCTRL_PPE0_V2;
++ else
++ rst_mask = RSTCTRL_ETH | RSTCTRL_PPE0;
++
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
++ rst_mask |= RSTCTRL_PPE1;
++
++ regmap_update_bits(eth->ethsys, ETHSYS_RSTCTRL, rst_mask, rst_mask);
++
++ udelay(1);
++ val = mtk_hw_reset_read(eth);
++ if (!(val & rst_mask))
++ dev_err(eth->dev, "warm reset stage0 failed %08x (%08x)\n",
++ val, rst_mask);
++
++ rst_mask |= RSTCTRL_FE;
++ regmap_update_bits(eth->ethsys, ETHSYS_RSTCTRL, rst_mask, ~rst_mask);
++
++ udelay(1);
++ val = mtk_hw_reset_read(eth);
++ if (val & rst_mask)
++ dev_err(eth->dev, "warm reset stage1 failed %08x (%08x)\n",
++ val, rst_mask);
++}
++
++static int mtk_hw_init(struct mtk_eth *eth, bool reset)
+ {
+ u32 dma_mask = ETHSYS_DMA_AG_MAP_PDMA | ETHSYS_DMA_AG_MAP_QDMA |
+ ETHSYS_DMA_AG_MAP_PPE;
+@@ -3316,7 +3363,12 @@ static int mtk_hw_init(struct mtk_eth *e
+ return 0;
+ }
+
+- mtk_hw_reset(eth);
++ msleep(100);
++
++ if (reset)
++ mtk_hw_warm_reset(eth);
++ else
++ mtk_hw_reset(eth);
+
+ if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+ /* Set FE to PDMAv2 if necessary */
+@@ -3507,7 +3559,7 @@ static void mtk_pending_work(struct work
+ if (eth->dev->pins)
+ pinctrl_select_state(eth->dev->pins->p,
+ eth->dev->pins->default_state);
+- mtk_hw_init(eth);
++ mtk_hw_init(eth, true);
+
+ /* restart DMA and enable IRQs */
+ for (i = 0; i < MTK_MAC_COUNT; i++) {
+@@ -4109,7 +4161,7 @@ static int mtk_probe(struct platform_dev
+ eth->msg_enable = netif_msg_init(mtk_msg_level, MTK_DEFAULT_MSG_ENABLE);
+ INIT_WORK(&eth->pending_work, mtk_pending_work);
+
+- err = mtk_hw_init(eth);
++ err = mtk_hw_init(eth, false);
+ if (err)
+ goto err_wed_exit;
+
diff --git a/target/linux/generic/backport-6.6/729-20-v6.3-net-ethernet-mtk_eth_soc-align-reset-procedure-to-ve.patch b/target/linux/generic/backport-6.6/729-20-v6.3-net-ethernet-mtk_eth_soc-align-reset-procedure-to-ve.patch
new file mode 100644
index 0000000000..55ab19f4c8
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-20-v6.3-net-ethernet-mtk_eth_soc-align-reset-procedure-to-ve.patch
@@ -0,0 +1,262 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 14 Jan 2023 18:01:30 +0100
+Subject: [PATCH] net: ethernet: mtk_eth_soc: align reset procedure to vendor
+ sdk
+
+Avoid to power-down the ethernet chip during hw reset and align reset
+procedure to vendor sdk.
+
+Reviewed-by: Leon Romanovsky <leonro@nvidia.com>
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -2844,14 +2844,29 @@ static void mtk_dma_free(struct mtk_eth
+ kfree(eth->scratch_head);
+ }
+
++static bool mtk_hw_reset_check(struct mtk_eth *eth)
++{
++ u32 val = mtk_r32(eth, MTK_INT_STATUS2);
++
++ return (val & MTK_FE_INT_FQ_EMPTY) || (val & MTK_FE_INT_RFIFO_UF) ||
++ (val & MTK_FE_INT_RFIFO_OV) || (val & MTK_FE_INT_TSO_FAIL) ||
++ (val & MTK_FE_INT_TSO_ALIGN) || (val & MTK_FE_INT_TSO_ILLEGAL);
++}
++
+ static void mtk_tx_timeout(struct net_device *dev, unsigned int txqueue)
+ {
+ struct mtk_mac *mac = netdev_priv(dev);
+ struct mtk_eth *eth = mac->hw;
+
++ if (test_bit(MTK_RESETTING, &eth->state))
++ return;
++
++ if (!mtk_hw_reset_check(eth))
++ return;
++
+ eth->netdev[mac->id]->stats.tx_errors++;
+- netif_err(eth, tx_err, dev,
+- "transmit timed out\n");
++ netif_err(eth, tx_err, dev, "transmit timed out\n");
++
+ schedule_work(&eth->pending_work);
+ }
+
+@@ -3331,15 +3346,17 @@ static int mtk_hw_init(struct mtk_eth *e
+ 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))
++ if (!reset && test_and_set_bit(MTK_HW_INIT, &eth->state))
+ return 0;
+
+- pm_runtime_enable(eth->dev);
+- pm_runtime_get_sync(eth->dev);
++ if (!reset) {
++ pm_runtime_enable(eth->dev);
++ pm_runtime_get_sync(eth->dev);
+
+- ret = mtk_clk_enable(eth);
+- if (ret)
+- goto err_disable_pm;
++ ret = mtk_clk_enable(eth);
++ if (ret)
++ goto err_disable_pm;
++ }
+
+ if (eth->ethsys)
+ regmap_update_bits(eth->ethsys, ETHSYS_DMA_AG_MAP, dma_mask,
+@@ -3468,8 +3485,10 @@ static int mtk_hw_init(struct mtk_eth *e
+ return 0;
+
+ err_disable_pm:
+- pm_runtime_put_sync(eth->dev);
+- pm_runtime_disable(eth->dev);
++ if (!reset) {
++ pm_runtime_put_sync(eth->dev);
++ pm_runtime_disable(eth->dev);
++ }
+
+ return ret;
+ }
+@@ -3531,30 +3550,53 @@ static int mtk_do_ioctl(struct net_devic
+ return -EOPNOTSUPP;
+ }
+
++static void mtk_prepare_for_reset(struct mtk_eth *eth)
++{
++ u32 val;
++ int i;
++
++ /* disabe FE P3 and P4 */
++ val = mtk_r32(eth, MTK_FE_GLO_CFG) | MTK_FE_LINK_DOWN_P3;
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
++ val |= MTK_FE_LINK_DOWN_P4;
++ mtk_w32(eth, val, MTK_FE_GLO_CFG);
++
++ /* adjust PPE configurations to prepare for reset */
++ for (i = 0; i < ARRAY_SIZE(eth->ppe); i++)
++ mtk_ppe_prepare_reset(eth->ppe[i]);
++
++ /* disable NETSYS interrupts */
++ mtk_w32(eth, 0, MTK_FE_INT_ENABLE);
++
++ /* force link down GMAC */
++ for (i = 0; i < 2; i++) {
++ val = mtk_r32(eth, MTK_MAC_MCR(i)) & ~MAC_MCR_FORCE_LINK;
++ mtk_w32(eth, val, MTK_MAC_MCR(i));
++ }
++}
++
+ static void mtk_pending_work(struct work_struct *work)
+ {
+ struct mtk_eth *eth = container_of(work, struct mtk_eth, pending_work);
+- int err, i;
+ unsigned long restart = 0;
++ u32 val;
++ int i;
+
+ rtnl_lock();
+-
+- dev_dbg(eth->dev, "[%s][%d] reset\n", __func__, __LINE__);
+ set_bit(MTK_RESETTING, &eth->state);
+
++ mtk_prepare_for_reset(eth);
++
+ /* stop all devices to make sure that dma is properly shut down */
+ for (i = 0; i < MTK_MAC_COUNT; i++) {
+- if (!eth->netdev[i])
++ if (!eth->netdev[i] || !netif_running(eth->netdev[i]))
+ continue;
++
+ mtk_stop(eth->netdev[i]);
+ __set_bit(i, &restart);
+ }
+- dev_dbg(eth->dev, "[%s][%d] mtk_stop ends\n", __func__, __LINE__);
+
+- /* restart underlying hardware such as power, clock, pin mux
+- * and the connected phy
+- */
+- mtk_hw_deinit(eth);
++ usleep_range(15000, 16000);
+
+ if (eth->dev->pins)
+ pinctrl_select_state(eth->dev->pins->p,
+@@ -3565,15 +3607,19 @@ static void mtk_pending_work(struct work
+ for (i = 0; i < MTK_MAC_COUNT; i++) {
+ if (!test_bit(i, &restart))
+ continue;
+- err = mtk_open(eth->netdev[i]);
+- if (err) {
++
++ if (mtk_open(eth->netdev[i])) {
+ netif_alert(eth, ifup, eth->netdev[i],
+- "Driver up/down cycle failed, closing device.\n");
++ "Driver up/down cycle failed\n");
+ dev_close(eth->netdev[i]);
+ }
+ }
+
+- dev_dbg(eth->dev, "[%s][%d] reset done\n", __func__, __LINE__);
++ /* enabe FE P3 and P4 */
++ val = mtk_r32(eth, MTK_FE_GLO_CFG) & ~MTK_FE_LINK_DOWN_P3;
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
++ val &= ~MTK_FE_LINK_DOWN_P4;
++ mtk_w32(eth, val, MTK_FE_GLO_CFG);
+
+ clear_bit(MTK_RESETTING, &eth->state);
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -72,12 +72,24 @@
+ #define MTK_HW_LRO_REPLACE_DELTA 1000
+ #define MTK_HW_LRO_SDL_REMAIN_ROOM 1522
+
++/* Frame Engine Global Configuration */
++#define MTK_FE_GLO_CFG 0x00
++#define MTK_FE_LINK_DOWN_P3 BIT(11)
++#define MTK_FE_LINK_DOWN_P4 BIT(12)
++
+ /* Frame Engine Global Reset Register */
+ #define MTK_RST_GL 0x04
+ #define RST_GL_PSE BIT(0)
+
+ /* Frame Engine Interrupt Status Register */
+ #define MTK_INT_STATUS2 0x08
++#define MTK_FE_INT_ENABLE 0x0c
++#define MTK_FE_INT_FQ_EMPTY BIT(8)
++#define MTK_FE_INT_TSO_FAIL BIT(12)
++#define MTK_FE_INT_TSO_ILLEGAL BIT(13)
++#define MTK_FE_INT_TSO_ALIGN BIT(14)
++#define MTK_FE_INT_RFIFO_OV BIT(18)
++#define MTK_FE_INT_RFIFO_UF BIT(19)
+ #define MTK_GDM1_AF BIT(28)
+ #define MTK_GDM2_AF BIT(29)
+
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
+@@ -710,6 +710,33 @@ int mtk_foe_entry_idle_time(struct mtk_p
+ return __mtk_foe_entry_idle_time(ppe, entry->data.ib1);
+ }
+
++int mtk_ppe_prepare_reset(struct mtk_ppe *ppe)
++{
++ if (!ppe)
++ return -EINVAL;
++
++ /* disable KA */
++ ppe_clear(ppe, MTK_PPE_TB_CFG, MTK_PPE_TB_CFG_KEEPALIVE);
++ ppe_clear(ppe, MTK_PPE_BIND_LMT1, MTK_PPE_NTU_KEEPALIVE);
++ ppe_w32(ppe, MTK_PPE_KEEPALIVE, 0);
++ usleep_range(10000, 11000);
++
++ /* set KA timer to maximum */
++ ppe_set(ppe, MTK_PPE_BIND_LMT1, MTK_PPE_NTU_KEEPALIVE);
++ ppe_w32(ppe, MTK_PPE_KEEPALIVE, 0xffffffff);
++
++ /* set KA tick select */
++ ppe_set(ppe, MTK_PPE_TB_CFG, MTK_PPE_TB_TICK_SEL);
++ ppe_set(ppe, MTK_PPE_TB_CFG, MTK_PPE_TB_CFG_KEEPALIVE);
++ usleep_range(10000, 11000);
++
++ /* disable scan mode */
++ ppe_clear(ppe, MTK_PPE_TB_CFG, MTK_PPE_TB_CFG_SCAN_MODE);
++ usleep_range(10000, 11000);
++
++ return mtk_ppe_wait_busy(ppe);
++}
++
+ struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base,
+ int version, int index)
+ {
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
+@@ -306,6 +306,7 @@ struct mtk_ppe *mtk_ppe_init(struct mtk_
+ void mtk_ppe_deinit(struct mtk_eth *eth);
+ void mtk_ppe_start(struct mtk_ppe *ppe);
+ int mtk_ppe_stop(struct mtk_ppe *ppe);
++int mtk_ppe_prepare_reset(struct mtk_ppe *ppe);
+
+ void __mtk_ppe_check_skb(struct mtk_ppe *ppe, struct sk_buff *skb, u16 hash);
+
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_regs.h
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_regs.h
+@@ -58,6 +58,12 @@
+ #define MTK_PPE_TB_CFG_SCAN_MODE GENMASK(17, 16)
+ #define MTK_PPE_TB_CFG_HASH_DEBUG GENMASK(19, 18)
+ #define MTK_PPE_TB_CFG_INFO_SEL BIT(20)
++#define MTK_PPE_TB_TICK_SEL BIT(24)
++
++#define MTK_PPE_BIND_LMT1 0x230
++#define MTK_PPE_NTU_KEEPALIVE GENMASK(23, 16)
++
++#define MTK_PPE_KEEPALIVE 0x234
+
+ enum {
+ MTK_PPE_SCAN_MODE_DISABLED,
diff --git a/target/linux/generic/backport-6.6/729-21-v6.3-net-ethernet-mtk_eth_soc-add-dma-checks-to-mtk_hw_re.patch b/target/linux/generic/backport-6.6/729-21-v6.3-net-ethernet-mtk_eth_soc-add-dma-checks-to-mtk_hw_re.patch
new file mode 100644
index 0000000000..d5a7c0eba2
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-21-v6.3-net-ethernet-mtk_eth_soc-add-dma-checks-to-mtk_hw_re.patch
@@ -0,0 +1,249 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 14 Jan 2023 18:01:31 +0100
+Subject: [PATCH] net: ethernet: mtk_eth_soc: add dma checks to
+ mtk_hw_reset_check
+
+Introduce mtk_hw_check_dma_hang routine to monitor possible dma hangs.
+
+Reviewed-by: Leon Romanovsky <leonro@nvidia.com>
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -50,6 +50,7 @@ static const struct mtk_reg_map mtk_reg_
+ .delay_irq = 0x0a0c,
+ .irq_status = 0x0a20,
+ .irq_mask = 0x0a28,
++ .adma_rx_dbg0 = 0x0a38,
+ .int_grp = 0x0a50,
+ },
+ .qdma = {
+@@ -79,6 +80,8 @@ static const struct mtk_reg_map mtk_reg_
+ [0] = 0x2800,
+ [1] = 0x2c00,
+ },
++ .pse_iq_sta = 0x0110,
++ .pse_oq_sta = 0x0118,
+ };
+
+ static const struct mtk_reg_map mt7628_reg_map = {
+@@ -109,6 +112,7 @@ static const struct mtk_reg_map mt7986_r
+ .delay_irq = 0x620c,
+ .irq_status = 0x6220,
+ .irq_mask = 0x6228,
++ .adma_rx_dbg0 = 0x6238,
+ .int_grp = 0x6250,
+ },
+ .qdma = {
+@@ -138,6 +142,8 @@ static const struct mtk_reg_map mt7986_r
+ [0] = 0x4800,
+ [1] = 0x4c00,
+ },
++ .pse_iq_sta = 0x0180,
++ .pse_oq_sta = 0x01a0,
+ };
+
+ /* strings used by ethtool */
+@@ -3339,6 +3345,102 @@ static void mtk_hw_warm_reset(struct mtk
+ val, rst_mask);
+ }
+
++static bool mtk_hw_check_dma_hang(struct mtk_eth *eth)
++{
++ const struct mtk_reg_map *reg_map = eth->soc->reg_map;
++ bool gmac1_tx, gmac2_tx, gdm1_tx, gdm2_tx;
++ bool oq_hang, cdm1_busy, adma_busy;
++ bool wtx_busy, cdm_full, oq_free;
++ u32 wdidx, val, gdm1_fc, gdm2_fc;
++ bool qfsm_hang, qfwd_hang;
++ bool ret = false;
++
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628))
++ return false;
++
++ /* WDMA sanity checks */
++ wdidx = mtk_r32(eth, reg_map->wdma_base[0] + 0xc);
++
++ val = mtk_r32(eth, reg_map->wdma_base[0] + 0x204);
++ wtx_busy = FIELD_GET(MTK_TX_DMA_BUSY, val);
++
++ val = mtk_r32(eth, reg_map->wdma_base[0] + 0x230);
++ cdm_full = !FIELD_GET(MTK_CDM_TXFIFO_RDY, val);
++
++ oq_free = (!(mtk_r32(eth, reg_map->pse_oq_sta) & GENMASK(24, 16)) &&
++ !(mtk_r32(eth, reg_map->pse_oq_sta + 0x4) & GENMASK(8, 0)) &&
++ !(mtk_r32(eth, reg_map->pse_oq_sta + 0x10) & GENMASK(24, 16)));
++
++ if (wdidx == eth->reset.wdidx && wtx_busy && cdm_full && oq_free) {
++ if (++eth->reset.wdma_hang_count > 2) {
++ eth->reset.wdma_hang_count = 0;
++ ret = true;
++ }
++ goto out;
++ }
++
++ /* QDMA sanity checks */
++ qfsm_hang = !!mtk_r32(eth, reg_map->qdma.qtx_cfg + 0x234);
++ qfwd_hang = !mtk_r32(eth, reg_map->qdma.qtx_cfg + 0x308);
++
++ gdm1_tx = FIELD_GET(GENMASK(31, 16), mtk_r32(eth, MTK_FE_GDM1_FSM)) > 0;
++ gdm2_tx = FIELD_GET(GENMASK(31, 16), mtk_r32(eth, MTK_FE_GDM2_FSM)) > 0;
++ gmac1_tx = FIELD_GET(GENMASK(31, 24), mtk_r32(eth, MTK_MAC_FSM(0))) != 1;
++ gmac2_tx = FIELD_GET(GENMASK(31, 24), mtk_r32(eth, MTK_MAC_FSM(1))) != 1;
++ gdm1_fc = mtk_r32(eth, reg_map->gdm1_cnt + 0x24);
++ gdm2_fc = mtk_r32(eth, reg_map->gdm1_cnt + 0x64);
++
++ if (qfsm_hang && qfwd_hang &&
++ ((gdm1_tx && gmac1_tx && gdm1_fc < 1) ||
++ (gdm2_tx && gmac2_tx && gdm2_fc < 1))) {
++ if (++eth->reset.qdma_hang_count > 2) {
++ eth->reset.qdma_hang_count = 0;
++ ret = true;
++ }
++ goto out;
++ }
++
++ /* ADMA sanity checks */
++ oq_hang = !!(mtk_r32(eth, reg_map->pse_oq_sta) & GENMASK(8, 0));
++ cdm1_busy = !!(mtk_r32(eth, MTK_FE_CDM1_FSM) & GENMASK(31, 16));
++ adma_busy = !(mtk_r32(eth, reg_map->pdma.adma_rx_dbg0) & GENMASK(4, 0)) &&
++ !(mtk_r32(eth, reg_map->pdma.adma_rx_dbg0) & BIT(6));
++
++ if (oq_hang && cdm1_busy && adma_busy) {
++ if (++eth->reset.adma_hang_count > 2) {
++ eth->reset.adma_hang_count = 0;
++ ret = true;
++ }
++ goto out;
++ }
++
++ eth->reset.wdma_hang_count = 0;
++ eth->reset.qdma_hang_count = 0;
++ eth->reset.adma_hang_count = 0;
++out:
++ eth->reset.wdidx = wdidx;
++
++ return ret;
++}
++
++static void mtk_hw_reset_monitor_work(struct work_struct *work)
++{
++ struct delayed_work *del_work = to_delayed_work(work);
++ struct mtk_eth *eth = container_of(del_work, struct mtk_eth,
++ reset.monitor_work);
++
++ if (test_bit(MTK_RESETTING, &eth->state))
++ goto out;
++
++ /* DMA stuck checks */
++ if (mtk_hw_check_dma_hang(eth))
++ schedule_work(&eth->pending_work);
++
++out:
++ schedule_delayed_work(&eth->reset.monitor_work,
++ MTK_DMA_MONITOR_TIMEOUT);
++}
++
+ static int mtk_hw_init(struct mtk_eth *eth, bool reset)
+ {
+ u32 dma_mask = ETHSYS_DMA_AG_MAP_PDMA | ETHSYS_DMA_AG_MAP_QDMA |
+@@ -3657,6 +3759,7 @@ static int mtk_cleanup(struct mtk_eth *e
+ mtk_unreg_dev(eth);
+ mtk_free_dev(eth);
+ cancel_work_sync(&eth->pending_work);
++ cancel_delayed_work_sync(&eth->reset.monitor_work);
+
+ return 0;
+ }
+@@ -4094,6 +4197,7 @@ static int mtk_probe(struct platform_dev
+
+ eth->rx_dim.mode = DIM_CQ_PERIOD_MODE_START_FROM_EQE;
+ INIT_WORK(&eth->rx_dim.work, mtk_dim_rx);
++ INIT_DELAYED_WORK(&eth->reset.monitor_work, mtk_hw_reset_monitor_work);
+
+ eth->tx_dim.mode = DIM_CQ_PERIOD_MODE_START_FROM_EQE;
+ INIT_WORK(&eth->tx_dim.work, mtk_dim_tx);
+@@ -4296,6 +4400,8 @@ static int mtk_probe(struct platform_dev
+ netif_napi_add(&eth->dummy_dev, &eth->rx_napi, mtk_napi_rx);
+
+ platform_set_drvdata(pdev, eth);
++ schedule_delayed_work(&eth->reset.monitor_work,
++ MTK_DMA_MONITOR_TIMEOUT);
+
+ return 0;
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -257,6 +257,8 @@
+
+ #define MTK_RX_DONE_INT_V2 BIT(14)
+
++#define MTK_CDM_TXFIFO_RDY BIT(7)
++
+ /* QDMA Interrupt grouping registers */
+ #define MTK_RLS_DONE_INT BIT(0)
+
+@@ -542,6 +544,17 @@
+ #define MT7628_SDM_RBCNT (MT7628_SDM_OFFSET + 0x10c)
+ #define MT7628_SDM_CS_ERR (MT7628_SDM_OFFSET + 0x110)
+
++#define MTK_FE_CDM1_FSM 0x220
++#define MTK_FE_CDM2_FSM 0x224
++#define MTK_FE_CDM3_FSM 0x238
++#define MTK_FE_CDM4_FSM 0x298
++#define MTK_FE_CDM5_FSM 0x318
++#define MTK_FE_CDM6_FSM 0x328
++#define MTK_FE_GDM1_FSM 0x228
++#define MTK_FE_GDM2_FSM 0x22C
++
++#define MTK_MAC_FSM(x) (0x1010C + ((x) * 0x100))
++
+ struct mtk_rx_dma {
+ unsigned int rxd1;
+ unsigned int rxd2;
+@@ -938,6 +951,7 @@ struct mtk_reg_map {
+ u32 delay_irq; /* delay interrupt */
+ u32 irq_status; /* interrupt status */
+ u32 irq_mask; /* interrupt mask */
++ u32 adma_rx_dbg0;
+ u32 int_grp;
+ } pdma;
+ struct {
+@@ -964,6 +978,8 @@ struct mtk_reg_map {
+ u32 gdma_to_ppe;
+ u32 ppe_base;
+ u32 wdma_base[2];
++ u32 pse_iq_sta;
++ u32 pse_oq_sta;
+ };
+
+ /* struct mtk_eth_data - This is the structure holding all differences
+@@ -1006,6 +1022,8 @@ struct mtk_soc_data {
+ } txrx;
+ };
+
++#define MTK_DMA_MONITOR_TIMEOUT msecs_to_jiffies(1000)
++
+ /* currently no SoC has more than 2 macs */
+ #define MTK_MAX_DEVS 2
+
+@@ -1128,6 +1146,14 @@ struct mtk_eth {
+ struct rhashtable flow_table;
+
+ struct bpf_prog __rcu *prog;
++
++ struct {
++ struct delayed_work monitor_work;
++ u32 wdidx;
++ u8 wdma_hang_count;
++ u8 qdma_hang_count;
++ u8 adma_hang_count;
++ } reset;
+ };
+
+ /* struct mtk_mac - the structure that holds the info about the MACs of the
diff --git a/target/linux/generic/backport-6.6/729-22-v6.3-net-ethernet-mtk_wed-add-reset-reset_complete-callba.patch b/target/linux/generic/backport-6.6/729-22-v6.3-net-ethernet-mtk_wed-add-reset-reset_complete-callba.patch
new file mode 100644
index 0000000000..c21d094ae8
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-22-v6.3-net-ethernet-mtk_wed-add-reset-reset_complete-callba.patch
@@ -0,0 +1,124 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 14 Jan 2023 18:01:32 +0100
+Subject: [PATCH] net: ethernet: mtk_wed: add reset/reset_complete callbacks
+
+Introduce reset and reset_complete wlan callback to schedule WLAN driver
+reset when ethernet/wed driver is resetting.
+
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -3688,6 +3688,11 @@ static void mtk_pending_work(struct work
+ set_bit(MTK_RESETTING, &eth->state);
+
+ mtk_prepare_for_reset(eth);
++ mtk_wed_fe_reset();
++ /* Run again reset preliminary configuration in order to avoid any
++ * possible race during FE reset since it can run releasing RTNL lock.
++ */
++ mtk_prepare_for_reset(eth);
+
+ /* stop all devices to make sure that dma is properly shut down */
+ for (i = 0; i < MTK_MAC_COUNT; i++) {
+@@ -3725,6 +3730,8 @@ static void mtk_pending_work(struct work
+
+ clear_bit(MTK_RESETTING, &eth->state);
+
++ mtk_wed_fe_reset_complete();
++
+ rtnl_unlock();
+ }
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -205,6 +205,48 @@ mtk_wed_wo_reset(struct mtk_wed_device *
+ iounmap(reg);
+ }
+
++void mtk_wed_fe_reset(void)
++{
++ int i;
++
++ mutex_lock(&hw_lock);
++
++ for (i = 0; i < ARRAY_SIZE(hw_list); i++) {
++ struct mtk_wed_hw *hw = hw_list[i];
++ struct mtk_wed_device *dev = hw->wed_dev;
++ int err;
++
++ if (!dev || !dev->wlan.reset)
++ continue;
++
++ /* reset callback blocks until WLAN reset is completed */
++ err = dev->wlan.reset(dev);
++ if (err)
++ dev_err(dev->dev, "wlan reset failed: %d\n", err);
++ }
++
++ mutex_unlock(&hw_lock);
++}
++
++void mtk_wed_fe_reset_complete(void)
++{
++ int i;
++
++ mutex_lock(&hw_lock);
++
++ for (i = 0; i < ARRAY_SIZE(hw_list); i++) {
++ struct mtk_wed_hw *hw = hw_list[i];
++ struct mtk_wed_device *dev = hw->wed_dev;
++
++ if (!dev || !dev->wlan.reset_complete)
++ continue;
++
++ dev->wlan.reset_complete(dev);
++ }
++
++ mutex_unlock(&hw_lock);
++}
++
+ static struct mtk_wed_hw *
+ mtk_wed_assign(struct mtk_wed_device *dev)
+ {
+--- a/drivers/net/ethernet/mediatek/mtk_wed.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed.h
+@@ -128,6 +128,8 @@ void mtk_wed_add_hw(struct device_node *
+ void mtk_wed_exit(void);
+ int mtk_wed_flow_add(int index);
+ void mtk_wed_flow_remove(int index);
++void mtk_wed_fe_reset(void);
++void mtk_wed_fe_reset_complete(void);
+ #else
+ static inline void
+ mtk_wed_add_hw(struct device_node *np, struct mtk_eth *eth,
+@@ -147,6 +149,13 @@ static inline void mtk_wed_flow_remove(i
+ {
+ }
+
++static inline void mtk_wed_fe_reset(void)
++{
++}
++
++static inline void mtk_wed_fe_reset_complete(void)
++{
++}
+ #endif
+
+ #ifdef CONFIG_DEBUG_FS
+--- a/include/linux/soc/mediatek/mtk_wed.h
++++ b/include/linux/soc/mediatek/mtk_wed.h
+@@ -151,6 +151,8 @@ struct mtk_wed_device {
+ void (*release_rx_buf)(struct mtk_wed_device *wed);
+ void (*update_wo_rx_stats)(struct mtk_wed_device *wed,
+ struct mtk_wed_wo_rx_stats *stats);
++ int (*reset)(struct mtk_wed_device *wed);
++ void (*reset_complete)(struct mtk_wed_device *wed);
+ } wlan;
+ #endif
+ };
diff --git a/target/linux/generic/backport-6.6/729-23-v6.3-net-ethernet-mtk_wed-add-reset-to-rx_ring_setup-call.patch b/target/linux/generic/backport-6.6/729-23-v6.3-net-ethernet-mtk_wed-add-reset-to-rx_ring_setup-call.patch
new file mode 100644
index 0000000000..c63628da99
--- /dev/null
+++ b/target/linux/generic/backport-6.6/729-23-v6.3-net-ethernet-mtk_wed-add-reset-to-rx_ring_setup-call.patch
@@ -0,0 +1,106 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Mon, 5 Dec 2022 12:34:42 +0100
+Subject: [PATCH] net: ethernet: mtk_wed: add reset to rx_ring_setup callback
+
+This patch adds reset parameter to mtk_wed_rx_ring_setup signature
+in order to align rx_ring_setup callback to tx_ring_setup one introduced
+in 'commit 23dca7a90017 ("net: ethernet: mtk_wed: add reset to
+tx_ring_setup callback")'
+
+Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Reviewed-by: Leon Romanovsky <leonro@nvidia.com>
+Link: https://lore.kernel.org/r/29c6e7a5469e784406cf3e2920351d1207713d05.1670239984.git.lorenzo@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -1252,7 +1252,8 @@ mtk_wed_wdma_rx_ring_setup(struct mtk_we
+ }
+
+ static int
+-mtk_wed_wdma_tx_ring_setup(struct mtk_wed_device *dev, int idx, int size)
++mtk_wed_wdma_tx_ring_setup(struct mtk_wed_device *dev, int idx, int size,
++ bool reset)
+ {
+ u32 desc_size = sizeof(struct mtk_wdma_desc) * dev->hw->version;
+ struct mtk_wed_ring *wdma;
+@@ -1261,8 +1262,8 @@ mtk_wed_wdma_tx_ring_setup(struct mtk_we
+ return -EINVAL;
+
+ wdma = &dev->tx_wdma[idx];
+- if (mtk_wed_ring_alloc(dev, wdma, MTK_WED_WDMA_RING_SIZE, desc_size,
+- true))
++ if (!reset && mtk_wed_ring_alloc(dev, wdma, MTK_WED_WDMA_RING_SIZE,
++ desc_size, true))
+ return -ENOMEM;
+
+ wdma_w32(dev, MTK_WDMA_RING_TX(idx) + MTK_WED_RING_OFS_BASE,
+@@ -1272,6 +1273,9 @@ mtk_wed_wdma_tx_ring_setup(struct mtk_we
+ wdma_w32(dev, MTK_WDMA_RING_TX(idx) + MTK_WED_RING_OFS_CPU_IDX, 0);
+ wdma_w32(dev, MTK_WDMA_RING_TX(idx) + MTK_WED_RING_OFS_DMA_IDX, 0);
+
++ if (reset)
++ mtk_wed_ring_reset(wdma, MTK_WED_WDMA_RING_SIZE, true);
++
+ if (!idx) {
+ wed_w32(dev, MTK_WED_WDMA_RING_TX + MTK_WED_RING_OFS_BASE,
+ wdma->desc_phys);
+@@ -1611,18 +1615,20 @@ mtk_wed_txfree_ring_setup(struct mtk_wed
+ }
+
+ static int
+-mtk_wed_rx_ring_setup(struct mtk_wed_device *dev, int idx, void __iomem *regs)
++mtk_wed_rx_ring_setup(struct mtk_wed_device *dev, int idx, void __iomem *regs,
++ bool reset)
+ {
+ struct mtk_wed_ring *ring = &dev->rx_ring[idx];
+
+ if (WARN_ON(idx >= ARRAY_SIZE(dev->rx_ring)))
+ return -EINVAL;
+
+- if (mtk_wed_ring_alloc(dev, ring, MTK_WED_RX_RING_SIZE,
+- sizeof(*ring->desc), false))
++ if (!reset && mtk_wed_ring_alloc(dev, ring, MTK_WED_RX_RING_SIZE,
++ sizeof(*ring->desc), false))
+ return -ENOMEM;
+
+- if (mtk_wed_wdma_tx_ring_setup(dev, idx, MTK_WED_WDMA_RING_SIZE))
++ if (mtk_wed_wdma_tx_ring_setup(dev, idx, MTK_WED_WDMA_RING_SIZE,
++ reset))
+ return -ENOMEM;
+
+ ring->reg_base = MTK_WED_RING_RX_DATA(idx);
+--- a/include/linux/soc/mediatek/mtk_wed.h
++++ b/include/linux/soc/mediatek/mtk_wed.h
+@@ -162,7 +162,7 @@ struct mtk_wed_ops {
+ int (*tx_ring_setup)(struct mtk_wed_device *dev, int ring,
+ void __iomem *regs, bool reset);
+ int (*rx_ring_setup)(struct mtk_wed_device *dev, int ring,
+- void __iomem *regs);
++ void __iomem *regs, bool reset);
+ int (*txfree_ring_setup)(struct mtk_wed_device *dev,
+ void __iomem *regs);
+ int (*msg_update)(struct mtk_wed_device *dev, int cmd_id,
+@@ -230,8 +230,8 @@ mtk_wed_get_rx_capa(struct mtk_wed_devic
+ (_dev)->ops->irq_get(_dev, _mask)
+ #define mtk_wed_device_irq_set_mask(_dev, _mask) \
+ (_dev)->ops->irq_set_mask(_dev, _mask)
+-#define mtk_wed_device_rx_ring_setup(_dev, _ring, _regs) \
+- (_dev)->ops->rx_ring_setup(_dev, _ring, _regs)
++#define mtk_wed_device_rx_ring_setup(_dev, _ring, _regs, _reset) \
++ (_dev)->ops->rx_ring_setup(_dev, _ring, _regs, _reset)
+ #define mtk_wed_device_ppe_check(_dev, _skb, _reason, _hash) \
+ (_dev)->ops->ppe_check(_dev, _skb, _reason, _hash)
+ #define mtk_wed_device_update_msg(_dev, _id, _msg, _len) \
+@@ -251,7 +251,7 @@ static inline bool mtk_wed_device_active
+ #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)
+-#define mtk_wed_device_rx_ring_setup(_dev, _ring, _regs) -ENODEV
++#define mtk_wed_device_rx_ring_setup(_dev, _ring, _regs, _reset) -ENODEV
+ #define mtk_wed_device_ppe_check(_dev, _skb, _reason, _hash) do {} while (0)
+ #define mtk_wed_device_update_msg(_dev, _id, _msg, _len) -ENODEV
+ #define mtk_wed_device_stop(_dev) do {} while (0)
diff --git a/target/linux/generic/backport-6.6/730-01-v6.3-net-ethernet-mtk_eth_soc-account-for-vlan-in-rx-head.patch b/target/linux/generic/backport-6.6/730-01-v6.3-net-ethernet-mtk_eth_soc-account-for-vlan-in-rx-head.patch
new file mode 100644
index 0000000000..45af898cf0
--- /dev/null
+++ b/target/linux/generic/backport-6.6/730-01-v6.3-net-ethernet-mtk_eth_soc-account-for-vlan-in-rx-head.patch
@@ -0,0 +1,22 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 27 Oct 2022 19:50:31 +0200
+Subject: [PATCH] net: ethernet: mtk_eth_soc: account for vlan in rx
+ header length
+
+The network stack assumes that devices can handle an extra VLAN tag without
+increasing the MTU
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -29,7 +29,7 @@
+ #define MTK_TX_DMA_BUF_LEN_V2 0xffff
+ #define MTK_DMA_SIZE 512
+ #define MTK_MAC_COUNT 2
+-#define MTK_RX_ETH_HLEN (ETH_HLEN + ETH_FCS_LEN)
++#define MTK_RX_ETH_HLEN (VLAN_ETH_HLEN + ETH_FCS_LEN)
+ #define MTK_RX_HLEN (NET_SKB_PAD + MTK_RX_ETH_HLEN + NET_IP_ALIGN)
+ #define MTK_DMA_DUMMY_DESC 0xffffffff
+ #define MTK_DEFAULT_MSG_ENABLE (NETIF_MSG_DRV | \
diff --git a/target/linux/generic/backport-6.6/730-02-v6.3-net-ethernet-mtk_eth_soc-increase-tx-ring-side-for-Q.patch b/target/linux/generic/backport-6.6/730-02-v6.3-net-ethernet-mtk_eth_soc-increase-tx-ring-side-for-Q.patch
new file mode 100644
index 0000000000..046a581224
--- /dev/null
+++ b/target/linux/generic/backport-6.6/730-02-v6.3-net-ethernet-mtk_eth_soc-increase-tx-ring-side-for-Q.patch
@@ -0,0 +1,143 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 27 Oct 2022 19:53:57 +0200
+Subject: [PATCH] net: ethernet: mtk_eth_soc: increase tx ring side for
+ QDMA devices
+
+In order to use the hardware traffic shaper feature, a larger tx ring is
+needed, especially for the scratch ring, which the hardware shaper uses to
+reorder packets.
+
+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
+@@ -944,7 +944,7 @@ static int mtk_init_fq_dma(struct mtk_et
+ {
+ const struct mtk_soc_data *soc = eth->soc;
+ dma_addr_t phy_ring_tail;
+- int cnt = MTK_DMA_SIZE;
++ int cnt = MTK_QDMA_RING_SIZE;
+ dma_addr_t dma_addr;
+ int i;
+
+@@ -2208,19 +2208,25 @@ static int mtk_tx_alloc(struct mtk_eth *
+ struct mtk_tx_ring *ring = &eth->tx_ring;
+ int i, sz = soc->txrx.txd_size;
+ struct mtk_tx_dma_v2 *txd;
++ int ring_size;
+
+- ring->buf = kcalloc(MTK_DMA_SIZE, sizeof(*ring->buf),
++ if (MTK_HAS_CAPS(soc->caps, MTK_QDMA))
++ ring_size = MTK_QDMA_RING_SIZE;
++ else
++ ring_size = MTK_DMA_SIZE;
++
++ ring->buf = kcalloc(ring_size, sizeof(*ring->buf),
+ GFP_KERNEL);
+ if (!ring->buf)
+ goto no_tx_mem;
+
+- ring->dma = dma_alloc_coherent(eth->dma_dev, MTK_DMA_SIZE * sz,
++ ring->dma = dma_alloc_coherent(eth->dma_dev, ring_size * sz,
+ &ring->phys, GFP_KERNEL);
+ if (!ring->dma)
+ goto no_tx_mem;
+
+- for (i = 0; i < MTK_DMA_SIZE; i++) {
+- int next = (i + 1) % MTK_DMA_SIZE;
++ for (i = 0; i < ring_size; i++) {
++ int next = (i + 1) % ring_size;
+ u32 next_ptr = ring->phys + next * sz;
+
+ txd = ring->dma + i * sz;
+@@ -2240,22 +2246,22 @@ static int mtk_tx_alloc(struct mtk_eth *
+ * descriptors in ring->dma_pdma.
+ */
+ if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
+- ring->dma_pdma = dma_alloc_coherent(eth->dma_dev, MTK_DMA_SIZE * sz,
++ ring->dma_pdma = dma_alloc_coherent(eth->dma_dev, ring_size * sz,
+ &ring->phys_pdma, GFP_KERNEL);
+ if (!ring->dma_pdma)
+ goto no_tx_mem;
+
+- for (i = 0; i < MTK_DMA_SIZE; i++) {
++ for (i = 0; i < ring_size; i++) {
+ ring->dma_pdma[i].txd2 = TX_DMA_DESP2_DEF;
+ ring->dma_pdma[i].txd4 = 0;
+ }
+ }
+
+- ring->dma_size = MTK_DMA_SIZE;
+- atomic_set(&ring->free_count, MTK_DMA_SIZE - 2);
++ ring->dma_size = ring_size;
++ atomic_set(&ring->free_count, ring_size - 2);
+ ring->next_free = ring->dma;
+ ring->last_free = (void *)txd;
+- ring->last_free_ptr = (u32)(ring->phys + ((MTK_DMA_SIZE - 1) * sz));
++ ring->last_free_ptr = (u32)(ring->phys + ((ring_size - 1) * sz));
+ ring->thresh = MAX_SKB_FRAGS;
+
+ /* make sure that all changes to the dma ring are flushed before we
+@@ -2267,14 +2273,14 @@ static int mtk_tx_alloc(struct mtk_eth *
+ 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),
++ ring->phys + ((ring_size - 1) * sz),
+ 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,
+ 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, ring_size, MT7628_TX_MAX_CNT0);
+ mtk_w32(eth, 0, MT7628_TX_CTX_IDX0);
+ mtk_w32(eth, MT7628_PST_DTX_IDX0, soc->reg_map->pdma.rst_idx);
+ }
+@@ -2292,7 +2298,7 @@ static void mtk_tx_clean(struct mtk_eth
+ int i;
+
+ if (ring->buf) {
+- for (i = 0; i < MTK_DMA_SIZE; i++)
++ for (i = 0; i < ring->dma_size; i++)
+ mtk_tx_unmap(eth, &ring->buf[i], NULL, false);
+ kfree(ring->buf);
+ ring->buf = NULL;
+@@ -2300,14 +2306,14 @@ static void mtk_tx_clean(struct mtk_eth
+
+ if (ring->dma) {
+ dma_free_coherent(eth->dma_dev,
+- MTK_DMA_SIZE * soc->txrx.txd_size,
++ ring->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 * soc->txrx.txd_size,
++ ring->dma_size * soc->txrx.txd_size,
+ ring->dma_pdma, ring->phys_pdma);
+ ring->dma_pdma = NULL;
+ }
+@@ -2832,7 +2838,7 @@ 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 * soc->txrx.txd_size,
++ MTK_QDMA_RING_SIZE * soc->txrx.txd_size,
+ eth->scratch_ring, eth->phy_scratch_ring);
+ eth->scratch_ring = NULL;
+ eth->phy_scratch_ring = 0;
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -27,6 +27,7 @@
+ #define MTK_MAX_RX_LENGTH_2K 2048
+ #define MTK_TX_DMA_BUF_LEN 0x3fff
+ #define MTK_TX_DMA_BUF_LEN_V2 0xffff
++#define MTK_QDMA_RING_SIZE 2048
+ #define MTK_DMA_SIZE 512
+ #define MTK_MAC_COUNT 2
+ #define MTK_RX_ETH_HLEN (VLAN_ETH_HLEN + ETH_FCS_LEN)
diff --git a/target/linux/generic/backport-6.6/730-03-v6.3-net-ethernet-mtk_eth_soc-avoid-port_mg-assignment-on.patch b/target/linux/generic/backport-6.6/730-03-v6.3-net-ethernet-mtk_eth_soc-avoid-port_mg-assignment-on.patch
new file mode 100644
index 0000000000..7e879ca1d5
--- /dev/null
+++ b/target/linux/generic/backport-6.6/730-03-v6.3-net-ethernet-mtk_eth_soc-avoid-port_mg-assignment-on.patch
@@ -0,0 +1,52 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 4 Nov 2022 19:49:08 +0100
+Subject: [PATCH] net: ethernet: mtk_eth_soc: avoid port_mg assignment on
+ MT7622 and newer
+
+On newer chips, this field is unused and contains some bits related to queue
+assignment. Initialize it to 0 in those cases.
+Fix offload_version on MT7621 and MT7623, which still need the previous value.
+
+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
+@@ -4479,7 +4479,7 @@ static const struct mtk_soc_data mt7621_
+ .hw_features = MTK_HW_FEATURES,
+ .required_clks = MT7621_CLKS_BITMAP,
+ .required_pctl = false,
+- .offload_version = 2,
++ .offload_version = 1,
+ .hash_offset = 2,
+ .foe_entry_size = sizeof(struct mtk_foe_entry) - 16,
+ .txrx = {
+@@ -4518,7 +4518,7 @@ static const struct mtk_soc_data mt7623_
+ .hw_features = MTK_HW_FEATURES,
+ .required_clks = MT7623_CLKS_BITMAP,
+ .required_pctl = true,
+- .offload_version = 2,
++ .offload_version = 1,
+ .hash_offset = 2,
+ .foe_entry_size = sizeof(struct mtk_foe_entry) - 16,
+ .txrx = {
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
+@@ -175,6 +175,8 @@ int mtk_foe_entry_prepare(struct mtk_eth
+ val = FIELD_PREP(MTK_FOE_IB2_DEST_PORT_V2, pse_port) |
+ FIELD_PREP(MTK_FOE_IB2_PORT_AG_V2, 0xf);
+ } else {
++ int port_mg = eth->soc->offload_version > 1 ? 0 : 0x3f;
++
+ val = FIELD_PREP(MTK_FOE_IB1_STATE, MTK_FOE_STATE_BIND) |
+ FIELD_PREP(MTK_FOE_IB1_PACKET_TYPE, type) |
+ FIELD_PREP(MTK_FOE_IB1_UDP, l4proto == IPPROTO_UDP) |
+@@ -182,7 +184,7 @@ int mtk_foe_entry_prepare(struct mtk_eth
+ entry->ib1 = val;
+
+ val = FIELD_PREP(MTK_FOE_IB2_DEST_PORT, pse_port) |
+- FIELD_PREP(MTK_FOE_IB2_PORT_MG, 0x3f) |
++ FIELD_PREP(MTK_FOE_IB2_PORT_MG, port_mg) |
+ FIELD_PREP(MTK_FOE_IB2_PORT_AG, 0x1f);
+ }
+
diff --git a/target/linux/generic/backport-6.6/730-04-v6.3-net-ethernet-mtk_eth_soc-implement-multi-queue-suppo.patch b/target/linux/generic/backport-6.6/730-04-v6.3-net-ethernet-mtk_eth_soc-implement-multi-queue-suppo.patch
new file mode 100644
index 0000000000..8ceba7831e
--- /dev/null
+++ b/target/linux/generic/backport-6.6/730-04-v6.3-net-ethernet-mtk_eth_soc-implement-multi-queue-suppo.patch
@@ -0,0 +1,654 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 27 Oct 2022 20:17:27 +0200
+Subject: [PATCH] net: ethernet: mtk_eth_soc: implement multi-queue
+ support for per-port queues
+
+When sending traffic to multiple ports with different link speeds, queued
+packets to one port can drown out tx to other ports.
+In order to better handle transmission to multiple ports, use the hardware
+shaper feature to implement weighted fair queueing between ports.
+Weight and maximum rate are automatically adjusted based on the link speed
+of the port.
+The first 3 queues are unrestricted and reserved for non-DSA direct tx on
+GMAC ports. The following queues are automatically assigned by the MTK DSA
+tag driver based on the target port number.
+The PPE offload code configures the queues for offloaded traffic in the same
+way.
+This feature is only supported on devices supporting QDMA. All queues still
+share the same DMA ring and descriptor pool.
+
+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
+@@ -55,6 +55,7 @@ static const struct mtk_reg_map mtk_reg_
+ },
+ .qdma = {
+ .qtx_cfg = 0x1800,
++ .qtx_sch = 0x1804,
+ .rx_ptr = 0x1900,
+ .rx_cnt_cfg = 0x1904,
+ .qcrx_ptr = 0x1908,
+@@ -62,6 +63,7 @@ static const struct mtk_reg_map mtk_reg_
+ .rst_idx = 0x1a08,
+ .delay_irq = 0x1a0c,
+ .fc_th = 0x1a10,
++ .tx_sch_rate = 0x1a14,
+ .int_grp = 0x1a20,
+ .hred = 0x1a44,
+ .ctx_ptr = 0x1b00,
+@@ -117,6 +119,7 @@ static const struct mtk_reg_map mt7986_r
+ },
+ .qdma = {
+ .qtx_cfg = 0x4400,
++ .qtx_sch = 0x4404,
+ .rx_ptr = 0x4500,
+ .rx_cnt_cfg = 0x4504,
+ .qcrx_ptr = 0x4508,
+@@ -134,6 +137,7 @@ static const struct mtk_reg_map mt7986_r
+ .fq_tail = 0x4724,
+ .fq_count = 0x4728,
+ .fq_blen = 0x472c,
++ .tx_sch_rate = 0x4798,
+ },
+ .gdm1_cnt = 0x1c00,
+ .gdma_to_ppe = 0x3333,
+@@ -620,6 +624,75 @@ static void mtk_mac_link_down(struct phy
+ mtk_w32(mac->hw, mcr, MTK_MAC_MCR(mac->id));
+ }
+
++static void mtk_set_queue_speed(struct mtk_eth *eth, unsigned int idx,
++ int speed)
++{
++ const struct mtk_soc_data *soc = eth->soc;
++ u32 ofs, val;
++
++ if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA))
++ return;
++
++ val = MTK_QTX_SCH_MIN_RATE_EN |
++ /* minimum: 10 Mbps */
++ FIELD_PREP(MTK_QTX_SCH_MIN_RATE_MAN, 1) |
++ FIELD_PREP(MTK_QTX_SCH_MIN_RATE_EXP, 4) |
++ MTK_QTX_SCH_LEAKY_BUCKET_SIZE;
++ if (!MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ val |= MTK_QTX_SCH_LEAKY_BUCKET_EN;
++
++ if (IS_ENABLED(CONFIG_SOC_MT7621)) {
++ switch (speed) {
++ case SPEED_10:
++ val |= MTK_QTX_SCH_MAX_RATE_EN |
++ FIELD_PREP(MTK_QTX_SCH_MAX_RATE_MAN, 103) |
++ FIELD_PREP(MTK_QTX_SCH_MAX_RATE_EXP, 2) |
++ FIELD_PREP(MTK_QTX_SCH_MAX_RATE_WEIGHT, 1);
++ break;
++ case SPEED_100:
++ val |= MTK_QTX_SCH_MAX_RATE_EN |
++ FIELD_PREP(MTK_QTX_SCH_MAX_RATE_MAN, 103) |
++ FIELD_PREP(MTK_QTX_SCH_MAX_RATE_EXP, 3);
++ FIELD_PREP(MTK_QTX_SCH_MAX_RATE_WEIGHT, 1);
++ break;
++ case SPEED_1000:
++ val |= MTK_QTX_SCH_MAX_RATE_EN |
++ FIELD_PREP(MTK_QTX_SCH_MAX_RATE_MAN, 105) |
++ FIELD_PREP(MTK_QTX_SCH_MAX_RATE_EXP, 4) |
++ FIELD_PREP(MTK_QTX_SCH_MAX_RATE_WEIGHT, 10);
++ break;
++ default:
++ break;
++ }
++ } else {
++ switch (speed) {
++ case SPEED_10:
++ val |= MTK_QTX_SCH_MAX_RATE_EN |
++ FIELD_PREP(MTK_QTX_SCH_MAX_RATE_MAN, 1) |
++ FIELD_PREP(MTK_QTX_SCH_MAX_RATE_EXP, 4) |
++ FIELD_PREP(MTK_QTX_SCH_MAX_RATE_WEIGHT, 1);
++ break;
++ case SPEED_100:
++ val |= MTK_QTX_SCH_MAX_RATE_EN |
++ FIELD_PREP(MTK_QTX_SCH_MAX_RATE_MAN, 1) |
++ FIELD_PREP(MTK_QTX_SCH_MAX_RATE_EXP, 5);
++ FIELD_PREP(MTK_QTX_SCH_MAX_RATE_WEIGHT, 1);
++ break;
++ case SPEED_1000:
++ val |= MTK_QTX_SCH_MAX_RATE_EN |
++ FIELD_PREP(MTK_QTX_SCH_MAX_RATE_MAN, 10) |
++ FIELD_PREP(MTK_QTX_SCH_MAX_RATE_EXP, 5) |
++ FIELD_PREP(MTK_QTX_SCH_MAX_RATE_WEIGHT, 10);
++ break;
++ default:
++ break;
++ }
++ }
++
++ ofs = MTK_QTX_OFFSET * idx;
++ mtk_w32(eth, val, soc->reg_map->qdma.qtx_sch + ofs);
++}
++
+ static void mtk_mac_link_up(struct phylink_config *config,
+ struct phy_device *phy,
+ unsigned int mode, phy_interface_t interface,
+@@ -645,6 +718,8 @@ static void mtk_mac_link_up(struct phyli
+ break;
+ }
+
++ mtk_set_queue_speed(mac->hw, mac->id, speed);
++
+ /* Configure duplex */
+ if (duplex == DUPLEX_FULL)
+ mcr |= MAC_MCR_FORCE_DPX;
+@@ -1105,7 +1180,8 @@ static void mtk_tx_set_dma_desc_v1(struc
+
+ WRITE_ONCE(desc->txd1, info->addr);
+
+- data = TX_DMA_SWC | TX_DMA_PLEN0(info->size);
++ data = TX_DMA_SWC | TX_DMA_PLEN0(info->size) |
++ FIELD_PREP(TX_DMA_PQID, info->qid);
+ if (info->last)
+ data |= TX_DMA_LS0;
+ WRITE_ONCE(desc->txd3, data);
+@@ -1139,9 +1215,6 @@ static void mtk_tx_set_dma_desc_v2(struc
+ 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);
+@@ -1185,11 +1258,12 @@ 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,
++ .qid = skb_get_queue_mapping(skb),
+ .vlan_tci = skb_vlan_tag_get(skb),
+ .first = true,
+ .last = !skb_is_nonlinear(skb),
+ };
++ struct netdev_queue *txq;
+ struct mtk_mac *mac = netdev_priv(dev);
+ struct mtk_eth *eth = mac->hw;
+ const struct mtk_soc_data *soc = eth->soc;
+@@ -1197,8 +1271,10 @@ static int mtk_tx_map(struct sk_buff *sk
+ struct mtk_tx_dma *itxd_pdma, *txd_pdma;
+ struct mtk_tx_buf *itx_buf, *tx_buf;
+ int i, n_desc = 1;
++ int queue = skb_get_queue_mapping(skb);
+ int k = 0;
+
++ txq = netdev_get_tx_queue(dev, queue);
+ itxd = ring->next_free;
+ itxd_pdma = qdma_to_pdma(ring, itxd);
+ if (itxd == ring->last_free)
+@@ -1247,7 +1323,7 @@ static int mtk_tx_map(struct sk_buff *sk
+ memset(&txd_info, 0, sizeof(struct mtk_tx_dma_desc_info));
+ 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.qid = queue;
+ 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,
+@@ -1286,7 +1362,7 @@ static int mtk_tx_map(struct sk_buff *sk
+ txd_pdma->txd2 |= TX_DMA_LS1;
+ }
+
+- netdev_sent_queue(dev, skb->len);
++ netdev_tx_sent_queue(txq, skb->len);
+ skb_tx_timestamp(skb);
+
+ ring->next_free = mtk_qdma_phys_to_virt(ring, txd->txd2);
+@@ -1298,8 +1374,7 @@ static int mtk_tx_map(struct sk_buff *sk
+ wmb();
+
+ if (MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
+- if (netif_xmit_stopped(netdev_get_tx_queue(dev, 0)) ||
+- !netdev_xmit_more())
++ if (netif_xmit_stopped(txq) || !netdev_xmit_more())
+ mtk_w32(eth, txd->txd2, soc->reg_map->qdma.ctx_ptr);
+ } else {
+ int next_idx;
+@@ -1368,7 +1443,7 @@ static void mtk_wake_queue(struct mtk_et
+ for (i = 0; i < MTK_MAC_COUNT; i++) {
+ if (!eth->netdev[i])
+ continue;
+- netif_wake_queue(eth->netdev[i]);
++ netif_tx_wake_all_queues(eth->netdev[i]);
+ }
+ }
+
+@@ -1392,7 +1467,7 @@ static netdev_tx_t mtk_start_xmit(struct
+
+ tx_num = mtk_cal_txd_req(eth, skb);
+ if (unlikely(atomic_read(&ring->free_count) <= tx_num)) {
+- netif_stop_queue(dev);
++ netif_tx_stop_all_queues(dev);
+ netif_err(eth, tx_queued, dev,
+ "Tx Ring full when queue awake!\n");
+ spin_unlock(&eth->page_lock);
+@@ -1418,7 +1493,7 @@ static netdev_tx_t mtk_start_xmit(struct
+ goto drop;
+
+ if (unlikely(atomic_read(&ring->free_count) <= ring->thresh))
+- netif_stop_queue(dev);
++ netif_tx_stop_all_queues(dev);
+
+ spin_unlock(&eth->page_lock);
+
+@@ -1585,10 +1660,12 @@ static int mtk_xdp_submit_frame(struct m
+ struct skb_shared_info *sinfo = xdp_get_shared_info_from_frame(xdpf);
+ const struct mtk_soc_data *soc = eth->soc;
+ struct mtk_tx_ring *ring = &eth->tx_ring;
++ struct mtk_mac *mac = netdev_priv(dev);
+ struct mtk_tx_dma_desc_info txd_info = {
+ .size = xdpf->len,
+ .first = true,
+ .last = !xdp_frame_has_frags(xdpf),
++ .qid = mac->id,
+ };
+ int err, index = 0, n_desc = 1, nr_frags;
+ struct mtk_tx_buf *htx_buf, *tx_buf;
+@@ -1638,6 +1715,7 @@ static int mtk_xdp_submit_frame(struct m
+ memset(&txd_info, 0, sizeof(struct mtk_tx_dma_desc_info));
+ txd_info.size = skb_frag_size(&sinfo->frags[index]);
+ txd_info.last = index + 1 == nr_frags;
++ txd_info.qid = mac->id;
+ data = skb_frag_address(&sinfo->frags[index]);
+
+ index++;
+@@ -1992,8 +2070,46 @@ rx_done:
+ return done;
+ }
+
++struct mtk_poll_state {
++ struct netdev_queue *txq;
++ unsigned int total;
++ unsigned int done;
++ unsigned int bytes;
++};
++
++static void
++mtk_poll_tx_done(struct mtk_eth *eth, struct mtk_poll_state *state, u8 mac,
++ struct sk_buff *skb)
++{
++ struct netdev_queue *txq;
++ struct net_device *dev;
++ unsigned int bytes = skb->len;
++
++ state->total++;
++ eth->tx_packets++;
++ eth->tx_bytes += bytes;
++
++ dev = eth->netdev[mac];
++ if (!dev)
++ return;
++
++ txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb));
++ if (state->txq == txq) {
++ state->done++;
++ state->bytes += bytes;
++ return;
++ }
++
++ if (state->txq)
++ netdev_tx_completed_queue(state->txq, state->done, state->bytes);
++
++ state->txq = txq;
++ state->done = 1;
++ state->bytes = bytes;
++}
++
+ static int mtk_poll_tx_qdma(struct mtk_eth *eth, int budget,
+- unsigned int *done, unsigned int *bytes)
++ struct mtk_poll_state *state)
+ {
+ const struct mtk_reg_map *reg_map = eth->soc->reg_map;
+ struct mtk_tx_ring *ring = &eth->tx_ring;
+@@ -2025,12 +2141,9 @@ static int mtk_poll_tx_qdma(struct mtk_e
+ break;
+
+ if (tx_buf->data != (void *)MTK_DMA_DUMMY_DESC) {
+- if (tx_buf->type == MTK_TYPE_SKB) {
+- struct sk_buff *skb = tx_buf->data;
++ if (tx_buf->type == MTK_TYPE_SKB)
++ mtk_poll_tx_done(eth, state, mac, tx_buf->data);
+
+- bytes[mac] += skb->len;
+- done[mac]++;
+- }
+ budget--;
+ }
+ mtk_tx_unmap(eth, tx_buf, &bq, true);
+@@ -2049,7 +2162,7 @@ static int mtk_poll_tx_qdma(struct mtk_e
+ }
+
+ static int mtk_poll_tx_pdma(struct mtk_eth *eth, int budget,
+- unsigned int *done, unsigned int *bytes)
++ struct mtk_poll_state *state)
+ {
+ struct mtk_tx_ring *ring = &eth->tx_ring;
+ struct mtk_tx_buf *tx_buf;
+@@ -2067,12 +2180,8 @@ static int mtk_poll_tx_pdma(struct mtk_e
+ break;
+
+ if (tx_buf->data != (void *)MTK_DMA_DUMMY_DESC) {
+- if (tx_buf->type == MTK_TYPE_SKB) {
+- struct sk_buff *skb = tx_buf->data;
+-
+- bytes[0] += skb->len;
+- done[0]++;
+- }
++ if (tx_buf->type == MTK_TYPE_SKB)
++ mtk_poll_tx_done(eth, state, 0, tx_buf->data);
+ budget--;
+ }
+ mtk_tx_unmap(eth, tx_buf, &bq, true);
+@@ -2094,26 +2203,15 @@ static int mtk_poll_tx(struct mtk_eth *e
+ {
+ struct mtk_tx_ring *ring = &eth->tx_ring;
+ struct dim_sample dim_sample = {};
+- unsigned int done[MTK_MAX_DEVS];
+- unsigned int bytes[MTK_MAX_DEVS];
+- int total = 0, i;
+-
+- memset(done, 0, sizeof(done));
+- memset(bytes, 0, sizeof(bytes));
++ struct mtk_poll_state state = {};
+
+ if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA))
+- budget = mtk_poll_tx_qdma(eth, budget, done, bytes);
++ budget = mtk_poll_tx_qdma(eth, budget, &state);
+ else
+- budget = mtk_poll_tx_pdma(eth, budget, done, bytes);
++ budget = mtk_poll_tx_pdma(eth, budget, &state);
+
+- for (i = 0; i < MTK_MAC_COUNT; i++) {
+- if (!eth->netdev[i] || !done[i])
+- continue;
+- netdev_completed_queue(eth->netdev[i], done[i], bytes[i]);
+- total += done[i];
+- eth->tx_packets += done[i];
+- eth->tx_bytes += bytes[i];
+- }
++ if (state.txq)
++ netdev_tx_completed_queue(state.txq, state.done, state.bytes);
+
+ dim_update_sample(eth->tx_events, eth->tx_packets, eth->tx_bytes,
+ &dim_sample);
+@@ -2123,7 +2221,7 @@ static int mtk_poll_tx(struct mtk_eth *e
+ (atomic_read(&ring->free_count) > ring->thresh))
+ mtk_wake_queue(eth);
+
+- return total;
++ return state.total;
+ }
+
+ static void mtk_handle_status_irq(struct mtk_eth *eth)
+@@ -2209,6 +2307,7 @@ static int mtk_tx_alloc(struct mtk_eth *
+ int i, sz = soc->txrx.txd_size;
+ struct mtk_tx_dma_v2 *txd;
+ int ring_size;
++ u32 ofs, val;
+
+ if (MTK_HAS_CAPS(soc->caps, MTK_QDMA))
+ ring_size = MTK_QDMA_RING_SIZE;
+@@ -2276,8 +2375,25 @@ static int mtk_tx_alloc(struct mtk_eth *
+ ring->phys + ((ring_size - 1) * sz),
+ 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,
+- soc->reg_map->qdma.qtx_cfg);
++
++ for (i = 0, ofs = 0; i < MTK_QDMA_NUM_QUEUES; i++) {
++ val = (QDMA_RES_THRES << 8) | QDMA_RES_THRES;
++ mtk_w32(eth, val, soc->reg_map->qdma.qtx_cfg + ofs);
++
++ val = MTK_QTX_SCH_MIN_RATE_EN |
++ /* minimum: 10 Mbps */
++ FIELD_PREP(MTK_QTX_SCH_MIN_RATE_MAN, 1) |
++ FIELD_PREP(MTK_QTX_SCH_MIN_RATE_EXP, 4) |
++ MTK_QTX_SCH_LEAKY_BUCKET_SIZE;
++ if (!MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ val |= MTK_QTX_SCH_LEAKY_BUCKET_EN;
++ mtk_w32(eth, val, soc->reg_map->qdma.qtx_sch + ofs);
++ ofs += MTK_QTX_OFFSET;
++ }
++ val = MTK_QDMA_TX_SCH_MAX_WFQ | (MTK_QDMA_TX_SCH_MAX_WFQ << 16);
++ mtk_w32(eth, val, soc->reg_map->qdma.tx_sch_rate);
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ mtk_w32(eth, val, soc->reg_map->qdma.tx_sch_rate + 4);
+ } else {
+ mtk_w32(eth, ring->phys_pdma, MT7628_TX_BASE_PTR0);
+ mtk_w32(eth, ring_size, MT7628_TX_MAX_CNT0);
+@@ -2962,7 +3078,7 @@ static int mtk_start_dma(struct mtk_eth
+ 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;
++ MTK_CHK_DDONE_EN | MTK_LEAKY_BUCKET_EN;
+ else
+ val |= MTK_RX_BT_32DWORDS;
+ mtk_w32(eth, val, reg_map->qdma.glo_cfg);
+@@ -3008,6 +3124,45 @@ static void mtk_gdm_config(struct mtk_et
+ mtk_w32(eth, 0, MTK_RST_GL);
+ }
+
++static int mtk_device_event(struct notifier_block *n, unsigned long event, void *ptr)
++{
++ struct mtk_mac *mac = container_of(n, struct mtk_mac, device_notifier);
++ struct mtk_eth *eth = mac->hw;
++ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
++ struct ethtool_link_ksettings s;
++ struct net_device *ldev;
++ struct list_head *iter;
++ struct dsa_port *dp;
++
++ if (event != NETDEV_CHANGE)
++ return NOTIFY_DONE;
++
++ netdev_for_each_lower_dev(dev, ldev, iter) {
++ if (netdev_priv(ldev) == mac)
++ goto found;
++ }
++
++ return NOTIFY_DONE;
++
++found:
++ if (!dsa_slave_dev_check(dev))
++ return NOTIFY_DONE;
++
++ if (__ethtool_get_link_ksettings(dev, &s))
++ return NOTIFY_DONE;
++
++ if (s.base.speed == 0 || s.base.speed == ((__u32)-1))
++ return NOTIFY_DONE;
++
++ dp = dsa_port_from_netdev(dev);
++ if (dp->index >= MTK_QDMA_NUM_QUEUES)
++ return NOTIFY_DONE;
++
++ mtk_set_queue_speed(eth, dp->index + 3, s.base.speed);
++
++ return NOTIFY_DONE;
++}
++
+ static int mtk_open(struct net_device *dev)
+ {
+ struct mtk_mac *mac = netdev_priv(dev);
+@@ -3050,7 +3205,8 @@ static int mtk_open(struct net_device *d
+ refcount_inc(&eth->dma_refcnt);
+
+ phylink_start(mac->phylink);
+- netif_start_queue(dev);
++ netif_tx_start_all_queues(dev);
++
+ return 0;
+ }
+
+@@ -3759,8 +3915,12 @@ static int mtk_unreg_dev(struct mtk_eth
+ int i;
+
+ for (i = 0; i < MTK_MAC_COUNT; i++) {
++ struct mtk_mac *mac;
+ if (!eth->netdev[i])
+ continue;
++ mac = netdev_priv(eth->netdev[i]);
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA))
++ unregister_netdevice_notifier(&mac->device_notifier);
+ unregister_netdev(eth->netdev[i]);
+ }
+
+@@ -3977,6 +4137,23 @@ static int mtk_set_rxnfc(struct net_devi
+ return ret;
+ }
+
++static u16 mtk_select_queue(struct net_device *dev, struct sk_buff *skb,
++ struct net_device *sb_dev)
++{
++ struct mtk_mac *mac = netdev_priv(dev);
++ unsigned int queue = 0;
++
++ if (netdev_uses_dsa(dev))
++ queue = skb_get_queue_mapping(skb) + 3;
++ else
++ queue = mac->id;
++
++ if (queue >= dev->num_tx_queues)
++ queue = 0;
++
++ return queue;
++}
++
+ static const struct ethtool_ops mtk_ethtool_ops = {
+ .get_link_ksettings = mtk_get_link_ksettings,
+ .set_link_ksettings = mtk_set_link_ksettings,
+@@ -4011,6 +4188,7 @@ static const struct net_device_ops mtk_n
+ .ndo_setup_tc = mtk_eth_setup_tc,
+ .ndo_bpf = mtk_xdp,
+ .ndo_xdp_xmit = mtk_xdp_xmit,
++ .ndo_select_queue = mtk_select_queue,
+ };
+
+ static int mtk_add_mac(struct mtk_eth *eth, struct device_node *np)
+@@ -4020,6 +4198,7 @@ static int mtk_add_mac(struct mtk_eth *e
+ struct phylink *phylink;
+ struct mtk_mac *mac;
+ int id, err;
++ int txqs = 1;
+
+ if (!_id) {
+ dev_err(eth->dev, "missing mac id\n");
+@@ -4037,7 +4216,10 @@ static int mtk_add_mac(struct mtk_eth *e
+ return -EINVAL;
+ }
+
+- eth->netdev[id] = alloc_etherdev(sizeof(*mac));
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA))
++ txqs = MTK_QDMA_NUM_QUEUES;
++
++ eth->netdev[id] = alloc_etherdev_mqs(sizeof(*mac), txqs, 1);
+ if (!eth->netdev[id]) {
+ dev_err(eth->dev, "alloc_etherdev failed\n");
+ return -ENOMEM;
+@@ -4145,6 +4327,11 @@ static int mtk_add_mac(struct mtk_eth *e
+ else
+ eth->netdev[id]->max_mtu = MTK_MAX_RX_LENGTH_2K - MTK_RX_ETH_HLEN;
+
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
++ mac->device_notifier.notifier_call = mtk_device_event;
++ register_netdevice_notifier(&mac->device_notifier);
++ }
++
+ return 0;
+
+ free_netdev:
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -22,6 +22,7 @@
+ #include <linux/bpf_trace.h>
+ #include "mtk_ppe.h"
+
++#define MTK_QDMA_NUM_QUEUES 16
+ #define MTK_QDMA_PAGE_SIZE 2048
+ #define MTK_MAX_RX_LENGTH 1536
+ #define MTK_MAX_RX_LENGTH_2K 2048
+@@ -216,8 +217,26 @@
+ #define MTK_RING_MAX_AGG_CNT_H ((MTK_HW_LRO_MAX_AGG_CNT >> 6) & 0x3)
+
+ /* QDMA TX Queue Configuration Registers */
++#define MTK_QTX_OFFSET 0x10
+ #define QDMA_RES_THRES 4
+
++/* QDMA Tx Queue Scheduler Configuration Registers */
++#define MTK_QTX_SCH_TX_SEL BIT(31)
++#define MTK_QTX_SCH_TX_SEL_V2 GENMASK(31, 30)
++
++#define MTK_QTX_SCH_LEAKY_BUCKET_EN BIT(30)
++#define MTK_QTX_SCH_LEAKY_BUCKET_SIZE GENMASK(29, 28)
++#define MTK_QTX_SCH_MIN_RATE_EN BIT(27)
++#define MTK_QTX_SCH_MIN_RATE_MAN GENMASK(26, 20)
++#define MTK_QTX_SCH_MIN_RATE_EXP GENMASK(19, 16)
++#define MTK_QTX_SCH_MAX_RATE_WEIGHT GENMASK(15, 12)
++#define MTK_QTX_SCH_MAX_RATE_EN BIT(11)
++#define MTK_QTX_SCH_MAX_RATE_MAN GENMASK(10, 4)
++#define MTK_QTX_SCH_MAX_RATE_EXP GENMASK(3, 0)
++
++/* QDMA TX Scheduler Rate Control Register */
++#define MTK_QDMA_TX_SCH_MAX_WFQ BIT(15)
++
+ /* QDMA Global Configuration Register */
+ #define MTK_RX_2B_OFFSET BIT(31)
+ #define MTK_RX_BT_32DWORDS (3 << 11)
+@@ -236,6 +255,7 @@
+ #define MTK_WCOMP_EN BIT(24)
+ #define MTK_RESV_BUF (0x40 << 16)
+ #define MTK_MUTLI_CNT (0x4 << 12)
++#define MTK_LEAKY_BUCKET_EN BIT(11)
+
+ /* QDMA Flow Control Register */
+ #define FC_THRES_DROP_MODE BIT(20)
+@@ -266,8 +286,6 @@
+ #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
+
+@@ -297,6 +315,7 @@
+ #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_PQID GENMASK(3, 0)
+
+ /* PDMA on MT7628 */
+ #define TX_DMA_DONE BIT(31)
+@@ -957,6 +976,7 @@ struct mtk_reg_map {
+ } pdma;
+ struct {
+ u32 qtx_cfg; /* tx queue configuration */
++ u32 qtx_sch; /* tx queue scheduler configuration */
+ u32 rx_ptr; /* rx base pointer */
+ u32 rx_cnt_cfg; /* rx max count configuration */
+ u32 qcrx_ptr; /* rx cpu pointer */
+@@ -974,6 +994,7 @@ struct mtk_reg_map {
+ u32 fq_tail; /* fq tail pointer */
+ u32 fq_count; /* fq free page count */
+ u32 fq_blen; /* fq free page buffer length */
++ u32 tx_sch_rate; /* tx scheduler rate control registers */
+ } qdma;
+ u32 gdm1_cnt;
+ u32 gdma_to_ppe;
+@@ -1177,6 +1198,7 @@ struct mtk_mac {
+ __be32 hwlro_ip[MTK_MAX_LRO_IP_CNT];
+ int hwlro_ip_cnt;
+ unsigned int syscfg0;
++ struct notifier_block device_notifier;
+ };
+
+ /* the struct describing the SoC. these are declared in the soc_xyz.c files */
diff --git a/target/linux/generic/backport-6.6/730-05-v6.3-net-dsa-tag_mtk-assign-per-port-queues.patch b/target/linux/generic/backport-6.6/730-05-v6.3-net-dsa-tag_mtk-assign-per-port-queues.patch
new file mode 100644
index 0000000000..186df4bdc9
--- /dev/null
+++ b/target/linux/generic/backport-6.6/730-05-v6.3-net-dsa-tag_mtk-assign-per-port-queues.patch
@@ -0,0 +1,20 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 28 Oct 2022 18:16:03 +0200
+Subject: [PATCH] net: dsa: tag_mtk: assign per-port queues
+
+Keeps traffic sent to the switch within link speed limits
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/net/dsa/tag_mtk.c
++++ b/net/dsa/tag_mtk.c
+@@ -25,6 +25,8 @@ static struct sk_buff *mtk_tag_xmit(stru
+ u8 xmit_tpid;
+ u8 *mtk_tag;
+
++ skb_set_queue_mapping(skb, dp->index);
++
+ /* Build the special tag after the MAC Source Address. If VLAN header
+ * is present, it's required that VLAN header and special tag is
+ * being combined. Only in this way we can allow the switch can parse
diff --git a/target/linux/generic/backport-6.6/730-06-v6.3-net-ethernet-mediatek-ppe-assign-per-port-queues-for.patch b/target/linux/generic/backport-6.6/730-06-v6.3-net-ethernet-mediatek-ppe-assign-per-port-queues-for.patch
new file mode 100644
index 0000000000..d29177eee7
--- /dev/null
+++ b/target/linux/generic/backport-6.6/730-06-v6.3-net-ethernet-mediatek-ppe-assign-per-port-queues-for.patch
@@ -0,0 +1,93 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 3 Nov 2022 17:49:44 +0100
+Subject: [PATCH] net: ethernet: mediatek: ppe: assign per-port queues
+ for offloaded traffic
+
+Keeps traffic sent to the switch within link speed limits
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
+@@ -399,6 +399,24 @@ int mtk_foe_entry_set_wdma(struct mtk_et
+ return 0;
+ }
+
++int mtk_foe_entry_set_queue(struct mtk_eth *eth, struct mtk_foe_entry *entry,
++ unsigned int queue)
++{
++ u32 *ib2 = mtk_foe_entry_ib2(eth, entry);
++
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
++ *ib2 &= ~MTK_FOE_IB2_QID_V2;
++ *ib2 |= FIELD_PREP(MTK_FOE_IB2_QID_V2, queue);
++ *ib2 |= MTK_FOE_IB2_PSE_QOS_V2;
++ } else {
++ *ib2 &= ~MTK_FOE_IB2_QID;
++ *ib2 |= FIELD_PREP(MTK_FOE_IB2_QID, queue);
++ *ib2 |= MTK_FOE_IB2_PSE_QOS;
++ }
++
++ return 0;
++}
++
+ static bool
+ mtk_flow_entry_match(struct mtk_eth *eth, struct mtk_flow_entry *entry,
+ struct mtk_foe_entry *data)
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
+@@ -68,7 +68,9 @@ enum {
+ #define MTK_FOE_IB2_DSCP GENMASK(31, 24)
+
+ /* CONFIG_MEDIATEK_NETSYS_V2 */
++#define MTK_FOE_IB2_QID_V2 GENMASK(6, 0)
+ #define MTK_FOE_IB2_PORT_MG_V2 BIT(7)
++#define MTK_FOE_IB2_PSE_QOS_V2 BIT(8)
+ #define MTK_FOE_IB2_DEST_PORT_V2 GENMASK(12, 9)
+ #define MTK_FOE_IB2_MULTICAST_V2 BIT(13)
+ #define MTK_FOE_IB2_WDMA_WINFO_V2 BIT(19)
+@@ -351,6 +353,8 @@ int mtk_foe_entry_set_pppoe(struct mtk_e
+ int sid);
+ int mtk_foe_entry_set_wdma(struct mtk_eth *eth, struct mtk_foe_entry *entry,
+ int wdma_idx, int txq, int bss, int wcid);
++int mtk_foe_entry_set_queue(struct mtk_eth *eth, struct mtk_foe_entry *entry,
++ unsigned int queue);
+ int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
+ void mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
+ int mtk_foe_entry_idle_time(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+@@ -188,7 +188,7 @@ mtk_flow_set_output_device(struct mtk_et
+ int *wed_index)
+ {
+ struct mtk_wdma_info info = {};
+- int pse_port, dsa_port;
++ int pse_port, dsa_port, queue;
+
+ if (mtk_flow_get_wdma_info(dev, dest_mac, &info) == 0) {
+ mtk_foe_entry_set_wdma(eth, foe, info.wdma_idx, info.queue,
+@@ -212,8 +212,6 @@ mtk_flow_set_output_device(struct mtk_et
+ }
+
+ dsa_port = mtk_flow_get_dsa_port(&dev);
+- if (dsa_port >= 0)
+- mtk_foe_entry_set_dsa(eth, foe, dsa_port);
+
+ if (dev == eth->netdev[0])
+ pse_port = 1;
+@@ -222,6 +220,14 @@ mtk_flow_set_output_device(struct mtk_et
+ else
+ return -EOPNOTSUPP;
+
++ if (dsa_port >= 0) {
++ mtk_foe_entry_set_dsa(eth, foe, dsa_port);
++ queue = 3 + dsa_port;
++ } else {
++ queue = pse_port - 1;
++ }
++ mtk_foe_entry_set_queue(eth, foe, queue);
++
+ out:
+ mtk_foe_entry_set_pse_port(eth, foe, pse_port);
+
diff --git a/target/linux/generic/backport-6.6/730-08-v6.3-net-dsa-add-support-for-DSA-rx-offloading-via-metada.patch b/target/linux/generic/backport-6.6/730-08-v6.3-net-dsa-add-support-for-DSA-rx-offloading-via-metada.patch
new file mode 100644
index 0000000000..6b7f3d6018
--- /dev/null
+++ b/target/linux/generic/backport-6.6/730-08-v6.3-net-dsa-add-support-for-DSA-rx-offloading-via-metada.patch
@@ -0,0 +1,72 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 8 Nov 2022 15:03:15 +0100
+Subject: [PATCH] net: dsa: add support for DSA rx offloading via
+ metadata dst
+
+If a metadata dst is present with the type METADATA_HW_PORT_MUX on a dsa cpu
+port netdev, assume that it carries the port number and that there is no DSA
+tag present in the skb data.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/net/core/flow_dissector.c
++++ b/net/core/flow_dissector.c
+@@ -971,12 +971,14 @@ bool __skb_flow_dissect(const struct net
+ #if IS_ENABLED(CONFIG_NET_DSA)
+ if (unlikely(skb->dev && netdev_uses_dsa(skb->dev) &&
+ proto == htons(ETH_P_XDSA))) {
++ struct metadata_dst *md_dst = skb_metadata_dst(skb);
+ const struct dsa_device_ops *ops;
+ int offset = 0;
+
+ ops = skb->dev->dsa_ptr->tag_ops;
+ /* Only DSA header taggers break flow dissection */
+- if (ops->needed_headroom) {
++ if (ops->needed_headroom &&
++ (!md_dst || md_dst->type != METADATA_HW_PORT_MUX)) {
+ if (ops->flow_dissect)
+ ops->flow_dissect(skb, &proto, &offset);
+ else
+--- a/net/dsa/dsa.c
++++ b/net/dsa/dsa.c
+@@ -11,6 +11,7 @@
+ #include <linux/netdevice.h>
+ #include <linux/sysfs.h>
+ #include <linux/ptp_classify.h>
++#include <net/dst_metadata.h>
+
+ #include "dsa_priv.h"
+
+@@ -216,6 +217,7 @@ static bool dsa_skb_defer_rx_timestamp(s
+ static int dsa_switch_rcv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *pt, struct net_device *unused)
+ {
++ struct metadata_dst *md_dst = skb_metadata_dst(skb);
+ struct dsa_port *cpu_dp = dev->dsa_ptr;
+ struct sk_buff *nskb = NULL;
+ struct dsa_slave_priv *p;
+@@ -229,7 +231,22 @@ static int dsa_switch_rcv(struct sk_buff
+ if (!skb)
+ return 0;
+
+- nskb = cpu_dp->rcv(skb, dev);
++ if (md_dst && md_dst->type == METADATA_HW_PORT_MUX) {
++ unsigned int port = md_dst->u.port_info.port_id;
++
++ skb_dst_drop(skb);
++ if (!skb_has_extensions(skb))
++ skb->slow_gro = 0;
++
++ skb->dev = dsa_master_find_slave(dev, 0, port);
++ if (likely(skb->dev)) {
++ dsa_default_offload_fwd_mark(skb);
++ nskb = skb;
++ }
++ } else {
++ nskb = cpu_dp->rcv(skb, dev);
++ }
++
+ if (!nskb) {
+ kfree_skb(skb);
+ return 0;
diff --git a/target/linux/generic/backport-6.6/730-09-v6.3-net-ethernet-mtk_eth_soc-fix-VLAN-rx-hardware-accele.patch b/target/linux/generic/backport-6.6/730-09-v6.3-net-ethernet-mtk_eth_soc-fix-VLAN-rx-hardware-accele.patch
new file mode 100644
index 0000000000..b8e3452f30
--- /dev/null
+++ b/target/linux/generic/backport-6.6/730-09-v6.3-net-ethernet-mtk_eth_soc-fix-VLAN-rx-hardware-accele.patch
@@ -0,0 +1,192 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 28 Oct 2022 11:01:12 +0200
+Subject: [PATCH] net: ethernet: mtk_eth_soc: fix VLAN rx hardware
+ acceleration
+
+- enable VLAN untagging for PDMA rx
+- make it possible to disable the feature via ethtool
+- pass VLAN tag to the DSA driver
+- untag special tag on PDMA only if no non-DSA devices are in use
+- disable special tag untagging on 7986 for now, since it's not working yet
+
+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
+@@ -23,6 +23,7 @@
+ #include <linux/jhash.h>
+ #include <linux/bitfield.h>
+ #include <net/dsa.h>
++#include <net/dst_metadata.h>
+
+ #include "mtk_eth_soc.h"
+ #include "mtk_wed.h"
+@@ -2021,16 +2022,22 @@ static int mtk_poll_rx(struct napi_struc
+ 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),
++ __vlan_hwaccel_put_tag(skb, htons(RX_DMA_VPID(trxd.rxd3)),
+ RX_DMA_VID(trxd.rxd3));
+ }
++ }
++
++ /* When using VLAN untagging in combination with DSA, the
++ * hardware treats the MTK special tag as a VLAN and untags it.
++ */
++ if (skb_vlan_tag_present(skb) && netdev_uses_dsa(netdev)) {
++ unsigned int port = ntohs(skb->vlan_proto) & GENMASK(2, 0);
+
+- /* If the device is attached to a dsa switch, the special
+- * tag inserted in VLAN field by hw switch can * be offloaded
+- * by RX HW VLAN offload. Clear vlan info.
+- */
+- if (netdev_uses_dsa(netdev))
+- __vlan_hwaccel_clear_tag(skb);
++ if (port < ARRAY_SIZE(eth->dsa_meta) &&
++ eth->dsa_meta[port])
++ skb_dst_set_noref(skb, &eth->dsa_meta[port]->dst);
++
++ __vlan_hwaccel_clear_tag(skb);
+ }
+
+ skb_record_rx_queue(skb, 0);
+@@ -2858,15 +2865,30 @@ static netdev_features_t mtk_fix_feature
+
+ static int mtk_set_features(struct net_device *dev, netdev_features_t features)
+ {
+- int err = 0;
++ struct mtk_mac *mac = netdev_priv(dev);
++ struct mtk_eth *eth = mac->hw;
++ netdev_features_t diff = dev->features ^ features;
++ int i;
++
++ if ((diff & NETIF_F_LRO) && !(features & NETIF_F_LRO))
++ mtk_hwlro_netdev_disable(dev);
+
+- if (!((dev->features ^ features) & NETIF_F_LRO))
++ /* Set RX VLAN offloading */
++ if (!(diff & NETIF_F_HW_VLAN_CTAG_RX))
+ return 0;
+
+- if (!(features & NETIF_F_LRO))
+- mtk_hwlro_netdev_disable(dev);
++ mtk_w32(eth, !!(features & NETIF_F_HW_VLAN_CTAG_RX),
++ MTK_CDMP_EG_CTRL);
+
+- return err;
++ /* sync features with other MAC */
++ for (i = 0; i < MTK_MAC_COUNT; i++) {
++ if (!eth->netdev[i] || eth->netdev[i] == dev)
++ continue;
++ eth->netdev[i]->features &= ~NETIF_F_HW_VLAN_CTAG_RX;
++ eth->netdev[i]->features |= features & NETIF_F_HW_VLAN_CTAG_RX;
++ }
++
++ return 0;
+ }
+
+ /* wait for DMA to finish whatever it is doing before we start using it again */
+@@ -3163,11 +3185,45 @@ found:
+ return NOTIFY_DONE;
+ }
+
++static bool mtk_uses_dsa(struct net_device *dev)
++{
++#if IS_ENABLED(CONFIG_NET_DSA)
++ return netdev_uses_dsa(dev) &&
++ dev->dsa_ptr->tag_ops->proto == DSA_TAG_PROTO_MTK;
++#else
++ return false;
++#endif
++}
++
+ static int mtk_open(struct net_device *dev)
+ {
+ struct mtk_mac *mac = netdev_priv(dev);
+ struct mtk_eth *eth = mac->hw;
+- int err;
++ int i, err;
++
++ if (mtk_uses_dsa(dev) && !eth->prog) {
++ for (i = 0; i < ARRAY_SIZE(eth->dsa_meta); i++) {
++ struct metadata_dst *md_dst = eth->dsa_meta[i];
++
++ if (md_dst)
++ continue;
++
++ md_dst = metadata_dst_alloc(0, METADATA_HW_PORT_MUX,
++ GFP_KERNEL);
++ if (!md_dst)
++ return -ENOMEM;
++
++ md_dst->u.port_info.port_id = i;
++ eth->dsa_meta[i] = md_dst;
++ }
++ } else {
++ /* Hardware special tag parsing needs to be disabled if at least
++ * one MAC does not use DSA.
++ */
++ u32 val = mtk_r32(eth, MTK_CDMP_IG_CTRL);
++ val &= ~MTK_CDMP_STAG_EN;
++ mtk_w32(eth, val, MTK_CDMP_IG_CTRL);
++ }
+
+ err = phylink_of_phy_connect(mac->phylink, mac->of_node, 0);
+ if (err) {
+@@ -3688,6 +3744,10 @@ static int mtk_hw_init(struct mtk_eth *e
+ */
+ val = mtk_r32(eth, MTK_CDMQ_IG_CTRL);
+ mtk_w32(eth, val | MTK_CDMQ_STAG_EN, MTK_CDMQ_IG_CTRL);
++ if (!MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
++ val = mtk_r32(eth, MTK_CDMP_IG_CTRL);
++ mtk_w32(eth, val | MTK_CDMP_STAG_EN, MTK_CDMP_IG_CTRL);
++ }
+
+ /* Enable RX VLan Offloading */
+ mtk_w32(eth, 1, MTK_CDMP_EG_CTRL);
+@@ -3907,6 +3967,12 @@ static int mtk_free_dev(struct mtk_eth *
+ free_netdev(eth->netdev[i]);
+ }
+
++ for (i = 0; i < ARRAY_SIZE(eth->dsa_meta); i++) {
++ if (!eth->dsa_meta[i])
++ break;
++ metadata_dst_free(eth->dsa_meta[i]);
++ }
++
+ return 0;
+ }
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -22,6 +22,9 @@
+ #include <linux/bpf_trace.h>
+ #include "mtk_ppe.h"
+
++#define MTK_MAX_DSA_PORTS 7
++#define MTK_DSA_PORT_MASK GENMASK(2, 0)
++
+ #define MTK_QDMA_NUM_QUEUES 16
+ #define MTK_QDMA_PAGE_SIZE 2048
+ #define MTK_MAX_RX_LENGTH 1536
+@@ -105,6 +108,9 @@
+ #define MTK_CDMQ_IG_CTRL 0x1400
+ #define MTK_CDMQ_STAG_EN BIT(0)
+
++/* CDMQ Exgress Control Register */
++#define MTK_CDMQ_EG_CTRL 0x1404
++
+ /* CDMP Ingress Control Register */
+ #define MTK_CDMP_IG_CTRL 0x400
+ #define MTK_CDMP_STAG_EN BIT(0)
+@@ -1164,6 +1170,8 @@ struct mtk_eth {
+
+ int ip_align;
+
++ struct metadata_dst *dsa_meta[MTK_MAX_DSA_PORTS];
++
+ struct mtk_ppe *ppe[2];
+ struct rhashtable flow_table;
+
diff --git a/target/linux/generic/backport-6.6/730-12-v6.3-net-ethernet-mtk_eth_soc-disable-hardware-DSA-untagg.patch b/target/linux/generic/backport-6.6/730-12-v6.3-net-ethernet-mtk_eth_soc-disable-hardware-DSA-untagg.patch
new file mode 100644
index 0000000000..a88df2b8e3
--- /dev/null
+++ b/target/linux/generic/backport-6.6/730-12-v6.3-net-ethernet-mtk_eth_soc-disable-hardware-DSA-untagg.patch
@@ -0,0 +1,42 @@
+From: =?UTF-8?q?Ar=C4=B1n=C3=A7=20=C3=9CNAL?= <arinc.unal@arinc9.com>
+Date: Sat, 28 Jan 2023 12:42:32 +0300
+Subject: [PATCH] net: ethernet: mtk_eth_soc: disable hardware DSA untagging
+ for second MAC
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+According to my tests on MT7621AT and MT7623NI SoCs, hardware DSA untagging
+won't work on the second MAC. Therefore, disable this feature when the
+second MAC of the MT7621 and MT7623 SoCs is being used.
+
+Fixes: 2d7605a72906 ("net: ethernet: mtk_eth_soc: enable hardware DSA untagging")
+Link: https://lore.kernel.org/netdev/6249fc14-b38a-c770-36b4-5af6d41c21d3@arinc9.com/
+Tested-by: Arınç ÜNAL <arinc.unal@arinc9.com>
+Signed-off-by: Arınç ÜNAL <arinc.unal@arinc9.com>
+Link: https://lore.kernel.org/r/20230128094232.2451947-1-arinc.unal@arinc9.com
+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
+@@ -3201,7 +3201,8 @@ static int mtk_open(struct net_device *d
+ struct mtk_eth *eth = mac->hw;
+ int i, err;
+
+- if (mtk_uses_dsa(dev) && !eth->prog) {
++ if ((mtk_uses_dsa(dev) && !eth->prog) &&
++ !(mac->id == 1 && MTK_HAS_CAPS(eth->soc->caps, MTK_GMAC1_TRGMII))) {
+ for (i = 0; i < ARRAY_SIZE(eth->dsa_meta); i++) {
+ struct metadata_dst *md_dst = eth->dsa_meta[i];
+
+@@ -3218,7 +3219,8 @@ static int mtk_open(struct net_device *d
+ }
+ } else {
+ /* Hardware special tag parsing needs to be disabled if at least
+- * one MAC does not use DSA.
++ * one MAC does not use DSA, or the second MAC of the MT7621 and
++ * MT7623 SoCs is being used.
+ */
+ u32 val = mtk_r32(eth, MTK_CDMP_IG_CTRL);
+ val &= ~MTK_CDMP_STAG_EN;
diff --git a/target/linux/generic/backport-6.6/730-13-v6.3-net-ethernet-mtk_eth_soc-enable-special-tag-when-any.patch b/target/linux/generic/backport-6.6/730-13-v6.3-net-ethernet-mtk_eth_soc-enable-special-tag-when-any.patch
new file mode 100644
index 0000000000..8da728b9e9
--- /dev/null
+++ b/target/linux/generic/backport-6.6/730-13-v6.3-net-ethernet-mtk_eth_soc-enable-special-tag-when-any.patch
@@ -0,0 +1,54 @@
+From: =?UTF-8?q?Ar=C4=B1n=C3=A7=20=C3=9CNAL?= <arinc.unal@arinc9.com>
+Date: Sun, 5 Feb 2023 20:53:31 +0300
+Subject: [PATCH] net: ethernet: mtk_eth_soc: enable special tag when any MAC
+ uses DSA
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The special tag is only enabled when the first MAC uses DSA. However, it
+must be enabled when any MAC uses DSA. Change the check accordingly.
+
+This fixes hardware DSA untagging not working on the second MAC of the
+MT7621 and MT7623 SoCs, and likely other SoCs too. Therefore, remove the
+check that disables hardware DSA untagging for the second MAC of the MT7621
+and MT7623 SoCs.
+
+Fixes: a1f47752fd62 ("net: ethernet: mtk_eth_soc: disable hardware DSA untagging for second MAC")
+Co-developed-by: Richard van Schagen <richard@routerhints.com>
+Signed-off-by: Richard van Schagen <richard@routerhints.com>
+Signed-off-by: Arınç ÜNAL <arinc.unal@arinc9.com>
+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
+@@ -3136,7 +3136,7 @@ static void mtk_gdm_config(struct mtk_et
+
+ val |= config;
+
+- if (!i && eth->netdev[0] && netdev_uses_dsa(eth->netdev[0]))
++ if (eth->netdev[i] && netdev_uses_dsa(eth->netdev[i]))
+ val |= MTK_GDMA_SPECIAL_TAG;
+
+ mtk_w32(eth, val, MTK_GDMA_FWD_CFG(i));
+@@ -3201,8 +3201,7 @@ static int mtk_open(struct net_device *d
+ struct mtk_eth *eth = mac->hw;
+ int i, err;
+
+- if ((mtk_uses_dsa(dev) && !eth->prog) &&
+- !(mac->id == 1 && MTK_HAS_CAPS(eth->soc->caps, MTK_GMAC1_TRGMII))) {
++ if (mtk_uses_dsa(dev) && !eth->prog) {
+ for (i = 0; i < ARRAY_SIZE(eth->dsa_meta); i++) {
+ struct metadata_dst *md_dst = eth->dsa_meta[i];
+
+@@ -3219,8 +3218,7 @@ static int mtk_open(struct net_device *d
+ }
+ } else {
+ /* Hardware special tag parsing needs to be disabled if at least
+- * one MAC does not use DSA, or the second MAC of the MT7621 and
+- * MT7623 SoCs is being used.
++ * one MAC does not use DSA.
+ */
+ u32 val = mtk_r32(eth, MTK_CDMP_IG_CTRL);
+ val &= ~MTK_CDMP_STAG_EN;
diff --git a/target/linux/generic/backport-6.6/730-14-v6.3-net-ethernet-mtk_eth_soc-fix-DSA-TX-tag-hwaccel-for-.patch b/target/linux/generic/backport-6.6/730-14-v6.3-net-ethernet-mtk_eth_soc-fix-DSA-TX-tag-hwaccel-for-.patch
new file mode 100644
index 0000000000..51cd572ab2
--- /dev/null
+++ b/target/linux/generic/backport-6.6/730-14-v6.3-net-ethernet-mtk_eth_soc-fix-DSA-TX-tag-hwaccel-for-.patch
@@ -0,0 +1,129 @@
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Tue, 7 Feb 2023 12:30:27 +0200
+Subject: [PATCH] net: ethernet: mtk_eth_soc: fix DSA TX tag hwaccel for switch
+ port 0
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Arınç reports that on his MT7621AT Unielec U7621-06 board and MT7623NI
+Bananapi BPI-R2, packets received by the CPU over mt7530 switch port 0
+(of which this driver acts as the DSA master) are not processed
+correctly by software. More precisely, they arrive without a DSA tag
+(in packet or in the hwaccel area - skb_metadata_dst()), so DSA cannot
+demux them towards the switch's interface for port 0. Traffic from other
+ports receives a skb_metadata_dst() with the correct port and is demuxed
+properly.
+
+Looking at mtk_poll_rx(), it becomes apparent that this driver uses the
+skb vlan hwaccel area:
+
+ union {
+ u32 vlan_all;
+ struct {
+ __be16 vlan_proto;
+ __u16 vlan_tci;
+ };
+ };
+
+as a temporary storage for the VLAN hwaccel tag, or the DSA hwaccel tag.
+If this is a DSA master it's a DSA hwaccel tag, and finally clears up
+the skb VLAN hwaccel header.
+
+I'm guessing that the problem is the (mis)use of API.
+skb_vlan_tag_present() looks like this:
+
+ #define skb_vlan_tag_present(__skb) (!!(__skb)->vlan_all)
+
+So if both vlan_proto and vlan_tci are zeroes, skb_vlan_tag_present()
+returns precisely false. I don't know for sure what is the format of the
+DSA hwaccel tag, but I surely know that lowermost 3 bits of vlan_proto
+are 0 when receiving from port 0:
+
+ unsigned int port = vlan_proto & GENMASK(2, 0);
+
+If the RX descriptor has no other bits set to non-zero values in
+RX_DMA_VTAG, then the call to __vlan_hwaccel_put_tag() will not, in
+fact, make the subsequent skb_vlan_tag_present() return true, because
+it's implemented like this:
+
+static inline void __vlan_hwaccel_put_tag(struct sk_buff *skb,
+ __be16 vlan_proto, u16 vlan_tci)
+{
+ skb->vlan_proto = vlan_proto;
+ skb->vlan_tci = vlan_tci;
+}
+
+What we need to do to fix this problem (assuming this is the problem) is
+to stop using skb->vlan_all as temporary storage for driver affairs, and
+just create some local variables that serve the same purpose, but
+hopefully better. Instead of calling skb_vlan_tag_present(), let's look
+at a boolean has_hwaccel_tag which we set to true when the RX DMA
+descriptors have something. Disambiguate based on netdev_uses_dsa()
+whether this is a VLAN or DSA hwaccel tag, and only call
+__vlan_hwaccel_put_tag() if we're certain it's a VLAN tag.
+
+Arınç confirms that the treatment works, so this validates the
+assumption.
+
+Link: https://lore.kernel.org/netdev/704f3a72-fc9e-714a-db54-272e17612637@arinc9.com/
+Fixes: 2d7605a72906 ("net: ethernet: mtk_eth_soc: enable hardware DSA untagging")
+Reported-by: Arınç ÜNAL <arinc.unal@arinc9.com>
+Tested-by: Arınç ÜNAL <arinc.unal@arinc9.com>
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Felix Fietkau <nbd@nbd.name>
+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
+@@ -1877,7 +1877,9 @@ static int mtk_poll_rx(struct napi_struc
+
+ while (done < budget) {
+ unsigned int pktlen, *rxdcsum;
++ bool has_hwaccel_tag = false;
+ struct net_device *netdev;
++ u16 vlan_proto, vlan_tci;
+ dma_addr_t dma_addr;
+ u32 hash, reason;
+ int mac = 0;
+@@ -2017,27 +2019,29 @@ static int mtk_poll_rx(struct napi_struc
+
+ 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));
++ if (trxd.rxd3 & RX_DMA_VTAG_V2) {
++ vlan_proto = RX_DMA_VPID(trxd.rxd4);
++ vlan_tci = RX_DMA_VID(trxd.rxd4);
++ has_hwaccel_tag = true;
++ }
+ } else if (trxd.rxd2 & RX_DMA_VTAG) {
+- __vlan_hwaccel_put_tag(skb, htons(RX_DMA_VPID(trxd.rxd3)),
+- RX_DMA_VID(trxd.rxd3));
++ vlan_proto = RX_DMA_VPID(trxd.rxd3);
++ vlan_tci = RX_DMA_VID(trxd.rxd3);
++ has_hwaccel_tag = true;
+ }
+ }
+
+ /* When using VLAN untagging in combination with DSA, the
+ * hardware treats the MTK special tag as a VLAN and untags it.
+ */
+- if (skb_vlan_tag_present(skb) && netdev_uses_dsa(netdev)) {
+- unsigned int port = ntohs(skb->vlan_proto) & GENMASK(2, 0);
++ if (has_hwaccel_tag && netdev_uses_dsa(netdev)) {
++ unsigned int port = vlan_proto & GENMASK(2, 0);
+
+ if (port < ARRAY_SIZE(eth->dsa_meta) &&
+ eth->dsa_meta[port])
+ skb_dst_set_noref(skb, &eth->dsa_meta[port]->dst);
+-
+- __vlan_hwaccel_clear_tag(skb);
++ } else if (has_hwaccel_tag) {
++ __vlan_hwaccel_put_tag(skb, htons(vlan_proto), vlan_tci);
+ }
+
+ skb_record_rx_queue(skb, 0);
diff --git a/target/linux/generic/backport-6.6/730-15-v6.3-net-ethernet-mtk_wed-No-need-to-clear-memory-after-a.patch b/target/linux/generic/backport-6.6/730-15-v6.3-net-ethernet-mtk_wed-No-need-to-clear-memory-after-a.patch
new file mode 100644
index 0000000000..a3bb1c5db7
--- /dev/null
+++ b/target/linux/generic/backport-6.6/730-15-v6.3-net-ethernet-mtk_wed-No-need-to-clear-memory-after-a.patch
@@ -0,0 +1,26 @@
+From: Christophe JAILLET <christophe.jaillet@wanadoo.fr>
+Date: Sun, 12 Feb 2023 07:51:51 +0100
+Subject: [PATCH] net: ethernet: mtk_wed: No need to clear memory after a
+ dma_alloc_coherent() call
+
+dma_alloc_coherent() already clears the allocated memory, there is no need
+to explicitly call memset().
+
+Moreover, it is likely that the size in the memset() is incorrect and
+should be "size * sizeof(*ring->desc)".
+
+Signed-off-by: Christophe JAILLET <christophe.jaillet@wanadoo.fr>
+Link: https://lore.kernel.org/r/d5acce7dd108887832c9719f62c7201b4c83b3fb.1676184599.git.christophe.jaillet@wanadoo.fr
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -779,7 +779,6 @@ mtk_wed_rro_ring_alloc(struct mtk_wed_de
+
+ ring->desc_size = sizeof(*ring->desc);
+ ring->size = size;
+- memset(ring->desc, 0, size);
+
+ return 0;
+ }
diff --git a/target/linux/generic/backport-6.6/730-16-v6.3-net-ethernet-mtk_wed-fix-some-possible-NULL-pointer-.patch b/target/linux/generic/backport-6.6/730-16-v6.3-net-ethernet-mtk_wed-fix-some-possible-NULL-pointer-.patch
new file mode 100644
index 0000000000..e043a681da
--- /dev/null
+++ b/target/linux/generic/backport-6.6/730-16-v6.3-net-ethernet-mtk_wed-fix-some-possible-NULL-pointer-.patch
@@ -0,0 +1,61 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Wed, 7 Dec 2022 15:04:54 +0100
+Subject: [PATCH] net: ethernet: mtk_wed: fix some possible NULL pointer
+ dereferences
+
+Fix possible NULL pointer dereference in mtk_wed_detach routine checking
+wo pointer is properly allocated before running mtk_wed_wo_reset() and
+mtk_wed_wo_deinit().
+Even if it is just a theoretical issue at the moment check wo pointer is
+not NULL in mtk_wed_mcu_msg_update.
+Moreover, honor mtk_wed_mcu_send_msg return value in mtk_wed_wo_reset()
+
+Fixes: 799684448e3e ("net: ethernet: mtk_wed: introduce wed wo support")
+Fixes: 4c5de09eb0d0 ("net: ethernet: mtk_wed: add configure wed wo support")
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Reviewed-by: Leon Romanovsky <leonro@nvidia.com>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -174,9 +174,10 @@ mtk_wed_wo_reset(struct mtk_wed_device *
+ mtk_wdma_tx_reset(dev);
+ mtk_wed_reset(dev, MTK_WED_RESET_WED);
+
+- mtk_wed_mcu_send_msg(wo, MTK_WED_MODULE_ID_WO,
+- MTK_WED_WO_CMD_CHANGE_STATE, &state,
+- sizeof(state), false);
++ if (mtk_wed_mcu_send_msg(wo, MTK_WED_MODULE_ID_WO,
++ MTK_WED_WO_CMD_CHANGE_STATE, &state,
++ sizeof(state), false))
++ return;
+
+ if (readx_poll_timeout(mtk_wed_wo_read_status, dev, val,
+ val == MTK_WED_WOIF_DISABLE_DONE,
+@@ -632,9 +633,11 @@ mtk_wed_detach(struct mtk_wed_device *de
+ mtk_wed_free_tx_rings(dev);
+
+ if (mtk_wed_get_rx_capa(dev)) {
+- mtk_wed_wo_reset(dev);
++ if (hw->wed_wo)
++ mtk_wed_wo_reset(dev);
+ mtk_wed_free_rx_rings(dev);
+- mtk_wed_wo_deinit(hw);
++ if (hw->wed_wo)
++ mtk_wed_wo_deinit(hw);
+ }
+
+ if (dev->wlan.bus_type == MTK_WED_BUS_PCIE) {
+--- a/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
+@@ -207,6 +207,9 @@ int mtk_wed_mcu_msg_update(struct mtk_we
+ if (dev->hw->version == 1)
+ return 0;
+
++ if (WARN_ON(!wo))
++ return -ENODEV;
++
+ return mtk_wed_mcu_send_msg(wo, MTK_WED_MODULE_ID_WO, id, data, len,
+ true);
+ }
diff --git a/target/linux/generic/backport-6.6/730-17-v6.3-net-ethernet-mtk_wed-fix-possible-deadlock-if-mtk_we.patch b/target/linux/generic/backport-6.6/730-17-v6.3-net-ethernet-mtk_wed-fix-possible-deadlock-if-mtk_we.patch
new file mode 100644
index 0000000000..0afe7106e5
--- /dev/null
+++ b/target/linux/generic/backport-6.6/730-17-v6.3-net-ethernet-mtk_wed-fix-possible-deadlock-if-mtk_we.patch
@@ -0,0 +1,58 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Wed, 7 Dec 2022 15:04:55 +0100
+Subject: [PATCH] net: ethernet: mtk_wed: fix possible deadlock if
+ mtk_wed_wo_init fails
+
+Introduce __mtk_wed_detach() in order to avoid a deadlock in
+mtk_wed_attach routine if mtk_wed_wo_init fails since both
+mtk_wed_attach and mtk_wed_detach run holding hw_lock mutex.
+
+Fixes: 4c5de09eb0d0 ("net: ethernet: mtk_wed: add configure wed wo support")
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Reviewed-by: Leon Romanovsky <leonro@nvidia.com>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -619,12 +619,10 @@ mtk_wed_deinit(struct mtk_wed_device *de
+ }
+
+ static void
+-mtk_wed_detach(struct mtk_wed_device *dev)
++__mtk_wed_detach(struct mtk_wed_device *dev)
+ {
+ struct mtk_wed_hw *hw = dev->hw;
+
+- mutex_lock(&hw_lock);
+-
+ mtk_wed_deinit(dev);
+
+ mtk_wdma_rx_reset(dev);
+@@ -657,6 +655,13 @@ mtk_wed_detach(struct mtk_wed_device *de
+ module_put(THIS_MODULE);
+
+ hw->wed_dev = NULL;
++}
++
++static void
++mtk_wed_detach(struct mtk_wed_device *dev)
++{
++ mutex_lock(&hw_lock);
++ __mtk_wed_detach(dev);
+ mutex_unlock(&hw_lock);
+ }
+
+@@ -1538,8 +1543,10 @@ mtk_wed_attach(struct mtk_wed_device *de
+ ret = mtk_wed_wo_init(hw);
+ }
+ out:
+- if (ret)
+- mtk_wed_detach(dev);
++ if (ret) {
++ dev_err(dev->hw->dev, "failed to attach wed device\n");
++ __mtk_wed_detach(dev);
++ }
+ unlock:
+ mutex_unlock(&hw_lock);
+
diff --git a/target/linux/generic/backport-6.6/730-18-v6.3-net-ethernet-mtk_eth_soc-fix-tx-throughput-regressio.patch b/target/linux/generic/backport-6.6/730-18-v6.3-net-ethernet-mtk_eth_soc-fix-tx-throughput-regressio.patch
new file mode 100644
index 0000000000..ca5b6b3a3e
--- /dev/null
+++ b/target/linux/generic/backport-6.6/730-18-v6.3-net-ethernet-mtk_eth_soc-fix-tx-throughput-regressio.patch
@@ -0,0 +1,31 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 24 Mar 2023 14:56:58 +0100
+Subject: [PATCH] net: ethernet: mtk_eth_soc: fix tx throughput regression with
+ direct 1G links
+
+Using the QDMA tx scheduler to throttle tx to line speed works fine for
+switch ports, but apparently caused a regression on non-switch ports.
+
+Based on a number of tests, it seems that this throttling can be safely
+dropped without re-introducing the issues on switch ports that the
+tx scheduling changes resolved.
+
+Link: https://lore.kernel.org/netdev/trinity-92c3826f-c2c8-40af-8339-bc6d0d3ffea4-1678213958520@3c-app-gmx-bs16/
+Fixes: f63959c7eec3 ("net: ethernet: mtk_eth_soc: implement multi-queue support for per-port queues")
+Reported-by: Frank Wunderlich <frank-w@public-files.de>
+Reported-by: Daniel Golle <daniel@makrotopia.org>
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+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
+@@ -719,8 +719,6 @@ static void mtk_mac_link_up(struct phyli
+ break;
+ }
+
+- mtk_set_queue_speed(mac->hw, mac->id, speed);
+-
+ /* Configure duplex */
+ if (duplex == DUPLEX_FULL)
+ mcr |= MAC_MCR_FORCE_DPX;
diff --git a/target/linux/generic/backport-6.6/733-v6.2-02-net-mtk_eth_soc-add-definitions-for-PCS.patch b/target/linux/generic/backport-6.6/733-v6.2-02-net-mtk_eth_soc-add-definitions-for-PCS.patch
new file mode 100644
index 0000000000..850b806410
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.2-02-net-mtk_eth_soc-add-definitions-for-PCS.patch
@@ -0,0 +1,55 @@
+From b6a709cb51f7bdc55c01cec886098a9753ce8c28 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 27 Oct 2022 14:10:42 +0100
+Subject: [PATCH 01/10] net: mtk_eth_soc: add definitions for PCS
+
+As a result of help from Frank Wunderlich to investigate and test, we
+know a bit more about the PCS on the Mediatek platforms. Update the
+definitions from this investigation.
+
+This PCS appears similar, but not identical to the Lynx PCS.
+
+Although not included in this patch, but for future reference, the PHY
+ID registers at offset 4 read as 0x4d544950 'MTIP'.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 13 ++++++++++---
+ 1 file changed, 10 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -504,8 +504,10 @@
+ #define ETHSYS_DMA_AG_MAP_PPE BIT(2)
+
+ /* SGMII subsystem config registers */
+-/* Register to auto-negotiation restart */
++/* BMCR (low 16) BMSR (high 16) */
+ #define SGMSYS_PCS_CONTROL_1 0x0
++#define SGMII_BMCR GENMASK(15, 0)
++#define SGMII_BMSR GENMASK(31, 16)
+ #define SGMII_AN_RESTART BIT(9)
+ #define SGMII_ISOLATE BIT(10)
+ #define SGMII_AN_ENABLE BIT(12)
+@@ -515,13 +517,18 @@
+ #define SGMII_PCS_FAULT BIT(23)
+ #define SGMII_AN_EXPANSION_CLR BIT(30)
+
++#define SGMSYS_PCS_ADVERTISE 0x8
++#define SGMII_ADVERTISE GENMASK(15, 0)
++#define SGMII_LPA GENMASK(31, 16)
++
+ /* Register to programmable link timer, the unit in 2 * 8ns */
+ #define SGMSYS_PCS_LINK_TIMER 0x18
+-#define SGMII_LINK_TIMER_DEFAULT (0x186a0 & GENMASK(19, 0))
++#define SGMII_LINK_TIMER_MASK GENMASK(19, 0)
++#define SGMII_LINK_TIMER_DEFAULT (0x186a0 & SGMII_LINK_TIMER_MASK)
+
+ /* Register to control remote fault */
+ #define SGMSYS_SGMII_MODE 0x20
+-#define SGMII_IF_MODE_BIT0 BIT(0)
++#define SGMII_IF_MODE_SGMII BIT(0)
+ #define SGMII_SPEED_DUPLEX_AN BIT(1)
+ #define SGMII_SPEED_MASK GENMASK(3, 2)
+ #define SGMII_SPEED_10 FIELD_PREP(SGMII_SPEED_MASK, 0)
diff --git a/target/linux/generic/backport-6.6/733-v6.2-03-net-mtk_eth_soc-eliminate-unnecessary-error-handling.patch b/target/linux/generic/backport-6.6/733-v6.2-03-net-mtk_eth_soc-eliminate-unnecessary-error-handling.patch
new file mode 100644
index 0000000000..4ea428c9d6
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.2-03-net-mtk_eth_soc-eliminate-unnecessary-error-handling.patch
@@ -0,0 +1,74 @@
+From 5cf7797526ee81bea0f627bccaa3d887f48f53e0 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 27 Oct 2022 14:10:47 +0100
+Subject: [PATCH 02/10] net: mtk_eth_soc: eliminate unnecessary error handling
+
+The functions called by the pcs_config() method always return zero, so
+there is no point trying to handle an error from these functions. Make
+these functions void, eliminate the "err" variable and simply return
+zero from the pcs_config() function itself.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_sgmii.c | 18 ++++++------------
+ 1 file changed, 6 insertions(+), 12 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
+@@ -20,7 +20,7 @@ static struct mtk_pcs *pcs_to_mtk_pcs(st
+ }
+
+ /* For SGMII interface mode */
+-static int mtk_pcs_setup_mode_an(struct mtk_pcs *mpcs)
++static void mtk_pcs_setup_mode_an(struct mtk_pcs *mpcs)
+ {
+ unsigned int val;
+
+@@ -39,16 +39,13 @@ static int mtk_pcs_setup_mode_an(struct
+ regmap_read(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, &val);
+ val &= ~SGMII_PHYA_PWD;
+ regmap_write(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, val);
+-
+- return 0;
+-
+ }
+
+ /* For 1000BASE-X and 2500BASE-X interface modes, which operate at a
+ * fixed speed.
+ */
+-static int mtk_pcs_setup_mode_force(struct mtk_pcs *mpcs,
+- phy_interface_t interface)
++static void mtk_pcs_setup_mode_force(struct mtk_pcs *mpcs,
++ phy_interface_t interface)
+ {
+ unsigned int val;
+
+@@ -73,8 +70,6 @@ static int mtk_pcs_setup_mode_force(stru
+ regmap_read(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, &val);
+ val &= ~SGMII_PHYA_PWD;
+ regmap_write(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, val);
+-
+- return 0;
+ }
+
+ static int mtk_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
+@@ -83,15 +78,14 @@ static int mtk_pcs_config(struct phylink
+ bool permit_pause_to_mac)
+ {
+ struct mtk_pcs *mpcs = pcs_to_mtk_pcs(pcs);
+- int err = 0;
+
+ /* Setup SGMIISYS with the determined property */
+ if (interface != PHY_INTERFACE_MODE_SGMII)
+- err = mtk_pcs_setup_mode_force(mpcs, interface);
++ mtk_pcs_setup_mode_force(mpcs, interface);
+ else if (phylink_autoneg_inband(mode))
+- err = mtk_pcs_setup_mode_an(mpcs);
++ mtk_pcs_setup_mode_an(mpcs);
+
+- return err;
++ return 0;
+ }
+
+ static void mtk_pcs_restart_an(struct phylink_pcs *pcs)
diff --git a/target/linux/generic/backport-6.6/733-v6.2-04-net-mtk_eth_soc-add-pcs_get_state-implementation.patch b/target/linux/generic/backport-6.6/733-v6.2-04-net-mtk_eth_soc-add-pcs_get_state-implementation.patch
new file mode 100644
index 0000000000..64a4a72fa6
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.2-04-net-mtk_eth_soc-add-pcs_get_state-implementation.patch
@@ -0,0 +1,46 @@
+From c000dca098002da193b98099df051c9ead0cacb4 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 27 Oct 2022 14:10:52 +0100
+Subject: [PATCH 03/10] net: mtk_eth_soc: add pcs_get_state() implementation
+
+Add a pcs_get_state() implementation which uses the advertisements
+to compute the resulting link modes, and BMSR contents to determine
+negotiation and link status.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_sgmii.c | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+
+--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
+@@ -19,6 +19,20 @@ static struct mtk_pcs *pcs_to_mtk_pcs(st
+ return container_of(pcs, struct mtk_pcs, pcs);
+ }
+
++static void mtk_pcs_get_state(struct phylink_pcs *pcs,
++ struct phylink_link_state *state)
++{
++ struct mtk_pcs *mpcs = pcs_to_mtk_pcs(pcs);
++ unsigned int bm, adv;
++
++ /* Read the BMSR and LPA */
++ regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &bm);
++ regmap_read(mpcs->regmap, SGMSYS_PCS_ADVERTISE, &adv);
++
++ phylink_mii_c22_pcs_decode_state(state, FIELD_GET(SGMII_BMSR, bm),
++ FIELD_GET(SGMII_LPA, adv));
++}
++
+ /* For SGMII interface mode */
+ static void mtk_pcs_setup_mode_an(struct mtk_pcs *mpcs)
+ {
+@@ -117,6 +131,7 @@ static void mtk_pcs_link_up(struct phyli
+ }
+
+ static const struct phylink_pcs_ops mtk_pcs_ops = {
++ .pcs_get_state = mtk_pcs_get_state,
+ .pcs_config = mtk_pcs_config,
+ .pcs_an_restart = mtk_pcs_restart_an,
+ .pcs_link_up = mtk_pcs_link_up,
diff --git a/target/linux/generic/backport-6.6/733-v6.2-05-net-mtk_eth_soc-convert-mtk_sgmii-to-use-regmap_upda.patch b/target/linux/generic/backport-6.6/733-v6.2-05-net-mtk_eth_soc-convert-mtk_sgmii-to-use-regmap_upda.patch
new file mode 100644
index 0000000000..24610fe11e
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.2-05-net-mtk_eth_soc-convert-mtk_sgmii-to-use-regmap_upda.patch
@@ -0,0 +1,130 @@
+From 0d2351dc2768061689abd4de1529fa206bbd574e Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 27 Oct 2022 14:10:58 +0100
+Subject: [PATCH 04/10] net: mtk_eth_soc: convert mtk_sgmii to use
+ regmap_update_bits()
+
+mtk_sgmii does a lot of read-modify-write operations, for which there
+is a specific regmap function. Use this function instead of open-coding
+the operations.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_sgmii.c | 61 ++++++++++-------------
+ 1 file changed, 26 insertions(+), 35 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
+@@ -36,23 +36,18 @@ static void mtk_pcs_get_state(struct phy
+ /* For SGMII interface mode */
+ static void mtk_pcs_setup_mode_an(struct mtk_pcs *mpcs)
+ {
+- unsigned int val;
+-
+ /* Setup the link timer and QPHY power up inside SGMIISYS */
+ regmap_write(mpcs->regmap, SGMSYS_PCS_LINK_TIMER,
+ SGMII_LINK_TIMER_DEFAULT);
+
+- regmap_read(mpcs->regmap, SGMSYS_SGMII_MODE, &val);
+- val |= SGMII_REMOTE_FAULT_DIS;
+- regmap_write(mpcs->regmap, SGMSYS_SGMII_MODE, val);
+-
+- regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &val);
+- val |= SGMII_AN_RESTART;
+- regmap_write(mpcs->regmap, SGMSYS_PCS_CONTROL_1, val);
+-
+- regmap_read(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, &val);
+- val &= ~SGMII_PHYA_PWD;
+- regmap_write(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, val);
++ regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
++ SGMII_REMOTE_FAULT_DIS, SGMII_REMOTE_FAULT_DIS);
++
++ regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1,
++ SGMII_AN_RESTART, SGMII_AN_RESTART);
++
++ regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL,
++ SGMII_PHYA_PWD, 0);
+ }
+
+ /* For 1000BASE-X and 2500BASE-X interface modes, which operate at a
+@@ -61,29 +56,26 @@ static void mtk_pcs_setup_mode_an(struct
+ static void mtk_pcs_setup_mode_force(struct mtk_pcs *mpcs,
+ phy_interface_t interface)
+ {
+- unsigned int val;
++ unsigned int rgc3;
+
+- regmap_read(mpcs->regmap, mpcs->ana_rgc3, &val);
+- val &= ~RG_PHY_SPEED_MASK;
+ if (interface == PHY_INTERFACE_MODE_2500BASEX)
+- val |= RG_PHY_SPEED_3_125G;
+- regmap_write(mpcs->regmap, mpcs->ana_rgc3, val);
++ rgc3 = RG_PHY_SPEED_3_125G;
++
++ regmap_update_bits(mpcs->regmap, mpcs->ana_rgc3,
++ RG_PHY_SPEED_3_125G, rgc3);
+
+ /* Disable SGMII AN */
+- regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &val);
+- val &= ~SGMII_AN_ENABLE;
+- regmap_write(mpcs->regmap, SGMSYS_PCS_CONTROL_1, val);
++ regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1,
++ SGMII_AN_ENABLE, 0);
+
+ /* Set the speed etc but leave the duplex unchanged */
+- regmap_read(mpcs->regmap, SGMSYS_SGMII_MODE, &val);
+- val &= SGMII_DUPLEX_FULL | ~SGMII_IF_MODE_MASK;
+- val |= SGMII_SPEED_1000;
+- regmap_write(mpcs->regmap, SGMSYS_SGMII_MODE, val);
++ regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
++ SGMII_IF_MODE_MASK & ~SGMII_DUPLEX_FULL,
++ SGMII_SPEED_1000);
+
+ /* Release PHYA power down state */
+- regmap_read(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, &val);
+- val &= ~SGMII_PHYA_PWD;
+- regmap_write(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, val);
++ regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL,
++ SGMII_PHYA_PWD, 0);
+ }
+
+ static int mtk_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
+@@ -105,29 +97,28 @@ static int mtk_pcs_config(struct phylink
+ static void mtk_pcs_restart_an(struct phylink_pcs *pcs)
+ {
+ struct mtk_pcs *mpcs = pcs_to_mtk_pcs(pcs);
+- unsigned int val;
+
+- regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &val);
+- val |= SGMII_AN_RESTART;
+- regmap_write(mpcs->regmap, SGMSYS_PCS_CONTROL_1, val);
++ regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1,
++ SGMII_AN_RESTART, SGMII_AN_RESTART);
+ }
+
+ static void mtk_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode,
+ phy_interface_t interface, int speed, int duplex)
+ {
+ struct mtk_pcs *mpcs = pcs_to_mtk_pcs(pcs);
+- unsigned int val;
++ unsigned int sgm_mode;
+
+ if (!phy_interface_mode_is_8023z(interface))
+ return;
+
+ /* SGMII force duplex setting */
+- regmap_read(mpcs->regmap, SGMSYS_SGMII_MODE, &val);
+- val &= ~SGMII_DUPLEX_FULL;
+ if (duplex == DUPLEX_FULL)
+- val |= SGMII_DUPLEX_FULL;
++ sgm_mode = SGMII_DUPLEX_FULL;
++ else
++ sgm_mode = 0;
+
+- regmap_write(mpcs->regmap, SGMSYS_SGMII_MODE, val);
++ regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
++ SGMII_DUPLEX_FULL, sgm_mode);
+ }
+
+ static const struct phylink_pcs_ops mtk_pcs_ops = {
diff --git a/target/linux/generic/backport-6.6/733-v6.2-06-net-mtk_eth_soc-add-out-of-band-forcing-of-speed-and.patch b/target/linux/generic/backport-6.6/733-v6.2-06-net-mtk_eth_soc-add-out-of-band-forcing-of-speed-and.patch
new file mode 100644
index 0000000000..ba76ca40ff
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.2-06-net-mtk_eth_soc-add-out-of-band-forcing-of-speed-and.patch
@@ -0,0 +1,52 @@
+From 12198c3a410fe69843e335c1bbf6d4c2a4d48e4e Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 27 Oct 2022 14:11:03 +0100
+Subject: [PATCH 05/10] net: mtk_eth_soc: add out of band forcing of speed and
+ duplex in pcs_link_up
+
+Add support for forcing the link speed and duplex setting in the
+pcs_link_up() method for out of band modes, which will be useful when
+we finish converting the pcs_config() method. Until then, we still have
+to force duplex for 802.3z modes to work correctly.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_sgmii.c | 28 ++++++++++++++---------
+ 1 file changed, 17 insertions(+), 11 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
+@@ -108,17 +108,23 @@ static void mtk_pcs_link_up(struct phyli
+ struct mtk_pcs *mpcs = pcs_to_mtk_pcs(pcs);
+ unsigned int sgm_mode;
+
+- if (!phy_interface_mode_is_8023z(interface))
+- return;
++ if (!phylink_autoneg_inband(mode) ||
++ phy_interface_mode_is_8023z(interface)) {
++ /* Force the speed and duplex setting */
++ if (speed == SPEED_10)
++ sgm_mode = SGMII_SPEED_10;
++ else if (speed == SPEED_100)
++ sgm_mode = SGMII_SPEED_100;
++ else
++ sgm_mode = SGMII_SPEED_1000;
+
+- /* SGMII force duplex setting */
+- if (duplex == DUPLEX_FULL)
+- sgm_mode = SGMII_DUPLEX_FULL;
+- else
+- sgm_mode = 0;
++ if (duplex == DUPLEX_FULL)
++ sgm_mode |= SGMII_DUPLEX_FULL;
+
+- regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
+- SGMII_DUPLEX_FULL, sgm_mode);
++ regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
++ SGMII_DUPLEX_FULL | SGMII_SPEED_MASK,
++ sgm_mode);
++ }
+ }
+
+ static const struct phylink_pcs_ops mtk_pcs_ops = {
diff --git a/target/linux/generic/backport-6.6/733-v6.2-07-net-mtk_eth_soc-move-PHY-power-up.patch b/target/linux/generic/backport-6.6/733-v6.2-07-net-mtk_eth_soc-move-PHY-power-up.patch
new file mode 100644
index 0000000000..b76e159275
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.2-07-net-mtk_eth_soc-move-PHY-power-up.patch
@@ -0,0 +1,48 @@
+From 6f38fffe2179dd29612aea2c67c46ed6682b4e46 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 27 Oct 2022 14:11:08 +0100
+Subject: [PATCH 06/10] net: mtk_eth_soc: move PHY power up
+
+The PHY power up is common to both configuration paths, so move it into
+the parent function. We need to do this for all serdes modes.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_sgmii.c | 11 ++++-------
+ 1 file changed, 4 insertions(+), 7 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
+@@ -45,9 +45,6 @@ static void mtk_pcs_setup_mode_an(struct
+
+ regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1,
+ SGMII_AN_RESTART, SGMII_AN_RESTART);
+-
+- regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL,
+- SGMII_PHYA_PWD, 0);
+ }
+
+ /* For 1000BASE-X and 2500BASE-X interface modes, which operate at a
+@@ -72,10 +69,6 @@ static void mtk_pcs_setup_mode_force(str
+ regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
+ SGMII_IF_MODE_MASK & ~SGMII_DUPLEX_FULL,
+ SGMII_SPEED_1000);
+-
+- /* Release PHYA power down state */
+- regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL,
+- SGMII_PHYA_PWD, 0);
+ }
+
+ static int mtk_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
+@@ -91,6 +84,10 @@ static int mtk_pcs_config(struct phylink
+ else if (phylink_autoneg_inband(mode))
+ mtk_pcs_setup_mode_an(mpcs);
+
++ /* Release PHYA power down state */
++ regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL,
++ SGMII_PHYA_PWD, 0);
++
+ return 0;
+ }
+
diff --git a/target/linux/generic/backport-6.6/733-v6.2-08-net-mtk_eth_soc-move-interface-speed-selection.patch b/target/linux/generic/backport-6.6/733-v6.2-08-net-mtk_eth_soc-move-interface-speed-selection.patch
new file mode 100644
index 0000000000..cd9f0699b3
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.2-08-net-mtk_eth_soc-move-interface-speed-selection.patch
@@ -0,0 +1,48 @@
+From f752c0df13dfeb721c11d3debb79f08cf437344f Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 27 Oct 2022 14:11:13 +0100
+Subject: [PATCH 07/10] net: mtk_eth_soc: move interface speed selection
+
+Move the selection of the underlying interface speed to the pcs_config
+function, so we always program the interface speed.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_sgmii.c | 18 ++++++++++--------
+ 1 file changed, 10 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
+@@ -53,14 +53,6 @@ static void mtk_pcs_setup_mode_an(struct
+ static void mtk_pcs_setup_mode_force(struct mtk_pcs *mpcs,
+ phy_interface_t interface)
+ {
+- unsigned int rgc3;
+-
+- if (interface == PHY_INTERFACE_MODE_2500BASEX)
+- rgc3 = RG_PHY_SPEED_3_125G;
+-
+- regmap_update_bits(mpcs->regmap, mpcs->ana_rgc3,
+- RG_PHY_SPEED_3_125G, rgc3);
+-
+ /* Disable SGMII AN */
+ regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1,
+ SGMII_AN_ENABLE, 0);
+@@ -77,6 +69,16 @@ static int mtk_pcs_config(struct phylink
+ bool permit_pause_to_mac)
+ {
+ struct mtk_pcs *mpcs = pcs_to_mtk_pcs(pcs);
++ unsigned int rgc3;
++
++ if (interface == PHY_INTERFACE_MODE_2500BASEX)
++ rgc3 = RG_PHY_SPEED_3_125G;
++ else
++ rgc3 = 0;
++
++ /* Configure the underlying interface speed */
++ regmap_update_bits(mpcs->regmap, mpcs->ana_rgc3,
++ RG_PHY_SPEED_3_125G, rgc3);
+
+ /* Setup SGMIISYS with the determined property */
+ if (interface != PHY_INTERFACE_MODE_SGMII)
diff --git a/target/linux/generic/backport-6.6/733-v6.2-09-net-mtk_eth_soc-add-advertisement-programming.patch b/target/linux/generic/backport-6.6/733-v6.2-09-net-mtk_eth_soc-add-advertisement-programming.patch
new file mode 100644
index 0000000000..f08358e963
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.2-09-net-mtk_eth_soc-add-advertisement-programming.patch
@@ -0,0 +1,52 @@
+From c125c66ea71b9377ae2478c4f1b87b180cc5c6ef Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 27 Oct 2022 14:11:18 +0100
+Subject: [PATCH 08/10] net: mtk_eth_soc: add advertisement programming
+
+Program the advertisement into the mtk PCS block.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_sgmii.c | 13 ++++++++++++-
+ 1 file changed, 12 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
+@@ -70,16 +70,27 @@ static int mtk_pcs_config(struct phylink
+ {
+ struct mtk_pcs *mpcs = pcs_to_mtk_pcs(pcs);
+ unsigned int rgc3;
++ int advertise;
++ bool changed;
+
+ if (interface == PHY_INTERFACE_MODE_2500BASEX)
+ rgc3 = RG_PHY_SPEED_3_125G;
+ else
+ rgc3 = 0;
+
++ advertise = phylink_mii_c22_pcs_encode_advertisement(interface,
++ advertising);
++ if (advertise < 0)
++ return advertise;
++
+ /* Configure the underlying interface speed */
+ regmap_update_bits(mpcs->regmap, mpcs->ana_rgc3,
+ RG_PHY_SPEED_3_125G, rgc3);
+
++ /* Update the advertisement, noting whether it has changed */
++ regmap_update_bits_check(mpcs->regmap, SGMSYS_PCS_ADVERTISE,
++ SGMII_ADVERTISE, advertise, &changed);
++
+ /* Setup SGMIISYS with the determined property */
+ if (interface != PHY_INTERFACE_MODE_SGMII)
+ mtk_pcs_setup_mode_force(mpcs, interface);
+@@ -90,7 +101,7 @@ static int mtk_pcs_config(struct phylink
+ regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL,
+ SGMII_PHYA_PWD, 0);
+
+- return 0;
++ return changed;
+ }
+
+ static void mtk_pcs_restart_an(struct phylink_pcs *pcs)
diff --git a/target/linux/generic/backport-6.6/733-v6.2-10-net-mtk_eth_soc-move-and-correct-link-timer-programm.patch b/target/linux/generic/backport-6.6/733-v6.2-10-net-mtk_eth_soc-move-and-correct-link-timer-programm.patch
new file mode 100644
index 0000000000..602d52c6f4
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.2-10-net-mtk_eth_soc-move-and-correct-link-timer-programm.patch
@@ -0,0 +1,63 @@
+From 3027d89f87707e7f3e5b683e0d37a32afb5bde96 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 27 Oct 2022 14:11:23 +0100
+Subject: [PATCH 09/10] net: mtk_eth_soc: move and correct link timer
+ programming
+
+Program the link timer appropriately for the interface mode being
+used, using the newly introduced phylink helper that provides the
+nanosecond link timer interval.
+
+The intervals are 1.6ms for SGMII based protocols and 10ms for
+802.3z based protocols.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_sgmii.c | 13 ++++++++-----
+ 1 file changed, 8 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
+@@ -36,10 +36,6 @@ static void mtk_pcs_get_state(struct phy
+ /* For SGMII interface mode */
+ static void mtk_pcs_setup_mode_an(struct mtk_pcs *mpcs)
+ {
+- /* Setup the link timer and QPHY power up inside SGMIISYS */
+- regmap_write(mpcs->regmap, SGMSYS_PCS_LINK_TIMER,
+- SGMII_LINK_TIMER_DEFAULT);
+-
+ regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
+ SGMII_REMOTE_FAULT_DIS, SGMII_REMOTE_FAULT_DIS);
+
+@@ -69,8 +65,8 @@ static int mtk_pcs_config(struct phylink
+ bool permit_pause_to_mac)
+ {
+ struct mtk_pcs *mpcs = pcs_to_mtk_pcs(pcs);
++ int advertise, link_timer;
+ unsigned int rgc3;
+- int advertise;
+ bool changed;
+
+ if (interface == PHY_INTERFACE_MODE_2500BASEX)
+@@ -83,6 +79,10 @@ static int mtk_pcs_config(struct phylink
+ if (advertise < 0)
+ return advertise;
+
++ link_timer = phylink_get_link_timer_ns(interface);
++ if (link_timer < 0)
++ return link_timer;
++
+ /* Configure the underlying interface speed */
+ regmap_update_bits(mpcs->regmap, mpcs->ana_rgc3,
+ RG_PHY_SPEED_3_125G, rgc3);
+@@ -91,6 +91,9 @@ static int mtk_pcs_config(struct phylink
+ regmap_update_bits_check(mpcs->regmap, SGMSYS_PCS_ADVERTISE,
+ SGMII_ADVERTISE, advertise, &changed);
+
++ /* Setup the link timer and QPHY power up inside SGMIISYS */
++ regmap_write(mpcs->regmap, SGMSYS_PCS_LINK_TIMER, link_timer / 2 / 8);
++
+ /* Setup SGMIISYS with the determined property */
+ if (interface != PHY_INTERFACE_MODE_SGMII)
+ mtk_pcs_setup_mode_force(mpcs, interface);
diff --git a/target/linux/generic/backport-6.6/733-v6.2-11-net-mtk_eth_soc-add-support-for-in-band-802.3z-negot.patch b/target/linux/generic/backport-6.6/733-v6.2-11-net-mtk_eth_soc-add-support-for-in-band-802.3z-negot.patch
new file mode 100644
index 0000000000..0e9a0535a7
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.2-11-net-mtk_eth_soc-add-support-for-in-band-802.3z-negot.patch
@@ -0,0 +1,132 @@
+From 81b0f12a2a8a1699a7d49c3995e5f71e4ec018e6 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 27 Oct 2022 14:11:28 +0100
+Subject: [PATCH 10/10] net: mtk_eth_soc: add support for in-band 802.3z
+ negotiation
+
+As a result of help from Frank Wunderlich to investigate and test, we
+now know how to program this PCS for in-band 802.3z negotiation. Add
+support for this by moving the contents of the two functions into the
+common mtk_pcs_config() function and adding the register settings for
+802.3z negotiation.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_sgmii.c | 77 ++++++++++++-----------
+ 1 file changed, 42 insertions(+), 35 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
+@@ -33,41 +33,15 @@ static void mtk_pcs_get_state(struct phy
+ FIELD_GET(SGMII_LPA, adv));
+ }
+
+-/* For SGMII interface mode */
+-static void mtk_pcs_setup_mode_an(struct mtk_pcs *mpcs)
+-{
+- regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
+- SGMII_REMOTE_FAULT_DIS, SGMII_REMOTE_FAULT_DIS);
+-
+- regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1,
+- SGMII_AN_RESTART, SGMII_AN_RESTART);
+-}
+-
+-/* For 1000BASE-X and 2500BASE-X interface modes, which operate at a
+- * fixed speed.
+- */
+-static void mtk_pcs_setup_mode_force(struct mtk_pcs *mpcs,
+- phy_interface_t interface)
+-{
+- /* Disable SGMII AN */
+- regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1,
+- SGMII_AN_ENABLE, 0);
+-
+- /* Set the speed etc but leave the duplex unchanged */
+- regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
+- SGMII_IF_MODE_MASK & ~SGMII_DUPLEX_FULL,
+- SGMII_SPEED_1000);
+-}
+-
+ static int mtk_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+ bool permit_pause_to_mac)
+ {
+ struct mtk_pcs *mpcs = pcs_to_mtk_pcs(pcs);
++ unsigned int rgc3, sgm_mode, bmcr;
+ int advertise, link_timer;
+- unsigned int rgc3;
+- bool changed;
++ bool changed, use_an;
+
+ if (interface == PHY_INTERFACE_MODE_2500BASEX)
+ rgc3 = RG_PHY_SPEED_3_125G;
+@@ -83,6 +57,37 @@ static int mtk_pcs_config(struct phylink
+ if (link_timer < 0)
+ return link_timer;
+
++ /* Clearing IF_MODE_BIT0 switches the PCS to BASE-X mode, and
++ * we assume that fixes it's speed at bitrate = line rate (in
++ * other words, 1000Mbps or 2500Mbps).
++ */
++ if (interface == PHY_INTERFACE_MODE_SGMII) {
++ sgm_mode = SGMII_IF_MODE_SGMII;
++ if (phylink_autoneg_inband(mode)) {
++ sgm_mode |= SGMII_REMOTE_FAULT_DIS |
++ SGMII_SPEED_DUPLEX_AN;
++ use_an = true;
++ } else {
++ use_an = false;
++ }
++ } else if (phylink_autoneg_inband(mode)) {
++ /* 1000base-X or 2500base-X autoneg */
++ sgm_mode = SGMII_REMOTE_FAULT_DIS;
++ use_an = linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++ advertising);
++ } else {
++ /* 1000base-X or 2500base-X without autoneg */
++ sgm_mode = 0;
++ use_an = false;
++ }
++
++ if (use_an) {
++ /* FIXME: Do we need to set AN_RESTART here? */
++ bmcr = SGMII_AN_RESTART | SGMII_AN_ENABLE;
++ } else {
++ bmcr = 0;
++ }
++
+ /* Configure the underlying interface speed */
+ regmap_update_bits(mpcs->regmap, mpcs->ana_rgc3,
+ RG_PHY_SPEED_3_125G, rgc3);
+@@ -94,11 +99,14 @@ static int mtk_pcs_config(struct phylink
+ /* Setup the link timer and QPHY power up inside SGMIISYS */
+ regmap_write(mpcs->regmap, SGMSYS_PCS_LINK_TIMER, link_timer / 2 / 8);
+
+- /* Setup SGMIISYS with the determined property */
+- if (interface != PHY_INTERFACE_MODE_SGMII)
+- mtk_pcs_setup_mode_force(mpcs, interface);
+- else if (phylink_autoneg_inband(mode))
+- mtk_pcs_setup_mode_an(mpcs);
++ /* Update the sgmsys mode register */
++ regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
++ SGMII_REMOTE_FAULT_DIS | SGMII_SPEED_DUPLEX_AN |
++ SGMII_IF_MODE_SGMII, sgm_mode);
++
++ /* Update the BMCR */
++ regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1,
++ SGMII_AN_RESTART | SGMII_AN_ENABLE, bmcr);
+
+ /* Release PHYA power down state */
+ regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL,
+@@ -121,8 +129,7 @@ static void mtk_pcs_link_up(struct phyli
+ struct mtk_pcs *mpcs = pcs_to_mtk_pcs(pcs);
+ unsigned int sgm_mode;
+
+- if (!phylink_autoneg_inband(mode) ||
+- phy_interface_mode_is_8023z(interface)) {
++ if (!phylink_autoneg_inband(mode)) {
+ /* Force the speed and duplex setting */
+ if (speed == SPEED_10)
+ sgm_mode = SGMII_SPEED_10;
diff --git a/target/linux/generic/backport-6.6/733-v6.2-12-net-mediatek-sgmii-ensure-the-SGMII-PHY-is-powered-d.patch b/target/linux/generic/backport-6.6/733-v6.2-12-net-mediatek-sgmii-ensure-the-SGMII-PHY-is-powered-d.patch
new file mode 100644
index 0000000000..a02c583deb
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.2-12-net-mediatek-sgmii-ensure-the-SGMII-PHY-is-powered-d.patch
@@ -0,0 +1,119 @@
+From 7ff82416de8295c61423ef6fd75f052d3837d2f7 Mon Sep 17 00:00:00 2001
+From: Alexander Couzens <lynxis@fe80.eu>
+Date: Wed, 1 Feb 2023 19:23:29 +0100
+Subject: [PATCH 11/13] net: mediatek: sgmii: ensure the SGMII PHY is powered
+ down on configuration
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The code expect the PHY to be in power down which is only true after reset.
+Allow changes of the SGMII parameters more than once.
+
+Only power down when reconfiguring to avoid bouncing the link when there's
+no reason to - based on code from Russell King.
+
+There are cases when the SGMII_PHYA_PWD register contains 0x9 which
+prevents SGMII from working. The SGMII still shows link but no traffic
+can flow. Writing 0x0 to the PHYA_PWD register fix the issue. 0x0 was
+taken from a good working state of the SGMII interface.
+
+Fixes: 42c03844e93d ("net-next: mediatek: add support for MediaTek MT7622 SoC")
+Suggested-by: Russell King (Oracle) <linux@armlinux.org.uk>
+Signed-off-by: Alexander Couzens <lynxis@fe80.eu>
+[ bmork: rebased and squashed into one patch ]
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Bjørn Mork <bjorn@mork.no>
+Acked-by: Daniel Golle <daniel@makrotopia.org>
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 2 ++
+ drivers/net/ethernet/mediatek/mtk_sgmii.c | 39 +++++++++++++++------
+ 2 files changed, 30 insertions(+), 11 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -1067,11 +1067,13 @@ struct mtk_soc_data {
+ * @regmap: The register map pointing at the range used to setup
+ * SGMII modes
+ * @ana_rgc3: The offset refers to register ANA_RGC3 related to regmap
++ * @interface: Currently configured interface mode
+ * @pcs: Phylink PCS structure
+ */
+ struct mtk_pcs {
+ struct regmap *regmap;
+ u32 ana_rgc3;
++ phy_interface_t interface;
+ struct phylink_pcs pcs;
+ };
+
+--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
+@@ -43,11 +43,6 @@ static int mtk_pcs_config(struct phylink
+ int advertise, link_timer;
+ bool changed, use_an;
+
+- if (interface == PHY_INTERFACE_MODE_2500BASEX)
+- rgc3 = RG_PHY_SPEED_3_125G;
+- else
+- rgc3 = 0;
+-
+ advertise = phylink_mii_c22_pcs_encode_advertisement(interface,
+ advertising);
+ if (advertise < 0)
+@@ -88,9 +83,22 @@ static int mtk_pcs_config(struct phylink
+ bmcr = 0;
+ }
+
+- /* Configure the underlying interface speed */
+- regmap_update_bits(mpcs->regmap, mpcs->ana_rgc3,
+- RG_PHY_SPEED_3_125G, rgc3);
++ if (mpcs->interface != interface) {
++ /* PHYA power down */
++ regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL,
++ SGMII_PHYA_PWD, SGMII_PHYA_PWD);
++
++ if (interface == PHY_INTERFACE_MODE_2500BASEX)
++ rgc3 = RG_PHY_SPEED_3_125G;
++ else
++ rgc3 = 0;
++
++ /* Configure the underlying interface speed */
++ regmap_update_bits(mpcs->regmap, mpcs->ana_rgc3,
++ RG_PHY_SPEED_3_125G, rgc3);
++
++ mpcs->interface = interface;
++ }
+
+ /* Update the advertisement, noting whether it has changed */
+ regmap_update_bits_check(mpcs->regmap, SGMSYS_PCS_ADVERTISE,
+@@ -108,9 +116,17 @@ static int mtk_pcs_config(struct phylink
+ regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1,
+ SGMII_AN_RESTART | SGMII_AN_ENABLE, bmcr);
+
+- /* Release PHYA power down state */
+- regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL,
+- SGMII_PHYA_PWD, 0);
++ /* Release PHYA power down state
++ * Only removing bit SGMII_PHYA_PWD isn't enough.
++ * There are cases when the SGMII_PHYA_PWD register contains 0x9 which
++ * prevents SGMII from working. The SGMII still shows link but no traffic
++ * can flow. Writing 0x0 to the PHYA_PWD register fix the issue. 0x0 was
++ * taken from a good working state of the SGMII interface.
++ * Unknown how much the QPHY needs but it is racy without a sleep.
++ * Tested on mt7622 & mt7986.
++ */
++ usleep_range(50, 100);
++ regmap_write(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, 0);
+
+ return changed;
+ }
+@@ -171,6 +187,7 @@ int mtk_sgmii_init(struct mtk_sgmii *ss,
+ return PTR_ERR(ss->pcs[i].regmap);
+
+ ss->pcs[i].pcs.ops = &mtk_pcs_ops;
++ ss->pcs[i].interface = PHY_INTERFACE_MODE_NA;
+ }
+
+ return 0;
diff --git a/target/linux/generic/backport-6.6/733-v6.2-13-net-mediatek-sgmii-fix-duplex-configuration.patch b/target/linux/generic/backport-6.6/733-v6.2-13-net-mediatek-sgmii-fix-duplex-configuration.patch
new file mode 100644
index 0000000000..a06298c0a9
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.2-13-net-mediatek-sgmii-fix-duplex-configuration.patch
@@ -0,0 +1,52 @@
+From 9d32637122de88f1ef614c29703f0e050cad342e Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Bj=C3=B8rn=20Mork?= <bjorn@mork.no>
+Date: Wed, 1 Feb 2023 19:23:30 +0100
+Subject: [PATCH 12/13] net: mediatek: sgmii: fix duplex configuration
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The logic of the duplex bit is inverted. Setting it means half
+duplex, not full duplex.
+
+Fix and rename macro to avoid confusion.
+
+Fixes: 7e538372694b ("net: ethernet: mediatek: Re-add support SGMII")
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Bjørn Mork <bjorn@mork.no>
+Acked-by: Daniel Golle <daniel@makrotopia.org>
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 2 +-
+ drivers/net/ethernet/mediatek/mtk_sgmii.c | 6 +++---
+ 2 files changed, 4 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -534,7 +534,7 @@
+ #define SGMII_SPEED_10 FIELD_PREP(SGMII_SPEED_MASK, 0)
+ #define SGMII_SPEED_100 FIELD_PREP(SGMII_SPEED_MASK, 1)
+ #define SGMII_SPEED_1000 FIELD_PREP(SGMII_SPEED_MASK, 2)
+-#define SGMII_DUPLEX_FULL BIT(4)
++#define SGMII_DUPLEX_HALF BIT(4)
+ #define SGMII_IF_MODE_BIT5 BIT(5)
+ #define SGMII_REMOTE_FAULT_DIS BIT(8)
+ #define SGMII_CODE_SYNC_SET_VAL BIT(9)
+--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
+@@ -154,11 +154,11 @@ static void mtk_pcs_link_up(struct phyli
+ else
+ sgm_mode = SGMII_SPEED_1000;
+
+- if (duplex == DUPLEX_FULL)
+- sgm_mode |= SGMII_DUPLEX_FULL;
++ if (duplex != DUPLEX_FULL)
++ sgm_mode |= SGMII_DUPLEX_HALF;
+
+ regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
+- SGMII_DUPLEX_FULL | SGMII_SPEED_MASK,
++ SGMII_DUPLEX_HALF | SGMII_SPEED_MASK,
+ sgm_mode);
+ }
+ }
diff --git a/target/linux/generic/backport-6.6/733-v6.2-14-mtk_sgmii-enable-PCS-polling-to-allow-SFP-work.patch b/target/linux/generic/backport-6.6/733-v6.2-14-mtk_sgmii-enable-PCS-polling-to-allow-SFP-work.patch
new file mode 100644
index 0000000000..56d7a1348f
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.2-14-mtk_sgmii-enable-PCS-polling-to-allow-SFP-work.patch
@@ -0,0 +1,33 @@
+From 3337a6e04ddf2923a1bdcf3d31b3b52412bf82dd Mon Sep 17 00:00:00 2001
+From: Alexander Couzens <lynxis@fe80.eu>
+Date: Wed, 1 Feb 2023 19:23:31 +0100
+Subject: [PATCH 13/13] mtk_sgmii: enable PCS polling to allow SFP work
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Currently there is no IRQ handling (even the SGMII supports it).
+Enable polling to support SFP ports.
+
+Fixes: 14a44ab0330d ("net: mtk_eth_soc: partially convert to phylink_pcs")
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Alexander Couzens <lynxis@fe80.eu>
+[ bmork: changed "1" => "true" ]
+Signed-off-by: Bjørn Mork <bjorn@mork.no>
+Acked-by: Daniel Golle <daniel@makrotopia.org>
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_sgmii.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
+@@ -187,6 +187,7 @@ int mtk_sgmii_init(struct mtk_sgmii *ss,
+ return PTR_ERR(ss->pcs[i].regmap);
+
+ ss->pcs[i].pcs.ops = &mtk_pcs_ops;
++ ss->pcs[i].pcs.poll = true;
+ ss->pcs[i].interface = PHY_INTERFACE_MODE_NA;
+ }
+
diff --git a/target/linux/generic/backport-6.6/733-v6.3-15-net-ethernet-mtk_eth_soc-reset-PCS-state.patch b/target/linux/generic/backport-6.6/733-v6.3-15-net-ethernet-mtk_eth_soc-reset-PCS-state.patch
new file mode 100644
index 0000000000..6acc62d4ab
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.3-15-net-ethernet-mtk_eth_soc-reset-PCS-state.patch
@@ -0,0 +1,48 @@
+From 611e2dabb4b3243d176739fd6a5a34d007fa3f86 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 14 Mar 2023 00:34:26 +0000
+Subject: [PATCH 1/2] net: ethernet: mtk_eth_soc: reset PCS state
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Reset the internal PCS state machine when changing interface mode.
+This prevents confusing the state machine when changing interface
+modes, e.g. from SGMII to 2500Base-X or vice-versa.
+
+Fixes: 7e538372694b ("net: ethernet: mediatek: Re-add support SGMII")
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Tested-by: Bjørn Mork <bjorn@mork.no>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 4 ++++
+ drivers/net/ethernet/mediatek/mtk_sgmii.c | 4 ++++
+ 2 files changed, 8 insertions(+)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -542,6 +542,10 @@
+ #define SGMII_SEND_AN_ERROR_EN BIT(11)
+ #define SGMII_IF_MODE_MASK GENMASK(5, 1)
+
++/* Register to reset SGMII design */
++#define SGMII_RESERVED_0 0x34
++#define SGMII_SW_RESET BIT(0)
++
+ /* Register to set SGMII speed, ANA RG_ Control Signals III*/
+ #define SGMSYS_ANA_RG_CS3 0x2028
+ #define RG_PHY_SPEED_MASK (BIT(2) | BIT(3))
+--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
+@@ -88,6 +88,10 @@ static int mtk_pcs_config(struct phylink
+ regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL,
+ SGMII_PHYA_PWD, SGMII_PHYA_PWD);
+
++ /* Reset SGMII PCS state */
++ regmap_update_bits(mpcs->regmap, SGMII_RESERVED_0,
++ SGMII_SW_RESET, SGMII_SW_RESET);
++
+ if (interface == PHY_INTERFACE_MODE_2500BASEX)
+ rgc3 = RG_PHY_SPEED_3_125G;
+ else
diff --git a/target/linux/generic/backport-6.6/733-v6.3-16-net-ethernet-mtk_eth_soc-only-write-values-if-needed.patch b/target/linux/generic/backport-6.6/733-v6.3-16-net-ethernet-mtk_eth_soc-only-write-values-if-needed.patch
new file mode 100644
index 0000000000..0fabeea20c
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.3-16-net-ethernet-mtk_eth_soc-only-write-values-if-needed.patch
@@ -0,0 +1,103 @@
+From 6e933a804c7db8be64f367f33e63cd7dcc302ebb Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 14 Mar 2023 00:34:45 +0000
+Subject: [PATCH 2/2] net: ethernet: mtk_eth_soc: only write values if needed
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Only restart auto-negotiation and write link timer if actually
+necessary. This prevents losing the link in case of minor
+changes.
+
+Fixes: 7e538372694b ("net: ethernet: mediatek: Re-add support SGMII")
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Tested-by: Bjørn Mork <bjorn@mork.no>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/ethernet/mediatek/mtk_sgmii.c | 24 +++++++++++------------
+ 1 file changed, 12 insertions(+), 12 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
+@@ -38,20 +38,16 @@ static int mtk_pcs_config(struct phylink
+ const unsigned long *advertising,
+ bool permit_pause_to_mac)
+ {
++ bool mode_changed = false, changed, use_an;
+ struct mtk_pcs *mpcs = pcs_to_mtk_pcs(pcs);
+ unsigned int rgc3, sgm_mode, bmcr;
+ int advertise, link_timer;
+- bool changed, use_an;
+
+ advertise = phylink_mii_c22_pcs_encode_advertisement(interface,
+ advertising);
+ if (advertise < 0)
+ return advertise;
+
+- link_timer = phylink_get_link_timer_ns(interface);
+- if (link_timer < 0)
+- return link_timer;
+-
+ /* Clearing IF_MODE_BIT0 switches the PCS to BASE-X mode, and
+ * we assume that fixes it's speed at bitrate = line rate (in
+ * other words, 1000Mbps or 2500Mbps).
+@@ -77,13 +73,16 @@ static int mtk_pcs_config(struct phylink
+ }
+
+ if (use_an) {
+- /* FIXME: Do we need to set AN_RESTART here? */
+- bmcr = SGMII_AN_RESTART | SGMII_AN_ENABLE;
++ bmcr = SGMII_AN_ENABLE;
+ } else {
+ bmcr = 0;
+ }
+
+ if (mpcs->interface != interface) {
++ link_timer = phylink_get_link_timer_ns(interface);
++ if (link_timer < 0)
++ return link_timer;
++
+ /* PHYA power down */
+ regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL,
+ SGMII_PHYA_PWD, SGMII_PHYA_PWD);
+@@ -101,16 +100,17 @@ static int mtk_pcs_config(struct phylink
+ regmap_update_bits(mpcs->regmap, mpcs->ana_rgc3,
+ RG_PHY_SPEED_3_125G, rgc3);
+
++ /* Setup the link timer */
++ regmap_write(mpcs->regmap, SGMSYS_PCS_LINK_TIMER, link_timer / 2 / 8);
++
+ mpcs->interface = interface;
++ mode_changed = true;
+ }
+
+ /* Update the advertisement, noting whether it has changed */
+ regmap_update_bits_check(mpcs->regmap, SGMSYS_PCS_ADVERTISE,
+ SGMII_ADVERTISE, advertise, &changed);
+
+- /* Setup the link timer and QPHY power up inside SGMIISYS */
+- regmap_write(mpcs->regmap, SGMSYS_PCS_LINK_TIMER, link_timer / 2 / 8);
+-
+ /* Update the sgmsys mode register */
+ regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
+ SGMII_REMOTE_FAULT_DIS | SGMII_SPEED_DUPLEX_AN |
+@@ -118,7 +118,7 @@ static int mtk_pcs_config(struct phylink
+
+ /* Update the BMCR */
+ regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1,
+- SGMII_AN_RESTART | SGMII_AN_ENABLE, bmcr);
++ SGMII_AN_ENABLE, bmcr);
+
+ /* Release PHYA power down state
+ * Only removing bit SGMII_PHYA_PWD isn't enough.
+@@ -132,7 +132,7 @@ static int mtk_pcs_config(struct phylink
+ usleep_range(50, 100);
+ regmap_write(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, 0);
+
+- return changed;
++ return changed || mode_changed;
+ }
+
+ static void mtk_pcs_restart_an(struct phylink_pcs *pcs)
diff --git a/target/linux/generic/backport-6.6/733-v6.3-18-net-ethernet-mtk_eth_soc-add-support-for-MT7981.patch b/target/linux/generic/backport-6.6/733-v6.3-18-net-ethernet-mtk_eth_soc-add-support-for-MT7981.patch
new file mode 100644
index 0000000000..a1247218b0
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.3-18-net-ethernet-mtk_eth_soc-add-support-for-MT7981.patch
@@ -0,0 +1,206 @@
+From f5d43ddd334b7c32fcaed9ba46afbd85cb467f1f Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sun, 19 Mar 2023 12:56:28 +0000
+Subject: [PATCH] net: ethernet: mtk_eth_soc: add support for MT7981 SoC
+
+The MediaTek MT7981 SoC comes with two 1G/2.5G SGMII ports, just like
+MT7986.
+
+In addition MT7981 is equipped with a built-in 1000Base-T PHY which can
+be used with GMAC1.
+
+As many MT7981 boards make use of inverting SGMII signal polarity, add
+new device-tree attribute 'mediatek,pn_swap' to support them.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_path.c | 14 +++++++--
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 21 +++++++++++++
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 31 ++++++++++++++++++++
+ drivers/net/ethernet/mediatek/mtk_sgmii.c | 10 +++++++
+ 4 files changed, 73 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_path.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_path.c
+@@ -96,12 +96,20 @@ static int set_mux_gmac2_gmac0_to_gephy(
+
+ static int set_mux_u3_gmac2_to_qphy(struct mtk_eth *eth, int path)
+ {
+- unsigned int val = 0;
++ unsigned int val = 0, mask = 0, reg = 0;
+ bool updated = true;
+
+ switch (path) {
+ case MTK_ETH_PATH_GMAC2_SGMII:
+- val = CO_QPHY_SEL;
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_U3_COPHY_V2)) {
++ reg = USB_PHY_SWITCH_REG;
++ val = SGMII_QPHY_SEL;
++ mask = QPHY_SEL_MASK;
++ } else {
++ reg = INFRA_MISC2;
++ val = CO_QPHY_SEL;
++ mask = val;
++ }
+ break;
+ default:
+ updated = false;
+@@ -109,7 +117,7 @@ static int set_mux_u3_gmac2_to_qphy(stru
+ }
+
+ if (updated)
+- regmap_update_bits(eth->infra, INFRA_MISC2, CO_QPHY_SEL, val);
++ regmap_update_bits(eth->infra, reg, mask, val);
+
+ dev_dbg(eth->dev, "path %s in %s updated = %d\n",
+ mtk_eth_path_name(path), __func__, updated);
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -4803,6 +4803,26 @@ static const struct mtk_soc_data mt7629_
+ },
+ };
+
++static const struct mtk_soc_data mt7981_data = {
++ .reg_map = &mt7986_reg_map,
++ .ana_rgc3 = 0x128,
++ .caps = MT7981_CAPS,
++ .hw_features = MTK_HW_FEATURES,
++ .required_clks = MT7981_CLKS_BITMAP,
++ .required_pctl = false,
++ .offload_version = 2,
++ .hash_offset = 4,
++ .foe_entry_size = sizeof(struct mtk_foe_entry),
++ .txrx = {
++ .txd_size = sizeof(struct mtk_tx_dma_v2),
++ .rxd_size = sizeof(struct mtk_rx_dma_v2),
++ .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,
++ },
++};
++
+ static const struct mtk_soc_data mt7986_data = {
+ .reg_map = &mt7986_reg_map,
+ .ana_rgc3 = 0x128,
+@@ -4845,6 +4865,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,mt7981-eth", .data = &mt7981_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
+@@ -556,11 +556,22 @@
+ #define SGMSYS_QPHY_PWR_STATE_CTRL 0xe8
+ #define SGMII_PHYA_PWD BIT(4)
+
++/* Register to QPHY wrapper control */
++#define SGMSYS_QPHY_WRAP_CTRL 0xec
++#define SGMII_PN_SWAP_MASK GENMASK(1, 0)
++#define SGMII_PN_SWAP_TX_RX (BIT(0) | BIT(1))
++#define MTK_SGMII_FLAG_PN_SWAP BIT(0)
++
+ /* Infrasys subsystem config registers */
+ #define INFRA_MISC2 0x70c
+ #define CO_QPHY_SEL BIT(0)
+ #define GEPHY_MAC_SEL BIT(1)
+
++/* Top misc registers */
++#define USB_PHY_SWITCH_REG 0x218
++#define QPHY_SEL_MASK GENMASK(1, 0)
++#define SGMII_QPHY_SEL 0x2
++
+ /* MT7628/88 specific stuff */
+ #define MT7628_PDMA_OFFSET 0x0800
+ #define MT7628_SDM_OFFSET 0x0c00
+@@ -741,6 +752,17 @@ enum mtk_clks_map {
+ BIT(MTK_CLK_SGMII2_CDR_FB) | \
+ BIT(MTK_CLK_SGMII_CK) | \
+ BIT(MTK_CLK_ETH2PLL) | BIT(MTK_CLK_SGMIITOP))
++#define MT7981_CLKS_BITMAP (BIT(MTK_CLK_FE) | BIT(MTK_CLK_GP2) | BIT(MTK_CLK_GP1) | \
++ 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) | \
++ BIT(MTK_CLK_SGMII_CK))
+ #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) | \
+@@ -854,6 +876,7 @@ enum mkt_eth_capabilities {
+ MTK_NETSYS_V2_BIT,
+ MTK_SOC_MT7628_BIT,
+ MTK_RSTCTRL_PPE1_BIT,
++ MTK_U3_COPHY_V2_BIT,
+
+ /* MUX BITS*/
+ MTK_ETH_MUX_GDM1_TO_GMAC1_ESW_BIT,
+@@ -888,6 +911,7 @@ enum mkt_eth_capabilities {
+ #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_U3_COPHY_V2 BIT(MTK_U3_COPHY_V2_BIT)
+
+ #define MTK_ETH_MUX_GDM1_TO_GMAC1_ESW \
+ BIT(MTK_ETH_MUX_GDM1_TO_GMAC1_ESW_BIT)
+@@ -960,6 +984,11 @@ enum mkt_eth_capabilities {
+ MTK_MUX_U3_GMAC2_TO_QPHY | \
+ MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA)
+
++#define MT7981_CAPS (MTK_GMAC1_SGMII | MTK_GMAC2_SGMII | MTK_GMAC2_GEPHY | \
++ MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA | \
++ MTK_MUX_U3_GMAC2_TO_QPHY | MTK_U3_COPHY_V2 | \
++ MTK_NETSYS_V2 | MTK_RSTCTRL_PPE1)
++
+ #define MT7986_CAPS (MTK_GMAC1_SGMII | MTK_GMAC2_SGMII | \
+ MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA | \
+ MTK_NETSYS_V2 | MTK_RSTCTRL_PPE1)
+@@ -1073,12 +1102,14 @@ struct mtk_soc_data {
+ * @ana_rgc3: The offset refers to register ANA_RGC3 related to regmap
+ * @interface: Currently configured interface mode
+ * @pcs: Phylink PCS structure
++ * @flags: Flags indicating hardware properties
+ */
+ struct mtk_pcs {
+ struct regmap *regmap;
+ u32 ana_rgc3;
+ phy_interface_t interface;
+ struct phylink_pcs pcs;
++ u32 flags;
+ };
+
+ /* struct mtk_sgmii - This is the structure holding sgmii regmap and its
+--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
++++ b/drivers/net/ethernet/mediatek/mtk_sgmii.c
+@@ -87,6 +87,11 @@ static int mtk_pcs_config(struct phylink
+ regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL,
+ SGMII_PHYA_PWD, SGMII_PHYA_PWD);
+
++ if (mpcs->flags & MTK_SGMII_FLAG_PN_SWAP)
++ regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_WRAP_CTRL,
++ SGMII_PN_SWAP_MASK,
++ SGMII_PN_SWAP_TX_RX);
++
+ /* Reset SGMII PCS state */
+ regmap_update_bits(mpcs->regmap, SGMII_RESERVED_0,
+ SGMII_SW_RESET, SGMII_SW_RESET);
+@@ -186,6 +191,11 @@ int mtk_sgmii_init(struct mtk_sgmii *ss,
+
+ ss->pcs[i].ana_rgc3 = ana_rgc3;
+ ss->pcs[i].regmap = syscon_node_to_regmap(np);
++
++ ss->pcs[i].flags = 0;
++ if (of_property_read_bool(np, "mediatek,pnswap"))
++ ss->pcs[i].flags |= MTK_SGMII_FLAG_PN_SWAP;
++
+ of_node_put(np);
+ if (IS_ERR(ss->pcs[i].regmap))
+ return PTR_ERR(ss->pcs[i].regmap);
diff --git a/target/linux/generic/backport-6.6/733-v6.3-19-net-ethernet-mtk_eth_soc-set-MDIO-bus-clock-frequenc.patch b/target/linux/generic/backport-6.6/733-v6.3-19-net-ethernet-mtk_eth_soc-set-MDIO-bus-clock-frequenc.patch
new file mode 100644
index 0000000000..1cb1f40538
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.3-19-net-ethernet-mtk_eth_soc-set-MDIO-bus-clock-frequenc.patch
@@ -0,0 +1,76 @@
+From c0a440031d4314d1023c1b87f43a4233634eebdb Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sun, 19 Mar 2023 12:57:15 +0000
+Subject: [PATCH] net: ethernet: mtk_eth_soc: set MDIO bus clock frequency
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Set MDIO bus clock frequency and allow setting a custom maximum
+frequency from device tree.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Tested-by: Bjørn Mork <bjorn@mork.no>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 21 +++++++++++++++++++++
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 7 +++++++
+ 2 files changed, 28 insertions(+)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -744,8 +744,10 @@ static const struct phylink_mac_ops mtk_
+
+ static int mtk_mdio_init(struct mtk_eth *eth)
+ {
++ unsigned int max_clk = 2500000, divider;
+ struct device_node *mii_np;
+ int ret;
++ u32 val;
+
+ mii_np = of_get_child_by_name(eth->dev->of_node, "mdio-bus");
+ if (!mii_np) {
+@@ -772,6 +774,25 @@ static int mtk_mdio_init(struct mtk_eth
+ eth->mii_bus->parent = eth->dev;
+
+ snprintf(eth->mii_bus->id, MII_BUS_ID_SIZE, "%pOFn", mii_np);
++
++ if (!of_property_read_u32(mii_np, "clock-frequency", &val)) {
++ if (val > MDC_MAX_FREQ || val < MDC_MAX_FREQ / MDC_MAX_DIVIDER) {
++ dev_err(eth->dev, "MDIO clock frequency out of range");
++ ret = -EINVAL;
++ goto err_put_node;
++ }
++ max_clk = val;
++ }
++ divider = min_t(unsigned int, DIV_ROUND_UP(MDC_MAX_FREQ, max_clk), 63);
++
++ /* Configure MDC Divider */
++ val = mtk_r32(eth, MTK_PPSC);
++ val &= ~PPSC_MDC_CFG;
++ val |= FIELD_PREP(PPSC_MDC_CFG, divider) | PPSC_MDC_TURBO;
++ mtk_w32(eth, val, MTK_PPSC);
++
++ dev_dbg(eth->dev, "MDC is running on %d Hz\n", MDC_MAX_FREQ / divider);
++
+ ret = of_mdiobus_register(eth->mii_bus, mii_np);
+
+ err_put_node:
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -363,6 +363,13 @@
+ #define RX_DMA_VTAG_V2 BIT(0)
+ #define RX_DMA_L4_VALID_V2 BIT(2)
+
++/* PHY Polling and SMI Master Control registers */
++#define MTK_PPSC 0x10000
++#define PPSC_MDC_CFG GENMASK(29, 24)
++#define PPSC_MDC_TURBO BIT(20)
++#define MDC_MAX_FREQ 25000000
++#define MDC_MAX_DIVIDER 63
++
+ /* PHY Indirect Access Control registers */
+ #define MTK_PHY_IAC 0x10004
+ #define PHY_IAC_ACCESS BIT(31)
diff --git a/target/linux/generic/backport-6.6/733-v6.3-20-net-ethernet-mtk_eth_soc-switch-to-external-PCS-driv.patch b/target/linux/generic/backport-6.6/733-v6.3-20-net-ethernet-mtk_eth_soc-switch-to-external-PCS-driv.patch
new file mode 100644
index 0000000000..110944658d
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.3-20-net-ethernet-mtk_eth_soc-switch-to-external-PCS-driv.patch
@@ -0,0 +1,512 @@
+From 2a3ec7ae313310c1092e4256208cc04d1958e469 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sun, 19 Mar 2023 12:58:02 +0000
+Subject: [PATCH] net: ethernet: mtk_eth_soc: switch to external PCS driver
+
+Now that we got a PCS driver, use it and remove the now redundant
+PCS code and it's header macros from the Ethernet driver.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Tested-by: Frank Wunderlich <frank-w@public-files.de>
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/Kconfig | 2 +
+ drivers/net/ethernet/mediatek/Makefile | 2 +-
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 61 +++++-
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 93 +--------
+ drivers/net/ethernet/mediatek/mtk_sgmii.c | 217 --------------------
+ 5 files changed, 56 insertions(+), 319 deletions(-)
+ delete mode 100644 drivers/net/ethernet/mediatek/mtk_sgmii.c
+
+--- a/drivers/net/ethernet/mediatek/Kconfig
++++ b/drivers/net/ethernet/mediatek/Kconfig
+@@ -19,6 +19,8 @@ config NET_MEDIATEK_SOC
+ select DIMLIB
+ select PAGE_POOL
+ select PAGE_POOL_STATS
++ select PCS_MTK_LYNXI
++ select REGMAP_MMIO
+ help
+ This driver supports the gigabit ethernet MACs in the
+ MediaTek SoC family.
+--- a/drivers/net/ethernet/mediatek/Makefile
++++ b/drivers/net/ethernet/mediatek/Makefile
+@@ -4,7 +4,7 @@
+ #
+
+ 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-y := mtk_eth_soc.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 mtk_wed_mcu.o mtk_wed_wo.o
+ ifdef CONFIG_DEBUG_FS
+ mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed_debugfs.o
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -20,6 +20,7 @@
+ #include <linux/interrupt.h>
+ #include <linux/pinctrl/devinfo.h>
+ #include <linux/phylink.h>
++#include <linux/pcs/pcs-mtk-lynxi.h>
+ #include <linux/jhash.h>
+ #include <linux/bitfield.h>
+ #include <net/dsa.h>
+@@ -400,7 +401,7 @@ static struct phylink_pcs *mtk_mac_selec
+ sid = (MTK_HAS_CAPS(eth->soc->caps, MTK_SHARED_SGMII)) ?
+ 0 : mac->id;
+
+- return mtk_sgmii_select_pcs(eth->sgmii, sid);
++ return eth->sgmii_pcs[sid];
+ }
+
+ return NULL;
+@@ -4016,8 +4017,17 @@ static int mtk_unreg_dev(struct mtk_eth
+ return 0;
+ }
+
++static void mtk_sgmii_destroy(struct mtk_eth *eth)
++{
++ int i;
++
++ for (i = 0; i < MTK_MAX_DEVS; i++)
++ mtk_pcs_lynxi_destroy(eth->sgmii_pcs[i]);
++}
++
+ static int mtk_cleanup(struct mtk_eth *eth)
+ {
++ mtk_sgmii_destroy(eth);
+ mtk_unreg_dev(eth);
+ mtk_free_dev(eth);
+ cancel_work_sync(&eth->pending_work);
+@@ -4457,6 +4467,36 @@ void mtk_eth_set_dma_device(struct mtk_e
+ rtnl_unlock();
+ }
+
++static int mtk_sgmii_init(struct mtk_eth *eth)
++{
++ struct device_node *np;
++ struct regmap *regmap;
++ u32 flags;
++ int i;
++
++ for (i = 0; i < MTK_MAX_DEVS; i++) {
++ np = of_parse_phandle(eth->dev->of_node, "mediatek,sgmiisys", i);
++ if (!np)
++ break;
++
++ regmap = syscon_node_to_regmap(np);
++ flags = 0;
++ if (of_property_read_bool(np, "mediatek,pnswap"))
++ flags |= MTK_SGMII_FLAG_PN_SWAP;
++
++ of_node_put(np);
++
++ if (IS_ERR(regmap))
++ return PTR_ERR(regmap);
++
++ eth->sgmii_pcs[i] = mtk_pcs_lynxi_create(eth->dev, regmap,
++ eth->soc->ana_rgc3,
++ flags);
++ }
++
++ return 0;
++}
++
+ static int mtk_probe(struct platform_device *pdev)
+ {
+ struct resource *res = NULL;
+@@ -4520,13 +4560,7 @@ static int mtk_probe(struct platform_dev
+ }
+
+ if (MTK_HAS_CAPS(eth->soc->caps, MTK_SGMII)) {
+- eth->sgmii = devm_kzalloc(eth->dev, sizeof(*eth->sgmii),
+- GFP_KERNEL);
+- if (!eth->sgmii)
+- return -ENOMEM;
+-
+- err = mtk_sgmii_init(eth->sgmii, pdev->dev.of_node,
+- eth->soc->ana_rgc3);
++ err = mtk_sgmii_init(eth);
+
+ if (err)
+ return err;
+@@ -4537,14 +4571,17 @@ static int mtk_probe(struct platform_dev
+ "mediatek,pctl");
+ if (IS_ERR(eth->pctl)) {
+ dev_err(&pdev->dev, "no pctl regmap found\n");
+- return PTR_ERR(eth->pctl);
++ err = PTR_ERR(eth->pctl);
++ goto err_destroy_sgmii;
+ }
+ }
+
+ if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+- if (!res)
+- return -EINVAL;
++ if (!res) {
++ err = -EINVAL;
++ goto err_destroy_sgmii;
++ }
+ }
+
+ if (eth->soc->offload_version) {
+@@ -4703,6 +4740,8 @@ err_deinit_hw:
+ mtk_hw_deinit(eth);
+ err_wed_exit:
+ mtk_wed_exit();
++err_destroy_sgmii:
++ mtk_sgmii_destroy(eth);
+
+ return err;
+ }
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -510,65 +510,6 @@
+ #define ETHSYS_DMA_AG_MAP_QDMA BIT(1)
+ #define ETHSYS_DMA_AG_MAP_PPE BIT(2)
+
+-/* SGMII subsystem config registers */
+-/* BMCR (low 16) BMSR (high 16) */
+-#define SGMSYS_PCS_CONTROL_1 0x0
+-#define SGMII_BMCR GENMASK(15, 0)
+-#define SGMII_BMSR GENMASK(31, 16)
+-#define SGMII_AN_RESTART BIT(9)
+-#define SGMII_ISOLATE BIT(10)
+-#define SGMII_AN_ENABLE BIT(12)
+-#define SGMII_LINK_STATYS BIT(18)
+-#define SGMII_AN_ABILITY BIT(19)
+-#define SGMII_AN_COMPLETE BIT(21)
+-#define SGMII_PCS_FAULT BIT(23)
+-#define SGMII_AN_EXPANSION_CLR BIT(30)
+-
+-#define SGMSYS_PCS_ADVERTISE 0x8
+-#define SGMII_ADVERTISE GENMASK(15, 0)
+-#define SGMII_LPA GENMASK(31, 16)
+-
+-/* Register to programmable link timer, the unit in 2 * 8ns */
+-#define SGMSYS_PCS_LINK_TIMER 0x18
+-#define SGMII_LINK_TIMER_MASK GENMASK(19, 0)
+-#define SGMII_LINK_TIMER_DEFAULT (0x186a0 & SGMII_LINK_TIMER_MASK)
+-
+-/* Register to control remote fault */
+-#define SGMSYS_SGMII_MODE 0x20
+-#define SGMII_IF_MODE_SGMII BIT(0)
+-#define SGMII_SPEED_DUPLEX_AN BIT(1)
+-#define SGMII_SPEED_MASK GENMASK(3, 2)
+-#define SGMII_SPEED_10 FIELD_PREP(SGMII_SPEED_MASK, 0)
+-#define SGMII_SPEED_100 FIELD_PREP(SGMII_SPEED_MASK, 1)
+-#define SGMII_SPEED_1000 FIELD_PREP(SGMII_SPEED_MASK, 2)
+-#define SGMII_DUPLEX_HALF BIT(4)
+-#define SGMII_IF_MODE_BIT5 BIT(5)
+-#define SGMII_REMOTE_FAULT_DIS BIT(8)
+-#define SGMII_CODE_SYNC_SET_VAL BIT(9)
+-#define SGMII_CODE_SYNC_SET_EN BIT(10)
+-#define SGMII_SEND_AN_ERROR_EN BIT(11)
+-#define SGMII_IF_MODE_MASK GENMASK(5, 1)
+-
+-/* Register to reset SGMII design */
+-#define SGMII_RESERVED_0 0x34
+-#define SGMII_SW_RESET BIT(0)
+-
+-/* Register to set SGMII speed, ANA RG_ Control Signals III*/
+-#define SGMSYS_ANA_RG_CS3 0x2028
+-#define RG_PHY_SPEED_MASK (BIT(2) | BIT(3))
+-#define RG_PHY_SPEED_1_25G 0x0
+-#define RG_PHY_SPEED_3_125G BIT(2)
+-
+-/* Register to power up QPHY */
+-#define SGMSYS_QPHY_PWR_STATE_CTRL 0xe8
+-#define SGMII_PHYA_PWD BIT(4)
+-
+-/* Register to QPHY wrapper control */
+-#define SGMSYS_QPHY_WRAP_CTRL 0xec
+-#define SGMII_PN_SWAP_MASK GENMASK(1, 0)
+-#define SGMII_PN_SWAP_TX_RX (BIT(0) | BIT(1))
+-#define MTK_SGMII_FLAG_PN_SWAP BIT(0)
+-
+ /* Infrasys subsystem config registers */
+ #define INFRA_MISC2 0x70c
+ #define CO_QPHY_SEL BIT(0)
+@@ -1102,31 +1043,6 @@ struct mtk_soc_data {
+ /* currently no SoC has more than 2 macs */
+ #define MTK_MAX_DEVS 2
+
+-/* struct mtk_pcs - This structure holds each sgmii regmap and associated
+- * data
+- * @regmap: The register map pointing at the range used to setup
+- * SGMII modes
+- * @ana_rgc3: The offset refers to register ANA_RGC3 related to regmap
+- * @interface: Currently configured interface mode
+- * @pcs: Phylink PCS structure
+- * @flags: Flags indicating hardware properties
+- */
+-struct mtk_pcs {
+- struct regmap *regmap;
+- u32 ana_rgc3;
+- phy_interface_t interface;
+- struct phylink_pcs pcs;
+- u32 flags;
+-};
+-
+-/* struct mtk_sgmii - This is the structure holding sgmii regmap and its
+- * characteristics
+- * @pcs Array of individual PCS structures
+- */
+-struct mtk_sgmii {
+- struct mtk_pcs pcs[MTK_MAX_DEVS];
+-};
+-
+ /* struct mtk_eth - This is the main datasructure for holding the state
+ * of the driver
+ * @dev: The device pointer
+@@ -1146,6 +1062,7 @@ struct mtk_sgmii {
+ * MII modes
+ * @infra: The register map pointing at the range used to setup
+ * SGMII and GePHY path
++ * @sgmii_pcs: Pointers to mtk-pcs-lynxi phylink_pcs instances
+ * @pctl: The register map pointing at the range used to setup
+ * GMAC port drive/slew values
+ * @dma_refcnt: track how many netdevs are using the DMA engine
+@@ -1186,8 +1103,8 @@ struct mtk_eth {
+ u32 msg_enable;
+ unsigned long sysclk;
+ struct regmap *ethsys;
+- struct regmap *infra;
+- struct mtk_sgmii *sgmii;
++ struct regmap *infra;
++ struct phylink_pcs *sgmii_pcs[MTK_MAX_DEVS];
+ struct regmap *pctl;
+ bool hwlro;
+ refcount_t dma_refcnt;
+@@ -1349,10 +1266,6 @@ void mtk_stats_update_mac(struct mtk_mac
+ void mtk_w32(struct mtk_eth *eth, u32 val, unsigned reg);
+ u32 mtk_r32(struct mtk_eth *eth, unsigned reg);
+
+-struct phylink_pcs *mtk_sgmii_select_pcs(struct mtk_sgmii *ss, int id);
+-int mtk_sgmii_init(struct mtk_sgmii *ss, struct device_node *np,
+- u32 ana_rgc3);
+-
+ int mtk_gmac_sgmii_path_setup(struct mtk_eth *eth, int mac_id);
+ int mtk_gmac_gephy_path_setup(struct mtk_eth *eth, int mac_id);
+ int mtk_gmac_rgmii_path_setup(struct mtk_eth *eth, int mac_id);
+--- a/drivers/net/ethernet/mediatek/mtk_sgmii.c
++++ /dev/null
+@@ -1,217 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0
+-// Copyright (c) 2018-2019 MediaTek Inc.
+-
+-/* A library for MediaTek SGMII circuit
+- *
+- * Author: Sean Wang <sean.wang@mediatek.com>
+- *
+- */
+-
+-#include <linux/mfd/syscon.h>
+-#include <linux/of.h>
+-#include <linux/phylink.h>
+-#include <linux/regmap.h>
+-
+-#include "mtk_eth_soc.h"
+-
+-static struct mtk_pcs *pcs_to_mtk_pcs(struct phylink_pcs *pcs)
+-{
+- return container_of(pcs, struct mtk_pcs, pcs);
+-}
+-
+-static void mtk_pcs_get_state(struct phylink_pcs *pcs,
+- struct phylink_link_state *state)
+-{
+- struct mtk_pcs *mpcs = pcs_to_mtk_pcs(pcs);
+- unsigned int bm, adv;
+-
+- /* Read the BMSR and LPA */
+- regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &bm);
+- regmap_read(mpcs->regmap, SGMSYS_PCS_ADVERTISE, &adv);
+-
+- phylink_mii_c22_pcs_decode_state(state, FIELD_GET(SGMII_BMSR, bm),
+- FIELD_GET(SGMII_LPA, adv));
+-}
+-
+-static int mtk_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
+- phy_interface_t interface,
+- const unsigned long *advertising,
+- bool permit_pause_to_mac)
+-{
+- bool mode_changed = false, changed, use_an;
+- struct mtk_pcs *mpcs = pcs_to_mtk_pcs(pcs);
+- unsigned int rgc3, sgm_mode, bmcr;
+- int advertise, link_timer;
+-
+- advertise = phylink_mii_c22_pcs_encode_advertisement(interface,
+- advertising);
+- if (advertise < 0)
+- return advertise;
+-
+- /* Clearing IF_MODE_BIT0 switches the PCS to BASE-X mode, and
+- * we assume that fixes it's speed at bitrate = line rate (in
+- * other words, 1000Mbps or 2500Mbps).
+- */
+- if (interface == PHY_INTERFACE_MODE_SGMII) {
+- sgm_mode = SGMII_IF_MODE_SGMII;
+- if (phylink_autoneg_inband(mode)) {
+- sgm_mode |= SGMII_REMOTE_FAULT_DIS |
+- SGMII_SPEED_DUPLEX_AN;
+- use_an = true;
+- } else {
+- use_an = false;
+- }
+- } else if (phylink_autoneg_inband(mode)) {
+- /* 1000base-X or 2500base-X autoneg */
+- sgm_mode = SGMII_REMOTE_FAULT_DIS;
+- use_an = linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+- advertising);
+- } else {
+- /* 1000base-X or 2500base-X without autoneg */
+- sgm_mode = 0;
+- use_an = false;
+- }
+-
+- if (use_an) {
+- bmcr = SGMII_AN_ENABLE;
+- } else {
+- bmcr = 0;
+- }
+-
+- if (mpcs->interface != interface) {
+- link_timer = phylink_get_link_timer_ns(interface);
+- if (link_timer < 0)
+- return link_timer;
+-
+- /* PHYA power down */
+- regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL,
+- SGMII_PHYA_PWD, SGMII_PHYA_PWD);
+-
+- if (mpcs->flags & MTK_SGMII_FLAG_PN_SWAP)
+- regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_WRAP_CTRL,
+- SGMII_PN_SWAP_MASK,
+- SGMII_PN_SWAP_TX_RX);
+-
+- /* Reset SGMII PCS state */
+- regmap_update_bits(mpcs->regmap, SGMII_RESERVED_0,
+- SGMII_SW_RESET, SGMII_SW_RESET);
+-
+- if (interface == PHY_INTERFACE_MODE_2500BASEX)
+- rgc3 = RG_PHY_SPEED_3_125G;
+- else
+- rgc3 = 0;
+-
+- /* Configure the underlying interface speed */
+- regmap_update_bits(mpcs->regmap, mpcs->ana_rgc3,
+- RG_PHY_SPEED_3_125G, rgc3);
+-
+- /* Setup the link timer */
+- regmap_write(mpcs->regmap, SGMSYS_PCS_LINK_TIMER, link_timer / 2 / 8);
+-
+- mpcs->interface = interface;
+- mode_changed = true;
+- }
+-
+- /* Update the advertisement, noting whether it has changed */
+- regmap_update_bits_check(mpcs->regmap, SGMSYS_PCS_ADVERTISE,
+- SGMII_ADVERTISE, advertise, &changed);
+-
+- /* Update the sgmsys mode register */
+- regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
+- SGMII_REMOTE_FAULT_DIS | SGMII_SPEED_DUPLEX_AN |
+- SGMII_IF_MODE_SGMII, sgm_mode);
+-
+- /* Update the BMCR */
+- regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1,
+- SGMII_AN_ENABLE, bmcr);
+-
+- /* Release PHYA power down state
+- * Only removing bit SGMII_PHYA_PWD isn't enough.
+- * There are cases when the SGMII_PHYA_PWD register contains 0x9 which
+- * prevents SGMII from working. The SGMII still shows link but no traffic
+- * can flow. Writing 0x0 to the PHYA_PWD register fix the issue. 0x0 was
+- * taken from a good working state of the SGMII interface.
+- * Unknown how much the QPHY needs but it is racy without a sleep.
+- * Tested on mt7622 & mt7986.
+- */
+- usleep_range(50, 100);
+- regmap_write(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, 0);
+-
+- return changed || mode_changed;
+-}
+-
+-static void mtk_pcs_restart_an(struct phylink_pcs *pcs)
+-{
+- struct mtk_pcs *mpcs = pcs_to_mtk_pcs(pcs);
+-
+- regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1,
+- SGMII_AN_RESTART, SGMII_AN_RESTART);
+-}
+-
+-static void mtk_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode,
+- phy_interface_t interface, int speed, int duplex)
+-{
+- struct mtk_pcs *mpcs = pcs_to_mtk_pcs(pcs);
+- unsigned int sgm_mode;
+-
+- if (!phylink_autoneg_inband(mode)) {
+- /* Force the speed and duplex setting */
+- if (speed == SPEED_10)
+- sgm_mode = SGMII_SPEED_10;
+- else if (speed == SPEED_100)
+- sgm_mode = SGMII_SPEED_100;
+- else
+- sgm_mode = SGMII_SPEED_1000;
+-
+- if (duplex != DUPLEX_FULL)
+- sgm_mode |= SGMII_DUPLEX_HALF;
+-
+- regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
+- SGMII_DUPLEX_HALF | SGMII_SPEED_MASK,
+- sgm_mode);
+- }
+-}
+-
+-static const struct phylink_pcs_ops mtk_pcs_ops = {
+- .pcs_get_state = mtk_pcs_get_state,
+- .pcs_config = mtk_pcs_config,
+- .pcs_an_restart = mtk_pcs_restart_an,
+- .pcs_link_up = mtk_pcs_link_up,
+-};
+-
+-int mtk_sgmii_init(struct mtk_sgmii *ss, struct device_node *r, u32 ana_rgc3)
+-{
+- struct device_node *np;
+- int i;
+-
+- for (i = 0; i < MTK_MAX_DEVS; i++) {
+- np = of_parse_phandle(r, "mediatek,sgmiisys", i);
+- if (!np)
+- break;
+-
+- ss->pcs[i].ana_rgc3 = ana_rgc3;
+- ss->pcs[i].regmap = syscon_node_to_regmap(np);
+-
+- ss->pcs[i].flags = 0;
+- if (of_property_read_bool(np, "mediatek,pnswap"))
+- ss->pcs[i].flags |= MTK_SGMII_FLAG_PN_SWAP;
+-
+- of_node_put(np);
+- if (IS_ERR(ss->pcs[i].regmap))
+- return PTR_ERR(ss->pcs[i].regmap);
+-
+- ss->pcs[i].pcs.ops = &mtk_pcs_ops;
+- ss->pcs[i].pcs.poll = true;
+- ss->pcs[i].interface = PHY_INTERFACE_MODE_NA;
+- }
+-
+- return 0;
+-}
+-
+-struct phylink_pcs *mtk_sgmii_select_pcs(struct mtk_sgmii *ss, int id)
+-{
+- if (!ss->pcs[id].regmap)
+- return NULL;
+-
+- return &ss->pcs[id].pcs;
+-}
diff --git a/target/linux/generic/backport-6.6/733-v6.4-21-net-mtk_eth_soc-use-WO-firmware-for-MT7981.patch b/target/linux/generic/backport-6.6/733-v6.4-21-net-mtk_eth_soc-use-WO-firmware-for-MT7981.patch
new file mode 100644
index 0000000000..9ce2735951
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.4-21-net-mtk_eth_soc-use-WO-firmware-for-MT7981.patch
@@ -0,0 +1,46 @@
+From f5af7931d2a2cae66d0f9dad4ba517b1b00620b3 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 19 Apr 2023 19:07:23 +0100
+Subject: [PATCH] net: mtk_eth_soc: use WO firmware for MT7981
+
+In order to support wireless offloading on MT7981 we need to load the
+appropriate firmware. Recognize MT7981 and load mt7981_wo.bin.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/ethernet/mediatek/mtk_wed_mcu.c | 7 ++++++-
+ drivers/net/ethernet/mediatek/mtk_wed_wo.h | 1 +
+ 2 files changed, 7 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
+@@ -326,7 +326,11 @@ mtk_wed_mcu_load_firmware(struct mtk_wed
+ wo->hw->index + 1);
+
+ /* load firmware */
+- fw_name = wo->hw->index ? MT7986_FIRMWARE_WO1 : MT7986_FIRMWARE_WO0;
++ if (of_device_is_compatible(wo->hw->node, "mediatek,mt7981-wed"))
++ fw_name = MT7981_FIRMWARE_WO;
++ else
++ fw_name = wo->hw->index ? MT7986_FIRMWARE_WO1 : MT7986_FIRMWARE_WO0;
++
+ ret = request_firmware(&fw, fw_name, wo->hw->dev);
+ if (ret)
+ return ret;
+@@ -386,5 +390,6 @@ int mtk_wed_mcu_init(struct mtk_wed_wo *
+ 100, MTK_FW_DL_TIMEOUT);
+ }
+
++MODULE_FIRMWARE(MT7981_FIRMWARE_WO);
+ MODULE_FIRMWARE(MT7986_FIRMWARE_WO0);
+ MODULE_FIRMWARE(MT7986_FIRMWARE_WO1);
+--- a/drivers/net/ethernet/mediatek/mtk_wed_wo.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed_wo.h
+@@ -88,6 +88,7 @@ enum mtk_wed_dummy_cr_idx {
+ MTK_WED_DUMMY_CR_WO_STATUS,
+ };
+
++#define MT7981_FIRMWARE_WO "mediatek/mt7981_wo.bin"
+ #define MT7986_FIRMWARE_WO0 "mediatek/mt7986_wo_0.bin"
+ #define MT7986_FIRMWARE_WO1 "mediatek/mt7986_wo_1.bin"
+
diff --git a/target/linux/generic/backport-6.6/733-v6.4-22-net-ethernet-mtk_eth_soc-fix-NULL-pointer-dereferenc.patch b/target/linux/generic/backport-6.6/733-v6.4-22-net-ethernet-mtk_eth_soc-fix-NULL-pointer-dereferenc.patch
new file mode 100644
index 0000000000..d715c4aa68
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.4-22-net-ethernet-mtk_eth_soc-fix-NULL-pointer-dereferenc.patch
@@ -0,0 +1,28 @@
+From 7c83e28f10830aa5105c25eaabe890e3adac36aa Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 9 May 2023 03:20:06 +0200
+Subject: [PATCH] net: ethernet: mtk_eth_soc: fix NULL pointer dereference
+
+Check for NULL pointer to avoid kernel crashing in case of missing WO
+firmware in case only a single WEDv2 device has been initialized, e.g. on
+MT7981 which can connect just one wireless frontend.
+
+Fixes: 86ce0d09e424 ("net: ethernet: mtk_eth_soc: use WO firmware for MT7981")
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/ethernet/mediatek/mtk_wed.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -647,7 +647,7 @@ __mtk_wed_detach(struct mtk_wed_device *
+ BIT(hw->index), BIT(hw->index));
+ }
+
+- if (!hw_list[!hw->index]->wed_dev &&
++ if ((!hw_list[!hw->index] || !hw_list[!hw->index]->wed_dev) &&
+ hw->eth->dma_dev != hw->eth->dev)
+ mtk_eth_set_dma_device(hw->eth, hw->eth->dev);
+
diff --git a/target/linux/generic/backport-6.6/733-v6.4-23-net-ethernet-mtk_eth_soc-ppe-add-support-for-flow-ac.patch b/target/linux/generic/backport-6.6/733-v6.4-23-net-ethernet-mtk_eth_soc-ppe-add-support-for-flow-ac.patch
new file mode 100644
index 0000000000..93eaffa19e
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.4-23-net-ethernet-mtk_eth_soc-ppe-add-support-for-flow-ac.patch
@@ -0,0 +1,403 @@
+From f601293f37c4be618c5efaef85d2ee21f97e82e0 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sun, 19 Mar 2023 12:57:35 +0000
+Subject: [PATCH 092/250] net: ethernet: mtk_eth_soc: ppe: add support for flow
+ accounting
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The PPE units found in MT7622 and newer support packet and byte
+accounting of hw-offloaded flows. Add support for reading those counters
+as found in MediaTek's SDK[1].
+
+[1]: https://git01.mediatek.com/plugins/gitiles/openwrt/feeds/mtk-openwrt-feeds/+/bc6a6a375c800dc2b80e1a325a2c732d1737df92
+Tested-by: Bjørn Mork <bjorn@mork.no>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 8 +-
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 3 +
+ drivers/net/ethernet/mediatek/mtk_ppe.c | 114 +++++++++++++++++-
+ drivers/net/ethernet/mediatek/mtk_ppe.h | 25 +++-
+ .../net/ethernet/mediatek/mtk_ppe_debugfs.c | 9 +-
+ .../net/ethernet/mediatek/mtk_ppe_offload.c | 8 ++
+ drivers/net/ethernet/mediatek/mtk_ppe_regs.h | 14 +++
+ 7 files changed, 172 insertions(+), 9 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -4691,8 +4691,8 @@ static int mtk_probe(struct platform_dev
+ for (i = 0; i < num_ppe; i++) {
+ u32 ppe_addr = eth->soc->reg_map->ppe_base + i * 0x400;
+
+- eth->ppe[i] = mtk_ppe_init(eth, eth->base + ppe_addr,
+- eth->soc->offload_version, i);
++ eth->ppe[i] = mtk_ppe_init(eth, eth->base + ppe_addr, i);
++
+ if (!eth->ppe[i]) {
+ err = -ENOMEM;
+ goto err_deinit_ppe;
+@@ -4816,6 +4816,7 @@ static const struct mtk_soc_data mt7622_
+ .required_pctl = false,
+ .offload_version = 2,
+ .hash_offset = 2,
++ .has_accounting = true,
+ .foe_entry_size = sizeof(struct mtk_foe_entry) - 16,
+ .txrx = {
+ .txd_size = sizeof(struct mtk_tx_dma),
+@@ -4853,6 +4854,7 @@ static const struct mtk_soc_data mt7629_
+ .hw_features = MTK_HW_FEATURES,
+ .required_clks = MT7629_CLKS_BITMAP,
+ .required_pctl = false,
++ .has_accounting = true,
+ .txrx = {
+ .txd_size = sizeof(struct mtk_tx_dma),
+ .rxd_size = sizeof(struct mtk_rx_dma),
+@@ -4873,6 +4875,7 @@ static const struct mtk_soc_data mt7981_
+ .offload_version = 2,
+ .hash_offset = 4,
+ .foe_entry_size = sizeof(struct mtk_foe_entry),
++ .has_accounting = true,
+ .txrx = {
+ .txd_size = sizeof(struct mtk_tx_dma_v2),
+ .rxd_size = sizeof(struct mtk_rx_dma_v2),
+@@ -4893,6 +4896,7 @@ static const struct mtk_soc_data mt7986_
+ .offload_version = 2,
+ .hash_offset = 4,
+ .foe_entry_size = sizeof(struct mtk_foe_entry),
++ .has_accounting = true,
+ .txrx = {
+ .txd_size = sizeof(struct mtk_tx_dma_v2),
+ .rxd_size = sizeof(struct mtk_rx_dma_v2),
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -1011,6 +1011,8 @@ struct mtk_reg_map {
+ * the extra setup for those pins used by GMAC.
+ * @hash_offset Flow table hash offset.
+ * @foe_entry_size Foe table entry size.
++ * @has_accounting Bool indicating support for accounting of
++ * offloaded flows.
+ * @txd_size Tx DMA descriptor size.
+ * @rxd_size Rx DMA descriptor size.
+ * @rx_irq_done_mask Rx irq done register mask.
+@@ -1028,6 +1030,7 @@ struct mtk_soc_data {
+ u8 hash_offset;
+ u16 foe_entry_size;
+ netdev_features_t hw_features;
++ bool has_accounting;
+ struct {
+ u32 txd_size;
+ u32 rxd_size;
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
+@@ -74,6 +74,48 @@ static int mtk_ppe_wait_busy(struct mtk_
+ return ret;
+ }
+
++static int mtk_ppe_mib_wait_busy(struct mtk_ppe *ppe)
++{
++ int ret;
++ u32 val;
++
++ ret = readl_poll_timeout(ppe->base + MTK_PPE_MIB_SER_CR, val,
++ !(val & MTK_PPE_MIB_SER_CR_ST),
++ 20, MTK_PPE_WAIT_TIMEOUT_US);
++
++ if (ret)
++ dev_err(ppe->dev, "MIB table busy");
++
++ return ret;
++}
++
++static int mtk_mib_entry_read(struct mtk_ppe *ppe, u16 index, u64 *bytes, u64 *packets)
++{
++ u32 byte_cnt_low, byte_cnt_high, pkt_cnt_low, pkt_cnt_high;
++ u32 val, cnt_r0, cnt_r1, cnt_r2;
++ int ret;
++
++ val = FIELD_PREP(MTK_PPE_MIB_SER_CR_ADDR, index) | MTK_PPE_MIB_SER_CR_ST;
++ ppe_w32(ppe, MTK_PPE_MIB_SER_CR, val);
++
++ ret = mtk_ppe_mib_wait_busy(ppe);
++ if (ret)
++ return ret;
++
++ cnt_r0 = readl(ppe->base + MTK_PPE_MIB_SER_R0);
++ cnt_r1 = readl(ppe->base + MTK_PPE_MIB_SER_R1);
++ cnt_r2 = readl(ppe->base + MTK_PPE_MIB_SER_R2);
++
++ byte_cnt_low = FIELD_GET(MTK_PPE_MIB_SER_R0_BYTE_CNT_LOW, cnt_r0);
++ byte_cnt_high = FIELD_GET(MTK_PPE_MIB_SER_R1_BYTE_CNT_HIGH, cnt_r1);
++ pkt_cnt_low = FIELD_GET(MTK_PPE_MIB_SER_R1_PKT_CNT_LOW, cnt_r1);
++ pkt_cnt_high = FIELD_GET(MTK_PPE_MIB_SER_R2_PKT_CNT_HIGH, cnt_r2);
++ *bytes = ((u64)byte_cnt_high << 32) | byte_cnt_low;
++ *packets = (pkt_cnt_high << 16) | pkt_cnt_low;
++
++ return 0;
++}
++
+ static void mtk_ppe_cache_clear(struct mtk_ppe *ppe)
+ {
+ ppe_set(ppe, MTK_PPE_CACHE_CTL, MTK_PPE_CACHE_CTL_CLEAR);
+@@ -459,6 +501,13 @@ __mtk_foe_entry_clear(struct mtk_ppe *pp
+ hwe->ib1 |= FIELD_PREP(MTK_FOE_IB1_STATE, MTK_FOE_STATE_INVALID);
+ dma_wmb();
+ mtk_ppe_cache_clear(ppe);
++ if (ppe->accounting) {
++ struct mtk_foe_accounting *acct;
++
++ acct = ppe->acct_table + entry->hash * sizeof(*acct);
++ acct->packets = 0;
++ acct->bytes = 0;
++ }
+ }
+ entry->hash = 0xffff;
+
+@@ -566,6 +615,9 @@ __mtk_foe_entry_commit(struct mtk_ppe *p
+ wmb();
+ hwe->ib1 = entry->ib1;
+
++ if (ppe->accounting)
++ *mtk_foe_entry_ib2(eth, hwe) |= MTK_FOE_IB2_MIB_CNT;
++
+ dma_wmb();
+
+ mtk_ppe_cache_clear(ppe);
+@@ -757,11 +809,39 @@ int mtk_ppe_prepare_reset(struct mtk_ppe
+ return mtk_ppe_wait_busy(ppe);
+ }
+
+-struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base,
+- int version, int index)
++struct mtk_foe_accounting *mtk_foe_entry_get_mib(struct mtk_ppe *ppe, u32 index,
++ struct mtk_foe_accounting *diff)
++{
++ struct mtk_foe_accounting *acct;
++ int size = sizeof(struct mtk_foe_accounting);
++ u64 bytes, packets;
++
++ if (!ppe->accounting)
++ return NULL;
++
++ if (mtk_mib_entry_read(ppe, index, &bytes, &packets))
++ return NULL;
++
++ acct = ppe->acct_table + index * size;
++
++ acct->bytes += bytes;
++ acct->packets += packets;
++
++ if (diff) {
++ diff->bytes = bytes;
++ diff->packets = packets;
++ }
++
++ return acct;
++}
++
++struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base, int index)
+ {
++ bool accounting = eth->soc->has_accounting;
+ const struct mtk_soc_data *soc = eth->soc;
++ struct mtk_foe_accounting *acct;
+ struct device *dev = eth->dev;
++ struct mtk_mib_entry *mib;
+ struct mtk_ppe *ppe;
+ u32 foe_flow_size;
+ void *foe;
+@@ -778,7 +858,8 @@ struct mtk_ppe *mtk_ppe_init(struct mtk_
+ ppe->base = base;
+ ppe->eth = eth;
+ ppe->dev = dev;
+- ppe->version = version;
++ ppe->version = eth->soc->offload_version;
++ ppe->accounting = accounting;
+
+ foe = dmam_alloc_coherent(ppe->dev,
+ MTK_PPE_ENTRIES * soc->foe_entry_size,
+@@ -794,6 +875,23 @@ struct mtk_ppe *mtk_ppe_init(struct mtk_
+ if (!ppe->foe_flow)
+ goto err_free_l2_flows;
+
++ if (accounting) {
++ mib = dmam_alloc_coherent(ppe->dev, MTK_PPE_ENTRIES * sizeof(*mib),
++ &ppe->mib_phys, GFP_KERNEL);
++ if (!mib)
++ return NULL;
++
++ ppe->mib_table = mib;
++
++ acct = devm_kzalloc(dev, MTK_PPE_ENTRIES * sizeof(*acct),
++ GFP_KERNEL);
++
++ if (!acct)
++ return NULL;
++
++ ppe->acct_table = acct;
++ }
++
+ mtk_ppe_debugfs_init(ppe, index);
+
+ return ppe;
+@@ -923,6 +1021,16 @@ void mtk_ppe_start(struct mtk_ppe *ppe)
+ ppe_w32(ppe, MTK_PPE_DEFAULT_CPU_PORT1, 0xcb777);
+ ppe_w32(ppe, MTK_PPE_SBW_CTRL, 0x7f);
+ }
++
++ if (ppe->accounting && ppe->mib_phys) {
++ ppe_w32(ppe, MTK_PPE_MIB_TB_BASE, ppe->mib_phys);
++ ppe_m32(ppe, MTK_PPE_MIB_CFG, MTK_PPE_MIB_CFG_EN,
++ MTK_PPE_MIB_CFG_EN);
++ ppe_m32(ppe, MTK_PPE_MIB_CFG, MTK_PPE_MIB_CFG_RD_CLR,
++ MTK_PPE_MIB_CFG_RD_CLR);
++ ppe_m32(ppe, MTK_PPE_MIB_CACHE_CTL, MTK_PPE_MIB_CACHE_CTL_EN,
++ MTK_PPE_MIB_CFG_RD_CLR);
++ }
+ }
+
+ int mtk_ppe_stop(struct mtk_ppe *ppe)
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
+@@ -57,6 +57,7 @@ enum {
+ #define MTK_FOE_IB2_MULTICAST BIT(8)
+
+ #define MTK_FOE_IB2_WDMA_QID2 GENMASK(13, 12)
++#define MTK_FOE_IB2_MIB_CNT BIT(15)
+ #define MTK_FOE_IB2_WDMA_DEVIDX BIT(16)
+ #define MTK_FOE_IB2_WDMA_WINFO BIT(17)
+
+@@ -285,16 +286,34 @@ struct mtk_flow_entry {
+ unsigned long cookie;
+ };
+
++struct mtk_mib_entry {
++ u32 byt_cnt_l;
++ u16 byt_cnt_h;
++ u32 pkt_cnt_l;
++ u8 pkt_cnt_h;
++ u8 _rsv0;
++ u32 _rsv1;
++} __packed;
++
++struct mtk_foe_accounting {
++ u64 bytes;
++ u64 packets;
++};
++
+ struct mtk_ppe {
+ struct mtk_eth *eth;
+ struct device *dev;
+ void __iomem *base;
+ int version;
+ char dirname[5];
++ bool accounting;
+
+ void *foe_table;
+ dma_addr_t foe_phys;
+
++ struct mtk_mib_entry *mib_table;
++ dma_addr_t mib_phys;
++
+ u16 foe_check_time[MTK_PPE_ENTRIES];
+ struct hlist_head *foe_flow;
+
+@@ -303,8 +322,8 @@ struct mtk_ppe {
+ void *acct_table;
+ };
+
+-struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base,
+- int version, int index);
++struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base, int index);
++
+ void mtk_ppe_deinit(struct mtk_eth *eth);
+ void mtk_ppe_start(struct mtk_ppe *ppe);
+ int mtk_ppe_stop(struct mtk_ppe *ppe);
+@@ -359,5 +378,7 @@ int mtk_foe_entry_commit(struct mtk_ppe
+ void mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
+ int mtk_foe_entry_idle_time(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
+ int mtk_ppe_debugfs_init(struct mtk_ppe *ppe, int index);
++struct mtk_foe_accounting *mtk_foe_entry_get_mib(struct mtk_ppe *ppe, u32 index,
++ struct mtk_foe_accounting *diff);
+
+ #endif
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c
+@@ -82,6 +82,7 @@ mtk_ppe_debugfs_foe_show(struct seq_file
+ struct mtk_foe_entry *entry = mtk_foe_get_entry(ppe, i);
+ struct mtk_foe_mac_info *l2;
+ struct mtk_flow_addr_info ai = {};
++ struct mtk_foe_accounting *acct;
+ unsigned char h_source[ETH_ALEN];
+ unsigned char h_dest[ETH_ALEN];
+ int type, state;
+@@ -95,6 +96,8 @@ mtk_ppe_debugfs_foe_show(struct seq_file
+ if (bind && state != MTK_FOE_STATE_BIND)
+ continue;
+
++ acct = mtk_foe_entry_get_mib(ppe, i, NULL);
++
+ type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->ib1);
+ seq_printf(m, "%05x %s %7s", i,
+ mtk_foe_entry_state_str(state),
+@@ -153,9 +156,11 @@ mtk_ppe_debugfs_foe_show(struct seq_file
+ *((__be16 *)&h_dest[4]) = htons(l2->dest_mac_lo);
+
+ seq_printf(m, " eth=%pM->%pM etype=%04x"
+- " vlan=%d,%d ib1=%08x ib2=%08x\n",
++ " vlan=%d,%d ib1=%08x ib2=%08x"
++ " packets=%llu bytes=%llu\n",
+ h_source, h_dest, ntohs(l2->etype),
+- l2->vlan1, l2->vlan2, entry->ib1, ib2);
++ l2->vlan1, l2->vlan2, entry->ib1, ib2,
++ acct ? acct->packets : 0, acct ? acct->bytes : 0);
+ }
+
+ return 0;
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+@@ -497,6 +497,7 @@ static int
+ mtk_flow_offload_stats(struct mtk_eth *eth, struct flow_cls_offload *f)
+ {
+ struct mtk_flow_entry *entry;
++ struct mtk_foe_accounting diff;
+ u32 idle;
+
+ entry = rhashtable_lookup(&eth->flow_table, &f->cookie,
+@@ -507,6 +508,13 @@ mtk_flow_offload_stats(struct mtk_eth *e
+ idle = mtk_foe_entry_idle_time(eth->ppe[entry->ppe_index], entry);
+ f->stats.lastused = jiffies - idle * HZ;
+
++ if (entry->hash != 0xFFFF &&
++ mtk_foe_entry_get_mib(eth->ppe[entry->ppe_index], entry->hash,
++ &diff)) {
++ f->stats.pkts += diff.packets;
++ f->stats.bytes += diff.bytes;
++ }
++
+ return 0;
+ }
+
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_regs.h
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_regs.h
+@@ -149,6 +149,20 @@ enum {
+
+ #define MTK_PPE_MIB_TB_BASE 0x338
+
++#define MTK_PPE_MIB_SER_CR 0x33C
++#define MTK_PPE_MIB_SER_CR_ST BIT(16)
++#define MTK_PPE_MIB_SER_CR_ADDR GENMASK(13, 0)
++
++#define MTK_PPE_MIB_SER_R0 0x340
++#define MTK_PPE_MIB_SER_R0_BYTE_CNT_LOW GENMASK(31, 0)
++
++#define MTK_PPE_MIB_SER_R1 0x344
++#define MTK_PPE_MIB_SER_R1_PKT_CNT_LOW GENMASK(31, 16)
++#define MTK_PPE_MIB_SER_R1_BYTE_CNT_HIGH GENMASK(15, 0)
++
++#define MTK_PPE_MIB_SER_R2 0x348
++#define MTK_PPE_MIB_SER_R2_PKT_CNT_HIGH GENMASK(23, 0)
++
+ #define MTK_PPE_MIB_CACHE_CTL 0x350
+ #define MTK_PPE_MIB_CACHE_CTL_EN BIT(0)
+ #define MTK_PPE_MIB_CACHE_CTL_FLUSH BIT(2)
diff --git a/target/linux/generic/backport-6.6/733-v6.4-24-net-ethernet-mediatek-fix-ppe-flow-accounting-for-v1.patch b/target/linux/generic/backport-6.6/733-v6.4-24-net-ethernet-mediatek-fix-ppe-flow-accounting-for-v1.patch
new file mode 100644
index 0000000000..64352426ae
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.4-24-net-ethernet-mediatek-fix-ppe-flow-accounting-for-v1.patch
@@ -0,0 +1,58 @@
+From 88a0fd5927b7c2c7aecd6dc747d898eb38043d2b Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 20 Apr 2023 22:06:42 +0100
+Subject: [PATCH 093/250] net: mtk_eth_soc: mediatek: fix ppe flow accounting
+ for v1 hardware
+
+Older chips (like MT7622) use a different bit in ib2 to enable hardware
+counter support. Add macros for both and select the appropriate bit.
+
+Fixes: 3fbe4d8c0e53 ("net: ethernet: mtk_eth_soc: ppe: add support for flow accounting")
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/ethernet/mediatek/mtk_ppe.c | 10 ++++++++--
+ drivers/net/ethernet/mediatek/mtk_ppe.h | 3 ++-
+ 2 files changed, 10 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
+@@ -599,6 +599,7 @@ __mtk_foe_entry_commit(struct mtk_ppe *p
+ struct mtk_eth *eth = ppe->eth;
+ u16 timestamp = mtk_eth_timestamp(eth);
+ struct mtk_foe_entry *hwe;
++ u32 val;
+
+ if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+ entry->ib1 &= ~MTK_FOE_IB1_BIND_TIMESTAMP_V2;
+@@ -615,8 +616,13 @@ __mtk_foe_entry_commit(struct mtk_ppe *p
+ wmb();
+ hwe->ib1 = entry->ib1;
+
+- if (ppe->accounting)
+- *mtk_foe_entry_ib2(eth, hwe) |= MTK_FOE_IB2_MIB_CNT;
++ if (ppe->accounting) {
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ val = MTK_FOE_IB2_MIB_CNT_V2;
++ else
++ val = MTK_FOE_IB2_MIB_CNT;
++ *mtk_foe_entry_ib2(eth, hwe) |= val;
++ }
+
+ dma_wmb();
+
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
+@@ -55,9 +55,10 @@ enum {
+ #define MTK_FOE_IB2_PSE_QOS BIT(4)
+ #define MTK_FOE_IB2_DEST_PORT GENMASK(7, 5)
+ #define MTK_FOE_IB2_MULTICAST BIT(8)
++#define MTK_FOE_IB2_MIB_CNT BIT(10)
+
+ #define MTK_FOE_IB2_WDMA_QID2 GENMASK(13, 12)
+-#define MTK_FOE_IB2_MIB_CNT BIT(15)
++#define MTK_FOE_IB2_MIB_CNT_V2 BIT(15)
+ #define MTK_FOE_IB2_WDMA_DEVIDX BIT(16)
+ #define MTK_FOE_IB2_WDMA_WINFO BIT(17)
+
diff --git a/target/linux/generic/backport-6.6/733-v6.4-25-net-ethernet-mtk_eth_soc-drop-generic-vlan-rx-offloa.patch b/target/linux/generic/backport-6.6/733-v6.4-25-net-ethernet-mtk_eth_soc-drop-generic-vlan-rx-offloa.patch
new file mode 100644
index 0000000000..217e517c3a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.4-25-net-ethernet-mtk_eth_soc-drop-generic-vlan-rx-offloa.patch
@@ -0,0 +1,201 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Sun, 20 Nov 2022 23:01:00 +0100
+Subject: [PATCH] net: ethernet: mtk_eth_soc: drop generic vlan rx offload,
+ only use DSA untagging
+
+Through testing I found out that hardware vlan rx offload support seems to
+have some hardware issues. At least when using multiple MACs and when receiving
+tagged packets on the secondary MAC, the hardware can sometimes start to emit
+wrong tags on the first MAC as well.
+
+In order to avoid such issues, drop the feature configuration and use the
+offload feature only for DSA hardware untagging on MT7621/MT7622 devices which
+only use one MAC.
+
+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
+@@ -1897,9 +1897,7 @@ static int mtk_poll_rx(struct napi_struc
+
+ while (done < budget) {
+ unsigned int pktlen, *rxdcsum;
+- bool has_hwaccel_tag = false;
+ struct net_device *netdev;
+- u16 vlan_proto, vlan_tci;
+ dma_addr_t dma_addr;
+ u32 hash, reason;
+ int mac = 0;
+@@ -2034,36 +2032,21 @@ static int mtk_poll_rx(struct napi_struc
+ skb_checksum_none_assert(skb);
+ skb->protocol = eth_type_trans(skb, netdev);
+
+- if (reason == MTK_PPE_CPU_REASON_HIT_UNBIND_RATE_REACHED)
+- mtk_ppe_check_skb(eth->ppe[0], skb, hash);
+-
+- if (netdev->features & NETIF_F_HW_VLAN_CTAG_RX) {
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+- if (trxd.rxd3 & RX_DMA_VTAG_V2) {
+- vlan_proto = RX_DMA_VPID(trxd.rxd4);
+- vlan_tci = RX_DMA_VID(trxd.rxd4);
+- has_hwaccel_tag = true;
+- }
+- } else if (trxd.rxd2 & RX_DMA_VTAG) {
+- vlan_proto = RX_DMA_VPID(trxd.rxd3);
+- vlan_tci = RX_DMA_VID(trxd.rxd3);
+- has_hwaccel_tag = true;
+- }
+- }
+-
+ /* When using VLAN untagging in combination with DSA, the
+ * hardware treats the MTK special tag as a VLAN and untags it.
+ */
+- if (has_hwaccel_tag && netdev_uses_dsa(netdev)) {
+- unsigned int port = vlan_proto & GENMASK(2, 0);
++ if (!MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2) &&
++ (trxd.rxd2 & RX_DMA_VTAG) && netdev_uses_dsa(netdev)) {
++ unsigned int port = RX_DMA_VPID(trxd.rxd3) & GENMASK(2, 0);
+
+ if (port < ARRAY_SIZE(eth->dsa_meta) &&
+ eth->dsa_meta[port])
+ skb_dst_set_noref(skb, &eth->dsa_meta[port]->dst);
+- } else if (has_hwaccel_tag) {
+- __vlan_hwaccel_put_tag(skb, htons(vlan_proto), vlan_tci);
+ }
+
++ if (reason == MTK_PPE_CPU_REASON_HIT_UNBIND_RATE_REACHED)
++ mtk_ppe_check_skb(eth->ppe[0], skb, hash);
++
+ skb_record_rx_queue(skb, 0);
+ napi_gro_receive(napi, skb);
+
+@@ -2889,29 +2872,11 @@ static netdev_features_t mtk_fix_feature
+
+ static int mtk_set_features(struct net_device *dev, netdev_features_t features)
+ {
+- struct mtk_mac *mac = netdev_priv(dev);
+- struct mtk_eth *eth = mac->hw;
+ netdev_features_t diff = dev->features ^ features;
+- int i;
+
+ if ((diff & NETIF_F_LRO) && !(features & NETIF_F_LRO))
+ mtk_hwlro_netdev_disable(dev);
+
+- /* Set RX VLAN offloading */
+- if (!(diff & NETIF_F_HW_VLAN_CTAG_RX))
+- return 0;
+-
+- mtk_w32(eth, !!(features & NETIF_F_HW_VLAN_CTAG_RX),
+- MTK_CDMP_EG_CTRL);
+-
+- /* sync features with other MAC */
+- for (i = 0; i < MTK_MAC_COUNT; i++) {
+- if (!eth->netdev[i] || eth->netdev[i] == dev)
+- continue;
+- eth->netdev[i]->features &= ~NETIF_F_HW_VLAN_CTAG_RX;
+- eth->netdev[i]->features |= features & NETIF_F_HW_VLAN_CTAG_RX;
+- }
+-
+ return 0;
+ }
+
+@@ -3225,30 +3190,6 @@ static int mtk_open(struct net_device *d
+ struct mtk_eth *eth = mac->hw;
+ int i, err;
+
+- if (mtk_uses_dsa(dev) && !eth->prog) {
+- for (i = 0; i < ARRAY_SIZE(eth->dsa_meta); i++) {
+- struct metadata_dst *md_dst = eth->dsa_meta[i];
+-
+- if (md_dst)
+- continue;
+-
+- md_dst = metadata_dst_alloc(0, METADATA_HW_PORT_MUX,
+- GFP_KERNEL);
+- if (!md_dst)
+- return -ENOMEM;
+-
+- md_dst->u.port_info.port_id = i;
+- eth->dsa_meta[i] = md_dst;
+- }
+- } else {
+- /* Hardware special tag parsing needs to be disabled if at least
+- * one MAC does not use DSA.
+- */
+- u32 val = mtk_r32(eth, MTK_CDMP_IG_CTRL);
+- val &= ~MTK_CDMP_STAG_EN;
+- mtk_w32(eth, val, MTK_CDMP_IG_CTRL);
+- }
+-
+ err = phylink_of_phy_connect(mac->phylink, mac->of_node, 0);
+ if (err) {
+ netdev_err(dev, "%s: could not attach PHY: %d\n", __func__,
+@@ -3287,6 +3228,35 @@ static int mtk_open(struct net_device *d
+ phylink_start(mac->phylink);
+ netif_tx_start_all_queues(dev);
+
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ return 0;
++
++ if (mtk_uses_dsa(dev) && !eth->prog) {
++ for (i = 0; i < ARRAY_SIZE(eth->dsa_meta); i++) {
++ struct metadata_dst *md_dst = eth->dsa_meta[i];
++
++ if (md_dst)
++ continue;
++
++ md_dst = metadata_dst_alloc(0, METADATA_HW_PORT_MUX,
++ GFP_KERNEL);
++ if (!md_dst)
++ return -ENOMEM;
++
++ md_dst->u.port_info.port_id = i;
++ eth->dsa_meta[i] = md_dst;
++ }
++ } else {
++ /* Hardware special tag parsing needs to be disabled if at least
++ * one MAC does not use DSA.
++ */
++ u32 val = mtk_r32(eth, MTK_CDMP_IG_CTRL);
++ val &= ~MTK_CDMP_STAG_EN;
++ mtk_w32(eth, val, MTK_CDMP_IG_CTRL);
++
++ mtk_w32(eth, 0, MTK_CDMP_EG_CTRL);
++ }
++
+ return 0;
+ }
+
+@@ -3771,10 +3741,9 @@ static int mtk_hw_init(struct mtk_eth *e
+ if (!MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
+ val = mtk_r32(eth, MTK_CDMP_IG_CTRL);
+ mtk_w32(eth, val | MTK_CDMP_STAG_EN, MTK_CDMP_IG_CTRL);
+- }
+
+- /* Enable RX VLan Offloading */
+- mtk_w32(eth, 1, MTK_CDMP_EG_CTRL);
++ mtk_w32(eth, 1, MTK_CDMP_EG_CTRL);
++ }
+
+ /* set interrupt delays based on current Net DIM sample */
+ mtk_dim_rx(&eth->rx_dim.work);
+@@ -4414,7 +4383,7 @@ static int mtk_add_mac(struct mtk_eth *e
+ eth->netdev[id]->hw_features |= NETIF_F_LRO;
+
+ eth->netdev[id]->vlan_features = eth->soc->hw_features &
+- ~(NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX);
++ ~NETIF_F_HW_VLAN_CTAG_TX;
+ eth->netdev[id]->features |= eth->soc->hw_features;
+ eth->netdev[id]->ethtool_ops = &mtk_ethtool_ops;
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -48,7 +48,6 @@
+ #define MTK_HW_FEATURES (NETIF_F_IP_CSUM | \
+ NETIF_F_RXCSUM | \
+ NETIF_F_HW_VLAN_CTAG_TX | \
+- NETIF_F_HW_VLAN_CTAG_RX | \
+ NETIF_F_SG | NETIF_F_TSO | \
+ NETIF_F_TSO6 | \
+ NETIF_F_IPV6_CSUM |\
diff --git a/target/linux/generic/backport-6.6/733-v6.5-26-net-ethernet-mtk_eth_soc-always-mtk_get_ib1_pkt_type.patch b/target/linux/generic/backport-6.6/733-v6.5-26-net-ethernet-mtk_eth_soc-always-mtk_get_ib1_pkt_type.patch
new file mode 100644
index 0000000000..2704faec12
--- /dev/null
+++ b/target/linux/generic/backport-6.6/733-v6.5-26-net-ethernet-mtk_eth_soc-always-mtk_get_ib1_pkt_type.patch
@@ -0,0 +1,31 @@
+From b804f765485109f9644cc05d1e8fc79ca6c6e4aa Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 19 Jul 2023 01:39:36 +0100
+Subject: [PATCH 094/250] net: ethernet: mtk_eth_soc: always
+ mtk_get_ib1_pkt_type
+
+entries and bind debugfs files would display wrong data on NETSYS_V2 and
+later because instead of using mtk_get_ib1_pkt_type the driver would use
+MTK_FOE_IB1_PACKET_TYPE which corresponds to NETSYS_V1(.x) SoCs.
+Use mtk_get_ib1_pkt_type so entries and bind records display correctly.
+
+Fixes: 03a3180e5c09e ("net: ethernet: mtk_eth_soc: introduce flow offloading support for mt7986")
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://lore.kernel.org/r/c0ae03d0182f4d27b874cbdf0059bc972c317f3c.1689727134.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c
+@@ -98,7 +98,7 @@ mtk_ppe_debugfs_foe_show(struct seq_file
+
+ acct = mtk_foe_entry_get_mib(ppe, i, NULL);
+
+- type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->ib1);
++ type = mtk_get_ib1_pkt_type(ppe->eth, entry->ib1);
+ seq_printf(m, "%05x %s %7s", i,
+ mtk_foe_entry_state_str(state),
+ mtk_foe_pkt_type_str(type));
diff --git a/target/linux/generic/backport-6.6/750-v6.5-01-net-ethernet-mtk_ppe-add-MTK_FOE_ENTRY_V-1-2-_SIZE-m.patch b/target/linux/generic/backport-6.6/750-v6.5-01-net-ethernet-mtk_ppe-add-MTK_FOE_ENTRY_V-1-2-_SIZE-m.patch
new file mode 100644
index 0000000000..d7d1c08fce
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-01-net-ethernet-mtk_ppe-add-MTK_FOE_ENTRY_V-1-2-_SIZE-m.patch
@@ -0,0 +1,78 @@
+From 5ea0e1312bcfebc06b5f91d1bb82b823d6395125 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Wed, 19 Jul 2023 12:29:49 +0200
+Subject: [PATCH 095/250] net: ethernet: mtk_ppe: add MTK_FOE_ENTRY_V{1,2}_SIZE
+ macros
+
+Introduce MTK_FOE_ENTRY_V{1,2}_SIZE macros in order to make more
+explicit foe_entry size for different chipset revisions.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 10 +++++-----
+ drivers/net/ethernet/mediatek/mtk_ppe.h | 3 +++
+ 2 files changed, 8 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -4765,7 +4765,7 @@ static const struct mtk_soc_data mt7621_
+ .required_pctl = false,
+ .offload_version = 1,
+ .hash_offset = 2,
+- .foe_entry_size = sizeof(struct mtk_foe_entry) - 16,
++ .foe_entry_size = MTK_FOE_ENTRY_V1_SIZE,
+ .txrx = {
+ .txd_size = sizeof(struct mtk_tx_dma),
+ .rxd_size = sizeof(struct mtk_rx_dma),
+@@ -4786,7 +4786,7 @@ static const struct mtk_soc_data mt7622_
+ .offload_version = 2,
+ .hash_offset = 2,
+ .has_accounting = true,
+- .foe_entry_size = sizeof(struct mtk_foe_entry) - 16,
++ .foe_entry_size = MTK_FOE_ENTRY_V1_SIZE,
+ .txrx = {
+ .txd_size = sizeof(struct mtk_tx_dma),
+ .rxd_size = sizeof(struct mtk_rx_dma),
+@@ -4805,7 +4805,7 @@ static const struct mtk_soc_data mt7623_
+ .required_pctl = true,
+ .offload_version = 1,
+ .hash_offset = 2,
+- .foe_entry_size = sizeof(struct mtk_foe_entry) - 16,
++ .foe_entry_size = MTK_FOE_ENTRY_V1_SIZE,
+ .txrx = {
+ .txd_size = sizeof(struct mtk_tx_dma),
+ .rxd_size = sizeof(struct mtk_rx_dma),
+@@ -4843,8 +4843,8 @@ static const struct mtk_soc_data mt7981_
+ .required_pctl = false,
+ .offload_version = 2,
+ .hash_offset = 4,
+- .foe_entry_size = sizeof(struct mtk_foe_entry),
+ .has_accounting = true,
++ .foe_entry_size = MTK_FOE_ENTRY_V2_SIZE,
+ .txrx = {
+ .txd_size = sizeof(struct mtk_tx_dma_v2),
+ .rxd_size = sizeof(struct mtk_rx_dma_v2),
+@@ -4864,8 +4864,8 @@ static const struct mtk_soc_data mt7986_
+ .required_pctl = false,
+ .offload_version = 2,
+ .hash_offset = 4,
+- .foe_entry_size = sizeof(struct mtk_foe_entry),
+ .has_accounting = true,
++ .foe_entry_size = MTK_FOE_ENTRY_V2_SIZE,
+ .txrx = {
+ .txd_size = sizeof(struct mtk_tx_dma_v2),
+ .rxd_size = sizeof(struct mtk_rx_dma_v2),
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
+@@ -216,6 +216,9 @@ struct mtk_foe_ipv6_6rd {
+ struct mtk_foe_mac_info l2;
+ };
+
++#define MTK_FOE_ENTRY_V1_SIZE 80
++#define MTK_FOE_ENTRY_V2_SIZE 96
++
+ struct mtk_foe_entry {
+ u32 ib1;
+
diff --git a/target/linux/generic/backport-6.6/750-v6.5-02-net-ethernet-mtk_eth_soc-remove-incorrect-PLL-config.patch b/target/linux/generic/backport-6.6/750-v6.5-02-net-ethernet-mtk_eth_soc-remove-incorrect-PLL-config.patch
new file mode 100644
index 0000000000..fb54f404b2
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-02-net-ethernet-mtk_eth_soc-remove-incorrect-PLL-config.patch
@@ -0,0 +1,141 @@
+From 8cfa2576d79f9379d167a8994f0fca935c07a8bc Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Sat, 22 Jul 2023 21:32:49 +0100
+Subject: [PATCH 096/250] net: ethernet: mtk_eth_soc: remove incorrect PLL
+ configuration
+
+MT7623 GMAC0 attempts to configure the system clocking according to the
+required speed in the .mac_config callback for non-SGMII, non-baseX and
+non-TRGMII modes.
+
+state->speed setting has never been reliable in the .mac_config
+callback - there are cases where this is not the link speed,
+particularly via ethtool paths, so this has always been unreliable (as
+detailed in phylink's documentation.)
+
+There is the additional issue that mtk_gmac0_rgmii_adjust() will only
+be called if state->interface changes, which means it only configures
+the system clocking on the very first .mac_config call, which will be
+made when the network device is first brought up before any link is
+established.
+
+Essentially, this code is incredibly buggy, and probably never worked.
+
+Moreover, checking the in-kernel DT files, it seems no platform makes
+use of this code path.
+
+Therefore, let's remove it, and disable interface modes for port 0 that
+are not SGMII, 1000base-X, 2500base-X or TRGMII on the MT7623.
+
+Reviewed-by: Daniel Golle <daniel@makrotopia.org>
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Tested-by: Frank Wunderlich <frank-w@public-files.de>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 54 ++++++---------------
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 1 +
+ 2 files changed, 17 insertions(+), 38 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -352,7 +352,7 @@ static int mt7621_gmac0_rgmii_adjust(str
+ }
+
+ static void mtk_gmac0_rgmii_adjust(struct mtk_eth *eth,
+- phy_interface_t interface, int speed)
++ phy_interface_t interface)
+ {
+ u32 val;
+ int ret;
+@@ -366,26 +366,7 @@ static void mtk_gmac0_rgmii_adjust(struc
+ return;
+ }
+
+- val = (speed == SPEED_1000) ?
+- INTF_MODE_RGMII_1000 : INTF_MODE_RGMII_10_100;
+- mtk_w32(eth, val, INTF_MODE);
+-
+- regmap_update_bits(eth->ethsys, ETHSYS_CLKCFG0,
+- ETHSYS_TRGMII_CLK_SEL362_5,
+- ETHSYS_TRGMII_CLK_SEL362_5);
+-
+- val = (speed == SPEED_1000) ? 250000000 : 500000000;
+- ret = clk_set_rate(eth->clks[MTK_CLK_TRGPLL], val);
+- if (ret)
+- dev_err(eth->dev, "Failed to set trgmii pll: %d\n", ret);
+-
+- val = (speed == SPEED_1000) ?
+- RCK_CTRL_RGMII_1000 : RCK_CTRL_RGMII_10_100;
+- mtk_w32(eth, val, TRGMII_RCK_CTRL);
+-
+- val = (speed == SPEED_1000) ?
+- TCK_CTRL_RGMII_1000 : TCK_CTRL_RGMII_10_100;
+- mtk_w32(eth, val, TRGMII_TCK_CTRL);
++ dev_err(eth->dev, "Missing PLL configuration, ethernet may not work\n");
+ }
+
+ static struct phylink_pcs *mtk_mac_select_pcs(struct phylink_config *config,
+@@ -471,17 +452,8 @@ static void mtk_mac_config(struct phylin
+ state->interface))
+ goto err_phy;
+ } else {
+- /* FIXME: this is incorrect. Not only does it
+- * use state->speed (which is not guaranteed
+- * to be correct) but it also makes use of it
+- * in a code path that will only be reachable
+- * when the PHY interface mode changes, not
+- * when the speed changes. Consequently, RGMII
+- * is probably broken.
+- */
+ mtk_gmac0_rgmii_adjust(mac->hw,
+- state->interface,
+- state->speed);
++ state->interface);
+
+ /* mt7623_pad_clk_setup */
+ for (i = 0 ; i < NUM_TRGMII_CTRL; i++)
+@@ -4342,13 +4314,19 @@ static int mtk_add_mac(struct mtk_eth *e
+ mac->phylink_config.mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+ MAC_10 | MAC_100 | MAC_1000 | MAC_2500FD;
+
+- __set_bit(PHY_INTERFACE_MODE_MII,
+- mac->phylink_config.supported_interfaces);
+- __set_bit(PHY_INTERFACE_MODE_GMII,
+- mac->phylink_config.supported_interfaces);
++ /* MT7623 gmac0 is now missing its speed-specific PLL configuration
++ * in its .mac_config method (since state->speed is not valid there.
++ * Disable support for MII, GMII and RGMII.
++ */
++ if (!mac->hw->soc->disable_pll_modes || mac->id != 0) {
++ __set_bit(PHY_INTERFACE_MODE_MII,
++ mac->phylink_config.supported_interfaces);
++ __set_bit(PHY_INTERFACE_MODE_GMII,
++ mac->phylink_config.supported_interfaces);
+
+- if (MTK_HAS_CAPS(mac->hw->soc->caps, MTK_RGMII))
+- phy_interface_set_rgmii(mac->phylink_config.supported_interfaces);
++ if (MTK_HAS_CAPS(mac->hw->soc->caps, MTK_RGMII))
++ phy_interface_set_rgmii(mac->phylink_config.supported_interfaces);
++ }
+
+ if (MTK_HAS_CAPS(mac->hw->soc->caps, MTK_TRGMII) && !mac->id)
+ __set_bit(PHY_INTERFACE_MODE_TRGMII,
+@@ -4806,6 +4784,7 @@ static const struct mtk_soc_data mt7623_
+ .offload_version = 1,
+ .hash_offset = 2,
+ .foe_entry_size = MTK_FOE_ENTRY_V1_SIZE,
++ .disable_pll_modes = true,
+ .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
+@@ -1030,6 +1030,7 @@ struct mtk_soc_data {
+ u16 foe_entry_size;
+ netdev_features_t hw_features;
+ bool has_accounting;
++ bool disable_pll_modes;
+ struct {
+ u32 txd_size;
+ u32 rxd_size;
diff --git a/target/linux/generic/backport-6.6/750-v6.5-03-net-ethernet-mtk_eth_soc-remove-mac_pcs_get_state-an.patch b/target/linux/generic/backport-6.6/750-v6.5-03-net-ethernet-mtk_eth_soc-remove-mac_pcs_get_state-an.patch
new file mode 100644
index 0000000000..293066fa9a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-03-net-ethernet-mtk_eth_soc-remove-mac_pcs_get_state-an.patch
@@ -0,0 +1,81 @@
+From a4c2233b1e4359b6c64b6f9ba98c8718a11fffee Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Sat, 22 Jul 2023 21:32:54 +0100
+Subject: [PATCH 097/250] net: ethernet: mtk_eth_soc: remove mac_pcs_get_state
+ and modernise
+
+Remove the .mac_pcs_get_state function, since as far as I can tell is
+never called - no DT appears to specify an in-band-status management
+nor SFP support for this driver.
+
+Removal of this, along with the previous patch to remove the incorrect
+clocking configuration, means that the driver becomes non-legacy, so
+we can remove the "legacy_pre_march2020" status from this driver.
+
+Reviewed-by: Daniel Golle <daniel@makrotopia.org>
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Tested-by: Frank Wunderlich <frank-w@public-files.de>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 35 ---------------------
+ 1 file changed, 35 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -555,38 +555,6 @@ static int mtk_mac_finish(struct phylink
+ return 0;
+ }
+
+-static void mtk_mac_pcs_get_state(struct phylink_config *config,
+- struct phylink_link_state *state)
+-{
+- struct mtk_mac *mac = container_of(config, struct mtk_mac,
+- phylink_config);
+- u32 pmsr = mtk_r32(mac->hw, MTK_MAC_MSR(mac->id));
+-
+- state->link = (pmsr & MAC_MSR_LINK);
+- state->duplex = (pmsr & MAC_MSR_DPX) >> 1;
+-
+- switch (pmsr & (MAC_MSR_SPEED_1000 | MAC_MSR_SPEED_100)) {
+- case 0:
+- state->speed = SPEED_10;
+- break;
+- case MAC_MSR_SPEED_100:
+- state->speed = SPEED_100;
+- break;
+- case MAC_MSR_SPEED_1000:
+- state->speed = SPEED_1000;
+- break;
+- default:
+- state->speed = SPEED_UNKNOWN;
+- break;
+- }
+-
+- state->pause &= (MLO_PAUSE_RX | MLO_PAUSE_TX);
+- if (pmsr & MAC_MSR_RX_FC)
+- state->pause |= MLO_PAUSE_RX;
+- if (pmsr & MAC_MSR_TX_FC)
+- state->pause |= MLO_PAUSE_TX;
+-}
+-
+ static void mtk_mac_link_down(struct phylink_config *config, unsigned int mode,
+ phy_interface_t interface)
+ {
+@@ -708,7 +676,6 @@ static void mtk_mac_link_up(struct phyli
+
+ static const struct phylink_mac_ops mtk_phylink_ops = {
+ .mac_select_pcs = mtk_mac_select_pcs,
+- .mac_pcs_get_state = mtk_mac_pcs_get_state,
+ .mac_config = mtk_mac_config,
+ .mac_finish = mtk_mac_finish,
+ .mac_link_down = mtk_mac_link_down,
+@@ -4309,8 +4276,6 @@ static int mtk_add_mac(struct mtk_eth *e
+
+ mac->phylink_config.dev = &eth->netdev[id]->dev;
+ mac->phylink_config.type = PHYLINK_NETDEV;
+- /* This driver makes use of state->speed in mac_config */
+- mac->phylink_config.legacy_pre_march2020 = true;
+ mac->phylink_config.mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+ MAC_10 | MAC_100 | MAC_1000 | MAC_2500FD;
+
diff --git a/target/linux/generic/backport-6.6/750-v6.5-05-net-ethernet-mtk_eth_soc-add-version-in-mtk_soc_data.patch b/target/linux/generic/backport-6.6/750-v6.5-05-net-ethernet-mtk_eth_soc-add-version-in-mtk_soc_data.patch
new file mode 100644
index 0000000000..25c87b0415
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-05-net-ethernet-mtk_eth_soc-add-version-in-mtk_soc_data.patch
@@ -0,0 +1,550 @@
+From 5d8d05fbf804b4485646d39551ac27452e45afd3 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Tue, 25 Jul 2023 01:52:02 +0100
+Subject: [PATCH 099/250] net: ethernet: mtk_eth_soc: add version in
+ mtk_soc_data
+
+Introduce version field in mtk_soc_data data structure in order to
+make mtk_eth driver easier to maintain for chipset configuration
+codebase. Get rid of MTK_NETSYS_V2 bit in chip capabilities.
+This is a preliminary patch to introduce support for MT7988 SoC.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/e52fae302ca135436e5cdd26d38d87be2da63055.1690246066.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 55 +++++++++++--------
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 36 +++++++-----
+ drivers/net/ethernet/mediatek/mtk_ppe.c | 18 +++---
+ .../net/ethernet/mediatek/mtk_ppe_offload.c | 2 +-
+ drivers/net/ethernet/mediatek/mtk_wed.c | 4 +-
+ 5 files changed, 66 insertions(+), 49 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -580,7 +580,7 @@ static void mtk_set_queue_speed(struct m
+ FIELD_PREP(MTK_QTX_SCH_MIN_RATE_MAN, 1) |
+ FIELD_PREP(MTK_QTX_SCH_MIN_RATE_EXP, 4) |
+ MTK_QTX_SCH_LEAKY_BUCKET_SIZE;
+- if (!MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v1(eth))
+ val |= MTK_QTX_SCH_LEAKY_BUCKET_EN;
+
+ if (IS_ENABLED(CONFIG_SOC_MT7621)) {
+@@ -955,7 +955,7 @@ static bool mtk_rx_get_desc(struct mtk_e
+ 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)) {
++ if (mtk_is_netsys_v2_or_greater(eth)) {
+ rxd->rxd5 = READ_ONCE(dma_rxd->rxd5);
+ rxd->rxd6 = READ_ONCE(dma_rxd->rxd6);
+ }
+@@ -1013,7 +1013,7 @@ 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)) {
++ if (mtk_is_netsys_v2_or_greater(eth)) {
+ txd->txd5 = 0;
+ txd->txd6 = 0;
+ txd->txd7 = 0;
+@@ -1204,7 +1204,7 @@ static void mtk_tx_set_dma_desc(struct n
+ struct mtk_mac *mac = netdev_priv(dev);
+ struct mtk_eth *eth = mac->hw;
+
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(eth))
+ mtk_tx_set_dma_desc_v2(dev, txd, info);
+ else
+ mtk_tx_set_dma_desc_v1(dev, txd, info);
+@@ -1511,7 +1511,7 @@ static void mtk_update_rx_cpu_idx(struct
+
+ static bool mtk_page_pool_enabled(struct mtk_eth *eth)
+ {
+- return MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2);
++ return eth->soc->version == 2;
+ }
+
+ static struct page_pool *mtk_create_page_pool(struct mtk_eth *eth,
+@@ -1853,7 +1853,7 @@ static int mtk_poll_rx(struct napi_struc
+ break;
+
+ /* find out which mac the packet come from. values start at 1 */
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(eth))
+ 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))
+@@ -1949,7 +1949,7 @@ static int mtk_poll_rx(struct napi_struc
+ skb->dev = netdev;
+ bytes += skb->len;
+
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
++ if (mtk_is_netsys_v2_or_greater(eth)) {
+ reason = FIELD_GET(MTK_RXD5_PPE_CPU_REASON, trxd.rxd5);
+ hash = trxd.rxd5 & MTK_RXD5_FOE_ENTRY;
+ if (hash != MTK_RXD5_FOE_ENTRY)
+@@ -1974,8 +1974,8 @@ static int mtk_poll_rx(struct napi_struc
+ /* When using VLAN untagging in combination with DSA, the
+ * hardware treats the MTK special tag as a VLAN and untags it.
+ */
+- if (!MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2) &&
+- (trxd.rxd2 & RX_DMA_VTAG) && netdev_uses_dsa(netdev)) {
++ if (mtk_is_netsys_v1(eth) && (trxd.rxd2 & RX_DMA_VTAG) &&
++ netdev_uses_dsa(netdev)) {
+ unsigned int port = RX_DMA_VPID(trxd.rxd3) & GENMASK(2, 0);
+
+ if (port < ARRAY_SIZE(eth->dsa_meta) &&
+@@ -2285,7 +2285,7 @@ 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)) {
++ if (mtk_is_netsys_v2_or_greater(eth)) {
+ txd->txd5 = 0;
+ txd->txd6 = 0;
+ txd->txd7 = 0;
+@@ -2338,14 +2338,14 @@ static int mtk_tx_alloc(struct mtk_eth *
+ FIELD_PREP(MTK_QTX_SCH_MIN_RATE_MAN, 1) |
+ FIELD_PREP(MTK_QTX_SCH_MIN_RATE_EXP, 4) |
+ MTK_QTX_SCH_LEAKY_BUCKET_SIZE;
+- if (!MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v1(eth))
+ val |= MTK_QTX_SCH_LEAKY_BUCKET_EN;
+ mtk_w32(eth, val, soc->reg_map->qdma.qtx_sch + ofs);
+ ofs += MTK_QTX_OFFSET;
+ }
+ val = MTK_QDMA_TX_SCH_MAX_WFQ | (MTK_QDMA_TX_SCH_MAX_WFQ << 16);
+ mtk_w32(eth, val, soc->reg_map->qdma.tx_sch_rate);
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(eth))
+ mtk_w32(eth, val, soc->reg_map->qdma.tx_sch_rate + 4);
+ } else {
+ mtk_w32(eth, ring->phys_pdma, MT7628_TX_BASE_PTR0);
+@@ -2474,7 +2474,7 @@ static int mtk_rx_alloc(struct mtk_eth *
+
+ rxd->rxd3 = 0;
+ rxd->rxd4 = 0;
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
++ if (mtk_is_netsys_v2_or_greater(eth)) {
+ rxd->rxd5 = 0;
+ rxd->rxd6 = 0;
+ rxd->rxd7 = 0;
+@@ -3025,7 +3025,7 @@ static int mtk_start_dma(struct mtk_eth
+ 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))
++ if (mtk_is_netsys_v2_or_greater(eth))
+ val |= MTK_MUTLI_CNT | MTK_RESV_BUF |
+ MTK_WCOMP_EN | MTK_DMAD_WR_WDONE |
+ MTK_CHK_DDONE_EN | MTK_LEAKY_BUCKET_EN;
+@@ -3167,7 +3167,7 @@ static int mtk_open(struct net_device *d
+ phylink_start(mac->phylink);
+ netif_tx_start_all_queues(dev);
+
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(eth))
+ return 0;
+
+ if (mtk_uses_dsa(dev) && !eth->prog) {
+@@ -3432,7 +3432,7 @@ static void mtk_hw_reset(struct mtk_eth
+ {
+ u32 val;
+
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
++ if (mtk_is_netsys_v2_or_greater(eth)) {
+ regmap_write(eth->ethsys, ETHSYS_FE_RST_CHK_IDLE_EN, 0);
+ val = RSTCTRL_PPE0_V2;
+ } else {
+@@ -3444,7 +3444,7 @@ static void mtk_hw_reset(struct mtk_eth
+
+ ethsys_reset(eth, RSTCTRL_ETH | RSTCTRL_FE | val);
+
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(eth))
+ regmap_write(eth->ethsys, ETHSYS_FE_RST_CHK_IDLE_EN,
+ 0x3ffffff);
+ }
+@@ -3470,7 +3470,7 @@ static void mtk_hw_warm_reset(struct mtk
+ return;
+ }
+
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(eth))
+ rst_mask = RSTCTRL_ETH | RSTCTRL_PPE0_V2;
+ else
+ rst_mask = RSTCTRL_ETH | RSTCTRL_PPE0;
+@@ -3640,7 +3640,7 @@ static int mtk_hw_init(struct mtk_eth *e
+ else
+ mtk_hw_reset(eth);
+
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
++ if (mtk_is_netsys_v2_or_greater(eth)) {
+ /* Set FE to PDMAv2 if necessary */
+ val = mtk_r32(eth, MTK_FE_GLO_MISC);
+ mtk_w32(eth, val | BIT(4), MTK_FE_GLO_MISC);
+@@ -3677,7 +3677,7 @@ static int mtk_hw_init(struct mtk_eth *e
+ */
+ val = mtk_r32(eth, MTK_CDMQ_IG_CTRL);
+ mtk_w32(eth, val | MTK_CDMQ_STAG_EN, MTK_CDMQ_IG_CTRL);
+- if (!MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
++ if (mtk_is_netsys_v1(eth)) {
+ val = mtk_r32(eth, MTK_CDMP_IG_CTRL);
+ mtk_w32(eth, val | MTK_CDMP_STAG_EN, MTK_CDMP_IG_CTRL);
+
+@@ -3699,7 +3699,7 @@ static int mtk_hw_init(struct mtk_eth *e
+ 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)) {
++ if (mtk_is_netsys_v2_or_greater(eth)) {
+ /* PSE should not drop port8 and port9 packets from WDMA Tx */
+ mtk_w32(eth, 0x00000300, PSE_DROP_CFG);
+
+@@ -4488,7 +4488,7 @@ static int mtk_probe(struct platform_dev
+ }
+ }
+
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
++ if (mtk_is_netsys_v2_or_greater(eth)) {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ err = -EINVAL;
+@@ -4596,9 +4596,8 @@ static int mtk_probe(struct platform_dev
+ }
+
+ if (eth->soc->offload_version) {
+- u32 num_ppe;
++ u32 num_ppe = mtk_is_netsys_v2_or_greater(eth) ? 2 : 1;
+
+- num_ppe = MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2) ? 2 : 1;
+ num_ppe = min_t(u32, ARRAY_SIZE(eth->ppe), num_ppe);
+ for (i = 0; i < num_ppe; i++) {
+ u32 ppe_addr = eth->soc->reg_map->ppe_base + i * 0x400;
+@@ -4690,6 +4689,7 @@ static const struct mtk_soc_data mt2701_
+ .hw_features = MTK_HW_FEATURES,
+ .required_clks = MT7623_CLKS_BITMAP,
+ .required_pctl = true,
++ .version = 1,
+ .txrx = {
+ .txd_size = sizeof(struct mtk_tx_dma),
+ .rxd_size = sizeof(struct mtk_rx_dma),
+@@ -4706,6 +4706,7 @@ static const struct mtk_soc_data mt7621_
+ .hw_features = MTK_HW_FEATURES,
+ .required_clks = MT7621_CLKS_BITMAP,
+ .required_pctl = false,
++ .version = 1,
+ .offload_version = 1,
+ .hash_offset = 2,
+ .foe_entry_size = MTK_FOE_ENTRY_V1_SIZE,
+@@ -4726,6 +4727,7 @@ static const struct mtk_soc_data mt7622_
+ .hw_features = MTK_HW_FEATURES,
+ .required_clks = MT7622_CLKS_BITMAP,
+ .required_pctl = false,
++ .version = 1,
+ .offload_version = 2,
+ .hash_offset = 2,
+ .has_accounting = true,
+@@ -4746,6 +4748,7 @@ static const struct mtk_soc_data mt7623_
+ .hw_features = MTK_HW_FEATURES,
+ .required_clks = MT7623_CLKS_BITMAP,
+ .required_pctl = true,
++ .version = 1,
+ .offload_version = 1,
+ .hash_offset = 2,
+ .foe_entry_size = MTK_FOE_ENTRY_V1_SIZE,
+@@ -4768,6 +4771,7 @@ static const struct mtk_soc_data mt7629_
+ .required_clks = MT7629_CLKS_BITMAP,
+ .required_pctl = false,
+ .has_accounting = true,
++ .version = 1,
+ .txrx = {
+ .txd_size = sizeof(struct mtk_tx_dma),
+ .rxd_size = sizeof(struct mtk_rx_dma),
+@@ -4785,6 +4789,7 @@ static const struct mtk_soc_data mt7981_
+ .hw_features = MTK_HW_FEATURES,
+ .required_clks = MT7981_CLKS_BITMAP,
+ .required_pctl = false,
++ .version = 2,
+ .offload_version = 2,
+ .hash_offset = 4,
+ .has_accounting = true,
+@@ -4806,6 +4811,7 @@ static const struct mtk_soc_data mt7986_
+ .hw_features = MTK_HW_FEATURES,
+ .required_clks = MT7986_CLKS_BITMAP,
+ .required_pctl = false,
++ .version = 2,
+ .offload_version = 2,
+ .hash_offset = 4,
+ .has_accounting = true,
+@@ -4826,6 +4832,7 @@ static const struct mtk_soc_data rt5350_
+ .hw_features = MTK_HW_FEATURES_MT7628,
+ .required_clks = MT7628_CLKS_BITMAP,
+ .required_pctl = false,
++ .version = 1,
+ .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
+@@ -820,7 +820,6 @@ 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,
+ MTK_U3_COPHY_V2_BIT,
+@@ -855,7 +854,6 @@ 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_U3_COPHY_V2 BIT(MTK_U3_COPHY_V2_BIT)
+@@ -934,11 +932,11 @@ enum mkt_eth_capabilities {
+ #define MT7981_CAPS (MTK_GMAC1_SGMII | MTK_GMAC2_SGMII | MTK_GMAC2_GEPHY | \
+ MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA | \
+ MTK_MUX_U3_GMAC2_TO_QPHY | MTK_U3_COPHY_V2 | \
+- MTK_NETSYS_V2 | MTK_RSTCTRL_PPE1)
++ MTK_RSTCTRL_PPE1)
+
+ #define MT7986_CAPS (MTK_GMAC1_SGMII | MTK_GMAC2_SGMII | \
+ MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA | \
+- MTK_NETSYS_V2 | MTK_RSTCTRL_PPE1)
++ MTK_RSTCTRL_PPE1)
+
+ struct mtk_tx_dma_desc_info {
+ dma_addr_t addr;
+@@ -1009,6 +1007,7 @@ struct mtk_reg_map {
+ * @required_pctl A bool value to show whether the SoC requires
+ * the extra setup for those pins used by GMAC.
+ * @hash_offset Flow table hash offset.
++ * @version SoC version.
+ * @foe_entry_size Foe table entry size.
+ * @has_accounting Bool indicating support for accounting of
+ * offloaded flows.
+@@ -1027,6 +1026,7 @@ struct mtk_soc_data {
+ bool required_pctl;
+ u8 offload_version;
+ u8 hash_offset;
++ u8 version;
+ u16 foe_entry_size;
+ netdev_features_t hw_features;
+ bool has_accounting;
+@@ -1183,6 +1183,16 @@ struct mtk_mac {
+ /* the struct describing the SoC. these are declared in the soc_xyz.c files */
+ extern const struct of_device_id of_mtk_match[];
+
++static inline bool mtk_is_netsys_v1(struct mtk_eth *eth)
++{
++ return eth->soc->version == 1;
++}
++
++static inline bool mtk_is_netsys_v2_or_greater(struct mtk_eth *eth)
++{
++ return eth->soc->version > 1;
++}
++
+ static inline struct mtk_foe_entry *
+ mtk_foe_get_entry(struct mtk_ppe *ppe, u16 hash)
+ {
+@@ -1193,7 +1203,7 @@ mtk_foe_get_entry(struct mtk_ppe *ppe, u
+
+ static inline u32 mtk_get_ib1_ts_mask(struct mtk_eth *eth)
+ {
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(eth))
+ return MTK_FOE_IB1_BIND_TIMESTAMP_V2;
+
+ return MTK_FOE_IB1_BIND_TIMESTAMP;
+@@ -1201,7 +1211,7 @@ static inline u32 mtk_get_ib1_ts_mask(st
+
+ static inline u32 mtk_get_ib1_ppoe_mask(struct mtk_eth *eth)
+ {
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(eth))
+ return MTK_FOE_IB1_BIND_PPPOE_V2;
+
+ return MTK_FOE_IB1_BIND_PPPOE;
+@@ -1209,7 +1219,7 @@ static inline u32 mtk_get_ib1_ppoe_mask(
+
+ static inline u32 mtk_get_ib1_vlan_tag_mask(struct mtk_eth *eth)
+ {
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(eth))
+ return MTK_FOE_IB1_BIND_VLAN_TAG_V2;
+
+ return MTK_FOE_IB1_BIND_VLAN_TAG;
+@@ -1217,7 +1227,7 @@ static inline u32 mtk_get_ib1_vlan_tag_m
+
+ static inline u32 mtk_get_ib1_vlan_layer_mask(struct mtk_eth *eth)
+ {
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(eth))
+ return MTK_FOE_IB1_BIND_VLAN_LAYER_V2;
+
+ return MTK_FOE_IB1_BIND_VLAN_LAYER;
+@@ -1225,7 +1235,7 @@ static inline u32 mtk_get_ib1_vlan_layer
+
+ static inline u32 mtk_prep_ib1_vlan_layer(struct mtk_eth *eth, u32 val)
+ {
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(eth))
+ return FIELD_PREP(MTK_FOE_IB1_BIND_VLAN_LAYER_V2, val);
+
+ return FIELD_PREP(MTK_FOE_IB1_BIND_VLAN_LAYER, val);
+@@ -1233,7 +1243,7 @@ static inline u32 mtk_prep_ib1_vlan_laye
+
+ static inline u32 mtk_get_ib1_vlan_layer(struct mtk_eth *eth, u32 val)
+ {
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(eth))
+ return FIELD_GET(MTK_FOE_IB1_BIND_VLAN_LAYER_V2, val);
+
+ return FIELD_GET(MTK_FOE_IB1_BIND_VLAN_LAYER, val);
+@@ -1241,7 +1251,7 @@ static inline u32 mtk_get_ib1_vlan_layer
+
+ static inline u32 mtk_get_ib1_pkt_type_mask(struct mtk_eth *eth)
+ {
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(eth))
+ return MTK_FOE_IB1_PACKET_TYPE_V2;
+
+ return MTK_FOE_IB1_PACKET_TYPE;
+@@ -1249,7 +1259,7 @@ static inline u32 mtk_get_ib1_pkt_type_m
+
+ static inline u32 mtk_get_ib1_pkt_type(struct mtk_eth *eth, u32 val)
+ {
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(eth))
+ return FIELD_GET(MTK_FOE_IB1_PACKET_TYPE_V2, val);
+
+ return FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, val);
+@@ -1257,7 +1267,7 @@ static inline u32 mtk_get_ib1_pkt_type(s
+
+ static inline u32 mtk_get_ib2_multicast_mask(struct mtk_eth *eth)
+ {
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(eth))
+ return MTK_FOE_IB2_MULTICAST_V2;
+
+ return MTK_FOE_IB2_MULTICAST;
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
+@@ -207,7 +207,7 @@ int mtk_foe_entry_prepare(struct mtk_eth
+
+ memset(entry, 0, sizeof(*entry));
+
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
++ if (mtk_is_netsys_v2_or_greater(eth)) {
+ val = FIELD_PREP(MTK_FOE_IB1_STATE, MTK_FOE_STATE_BIND) |
+ FIELD_PREP(MTK_FOE_IB1_PACKET_TYPE_V2, type) |
+ FIELD_PREP(MTK_FOE_IB1_UDP, l4proto == IPPROTO_UDP) |
+@@ -271,7 +271,7 @@ int mtk_foe_entry_set_pse_port(struct mt
+ u32 *ib2 = mtk_foe_entry_ib2(eth, entry);
+ u32 val = *ib2;
+
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
++ if (mtk_is_netsys_v2_or_greater(eth)) {
+ val &= ~MTK_FOE_IB2_DEST_PORT_V2;
+ val |= FIELD_PREP(MTK_FOE_IB2_DEST_PORT_V2, port);
+ } else {
+@@ -422,7 +422,7 @@ int mtk_foe_entry_set_wdma(struct mtk_et
+ struct mtk_foe_mac_info *l2 = mtk_foe_entry_l2(eth, entry);
+ u32 *ib2 = mtk_foe_entry_ib2(eth, entry);
+
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
++ if (mtk_is_netsys_v2_or_greater(eth)) {
+ *ib2 &= ~MTK_FOE_IB2_PORT_MG_V2;
+ *ib2 |= FIELD_PREP(MTK_FOE_IB2_RX_IDX, txq) |
+ MTK_FOE_IB2_WDMA_WINFO_V2;
+@@ -446,7 +446,7 @@ int mtk_foe_entry_set_queue(struct mtk_e
+ {
+ u32 *ib2 = mtk_foe_entry_ib2(eth, entry);
+
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
++ if (mtk_is_netsys_v2_or_greater(eth)) {
+ *ib2 &= ~MTK_FOE_IB2_QID_V2;
+ *ib2 |= FIELD_PREP(MTK_FOE_IB2_QID_V2, queue);
+ *ib2 |= MTK_FOE_IB2_PSE_QOS_V2;
+@@ -601,7 +601,7 @@ __mtk_foe_entry_commit(struct mtk_ppe *p
+ struct mtk_foe_entry *hwe;
+ u32 val;
+
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
++ if (mtk_is_netsys_v2_or_greater(eth)) {
+ entry->ib1 &= ~MTK_FOE_IB1_BIND_TIMESTAMP_V2;
+ entry->ib1 |= FIELD_PREP(MTK_FOE_IB1_BIND_TIMESTAMP_V2,
+ timestamp);
+@@ -617,7 +617,7 @@ __mtk_foe_entry_commit(struct mtk_ppe *p
+ hwe->ib1 = entry->ib1;
+
+ if (ppe->accounting) {
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(eth))
+ val = MTK_FOE_IB2_MIB_CNT_V2;
+ else
+ val = MTK_FOE_IB2_MIB_CNT;
+@@ -965,7 +965,7 @@ void mtk_ppe_start(struct mtk_ppe *ppe)
+ MTK_PPE_SCAN_MODE_KEEPALIVE_AGE) |
+ FIELD_PREP(MTK_PPE_TB_CFG_ENTRY_NUM,
+ MTK_PPE_ENTRIES_SHIFT);
+- if (MTK_HAS_CAPS(ppe->eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(ppe->eth))
+ val |= MTK_PPE_TB_CFG_INFO_SEL;
+ ppe_w32(ppe, MTK_PPE_TB_CFG, val);
+
+@@ -981,7 +981,7 @@ void mtk_ppe_start(struct mtk_ppe *ppe)
+ MTK_PPE_FLOW_CFG_IP4_NAPT |
+ MTK_PPE_FLOW_CFG_IP4_DSLITE |
+ MTK_PPE_FLOW_CFG_IP4_NAT_FRAG;
+- if (MTK_HAS_CAPS(ppe->eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(ppe->eth))
+ val |= MTK_PPE_MD_TOAP_BYP_CRSN0 |
+ MTK_PPE_MD_TOAP_BYP_CRSN1 |
+ MTK_PPE_MD_TOAP_BYP_CRSN2 |
+@@ -1023,7 +1023,7 @@ void mtk_ppe_start(struct mtk_ppe *ppe)
+
+ ppe_w32(ppe, MTK_PPE_DEFAULT_CPU_PORT, 0);
+
+- if (MTK_HAS_CAPS(ppe->eth->soc->caps, MTK_NETSYS_V2)) {
++ if (mtk_is_netsys_v2_or_greater(ppe->eth)) {
+ ppe_w32(ppe, MTK_PPE_DEFAULT_CPU_PORT1, 0xcb777);
+ ppe_w32(ppe, MTK_PPE_SBW_CTRL, 0x7f);
+ }
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+@@ -193,7 +193,7 @@ mtk_flow_set_output_device(struct mtk_et
+ if (mtk_flow_get_wdma_info(dev, dest_mac, &info) == 0) {
+ mtk_foe_entry_set_wdma(eth, foe, info.wdma_idx, info.queue,
+ info.bss, info.wcid);
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2)) {
++ if (mtk_is_netsys_v2_or_greater(eth)) {
+ switch (info.wdma_idx) {
+ case 0:
+ pse_port = 8;
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -1084,7 +1084,7 @@ mtk_wed_rx_reset(struct mtk_wed_device *
+ } else {
+ struct mtk_eth *eth = dev->hw->eth;
+
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2))
++ if (mtk_is_netsys_v2_or_greater(eth))
+ wed_set(dev, MTK_WED_RESET_IDX,
+ MTK_WED_RESET_IDX_RX_V2);
+ else
+@@ -1806,7 +1806,7 @@ void mtk_wed_add_hw(struct device_node *
+ hw->wdma = wdma;
+ hw->index = index;
+ hw->irq = irq;
+- hw->version = MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V2) ? 2 : 1;
++ hw->version = mtk_is_netsys_v1(eth) ? 1 : 2;
+
+ if (hw->version == 1) {
+ hw->mirror = syscon_regmap_lookup_by_phandle(eth_np,
diff --git a/target/linux/generic/backport-6.6/750-v6.5-06-net-ethernet-mtk_eth_soc-increase-MAX_DEVS-to-3.patch b/target/linux/generic/backport-6.6/750-v6.5-06-net-ethernet-mtk_eth_soc-increase-MAX_DEVS-to-3.patch
new file mode 100644
index 0000000000..7b945bb869
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-06-net-ethernet-mtk_eth_soc-increase-MAX_DEVS-to-3.patch
@@ -0,0 +1,29 @@
+From f8fb8dbd158c585be7574faf92db7d614b6722ff Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Tue, 25 Jul 2023 01:52:27 +0100
+Subject: [PATCH 100/250] net: ethernet: mtk_eth_soc: increase MAX_DEVS to 3
+
+This is a preliminary patch to add MT7988 SoC support since it runs 3
+macs instead of 2.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/3563e5fab367e7d79a7f1296fabaa5c20f202d7a.1690246066.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -1043,8 +1043,8 @@ struct mtk_soc_data {
+
+ #define MTK_DMA_MONITOR_TIMEOUT msecs_to_jiffies(1000)
+
+-/* currently no SoC has more than 2 macs */
+-#define MTK_MAX_DEVS 2
++/* currently no SoC has more than 3 macs */
++#define MTK_MAX_DEVS 3
+
+ /* struct mtk_eth - This is the main datasructure for holding the state
+ * of the driver
diff --git a/target/linux/generic/backport-6.6/750-v6.5-07-net-ethernet-mtk_eth_soc-rely-on-MTK_MAX_DEVS-and-re.patch b/target/linux/generic/backport-6.6/750-v6.5-07-net-ethernet-mtk_eth_soc-rely-on-MTK_MAX_DEVS-and-re.patch
new file mode 100644
index 0000000000..8071658313
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-07-net-ethernet-mtk_eth_soc-rely-on-MTK_MAX_DEVS-and-re.patch
@@ -0,0 +1,186 @@
+From 856be974290f28d7943be2ac5a382c4139486196 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Tue, 25 Jul 2023 01:52:44 +0100
+Subject: [PATCH 101/250] net: ethernet: mtk_eth_soc: rely on MTK_MAX_DEVS and
+ remove MTK_MAC_COUNT
+
+Get rid of MTK_MAC_COUNT since it is a duplicated of MTK_MAX_DEVS.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/1856f4266f2fc80677807b1bad867659e7b00c65.1690246066.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 49 ++++++++++++---------
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 1 -
+ 2 files changed, 27 insertions(+), 23 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -881,7 +881,7 @@ static void mtk_stats_update(struct mtk_
+ {
+ int i;
+
+- for (i = 0; i < MTK_MAC_COUNT; i++) {
++ for (i = 0; i < MTK_MAX_DEVS; i++) {
+ if (!eth->mac[i] || !eth->mac[i]->hw_stats)
+ continue;
+ if (spin_trylock(&eth->mac[i]->hw_stats->stats_lock)) {
+@@ -1386,7 +1386,7 @@ static int mtk_queue_stopped(struct mtk_
+ {
+ int i;
+
+- for (i = 0; i < MTK_MAC_COUNT; i++) {
++ for (i = 0; i < MTK_MAX_DEVS; i++) {
+ if (!eth->netdev[i])
+ continue;
+ if (netif_queue_stopped(eth->netdev[i]))
+@@ -1400,7 +1400,7 @@ static void mtk_wake_queue(struct mtk_et
+ {
+ int i;
+
+- for (i = 0; i < MTK_MAC_COUNT; i++) {
++ for (i = 0; i < MTK_MAX_DEVS; i++) {
+ if (!eth->netdev[i])
+ continue;
+ netif_tx_wake_all_queues(eth->netdev[i]);
+@@ -1859,7 +1859,7 @@ static int mtk_poll_rx(struct napi_struc
+ !(trxd.rxd4 & RX_DMA_SPECIAL_TAG))
+ mac = RX_DMA_GET_SPORT(trxd.rxd4) - 1;
+
+- if (unlikely(mac < 0 || mac >= MTK_MAC_COUNT ||
++ if (unlikely(mac < 0 || mac >= MTK_MAX_DEVS ||
+ !eth->netdev[mac]))
+ goto release_desc;
+
+@@ -2899,7 +2899,7 @@ static void mtk_dma_free(struct mtk_eth
+ const struct mtk_soc_data *soc = eth->soc;
+ int i;
+
+- for (i = 0; i < MTK_MAC_COUNT; i++)
++ for (i = 0; i < MTK_MAX_DEVS; i++)
+ if (eth->netdev[i])
+ netdev_reset_queue(eth->netdev[i]);
+ if (eth->scratch_ring) {
+@@ -3053,8 +3053,13 @@ static void mtk_gdm_config(struct mtk_et
+ if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628))
+ return;
+
+- for (i = 0; i < MTK_MAC_COUNT; i++) {
+- u32 val = mtk_r32(eth, MTK_GDMA_FWD_CFG(i));
++ for (i = 0; i < MTK_MAX_DEVS; i++) {
++ u32 val;
++
++ if (!eth->netdev[i])
++ continue;
++
++ val = mtk_r32(eth, MTK_GDMA_FWD_CFG(i));
+
+ /* default setup the forward port to send frame to PDMA */
+ val &= ~0xffff;
+@@ -3064,7 +3069,7 @@ static void mtk_gdm_config(struct mtk_et
+
+ val |= config;
+
+- if (eth->netdev[i] && netdev_uses_dsa(eth->netdev[i]))
++ if (netdev_uses_dsa(eth->netdev[i]))
+ val |= MTK_GDMA_SPECIAL_TAG;
+
+ mtk_w32(eth, val, MTK_GDMA_FWD_CFG(i));
+@@ -3661,15 +3666,15 @@ static int mtk_hw_init(struct mtk_eth *e
+ * up with the more appropriate value when mtk_mac_config call is being
+ * invoked.
+ */
+- for (i = 0; i < MTK_MAC_COUNT; i++) {
++ for (i = 0; i < MTK_MAX_DEVS; i++) {
+ struct net_device *dev = eth->netdev[i];
+
+- mtk_w32(eth, MAC_MCR_FORCE_LINK_DOWN, MTK_MAC_MCR(i));
+- if (dev) {
+- struct mtk_mac *mac = netdev_priv(dev);
++ if (!dev)
++ continue;
+
+- mtk_set_mcr_max_rx(mac, dev->mtu + MTK_RX_ETH_HLEN);
+- }
++ mtk_w32(eth, MAC_MCR_FORCE_LINK_DOWN, MTK_MAC_MCR(i));
++ mtk_set_mcr_max_rx(netdev_priv(dev),
++ dev->mtu + MTK_RX_ETH_HLEN);
+ }
+
+ /* Indicates CDM to parse the MTK special tag from CPU
+@@ -3849,7 +3854,7 @@ static void mtk_pending_work(struct work
+ mtk_prepare_for_reset(eth);
+
+ /* stop all devices to make sure that dma is properly shut down */
+- for (i = 0; i < MTK_MAC_COUNT; i++) {
++ for (i = 0; i < MTK_MAX_DEVS; i++) {
+ if (!eth->netdev[i] || !netif_running(eth->netdev[i]))
+ continue;
+
+@@ -3865,8 +3870,8 @@ static void mtk_pending_work(struct work
+ mtk_hw_init(eth, true);
+
+ /* restart DMA and enable IRQs */
+- for (i = 0; i < MTK_MAC_COUNT; i++) {
+- if (!test_bit(i, &restart))
++ for (i = 0; i < MTK_MAX_DEVS; i++) {
++ if (!eth->netdev[i] || !test_bit(i, &restart))
+ continue;
+
+ if (mtk_open(eth->netdev[i])) {
+@@ -3893,7 +3898,7 @@ static int mtk_free_dev(struct mtk_eth *
+ {
+ int i;
+
+- for (i = 0; i < MTK_MAC_COUNT; i++) {
++ for (i = 0; i < MTK_MAX_DEVS; i++) {
+ if (!eth->netdev[i])
+ continue;
+ free_netdev(eth->netdev[i]);
+@@ -3912,7 +3917,7 @@ static int mtk_unreg_dev(struct mtk_eth
+ {
+ int i;
+
+- for (i = 0; i < MTK_MAC_COUNT; i++) {
++ for (i = 0; i < MTK_MAX_DEVS; i++) {
+ struct mtk_mac *mac;
+ if (!eth->netdev[i])
+ continue;
+@@ -4213,7 +4218,7 @@ static int mtk_add_mac(struct mtk_eth *e
+ }
+
+ id = be32_to_cpup(_id);
+- if (id >= MTK_MAC_COUNT) {
++ if (id >= MTK_MAX_DEVS) {
+ dev_err(eth->dev, "%d is not a valid mac id\n", id);
+ return -EINVAL;
+ }
+@@ -4358,7 +4363,7 @@ void mtk_eth_set_dma_device(struct mtk_e
+
+ rtnl_lock();
+
+- for (i = 0; i < MTK_MAC_COUNT; i++) {
++ for (i = 0; i < MTK_MAX_DEVS; i++) {
+ dev = eth->netdev[i];
+
+ if (!dev || !(dev->flags & IFF_UP))
+@@ -4664,7 +4669,7 @@ static int mtk_remove(struct platform_de
+ int i;
+
+ /* stop all devices to make sure that dma is properly shut down */
+- for (i = 0; i < MTK_MAC_COUNT; i++) {
++ for (i = 0; i < MTK_MAX_DEVS; i++) {
+ if (!eth->netdev[i])
+ continue;
+ mtk_stop(eth->netdev[i]);
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -33,7 +33,6 @@
+ #define MTK_TX_DMA_BUF_LEN_V2 0xffff
+ #define MTK_QDMA_RING_SIZE 2048
+ #define MTK_DMA_SIZE 512
+-#define MTK_MAC_COUNT 2
+ #define MTK_RX_ETH_HLEN (VLAN_ETH_HLEN + ETH_FCS_LEN)
+ #define MTK_RX_HLEN (NET_SKB_PAD + MTK_RX_ETH_HLEN + NET_IP_ALIGN)
+ #define MTK_DMA_DUMMY_DESC 0xffffffff
diff --git a/target/linux/generic/backport-6.6/750-v6.5-08-net-ethernet-mtk_eth_soc-add-NETSYS_V3-version-suppo.patch b/target/linux/generic/backport-6.6/750-v6.5-08-net-ethernet-mtk_eth_soc-add-NETSYS_V3-version-suppo.patch
new file mode 100644
index 0000000000..1a9b31f526
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-08-net-ethernet-mtk_eth_soc-add-NETSYS_V3-version-suppo.patch
@@ -0,0 +1,307 @@
+From a41d535855976838d246c079143c948dcf0f7931 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Tue, 25 Jul 2023 01:52:59 +0100
+Subject: [PATCH 102/250] net: ethernet: mtk_eth_soc: add NETSYS_V3 version
+ support
+
+Introduce NETSYS_V3 chipset version support.
+This is a preliminary patch to introduce support for MT7988 SoC.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/0db2260910755d76fa48e303b9f9bdf4e5a82340.1690246066.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 105 ++++++++++++++------
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 48 +++++++--
+ 2 files changed, 116 insertions(+), 37 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -861,17 +861,32 @@ void mtk_stats_update_mac(struct mtk_mac
+ mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x20 + offs);
+ hw_stats->rx_flow_control_packets +=
+ mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x24 + offs);
+- hw_stats->tx_skip +=
+- mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x28 + offs);
+- hw_stats->tx_collisions +=
+- mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x2c + offs);
+- hw_stats->tx_bytes +=
+- 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, reg_map->gdm1_cnt + 0x38 + offs);
++
++ if (mtk_is_netsys_v3_or_greater(eth)) {
++ hw_stats->tx_skip +=
++ mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x50 + offs);
++ hw_stats->tx_collisions +=
++ mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x54 + offs);
++ hw_stats->tx_bytes +=
++ mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x40 + offs);
++ stats = mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x44 + offs);
++ if (stats)
++ hw_stats->tx_bytes += (stats << 32);
++ hw_stats->tx_packets +=
++ mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x48 + offs);
++ } else {
++ hw_stats->tx_skip +=
++ mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x28 + offs);
++ hw_stats->tx_collisions +=
++ mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x2c + offs);
++ hw_stats->tx_bytes +=
++ 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, reg_map->gdm1_cnt + 0x38 + offs);
++ }
+ }
+
+ u64_stats_update_end(&hw_stats->syncp);
+@@ -1175,7 +1190,10 @@ static void mtk_tx_set_dma_desc_v2(struc
+ data |= TX_DMA_LS0;
+ WRITE_ONCE(desc->txd3, data);
+
+- data = (mac->id + 1) << TX_DMA_FPORT_SHIFT_V2; /* forward port */
++ if (mac->id == MTK_GMAC3_ID)
++ data = PSE_GDM3_PORT;
++ else
++ 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);
+
+@@ -1186,6 +1204,8 @@ static void mtk_tx_set_dma_desc_v2(struc
+ /* tx checksum offload */
+ if (info->csum)
+ data |= TX_DMA_CHKSUM_V2;
++ if (mtk_is_netsys_v3_or_greater(eth) && netdev_uses_dsa(dev))
++ data |= TX_DMA_SPTAG_V3;
+ }
+ WRITE_ONCE(desc->txd5, data);
+
+@@ -1251,8 +1271,7 @@ static int mtk_tx_map(struct sk_buff *sk
+ 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;
++ itx_buf->mac_id = mac->id;
+ setup_tx_buf(eth, itx_buf, itxd_pdma, txd_info.addr, txd_info.size,
+ k++);
+
+@@ -1300,8 +1319,7 @@ static int mtk_tx_map(struct sk_buff *sk
+ memset(tx_buf, 0, sizeof(*tx_buf));
+ tx_buf->data = (void *)MTK_DMA_DUMMY_DESC;
+ tx_buf->flags |= MTK_TX_FLAGS_PAGE0;
+- tx_buf->flags |= (!mac->id) ? MTK_TX_FLAGS_FPORT0 :
+- MTK_TX_FLAGS_FPORT1;
++ tx_buf->mac_id = mac->id;
+
+ setup_tx_buf(eth, tx_buf, txd_pdma, txd_info.addr,
+ txd_info.size, k++);
+@@ -1603,7 +1621,7 @@ static int mtk_xdp_frame_map(struct mtk_
+ }
+ mtk_tx_set_dma_desc(dev, txd, txd_info);
+
+- tx_buf->flags |= !mac->id ? MTK_TX_FLAGS_FPORT0 : MTK_TX_FLAGS_FPORT1;
++ tx_buf->mac_id = mac->id;
+ tx_buf->type = dma_map ? MTK_TYPE_XDP_NDO : MTK_TYPE_XDP_TX;
+ tx_buf->data = (void *)MTK_DMA_DUMMY_DESC;
+
+@@ -1853,11 +1871,24 @@ static int mtk_poll_rx(struct napi_struc
+ break;
+
+ /* find out which mac the packet come from. values start at 1 */
+- if (mtk_is_netsys_v2_or_greater(eth))
+- 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))
++ if (mtk_is_netsys_v2_or_greater(eth)) {
++ u32 val = RX_DMA_GET_SPORT_V2(trxd.rxd5);
++
++ switch (val) {
++ case PSE_GDM1_PORT:
++ case PSE_GDM2_PORT:
++ mac = val - 1;
++ break;
++ case PSE_GDM3_PORT:
++ mac = MTK_GMAC3_ID;
++ break;
++ default:
++ break;
++ }
++ } 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_MAX_DEVS ||
+ !eth->netdev[mac]))
+@@ -2079,7 +2110,6 @@ static int mtk_poll_tx_qdma(struct mtk_e
+
+ while ((cpu != dma) && budget) {
+ u32 next_cpu = desc->txd2;
+- int mac = 0;
+
+ desc = mtk_qdma_phys_to_virt(ring, desc->txd2);
+ if ((desc->txd3 & TX_DMA_OWNER_CPU) == 0)
+@@ -2087,15 +2117,13 @@ static int mtk_poll_tx_qdma(struct mtk_e
+
+ tx_buf = mtk_desc_to_tx_buf(ring, desc,
+ eth->soc->txrx.txd_size);
+- if (tx_buf->flags & MTK_TX_FLAGS_FPORT1)
+- mac = 1;
+-
+ if (!tx_buf->data)
+ break;
+
+ if (tx_buf->data != (void *)MTK_DMA_DUMMY_DESC) {
+ if (tx_buf->type == MTK_TYPE_SKB)
+- mtk_poll_tx_done(eth, state, mac, tx_buf->data);
++ mtk_poll_tx_done(eth, state, tx_buf->mac_id,
++ tx_buf->data);
+
+ budget--;
+ }
+@@ -3704,7 +3732,24 @@ static int mtk_hw_init(struct mtk_eth *e
+ 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_is_netsys_v2_or_greater(eth)) {
++ if (mtk_is_netsys_v3_or_greater(eth)) {
++ /* PSE should not drop port1, port8 and port9 packets */
++ mtk_w32(eth, 0x00000302, PSE_DROP_CFG);
++
++ /* GDM and CDM Threshold */
++ mtk_w32(eth, 0x00000707, MTK_CDMW0_THRES);
++ mtk_w32(eth, 0x00000077, MTK_CDMW1_THRES);
++
++ /* Disable GDM1 RX CRC stripping */
++ mtk_m32(eth, MTK_GDMA_STRP_CRC, 0, MTK_GDMA_FWD_CFG(0));
++
++ /* PSE GDM3 MIB counter has incorrect hw default values,
++ * so the driver ought to read clear the values beforehand
++ * in case ethtool retrieve wrong mib values.
++ */
++ for (i = 0; i < 0x80; i += 0x4)
++ mtk_r32(eth, reg_map->gdm1_cnt + 0x100 + i);
++ } else if (!mtk_is_netsys_v1(eth)) {
+ /* PSE should not drop port8 and port9 packets from WDMA Tx */
+ mtk_w32(eth, 0x00000300, PSE_DROP_CFG);
+
+@@ -4266,7 +4311,11 @@ static int mtk_add_mac(struct mtk_eth *e
+ }
+ spin_lock_init(&mac->hw_stats->stats_lock);
+ u64_stats_init(&mac->hw_stats->syncp);
+- mac->hw_stats->reg_offset = id * MTK_STAT_OFFSET;
++
++ if (mtk_is_netsys_v3_or_greater(eth))
++ mac->hw_stats->reg_offset = id * 0x80;
++ else
++ mac->hw_stats->reg_offset = id * 0x40;
+
+ /* phylink create */
+ err = of_get_phy_mode(np, &phy_mode);
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -122,6 +122,7 @@
+ #define MTK_GDMA_ICS_EN BIT(22)
+ #define MTK_GDMA_TCS_EN BIT(21)
+ #define MTK_GDMA_UCS_EN BIT(20)
++#define MTK_GDMA_STRP_CRC BIT(16)
+ #define MTK_GDMA_TO_PDMA 0x0
+ #define MTK_GDMA_DROP_ALL 0x7777
+
+@@ -287,8 +288,6 @@
+ /* QDMA Interrupt grouping registers */
+ #define MTK_RLS_DONE_INT BIT(0)
+
+-#define MTK_STAT_OFFSET 0x40
+-
+ /* QDMA TX NUM */
+ #define QID_BITS_V2(x) (((x) & 0x3f) << 16)
+ #define MTK_QDMA_GMAC2_QID 8
+@@ -301,6 +300,8 @@
+ #define TX_DMA_CHKSUM_V2 (0x7 << 28)
+ #define TX_DMA_TSO_V2 BIT(31)
+
++#define TX_DMA_SPTAG_V3 BIT(27)
++
+ /* QDMA V2 descriptor txd4 */
+ #define TX_DMA_FPORT_SHIFT_V2 8
+ #define TX_DMA_FPORT_MASK_V2 0xf
+@@ -634,12 +635,6 @@ enum mtk_tx_flags {
+ */
+ MTK_TX_FLAGS_SINGLE0 = 0x01,
+ MTK_TX_FLAGS_PAGE0 = 0x02,
+-
+- /* MTK_TX_FLAGS_FPORTx allows tracking which port the transmitted
+- * SKB out instead of looking up through hardware TX descriptor.
+- */
+- MTK_TX_FLAGS_FPORT0 = 0x04,
+- MTK_TX_FLAGS_FPORT1 = 0x08,
+ };
+
+ /* This enum allows us to identify how the clock is defined on the array of the
+@@ -725,6 +720,35 @@ enum mtk_dev_state {
+ MTK_RESETTING
+ };
+
++/* PSE Port Definition */
++enum mtk_pse_port {
++ PSE_ADMA_PORT = 0,
++ PSE_GDM1_PORT,
++ PSE_GDM2_PORT,
++ PSE_PPE0_PORT,
++ PSE_PPE1_PORT,
++ PSE_QDMA_TX_PORT,
++ PSE_QDMA_RX_PORT,
++ PSE_DROP_PORT,
++ PSE_WDMA0_PORT,
++ PSE_WDMA1_PORT,
++ PSE_TDMA_PORT,
++ PSE_NONE_PORT,
++ PSE_PPE2_PORT,
++ PSE_WDMA2_PORT,
++ PSE_EIP197_PORT,
++ PSE_GDM3_PORT,
++ PSE_PORT_MAX
++};
++
++/* GMAC Identifier */
++enum mtk_gmac_id {
++ MTK_GMAC1_ID = 0,
++ MTK_GMAC2_ID,
++ MTK_GMAC3_ID,
++ MTK_GMAC_ID_MAX
++};
++
+ enum mtk_tx_buf_type {
+ MTK_TYPE_SKB,
+ MTK_TYPE_XDP_TX,
+@@ -743,7 +767,8 @@ struct mtk_tx_buf {
+ enum mtk_tx_buf_type type;
+ void *data;
+
+- u32 flags;
++ u16 mac_id;
++ u16 flags;
+ DEFINE_DMA_UNMAP_ADDR(dma_addr0);
+ DEFINE_DMA_UNMAP_LEN(dma_len0);
+ DEFINE_DMA_UNMAP_ADDR(dma_addr1);
+@@ -1192,6 +1217,11 @@ static inline bool mtk_is_netsys_v2_or_g
+ return eth->soc->version > 1;
+ }
+
++static inline bool mtk_is_netsys_v3_or_greater(struct mtk_eth *eth)
++{
++ return eth->soc->version > 2;
++}
++
+ static inline struct mtk_foe_entry *
+ mtk_foe_get_entry(struct mtk_ppe *ppe, u16 hash)
+ {
diff --git a/target/linux/generic/backport-6.6/750-v6.5-09-net-ethernet-mtk_eth_soc-convert-caps-in-mtk_soc_dat.patch b/target/linux/generic/backport-6.6/750-v6.5-09-net-ethernet-mtk_eth_soc-convert-caps-in-mtk_soc_dat.patch
new file mode 100644
index 0000000000..7a0b285f2c
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-09-net-ethernet-mtk_eth_soc-convert-caps-in-mtk_soc_dat.patch
@@ -0,0 +1,193 @@
+From db797ae0542220a98658229397da464c383c991c Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Tue, 25 Jul 2023 01:53:13 +0100
+Subject: [PATCH 103/250] net: ethernet: mtk_eth_soc: convert caps in
+ mtk_soc_data struct to u64
+
+This is a preliminary patch to introduce support for MT7988 SoC.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/9499ac3670b2fc5b444404b84e8a4a169beabbf2.1690246066.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_path.c | 22 ++++----
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 56 ++++++++++----------
+ 2 files changed, 39 insertions(+), 39 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_path.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_path.c
+@@ -15,10 +15,10 @@
+ struct mtk_eth_muxc {
+ const char *name;
+ int cap_bit;
+- int (*set_path)(struct mtk_eth *eth, int path);
++ int (*set_path)(struct mtk_eth *eth, u64 path);
+ };
+
+-static const char *mtk_eth_path_name(int path)
++static const char *mtk_eth_path_name(u64 path)
+ {
+ switch (path) {
+ case MTK_ETH_PATH_GMAC1_RGMII:
+@@ -40,7 +40,7 @@ static const char *mtk_eth_path_name(int
+ }
+ }
+
+-static int set_mux_gdm1_to_gmac1_esw(struct mtk_eth *eth, int path)
++static int set_mux_gdm1_to_gmac1_esw(struct mtk_eth *eth, u64 path)
+ {
+ bool updated = true;
+ u32 val, mask, set;
+@@ -71,7 +71,7 @@ static int set_mux_gdm1_to_gmac1_esw(str
+ return 0;
+ }
+
+-static int set_mux_gmac2_gmac0_to_gephy(struct mtk_eth *eth, int path)
++static int set_mux_gmac2_gmac0_to_gephy(struct mtk_eth *eth, u64 path)
+ {
+ unsigned int val = 0;
+ bool updated = true;
+@@ -94,7 +94,7 @@ static int set_mux_gmac2_gmac0_to_gephy(
+ return 0;
+ }
+
+-static int set_mux_u3_gmac2_to_qphy(struct mtk_eth *eth, int path)
++static int set_mux_u3_gmac2_to_qphy(struct mtk_eth *eth, u64 path)
+ {
+ unsigned int val = 0, mask = 0, reg = 0;
+ bool updated = true;
+@@ -125,7 +125,7 @@ static int set_mux_u3_gmac2_to_qphy(stru
+ return 0;
+ }
+
+-static int set_mux_gmac1_gmac2_to_sgmii_rgmii(struct mtk_eth *eth, int path)
++static int set_mux_gmac1_gmac2_to_sgmii_rgmii(struct mtk_eth *eth, u64 path)
+ {
+ unsigned int val = 0;
+ bool updated = true;
+@@ -163,7 +163,7 @@ static int set_mux_gmac1_gmac2_to_sgmii_
+ return 0;
+ }
+
+-static int set_mux_gmac12_to_gephy_sgmii(struct mtk_eth *eth, int path)
++static int set_mux_gmac12_to_gephy_sgmii(struct mtk_eth *eth, u64 path)
+ {
+ unsigned int val = 0;
+ bool updated = true;
+@@ -218,7 +218,7 @@ static const struct mtk_eth_muxc mtk_eth
+ },
+ };
+
+-static int mtk_eth_mux_setup(struct mtk_eth *eth, int path)
++static int mtk_eth_mux_setup(struct mtk_eth *eth, u64 path)
+ {
+ int i, err = 0;
+
+@@ -249,7 +249,7 @@ out:
+
+ int mtk_gmac_sgmii_path_setup(struct mtk_eth *eth, int mac_id)
+ {
+- int path;
++ u64 path;
+
+ path = (mac_id == 0) ? MTK_ETH_PATH_GMAC1_SGMII :
+ MTK_ETH_PATH_GMAC2_SGMII;
+@@ -260,7 +260,7 @@ int mtk_gmac_sgmii_path_setup(struct mtk
+
+ int mtk_gmac_gephy_path_setup(struct mtk_eth *eth, int mac_id)
+ {
+- int path = 0;
++ u64 path = 0;
+
+ if (mac_id == 1)
+ path = MTK_ETH_PATH_GMAC2_GEPHY;
+@@ -274,7 +274,7 @@ int mtk_gmac_gephy_path_setup(struct mtk
+
+ int mtk_gmac_rgmii_path_setup(struct mtk_eth *eth, int mac_id)
+ {
+- int path;
++ u64 path;
+
+ path = (mac_id == 0) ? MTK_ETH_PATH_GMAC1_RGMII :
+ MTK_ETH_PATH_GMAC2_RGMII;
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -866,41 +866,41 @@ enum mkt_eth_capabilities {
+ };
+
+ /* Supported hardware group on SoCs */
+-#define MTK_RGMII BIT(MTK_RGMII_BIT)
+-#define MTK_TRGMII BIT(MTK_TRGMII_BIT)
+-#define MTK_SGMII BIT(MTK_SGMII_BIT)
+-#define MTK_ESW BIT(MTK_ESW_BIT)
+-#define MTK_GEPHY BIT(MTK_GEPHY_BIT)
+-#define MTK_MUX BIT(MTK_MUX_BIT)
+-#define MTK_INFRA BIT(MTK_INFRA_BIT)
+-#define MTK_SHARED_SGMII BIT(MTK_SHARED_SGMII_BIT)
+-#define MTK_HWLRO BIT(MTK_HWLRO_BIT)
+-#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_SOC_MT7628 BIT(MTK_SOC_MT7628_BIT)
+-#define MTK_RSTCTRL_PPE1 BIT(MTK_RSTCTRL_PPE1_BIT)
+-#define MTK_U3_COPHY_V2 BIT(MTK_U3_COPHY_V2_BIT)
++#define MTK_RGMII BIT_ULL(MTK_RGMII_BIT)
++#define MTK_TRGMII BIT_ULL(MTK_TRGMII_BIT)
++#define MTK_SGMII BIT_ULL(MTK_SGMII_BIT)
++#define MTK_ESW BIT_ULL(MTK_ESW_BIT)
++#define MTK_GEPHY BIT_ULL(MTK_GEPHY_BIT)
++#define MTK_MUX BIT_ULL(MTK_MUX_BIT)
++#define MTK_INFRA BIT_ULL(MTK_INFRA_BIT)
++#define MTK_SHARED_SGMII BIT_ULL(MTK_SHARED_SGMII_BIT)
++#define MTK_HWLRO BIT_ULL(MTK_HWLRO_BIT)
++#define MTK_SHARED_INT BIT_ULL(MTK_SHARED_INT_BIT)
++#define MTK_TRGMII_MT7621_CLK BIT_ULL(MTK_TRGMII_MT7621_CLK_BIT)
++#define MTK_QDMA BIT_ULL(MTK_QDMA_BIT)
++#define MTK_SOC_MT7628 BIT_ULL(MTK_SOC_MT7628_BIT)
++#define MTK_RSTCTRL_PPE1 BIT_ULL(MTK_RSTCTRL_PPE1_BIT)
++#define MTK_U3_COPHY_V2 BIT_ULL(MTK_U3_COPHY_V2_BIT)
+
+ #define MTK_ETH_MUX_GDM1_TO_GMAC1_ESW \
+- BIT(MTK_ETH_MUX_GDM1_TO_GMAC1_ESW_BIT)
++ BIT_ULL(MTK_ETH_MUX_GDM1_TO_GMAC1_ESW_BIT)
+ #define MTK_ETH_MUX_GMAC2_GMAC0_TO_GEPHY \
+- BIT(MTK_ETH_MUX_GMAC2_GMAC0_TO_GEPHY_BIT)
++ BIT_ULL(MTK_ETH_MUX_GMAC2_GMAC0_TO_GEPHY_BIT)
+ #define MTK_ETH_MUX_U3_GMAC2_TO_QPHY \
+- BIT(MTK_ETH_MUX_U3_GMAC2_TO_QPHY_BIT)
++ BIT_ULL(MTK_ETH_MUX_U3_GMAC2_TO_QPHY_BIT)
+ #define MTK_ETH_MUX_GMAC1_GMAC2_TO_SGMII_RGMII \
+- BIT(MTK_ETH_MUX_GMAC1_GMAC2_TO_SGMII_RGMII_BIT)
++ BIT_ULL(MTK_ETH_MUX_GMAC1_GMAC2_TO_SGMII_RGMII_BIT)
+ #define MTK_ETH_MUX_GMAC12_TO_GEPHY_SGMII \
+- BIT(MTK_ETH_MUX_GMAC12_TO_GEPHY_SGMII_BIT)
++ BIT_ULL(MTK_ETH_MUX_GMAC12_TO_GEPHY_SGMII_BIT)
+
+ /* Supported path present on SoCs */
+-#define MTK_ETH_PATH_GMAC1_RGMII BIT(MTK_ETH_PATH_GMAC1_RGMII_BIT)
+-#define MTK_ETH_PATH_GMAC1_TRGMII BIT(MTK_ETH_PATH_GMAC1_TRGMII_BIT)
+-#define MTK_ETH_PATH_GMAC1_SGMII BIT(MTK_ETH_PATH_GMAC1_SGMII_BIT)
+-#define MTK_ETH_PATH_GMAC2_RGMII BIT(MTK_ETH_PATH_GMAC2_RGMII_BIT)
+-#define MTK_ETH_PATH_GMAC2_SGMII BIT(MTK_ETH_PATH_GMAC2_SGMII_BIT)
+-#define MTK_ETH_PATH_GMAC2_GEPHY BIT(MTK_ETH_PATH_GMAC2_GEPHY_BIT)
+-#define MTK_ETH_PATH_GDM1_ESW BIT(MTK_ETH_PATH_GDM1_ESW_BIT)
++#define MTK_ETH_PATH_GMAC1_RGMII BIT_ULL(MTK_ETH_PATH_GMAC1_RGMII_BIT)
++#define MTK_ETH_PATH_GMAC1_TRGMII BIT_ULL(MTK_ETH_PATH_GMAC1_TRGMII_BIT)
++#define MTK_ETH_PATH_GMAC1_SGMII BIT_ULL(MTK_ETH_PATH_GMAC1_SGMII_BIT)
++#define MTK_ETH_PATH_GMAC2_RGMII BIT_ULL(MTK_ETH_PATH_GMAC2_RGMII_BIT)
++#define MTK_ETH_PATH_GMAC2_SGMII BIT_ULL(MTK_ETH_PATH_GMAC2_SGMII_BIT)
++#define MTK_ETH_PATH_GMAC2_GEPHY BIT_ULL(MTK_ETH_PATH_GMAC2_GEPHY_BIT)
++#define MTK_ETH_PATH_GDM1_ESW BIT_ULL(MTK_ETH_PATH_GDM1_ESW_BIT)
+
+ #define MTK_GMAC1_RGMII (MTK_ETH_PATH_GMAC1_RGMII | MTK_RGMII)
+ #define MTK_GMAC1_TRGMII (MTK_ETH_PATH_GMAC1_TRGMII | MTK_TRGMII)
+@@ -1045,7 +1045,7 @@ struct mtk_reg_map {
+ struct mtk_soc_data {
+ const struct mtk_reg_map *reg_map;
+ u32 ana_rgc3;
+- u32 caps;
++ u64 caps;
+ u32 required_clks;
+ bool required_pctl;
+ u8 offload_version;
diff --git a/target/linux/generic/backport-6.6/750-v6.5-10-net-ethernet-mtk_eth_soc-convert-clock-bitmap-to-u64.patch b/target/linux/generic/backport-6.6/750-v6.5-10-net-ethernet-mtk_eth_soc-convert-clock-bitmap-to-u64.patch
new file mode 100644
index 0000000000..5947d385f2
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-10-net-ethernet-mtk_eth_soc-convert-clock-bitmap-to-u64.patch
@@ -0,0 +1,132 @@
+From a1c9f7d1d24e90294f6a6755b137fcf306851e93 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 25 Jul 2023 01:53:28 +0100
+Subject: [PATCH 104/250] net: ethernet: mtk_eth_soc: convert clock bitmap to
+ u64
+
+The to-be-added MT7988 SoC adds many new clocks which need to be
+controlled by the Ethernet driver, which will result in their total
+number exceeding 32.
+Prepare by converting clock bitmaps into 64-bit types.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/6960a39bb0078cf84d7642a9558e6a91c6cc9df3.1690246066.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 96 +++++++++++----------
+ 1 file changed, 49 insertions(+), 47 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -666,54 +666,56 @@ enum mtk_clks_map {
+ MTK_CLK_MAX
+ };
+
+-#define MT7623_CLKS_BITMAP (BIT(MTK_CLK_ETHIF) | BIT(MTK_CLK_ESW) | \
+- BIT(MTK_CLK_GP1) | BIT(MTK_CLK_GP2) | \
+- BIT(MTK_CLK_TRGPLL))
+-#define MT7622_CLKS_BITMAP (BIT(MTK_CLK_ETHIF) | BIT(MTK_CLK_ESW) | \
+- BIT(MTK_CLK_GP0) | BIT(MTK_CLK_GP1) | \
+- BIT(MTK_CLK_GP2) | \
+- 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_SGMII_CK) | \
+- BIT(MTK_CLK_ETH2PLL))
++#define MT7623_CLKS_BITMAP (BIT_ULL(MTK_CLK_ETHIF) | BIT_ULL(MTK_CLK_ESW) | \
++ BIT_ULL(MTK_CLK_GP1) | BIT_ULL(MTK_CLK_GP2) | \
++ BIT_ULL(MTK_CLK_TRGPLL))
++#define MT7622_CLKS_BITMAP (BIT_ULL(MTK_CLK_ETHIF) | BIT_ULL(MTK_CLK_ESW) | \
++ BIT_ULL(MTK_CLK_GP0) | BIT_ULL(MTK_CLK_GP1) | \
++ BIT_ULL(MTK_CLK_GP2) | \
++ BIT_ULL(MTK_CLK_SGMII_TX_250M) | \
++ BIT_ULL(MTK_CLK_SGMII_RX_250M) | \
++ BIT_ULL(MTK_CLK_SGMII_CDR_REF) | \
++ BIT_ULL(MTK_CLK_SGMII_CDR_FB) | \
++ BIT_ULL(MTK_CLK_SGMII_CK) | \
++ BIT_ULL(MTK_CLK_ETH2PLL))
+ #define MT7621_CLKS_BITMAP (0)
+ #define MT7628_CLKS_BITMAP (0)
+-#define MT7629_CLKS_BITMAP (BIT(MTK_CLK_ETHIF) | BIT(MTK_CLK_ESW) | \
+- BIT(MTK_CLK_GP0) | BIT(MTK_CLK_GP1) | \
+- BIT(MTK_CLK_GP2) | BIT(MTK_CLK_FE) | \
+- 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) | \
+- BIT(MTK_CLK_SGMII_CK) | \
+- BIT(MTK_CLK_ETH2PLL) | BIT(MTK_CLK_SGMIITOP))
+-#define MT7981_CLKS_BITMAP (BIT(MTK_CLK_FE) | BIT(MTK_CLK_GP2) | BIT(MTK_CLK_GP1) | \
+- 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) | \
+- BIT(MTK_CLK_SGMII_CK))
+-#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))
++#define MT7629_CLKS_BITMAP (BIT_ULL(MTK_CLK_ETHIF) | BIT_ULL(MTK_CLK_ESW) | \
++ BIT_ULL(MTK_CLK_GP0) | BIT_ULL(MTK_CLK_GP1) | \
++ BIT_ULL(MTK_CLK_GP2) | BIT_ULL(MTK_CLK_FE) | \
++ BIT_ULL(MTK_CLK_SGMII_TX_250M) | \
++ BIT_ULL(MTK_CLK_SGMII_RX_250M) | \
++ BIT_ULL(MTK_CLK_SGMII_CDR_REF) | \
++ BIT_ULL(MTK_CLK_SGMII_CDR_FB) | \
++ BIT_ULL(MTK_CLK_SGMII2_TX_250M) | \
++ BIT_ULL(MTK_CLK_SGMII2_RX_250M) | \
++ BIT_ULL(MTK_CLK_SGMII2_CDR_REF) | \
++ BIT_ULL(MTK_CLK_SGMII2_CDR_FB) | \
++ BIT_ULL(MTK_CLK_SGMII_CK) | \
++ BIT_ULL(MTK_CLK_ETH2PLL) | BIT_ULL(MTK_CLK_SGMIITOP))
++#define MT7981_CLKS_BITMAP (BIT_ULL(MTK_CLK_FE) | BIT_ULL(MTK_CLK_GP2) | \
++ BIT_ULL(MTK_CLK_GP1) | \
++ BIT_ULL(MTK_CLK_WOCPU0) | \
++ BIT_ULL(MTK_CLK_SGMII_TX_250M) | \
++ BIT_ULL(MTK_CLK_SGMII_RX_250M) | \
++ BIT_ULL(MTK_CLK_SGMII_CDR_REF) | \
++ BIT_ULL(MTK_CLK_SGMII_CDR_FB) | \
++ BIT_ULL(MTK_CLK_SGMII2_TX_250M) | \
++ BIT_ULL(MTK_CLK_SGMII2_RX_250M) | \
++ BIT_ULL(MTK_CLK_SGMII2_CDR_REF) | \
++ BIT_ULL(MTK_CLK_SGMII2_CDR_FB) | \
++ BIT_ULL(MTK_CLK_SGMII_CK))
++#define MT7986_CLKS_BITMAP (BIT_ULL(MTK_CLK_FE) | BIT_ULL(MTK_CLK_GP2) | \
++ BIT_ULL(MTK_CLK_GP1) | \
++ BIT_ULL(MTK_CLK_WOCPU1) | BIT_ULL(MTK_CLK_WOCPU0) | \
++ BIT_ULL(MTK_CLK_SGMII_TX_250M) | \
++ BIT_ULL(MTK_CLK_SGMII_RX_250M) | \
++ BIT_ULL(MTK_CLK_SGMII_CDR_REF) | \
++ BIT_ULL(MTK_CLK_SGMII_CDR_FB) | \
++ BIT_ULL(MTK_CLK_SGMII2_TX_250M) | \
++ BIT_ULL(MTK_CLK_SGMII2_RX_250M) | \
++ BIT_ULL(MTK_CLK_SGMII2_CDR_REF) | \
++ BIT_ULL(MTK_CLK_SGMII2_CDR_FB))
+
+ enum mtk_dev_state {
+ MTK_HW_INIT,
+@@ -1046,7 +1048,7 @@ struct mtk_soc_data {
+ const struct mtk_reg_map *reg_map;
+ u32 ana_rgc3;
+ u64 caps;
+- u32 required_clks;
++ u64 required_clks;
+ bool required_pctl;
+ u8 offload_version;
+ u8 hash_offset;
diff --git a/target/linux/generic/backport-6.6/750-v6.5-11-net-ethernet-mtk_eth_soc-add-basic-support-for-MT798.patch b/target/linux/generic/backport-6.6/750-v6.5-11-net-ethernet-mtk_eth_soc-add-basic-support-for-MT798.patch
new file mode 100644
index 0000000000..8c24321dd4
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-11-net-ethernet-mtk_eth_soc-add-basic-support-for-MT798.patch
@@ -0,0 +1,477 @@
+From 94f825a7eadfc8b4c8828efdb7705d9703f9c73e Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Tue, 25 Jul 2023 01:57:42 +0100
+Subject: [PATCH 105/250] net: ethernet: mtk_eth_soc: add basic support for
+ MT7988 SoC
+
+Introduce support for ethernet chip available in MT7988 SoC to
+mtk_eth_soc driver. As a first step support only the first GMAC which
+is hard-wired to the internal DSA switch having 4 built-in gigabit
+Ethernet PHYs.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/25c8377095b95d186872eeda7aa055da83e8f0ca.1690246605.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_path.c | 14 +-
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 201 +++++++++++++++++--
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 86 +++++++-
+ 3 files changed, 273 insertions(+), 28 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_path.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_path.c
+@@ -43,7 +43,7 @@ static const char *mtk_eth_path_name(u64
+ static int set_mux_gdm1_to_gmac1_esw(struct mtk_eth *eth, u64 path)
+ {
+ bool updated = true;
+- u32 val, mask, set;
++ u32 mask, set, reg;
+
+ switch (path) {
+ case MTK_ETH_PATH_GMAC1_SGMII:
+@@ -59,11 +59,13 @@ static int set_mux_gdm1_to_gmac1_esw(str
+ break;
+ }
+
+- if (updated) {
+- val = mtk_r32(eth, MTK_MAC_MISC);
+- val = (val & mask) | set;
+- mtk_w32(eth, val, MTK_MAC_MISC);
+- }
++ if (mtk_is_netsys_v3_or_greater(eth))
++ reg = MTK_MAC_MISC_V3;
++ else
++ reg = MTK_MAC_MISC;
++
++ if (updated)
++ mtk_m32(eth, mask, set, reg);
+
+ dev_dbg(eth->dev, "path %s in %s updated = %d\n",
+ mtk_eth_path_name(path), __func__, updated);
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -152,6 +152,54 @@ static const struct mtk_reg_map mt7986_r
+ .pse_oq_sta = 0x01a0,
+ };
+
++static const struct mtk_reg_map mt7988_reg_map = {
++ .tx_irq_mask = 0x461c,
++ .tx_irq_status = 0x4618,
++ .pdma = {
++ .rx_ptr = 0x6900,
++ .rx_cnt_cfg = 0x6904,
++ .pcrx_ptr = 0x6908,
++ .glo_cfg = 0x6a04,
++ .rst_idx = 0x6a08,
++ .delay_irq = 0x6a0c,
++ .irq_status = 0x6a20,
++ .irq_mask = 0x6a28,
++ .adma_rx_dbg0 = 0x6a38,
++ .int_grp = 0x6a50,
++ },
++ .qdma = {
++ .qtx_cfg = 0x4400,
++ .qtx_sch = 0x4404,
++ .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,
++ .tx_sch_rate = 0x4798,
++ },
++ .gdm1_cnt = 0x1c00,
++ .gdma_to_ppe = 0x3333,
++ .ppe_base = 0x2000,
++ .wdma_base = {
++ [0] = 0x4800,
++ [1] = 0x4c00,
++ },
++ .pse_iq_sta = 0x0180,
++ .pse_oq_sta = 0x01a0,
++};
++
+ /* strings used by ethtool */
+ static const struct mtk_ethtool_stats {
+ char str[ETH_GSTRING_LEN];
+@@ -179,10 +227,54 @@ static const struct mtk_ethtool_stats {
+ };
+
+ static const char * const mtk_clks_source_name[] = {
+- "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", "wocpu0", "wocpu1", "netsys0", "netsys1"
++ "ethif",
++ "sgmiitop",
++ "esw",
++ "gp0",
++ "gp1",
++ "gp2",
++ "gp3",
++ "xgp1",
++ "xgp2",
++ "xgp3",
++ "crypto",
++ "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",
++ "wocpu0",
++ "wocpu1",
++ "netsys0",
++ "netsys1",
++ "ethwarp_wocpu2",
++ "ethwarp_wocpu1",
++ "ethwarp_wocpu0",
++ "top_usxgmii0_sel",
++ "top_usxgmii1_sel",
++ "top_sgm0_sel",
++ "top_sgm1_sel",
++ "top_xfi_phy0_xtal_sel",
++ "top_xfi_phy1_xtal_sel",
++ "top_eth_gmii_sel",
++ "top_eth_refck_50m_sel",
++ "top_eth_sys_200m_sel",
++ "top_eth_sys_sel",
++ "top_eth_xgmii_sel",
++ "top_eth_mii_sel",
++ "top_netsys_sel",
++ "top_netsys_500m_sel",
++ "top_netsys_pao_2x_sel",
++ "top_netsys_sync_250m_sel",
++ "top_netsys_ppefb_250m_sel",
++ "top_netsys_warp_sel",
+ };
+
+ void mtk_w32(struct mtk_eth *eth, u32 val, unsigned reg)
+@@ -195,7 +287,7 @@ u32 mtk_r32(struct mtk_eth *eth, unsigne
+ return __raw_readl(eth->base + reg);
+ }
+
+-static u32 mtk_m32(struct mtk_eth *eth, u32 mask, u32 set, unsigned reg)
++u32 mtk_m32(struct mtk_eth *eth, u32 mask, u32 set, unsigned int reg)
+ {
+ u32 val;
+
+@@ -369,6 +461,19 @@ static void mtk_gmac0_rgmii_adjust(struc
+ dev_err(eth->dev, "Missing PLL configuration, ethernet may not work\n");
+ }
+
++static void mtk_setup_bridge_switch(struct mtk_eth *eth)
++{
++ /* Force Port1 XGMAC Link Up */
++ mtk_m32(eth, 0, MTK_XGMAC_FORCE_LINK(MTK_GMAC1_ID),
++ MTK_XGMAC_STS(MTK_GMAC1_ID));
++
++ /* Adjust GSW bridge IPG to 11 */
++ mtk_m32(eth, GSWTX_IPG_MASK | GSWRX_IPG_MASK,
++ (GSW_IPG_11 << GSWTX_IPG_SHIFT) |
++ (GSW_IPG_11 << GSWRX_IPG_SHIFT),
++ MTK_GSW_CFG);
++}
++
+ static struct phylink_pcs *mtk_mac_select_pcs(struct phylink_config *config,
+ phy_interface_t interface)
+ {
+@@ -438,6 +543,8 @@ static void mtk_mac_config(struct phylin
+ goto init_err;
+ }
+ break;
++ case PHY_INTERFACE_MODE_INTERNAL:
++ break;
+ default:
+ goto err_phy;
+ }
+@@ -515,6 +622,15 @@ static void mtk_mac_config(struct phylin
+ return;
+ }
+
++ /* Setup gmac */
++ if (mtk_is_netsys_v3_or_greater(eth) &&
++ mac->interface == PHY_INTERFACE_MODE_INTERNAL) {
++ mtk_w32(mac->hw, MTK_GDMA_XGDM_SEL, MTK_GDMA_EG_CTRL(mac->id));
++ mtk_w32(mac->hw, MAC_MCR_FORCE_LINK_DOWN, MTK_MAC_MCR(mac->id));
++
++ mtk_setup_bridge_switch(eth);
++ }
++
+ return;
+
+ err_phy:
+@@ -725,11 +841,15 @@ static int mtk_mdio_init(struct mtk_eth
+ }
+ divider = min_t(unsigned int, DIV_ROUND_UP(MDC_MAX_FREQ, max_clk), 63);
+
++ /* Configure MDC Turbo Mode */
++ if (mtk_is_netsys_v3_or_greater(eth))
++ mtk_m32(eth, 0, MISC_MDC_TURBO, MTK_MAC_MISC_V3);
++
+ /* Configure MDC Divider */
+- val = mtk_r32(eth, MTK_PPSC);
+- val &= ~PPSC_MDC_CFG;
+- val |= FIELD_PREP(PPSC_MDC_CFG, divider) | PPSC_MDC_TURBO;
+- mtk_w32(eth, val, MTK_PPSC);
++ val = FIELD_PREP(PPSC_MDC_CFG, divider);
++ if (!mtk_is_netsys_v3_or_greater(eth))
++ val |= PPSC_MDC_TURBO;
++ mtk_m32(eth, PPSC_MDC_CFG, val, MTK_PPSC);
+
+ dev_dbg(eth->dev, "MDC is running on %d Hz\n", MDC_MAX_FREQ / divider);
+
+@@ -1190,10 +1310,19 @@ static void mtk_tx_set_dma_desc_v2(struc
+ data |= TX_DMA_LS0;
+ WRITE_ONCE(desc->txd3, data);
+
+- if (mac->id == MTK_GMAC3_ID)
+- data = PSE_GDM3_PORT;
+- else
+- data = (mac->id + 1) << TX_DMA_FPORT_SHIFT_V2; /* forward port */
++ /* set forward port */
++ switch (mac->id) {
++ case MTK_GMAC1_ID:
++ data = PSE_GDM1_PORT << TX_DMA_FPORT_SHIFT_V2;
++ break;
++ case MTK_GMAC2_ID:
++ data = PSE_GDM2_PORT << TX_DMA_FPORT_SHIFT_V2;
++ break;
++ case MTK_GMAC3_ID:
++ data = PSE_GDM3_PORT << TX_DMA_FPORT_SHIFT_V2;
++ break;
++ }
++
+ data |= TX_DMA_SWC_V2 | QID_BITS_V2(info->qid);
+ WRITE_ONCE(desc->txd4, data);
+
+@@ -4360,6 +4489,17 @@ static int mtk_add_mac(struct mtk_eth *e
+ mac->phylink_config.supported_interfaces);
+ }
+
++ if (mtk_is_netsys_v3_or_greater(mac->hw) &&
++ MTK_HAS_CAPS(mac->hw->soc->caps, MTK_ESW_BIT) &&
++ id == MTK_GMAC1_ID) {
++ mac->phylink_config.mac_capabilities = MAC_ASYM_PAUSE |
++ MAC_SYM_PAUSE |
++ MAC_10000FD;
++ phy_interface_zero(mac->phylink_config.supported_interfaces);
++ __set_bit(PHY_INTERFACE_MODE_INTERNAL,
++ mac->phylink_config.supported_interfaces);
++ }
++
+ phylink = phylink_create(&mac->phylink_config,
+ of_fwnode_handle(mac->of_node),
+ phy_mode, &mtk_phylink_ops);
+@@ -4880,6 +5020,24 @@ static const struct mtk_soc_data mt7986_
+ },
+ };
+
++static const struct mtk_soc_data mt7988_data = {
++ .reg_map = &mt7988_reg_map,
++ .ana_rgc3 = 0x128,
++ .caps = MT7988_CAPS,
++ .hw_features = MTK_HW_FEATURES,
++ .required_clks = MT7988_CLKS_BITMAP,
++ .required_pctl = false,
++ .version = 3,
++ .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,
++ .rx_dma_l4_valid = RX_DMA_L4_VALID_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,
+@@ -4898,14 +5056,15 @@ static const struct mtk_soc_data rt5350_
+ };
+
+ const struct of_device_id of_mtk_match[] = {
+- { .compatible = "mediatek,mt2701-eth", .data = &mt2701_data},
+- { .compatible = "mediatek,mt7621-eth", .data = &mt7621_data},
+- { .compatible = "mediatek,mt7622-eth", .data = &mt7622_data},
+- { .compatible = "mediatek,mt7623-eth", .data = &mt7623_data},
+- { .compatible = "mediatek,mt7629-eth", .data = &mt7629_data},
+- { .compatible = "mediatek,mt7981-eth", .data = &mt7981_data},
+- { .compatible = "mediatek,mt7986-eth", .data = &mt7986_data},
+- { .compatible = "ralink,rt5350-eth", .data = &rt5350_data},
++ { .compatible = "mediatek,mt2701-eth", .data = &mt2701_data },
++ { .compatible = "mediatek,mt7621-eth", .data = &mt7621_data },
++ { .compatible = "mediatek,mt7622-eth", .data = &mt7622_data },
++ { .compatible = "mediatek,mt7623-eth", .data = &mt7623_data },
++ { .compatible = "mediatek,mt7629-eth", .data = &mt7629_data },
++ { .compatible = "mediatek,mt7981-eth", .data = &mt7981_data },
++ { .compatible = "mediatek,mt7986-eth", .data = &mt7986_data },
++ { .compatible = "mediatek,mt7988-eth", .data = &mt7988_data },
++ { .compatible = "ralink,rt5350-eth", .data = &rt5350_data },
+ {},
+ };
+ MODULE_DEVICE_TABLE(of, of_mtk_match);
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -117,7 +117,8 @@
+ #define MTK_CDMP_EG_CTRL 0x404
+
+ /* GDM Exgress Control Register */
+-#define MTK_GDMA_FWD_CFG(x) (0x500 + (x * 0x1000))
++#define MTK_GDMA_FWD_CFG(x) ({ typeof(x) _x = (x); (_x == MTK_GMAC3_ID) ? \
++ 0x540 : 0x500 + (_x * 0x1000); })
+ #define MTK_GDMA_SPECIAL_TAG BIT(24)
+ #define MTK_GDMA_ICS_EN BIT(22)
+ #define MTK_GDMA_TCS_EN BIT(21)
+@@ -126,6 +127,11 @@
+ #define MTK_GDMA_TO_PDMA 0x0
+ #define MTK_GDMA_DROP_ALL 0x7777
+
++/* GDM Egress Control Register */
++#define MTK_GDMA_EG_CTRL(x) ({ typeof(x) _x = (x); (_x == MTK_GMAC3_ID) ? \
++ 0x544 : 0x504 + (_x * 0x1000); })
++#define MTK_GDMA_XGDM_SEL BIT(31)
++
+ /* Unicast Filter MAC Address Register - Low */
+ #define MTK_GDMA_MAC_ADRL(x) (0x508 + (x * 0x1000))
+
+@@ -389,7 +395,26 @@
+ #define PHY_IAC_TIMEOUT HZ
+
+ #define MTK_MAC_MISC 0x1000c
++#define MTK_MAC_MISC_V3 0x10010
+ #define MTK_MUX_TO_ESW BIT(0)
++#define MISC_MDC_TURBO BIT(4)
++
++/* XMAC status registers */
++#define MTK_XGMAC_STS(x) (((x) == MTK_GMAC3_ID) ? 0x1001C : 0x1000C)
++#define MTK_XGMAC_FORCE_LINK(x) (((x) == MTK_GMAC2_ID) ? BIT(31) : BIT(15))
++#define MTK_USXGMII_PCS_LINK BIT(8)
++#define MTK_XGMAC_RX_FC BIT(5)
++#define MTK_XGMAC_TX_FC BIT(4)
++#define MTK_USXGMII_PCS_MODE GENMASK(3, 1)
++#define MTK_XGMAC_LINK_STS BIT(0)
++
++/* GSW bridge registers */
++#define MTK_GSW_CFG (0x10080)
++#define GSWTX_IPG_MASK GENMASK(19, 16)
++#define GSWTX_IPG_SHIFT 16
++#define GSWRX_IPG_MASK GENMASK(3, 0)
++#define GSWRX_IPG_SHIFT 0
++#define GSW_IPG_11 11
+
+ /* Mac control registers */
+ #define MTK_MAC_MCR(x) (0x10100 + (x * 0x100))
+@@ -647,6 +672,11 @@ enum mtk_clks_map {
+ MTK_CLK_GP0,
+ MTK_CLK_GP1,
+ MTK_CLK_GP2,
++ MTK_CLK_GP3,
++ MTK_CLK_XGP1,
++ MTK_CLK_XGP2,
++ MTK_CLK_XGP3,
++ MTK_CLK_CRYPTO,
+ MTK_CLK_FE,
+ MTK_CLK_TRGPLL,
+ MTK_CLK_SGMII_TX_250M,
+@@ -663,6 +693,27 @@ enum mtk_clks_map {
+ MTK_CLK_WOCPU1,
+ MTK_CLK_NETSYS0,
+ MTK_CLK_NETSYS1,
++ MTK_CLK_ETHWARP_WOCPU2,
++ MTK_CLK_ETHWARP_WOCPU1,
++ MTK_CLK_ETHWARP_WOCPU0,
++ MTK_CLK_TOP_USXGMII_SBUS_0_SEL,
++ MTK_CLK_TOP_USXGMII_SBUS_1_SEL,
++ MTK_CLK_TOP_SGM_0_SEL,
++ MTK_CLK_TOP_SGM_1_SEL,
++ MTK_CLK_TOP_XFI_PHY_0_XTAL_SEL,
++ MTK_CLK_TOP_XFI_PHY_1_XTAL_SEL,
++ MTK_CLK_TOP_ETH_GMII_SEL,
++ MTK_CLK_TOP_ETH_REFCK_50M_SEL,
++ MTK_CLK_TOP_ETH_SYS_200M_SEL,
++ MTK_CLK_TOP_ETH_SYS_SEL,
++ MTK_CLK_TOP_ETH_XGMII_SEL,
++ MTK_CLK_TOP_ETH_MII_SEL,
++ MTK_CLK_TOP_NETSYS_SEL,
++ MTK_CLK_TOP_NETSYS_500M_SEL,
++ MTK_CLK_TOP_NETSYS_PAO_2X_SEL,
++ MTK_CLK_TOP_NETSYS_SYNC_250M_SEL,
++ MTK_CLK_TOP_NETSYS_PPEFB_250M_SEL,
++ MTK_CLK_TOP_NETSYS_WARP_SEL,
+ MTK_CLK_MAX
+ };
+
+@@ -716,6 +767,36 @@ enum mtk_clks_map {
+ BIT_ULL(MTK_CLK_SGMII2_RX_250M) | \
+ BIT_ULL(MTK_CLK_SGMII2_CDR_REF) | \
+ BIT_ULL(MTK_CLK_SGMII2_CDR_FB))
++#define MT7988_CLKS_BITMAP (BIT_ULL(MTK_CLK_FE) | BIT_ULL(MTK_CLK_ESW) | \
++ BIT_ULL(MTK_CLK_GP1) | BIT_ULL(MTK_CLK_GP2) | \
++ BIT_ULL(MTK_CLK_GP3) | BIT_ULL(MTK_CLK_XGP1) | \
++ BIT_ULL(MTK_CLK_XGP2) | BIT_ULL(MTK_CLK_XGP3) | \
++ BIT_ULL(MTK_CLK_CRYPTO) | \
++ BIT_ULL(MTK_CLK_SGMII_TX_250M) | \
++ BIT_ULL(MTK_CLK_SGMII_RX_250M) | \
++ BIT_ULL(MTK_CLK_SGMII2_TX_250M) | \
++ BIT_ULL(MTK_CLK_SGMII2_RX_250M) | \
++ BIT_ULL(MTK_CLK_ETHWARP_WOCPU2) | \
++ BIT_ULL(MTK_CLK_ETHWARP_WOCPU1) | \
++ BIT_ULL(MTK_CLK_ETHWARP_WOCPU0) | \
++ BIT_ULL(MTK_CLK_TOP_USXGMII_SBUS_0_SEL) | \
++ BIT_ULL(MTK_CLK_TOP_USXGMII_SBUS_1_SEL) | \
++ BIT_ULL(MTK_CLK_TOP_SGM_0_SEL) | \
++ BIT_ULL(MTK_CLK_TOP_SGM_1_SEL) | \
++ BIT_ULL(MTK_CLK_TOP_XFI_PHY_0_XTAL_SEL) | \
++ BIT_ULL(MTK_CLK_TOP_XFI_PHY_1_XTAL_SEL) | \
++ BIT_ULL(MTK_CLK_TOP_ETH_GMII_SEL) | \
++ BIT_ULL(MTK_CLK_TOP_ETH_REFCK_50M_SEL) | \
++ BIT_ULL(MTK_CLK_TOP_ETH_SYS_200M_SEL) | \
++ BIT_ULL(MTK_CLK_TOP_ETH_SYS_SEL) | \
++ BIT_ULL(MTK_CLK_TOP_ETH_XGMII_SEL) | \
++ BIT_ULL(MTK_CLK_TOP_ETH_MII_SEL) | \
++ BIT_ULL(MTK_CLK_TOP_NETSYS_SEL) | \
++ BIT_ULL(MTK_CLK_TOP_NETSYS_500M_SEL) | \
++ BIT_ULL(MTK_CLK_TOP_NETSYS_PAO_2X_SEL) | \
++ BIT_ULL(MTK_CLK_TOP_NETSYS_SYNC_250M_SEL) | \
++ BIT_ULL(MTK_CLK_TOP_NETSYS_PPEFB_250M_SEL) | \
++ BIT_ULL(MTK_CLK_TOP_NETSYS_WARP_SEL))
+
+ enum mtk_dev_state {
+ MTK_HW_INIT,
+@@ -964,6 +1045,8 @@ enum mkt_eth_capabilities {
+ MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA | \
+ MTK_RSTCTRL_PPE1)
+
++#define MT7988_CAPS (MTK_GDM1_ESW | MTK_QDMA | MTK_RSTCTRL_PPE1)
++
+ struct mtk_tx_dma_desc_info {
+ dma_addr_t addr;
+ u32 size;
+@@ -1309,6 +1392,7 @@ void mtk_stats_update_mac(struct mtk_mac
+
+ void mtk_w32(struct mtk_eth *eth, u32 val, unsigned reg);
+ u32 mtk_r32(struct mtk_eth *eth, unsigned reg);
++u32 mtk_m32(struct mtk_eth *eth, u32 mask, u32 set, unsigned int reg);
+
+ int mtk_gmac_sgmii_path_setup(struct mtk_eth *eth, int mac_id);
+ int mtk_gmac_gephy_path_setup(struct mtk_eth *eth, int mac_id);
diff --git a/target/linux/generic/backport-6.6/750-v6.5-12-net-ethernet-mtk_eth_soc-enable-page_pool-support-fo.patch b/target/linux/generic/backport-6.6/750-v6.5-12-net-ethernet-mtk_eth_soc-enable-page_pool-support-fo.patch
new file mode 100644
index 0000000000..3dc4662d1a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-12-net-ethernet-mtk_eth_soc-enable-page_pool-support-fo.patch
@@ -0,0 +1,27 @@
+From 38a7eb76220731eff40602cf433f24880be0a6c2 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Thu, 27 Jul 2023 09:02:26 +0200
+Subject: [PATCH 106/250] net: ethernet: mtk_eth_soc: enable page_pool support
+ for MT7988 SoC
+
+In order to recycle pages, enable page_pool allocator for MT7988 SoC.
+
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://lore.kernel.org/r/fd4e8693980e47385a543e7b002eec0b88bd09df.1690440675.git.lorenzo@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -1658,7 +1658,7 @@ static void mtk_update_rx_cpu_idx(struct
+
+ static bool mtk_page_pool_enabled(struct mtk_eth *eth)
+ {
+- return eth->soc->version == 2;
++ return mtk_is_netsys_v2_or_greater(eth);
+ }
+
+ static struct page_pool *mtk_create_page_pool(struct mtk_eth *eth,
diff --git a/target/linux/generic/backport-6.6/750-v6.5-13-net-ethernet-mtk_eth_soc-enable-nft-hw-flowtable_off.patch b/target/linux/generic/backport-6.6/750-v6.5-13-net-ethernet-mtk_eth_soc-enable-nft-hw-flowtable_off.patch
new file mode 100644
index 0000000000..32f26d7d27
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-13-net-ethernet-mtk_eth_soc-enable-nft-hw-flowtable_off.patch
@@ -0,0 +1,135 @@
+From 199e7d5a7f03dd377f3a7a458360dbedd71d50ba Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Thu, 27 Jul 2023 09:07:28 +0200
+Subject: [PATCH 107/250] net: ethernet: mtk_eth_soc: enable nft hw
+ flowtable_offload for MT7988 SoC
+
+Enable hw Packet Process Engine (PPE) for MT7988 SoC.
+
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://lore.kernel.org/r/5e86341b0220a49620dadc02d77970de5ded9efc.1690441576.git.lorenzo@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 3 +++
+ drivers/net/ethernet/mediatek/mtk_ppe.c | 19 +++++++++++++++----
+ drivers/net/ethernet/mediatek/mtk_ppe.h | 19 ++++++++++++++++++-
+ 3 files changed, 36 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -5028,6 +5028,9 @@ static const struct mtk_soc_data mt7988_
+ .required_clks = MT7988_CLKS_BITMAP,
+ .required_pctl = false,
+ .version = 3,
++ .offload_version = 2,
++ .hash_offset = 4,
++ .foe_entry_size = MTK_FOE_ENTRY_V3_SIZE,
+ .txrx = {
+ .txd_size = sizeof(struct mtk_tx_dma_v2),
+ .rxd_size = sizeof(struct mtk_rx_dma_v2),
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
+@@ -422,13 +422,22 @@ int mtk_foe_entry_set_wdma(struct mtk_et
+ struct mtk_foe_mac_info *l2 = mtk_foe_entry_l2(eth, entry);
+ u32 *ib2 = mtk_foe_entry_ib2(eth, entry);
+
+- if (mtk_is_netsys_v2_or_greater(eth)) {
++ switch (eth->soc->version) {
++ case 3:
++ *ib2 &= ~MTK_FOE_IB2_PORT_MG_V2;
++ *ib2 |= FIELD_PREP(MTK_FOE_IB2_RX_IDX, txq) |
++ MTK_FOE_IB2_WDMA_WINFO_V2;
++ l2->w3info = FIELD_PREP(MTK_FOE_WINFO_WCID_V3, wcid) |
++ FIELD_PREP(MTK_FOE_WINFO_BSS_V3, bss);
++ break;
++ case 2:
+ *ib2 &= ~MTK_FOE_IB2_PORT_MG_V2;
+ *ib2 |= FIELD_PREP(MTK_FOE_IB2_RX_IDX, txq) |
+ MTK_FOE_IB2_WDMA_WINFO_V2;
+ l2->winfo = FIELD_PREP(MTK_FOE_WINFO_WCID, wcid) |
+ FIELD_PREP(MTK_FOE_WINFO_BSS, bss);
+- } else {
++ break;
++ default:
+ *ib2 &= ~MTK_FOE_IB2_PORT_MG;
+ *ib2 |= MTK_FOE_IB2_WDMA_WINFO;
+ if (wdma_idx)
+@@ -436,6 +445,7 @@ int mtk_foe_entry_set_wdma(struct mtk_et
+ 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);
++ break;
+ }
+
+ return 0;
+@@ -950,8 +960,7 @@ void mtk_ppe_start(struct mtk_ppe *ppe)
+ mtk_ppe_init_foe_table(ppe);
+ ppe_w32(ppe, MTK_PPE_TB_BASE, ppe->foe_phys);
+
+- val = MTK_PPE_TB_CFG_ENTRY_80B |
+- MTK_PPE_TB_CFG_AGE_NON_L4 |
++ val = MTK_PPE_TB_CFG_AGE_NON_L4 |
+ MTK_PPE_TB_CFG_AGE_UNBIND |
+ MTK_PPE_TB_CFG_AGE_TCP |
+ MTK_PPE_TB_CFG_AGE_UDP |
+@@ -967,6 +976,8 @@ void mtk_ppe_start(struct mtk_ppe *ppe)
+ MTK_PPE_ENTRIES_SHIFT);
+ if (mtk_is_netsys_v2_or_greater(ppe->eth))
+ val |= MTK_PPE_TB_CFG_INFO_SEL;
++ if (!mtk_is_netsys_v3_or_greater(ppe->eth))
++ val |= MTK_PPE_TB_CFG_ENTRY_80B;
+ ppe_w32(ppe, MTK_PPE_TB_CFG, val);
+
+ ppe_w32(ppe, MTK_PPE_IP_PROTO_CHK,
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
+@@ -85,6 +85,17 @@ enum {
+ #define MTK_FOE_WINFO_BSS GENMASK(5, 0)
+ #define MTK_FOE_WINFO_WCID GENMASK(15, 6)
+
++#define MTK_FOE_WINFO_BSS_V3 GENMASK(23, 16)
++#define MTK_FOE_WINFO_WCID_V3 GENMASK(15, 0)
++
++#define MTK_FOE_WINFO_PAO_USR_INFO GENMASK(15, 0)
++#define MTK_FOE_WINFO_PAO_TID GENMASK(19, 16)
++#define MTK_FOE_WINFO_PAO_IS_FIXEDRATE BIT(20)
++#define MTK_FOE_WINFO_PAO_IS_PRIOR BIT(21)
++#define MTK_FOE_WINFO_PAO_IS_SP BIT(22)
++#define MTK_FOE_WINFO_PAO_HF BIT(23)
++#define MTK_FOE_WINFO_PAO_AMSDU_EN BIT(24)
++
+ enum {
+ MTK_FOE_STATE_INVALID,
+ MTK_FOE_STATE_UNBIND,
+@@ -106,8 +117,13 @@ struct mtk_foe_mac_info {
+ u16 pppoe_id;
+ u16 src_mac_lo;
+
++ /* netsys_v2 */
+ u16 minfo;
+ u16 winfo;
++
++ /* netsys_v3 */
++ u32 w3info;
++ u32 wpao;
+ };
+
+ /* software-only entry type */
+@@ -218,6 +234,7 @@ struct mtk_foe_ipv6_6rd {
+
+ #define MTK_FOE_ENTRY_V1_SIZE 80
+ #define MTK_FOE_ENTRY_V2_SIZE 96
++#define MTK_FOE_ENTRY_V3_SIZE 128
+
+ struct mtk_foe_entry {
+ u32 ib1;
+@@ -228,7 +245,7 @@ struct mtk_foe_entry {
+ struct mtk_foe_ipv4_dslite dslite;
+ struct mtk_foe_ipv6 ipv6;
+ struct mtk_foe_ipv6_6rd ipv6_6rd;
+- u32 data[23];
++ u32 data[31];
+ };
+ };
+
diff --git a/target/linux/generic/backport-6.6/750-v6.5-14-net-ethernet-mtk_eth_soc-support-per-flow-accounting.patch b/target/linux/generic/backport-6.6/750-v6.5-14-net-ethernet-mtk_eth_soc-support-per-flow-accounting.patch
new file mode 100644
index 0000000000..876bdd5dd3
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-14-net-ethernet-mtk_eth_soc-support-per-flow-accounting.patch
@@ -0,0 +1,78 @@
+From 0c024632c1e7ff69914329bfd87bec749b9c0aed Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 2 Aug 2023 04:31:09 +0100
+Subject: [PATCH 108/250] net: ethernet: mtk_eth_soc: support per-flow
+ accounting on MT7988
+
+NETSYS_V3 uses 64 bits for each counters while older SoCs are using
+48/40 bits for each counter.
+Support reading per-flow byte and package counters on NETSYS_V3.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://lore.kernel.org/r/37a0928fa8c1253b197884c68ce1f54239421ac5.1690946442.git.daniel@makrotopia.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 1 +
+ drivers/net/ethernet/mediatek/mtk_ppe.c | 21 +++++++++++++-------
+ drivers/net/ethernet/mediatek/mtk_ppe_regs.h | 2 ++
+ 3 files changed, 17 insertions(+), 7 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -5030,6 +5030,7 @@ static const struct mtk_soc_data mt7988_
+ .version = 3,
+ .offload_version = 2,
+ .hash_offset = 4,
++ .has_accounting = true,
+ .foe_entry_size = MTK_FOE_ENTRY_V3_SIZE,
+ .txrx = {
+ .txd_size = sizeof(struct mtk_tx_dma_v2),
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
+@@ -91,7 +91,6 @@ static int mtk_ppe_mib_wait_busy(struct
+
+ static int mtk_mib_entry_read(struct mtk_ppe *ppe, u16 index, u64 *bytes, u64 *packets)
+ {
+- u32 byte_cnt_low, byte_cnt_high, pkt_cnt_low, pkt_cnt_high;
+ u32 val, cnt_r0, cnt_r1, cnt_r2;
+ int ret;
+
+@@ -106,12 +105,20 @@ static int mtk_mib_entry_read(struct mtk
+ cnt_r1 = readl(ppe->base + MTK_PPE_MIB_SER_R1);
+ cnt_r2 = readl(ppe->base + MTK_PPE_MIB_SER_R2);
+
+- byte_cnt_low = FIELD_GET(MTK_PPE_MIB_SER_R0_BYTE_CNT_LOW, cnt_r0);
+- byte_cnt_high = FIELD_GET(MTK_PPE_MIB_SER_R1_BYTE_CNT_HIGH, cnt_r1);
+- pkt_cnt_low = FIELD_GET(MTK_PPE_MIB_SER_R1_PKT_CNT_LOW, cnt_r1);
+- pkt_cnt_high = FIELD_GET(MTK_PPE_MIB_SER_R2_PKT_CNT_HIGH, cnt_r2);
+- *bytes = ((u64)byte_cnt_high << 32) | byte_cnt_low;
+- *packets = (pkt_cnt_high << 16) | pkt_cnt_low;
++ if (mtk_is_netsys_v3_or_greater(ppe->eth)) {
++ /* 64 bit for each counter */
++ u32 cnt_r3 = readl(ppe->base + MTK_PPE_MIB_SER_R3);
++ *bytes = ((u64)cnt_r1 << 32) | cnt_r0;
++ *packets = ((u64)cnt_r3 << 32) | cnt_r2;
++ } else {
++ /* 48 bit byte counter, 40 bit packet counter */
++ u32 byte_cnt_low = FIELD_GET(MTK_PPE_MIB_SER_R0_BYTE_CNT_LOW, cnt_r0);
++ u32 byte_cnt_high = FIELD_GET(MTK_PPE_MIB_SER_R1_BYTE_CNT_HIGH, cnt_r1);
++ u32 pkt_cnt_low = FIELD_GET(MTK_PPE_MIB_SER_R1_PKT_CNT_LOW, cnt_r1);
++ u32 pkt_cnt_high = FIELD_GET(MTK_PPE_MIB_SER_R2_PKT_CNT_HIGH, cnt_r2);
++ *bytes = ((u64)byte_cnt_high << 32) | byte_cnt_low;
++ *packets = (pkt_cnt_high << 16) | pkt_cnt_low;
++ }
+
+ return 0;
+ }
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_regs.h
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_regs.h
+@@ -163,6 +163,8 @@ enum {
+ #define MTK_PPE_MIB_SER_R2 0x348
+ #define MTK_PPE_MIB_SER_R2_PKT_CNT_HIGH GENMASK(23, 0)
+
++#define MTK_PPE_MIB_SER_R3 0x34c
++
+ #define MTK_PPE_MIB_CACHE_CTL 0x350
+ #define MTK_PPE_MIB_CACHE_CTL_EN BIT(0)
+ #define MTK_PPE_MIB_CACHE_CTL_FLUSH BIT(2)
diff --git a/target/linux/generic/backport-6.6/750-v6.5-15-net-ethernet-mtk_eth_soc-fix-NULL-pointer-on-hw-rese.patch b/target/linux/generic/backport-6.6/750-v6.5-15-net-ethernet-mtk_eth_soc-fix-NULL-pointer-on-hw-rese.patch
new file mode 100644
index 0000000000..6f1639a572
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-15-net-ethernet-mtk_eth_soc-fix-NULL-pointer-on-hw-rese.patch
@@ -0,0 +1,52 @@
+From 3b12f42772c26869d60398c1710aa27b27cd945c Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 21 Aug 2023 17:12:44 +0100
+Subject: [PATCH 109/250] net: ethernet: mtk_eth_soc: fix NULL pointer on hw
+ reset
+
+When a hardware reset is triggered on devices not initializing WED the
+calls to mtk_wed_fe_reset and mtk_wed_fe_reset_complete dereference a
+pointer on uninitialized stack memory.
+Break out of both functions in case a hw_list entry is 0.
+
+Fixes: 08a764a7c51b ("net: ethernet: mtk_wed: add reset/reset_complete callbacks")
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://lore.kernel.org/r/5465c1609b464cc7407ae1530c40821dcdf9d3e6.1692634266.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_wed.c | 12 ++++++++++--
+ 1 file changed, 10 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -214,9 +214,13 @@ void mtk_wed_fe_reset(void)
+
+ for (i = 0; i < ARRAY_SIZE(hw_list); i++) {
+ struct mtk_wed_hw *hw = hw_list[i];
+- struct mtk_wed_device *dev = hw->wed_dev;
++ struct mtk_wed_device *dev;
+ int err;
+
++ if (!hw)
++ break;
++
++ dev = hw->wed_dev;
+ if (!dev || !dev->wlan.reset)
+ continue;
+
+@@ -237,8 +241,12 @@ void mtk_wed_fe_reset_complete(void)
+
+ for (i = 0; i < ARRAY_SIZE(hw_list); i++) {
+ struct mtk_wed_hw *hw = hw_list[i];
+- struct mtk_wed_device *dev = hw->wed_dev;
++ struct mtk_wed_device *dev;
++
++ if (!hw)
++ break;
+
++ dev = hw->wed_dev;
+ if (!dev || !dev->wlan.reset_complete)
+ continue;
+
diff --git a/target/linux/generic/backport-6.6/750-v6.5-16-net-ethernet-mtk_eth_soc-fix-register-definitions-fo.patch b/target/linux/generic/backport-6.6/750-v6.5-16-net-ethernet-mtk_eth_soc-fix-register-definitions-fo.patch
new file mode 100644
index 0000000000..2190761fdd
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-16-net-ethernet-mtk_eth_soc-fix-register-definitions-fo.patch
@@ -0,0 +1,44 @@
+From 489aea123d74a846ce746bfdb3efe1e7ad512e0d Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 22 Aug 2023 17:31:24 +0100
+Subject: [PATCH 110/250] net: ethernet: mtk_eth_soc: fix register definitions
+ for MT7988
+
+More register macros need to be adjusted for the 3rd GMAC on MT7988.
+Account for added bit in SYSCFG0_SGMII_MASK.
+
+Fixes: 445eb6448ed3 ("net: ethernet: mtk_eth_soc: add basic support for MT7988 SoC")
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://lore.kernel.org/r/1c8da012e2ca80939906d85f314138c552139f0f.1692721443.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -133,10 +133,12 @@
+ #define MTK_GDMA_XGDM_SEL BIT(31)
+
+ /* Unicast Filter MAC Address Register - Low */
+-#define MTK_GDMA_MAC_ADRL(x) (0x508 + (x * 0x1000))
++#define MTK_GDMA_MAC_ADRL(x) ({ typeof(x) _x = (x); (_x == MTK_GMAC3_ID) ? \
++ 0x548 : 0x508 + (_x * 0x1000); })
+
+ /* Unicast Filter MAC Address Register - High */
+-#define MTK_GDMA_MAC_ADRH(x) (0x50C + (x * 0x1000))
++#define MTK_GDMA_MAC_ADRH(x) ({ typeof(x) _x = (x); (_x == MTK_GMAC3_ID) ? \
++ 0x54C : 0x50C + (_x * 0x1000); })
+
+ /* FE global misc reg*/
+ #define MTK_FE_GLO_MISC 0x124
+@@ -503,7 +505,7 @@
+ #define ETHSYS_SYSCFG0 0x14
+ #define SYSCFG0_GE_MASK 0x3
+ #define SYSCFG0_GE_MODE(x, y) (x << (12 + (y * 2)))
+-#define SYSCFG0_SGMII_MASK GENMASK(9, 8)
++#define SYSCFG0_SGMII_MASK GENMASK(9, 7)
+ #define SYSCFG0_SGMII_GMAC1 ((2 << 8) & SYSCFG0_SGMII_MASK)
+ #define SYSCFG0_SGMII_GMAC2 ((3 << 8) & SYSCFG0_SGMII_MASK)
+ #define SYSCFG0_SGMII_GMAC1_V2 BIT(9)
diff --git a/target/linux/generic/backport-6.6/750-v6.5-17-net-ethernet-mtk_eth_soc-add-reset-bits-for-MT7988.patch b/target/linux/generic/backport-6.6/750-v6.5-17-net-ethernet-mtk_eth_soc-add-reset-bits-for-MT7988.patch
new file mode 100644
index 0000000000..05a18364d6
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-17-net-ethernet-mtk_eth_soc-add-reset-bits-for-MT7988.patch
@@ -0,0 +1,188 @@
+From 15a84d1c44ae8c1451c265ee60500588a24e8cd6 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 22 Aug 2023 17:32:03 +0100
+Subject: [PATCH 111/250] net: ethernet: mtk_eth_soc: add reset bits for MT7988
+
+Add bits needed to reset the frame engine on MT7988.
+
+Fixes: 445eb6448ed3 ("net: ethernet: mtk_eth_soc: add basic support for MT7988 SoC")
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/89b6c38380e7a3800c1362aa7575600717bc7543.1692721443.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 76 +++++++++++++++------
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 16 +++--
+ 2 files changed, 68 insertions(+), 24 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -3594,19 +3594,34 @@ static void mtk_hw_reset(struct mtk_eth
+ {
+ u32 val;
+
+- if (mtk_is_netsys_v2_or_greater(eth)) {
++ if (mtk_is_netsys_v2_or_greater(eth))
+ regmap_write(eth->ethsys, ETHSYS_FE_RST_CHK_IDLE_EN, 0);
++
++ if (mtk_is_netsys_v3_or_greater(eth)) {
++ val = RSTCTRL_PPE0_V3;
++
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
++ val |= RSTCTRL_PPE1_V3;
++
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE2))
++ val |= RSTCTRL_PPE2;
++
++ val |= RSTCTRL_WDMA0 | RSTCTRL_WDMA1 | RSTCTRL_WDMA2;
++ } else if (mtk_is_netsys_v2_or_greater(eth)) {
+ val = RSTCTRL_PPE0_V2;
++
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
++ val |= RSTCTRL_PPE1;
+ } else {
+ val = RSTCTRL_PPE0;
+ }
+
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
+- val |= RSTCTRL_PPE1;
+-
+ ethsys_reset(eth, RSTCTRL_ETH | RSTCTRL_FE | val);
+
+- if (mtk_is_netsys_v2_or_greater(eth))
++ if (mtk_is_netsys_v3_or_greater(eth))
++ regmap_write(eth->ethsys, ETHSYS_FE_RST_CHK_IDLE_EN,
++ 0x6f8ff);
++ else if (mtk_is_netsys_v2_or_greater(eth))
+ regmap_write(eth->ethsys, ETHSYS_FE_RST_CHK_IDLE_EN,
+ 0x3ffffff);
+ }
+@@ -3632,13 +3647,21 @@ static void mtk_hw_warm_reset(struct mtk
+ return;
+ }
+
+- if (mtk_is_netsys_v2_or_greater(eth))
++ if (mtk_is_netsys_v3_or_greater(eth)) {
++ rst_mask = RSTCTRL_ETH | RSTCTRL_PPE0_V3;
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
++ rst_mask |= RSTCTRL_PPE1_V3;
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE2))
++ rst_mask |= RSTCTRL_PPE2;
++
++ rst_mask |= RSTCTRL_WDMA0 | RSTCTRL_WDMA1 | RSTCTRL_WDMA2;
++ } else if (mtk_is_netsys_v2_or_greater(eth)) {
+ rst_mask = RSTCTRL_ETH | RSTCTRL_PPE0_V2;
+- else
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
++ rst_mask |= RSTCTRL_PPE1;
++ } else {
+ rst_mask = RSTCTRL_ETH | RSTCTRL_PPE0;
+-
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
+- rst_mask |= RSTCTRL_PPE1;
++ }
+
+ regmap_update_bits(eth->ethsys, ETHSYS_RSTCTRL, rst_mask, rst_mask);
+
+@@ -3990,11 +4013,17 @@ static void mtk_prepare_for_reset(struct
+ u32 val;
+ int i;
+
+- /* disabe FE P3 and P4 */
+- val = mtk_r32(eth, MTK_FE_GLO_CFG) | MTK_FE_LINK_DOWN_P3;
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
+- val |= MTK_FE_LINK_DOWN_P4;
+- mtk_w32(eth, val, MTK_FE_GLO_CFG);
++ /* set FE PPE ports link down */
++ for (i = MTK_GMAC1_ID;
++ i <= (mtk_is_netsys_v3_or_greater(eth) ? MTK_GMAC3_ID : MTK_GMAC2_ID);
++ i += 2) {
++ val = mtk_r32(eth, MTK_FE_GLO_CFG(i)) | MTK_FE_LINK_DOWN_P(PSE_PPE0_PORT);
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
++ val |= MTK_FE_LINK_DOWN_P(PSE_PPE1_PORT);
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE2))
++ val |= MTK_FE_LINK_DOWN_P(PSE_PPE2_PORT);
++ mtk_w32(eth, val, MTK_FE_GLO_CFG(i));
++ }
+
+ /* adjust PPE configurations to prepare for reset */
+ for (i = 0; i < ARRAY_SIZE(eth->ppe); i++)
+@@ -4055,11 +4084,18 @@ static void mtk_pending_work(struct work
+ }
+ }
+
+- /* enabe FE P3 and P4 */
+- val = mtk_r32(eth, MTK_FE_GLO_CFG) & ~MTK_FE_LINK_DOWN_P3;
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
+- val &= ~MTK_FE_LINK_DOWN_P4;
+- mtk_w32(eth, val, MTK_FE_GLO_CFG);
++ /* set FE PPE ports link up */
++ for (i = MTK_GMAC1_ID;
++ i <= (mtk_is_netsys_v3_or_greater(eth) ? MTK_GMAC3_ID : MTK_GMAC2_ID);
++ i += 2) {
++ val = mtk_r32(eth, MTK_FE_GLO_CFG(i)) & ~MTK_FE_LINK_DOWN_P(PSE_PPE0_PORT);
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
++ val &= ~MTK_FE_LINK_DOWN_P(PSE_PPE1_PORT);
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE2))
++ val &= ~MTK_FE_LINK_DOWN_P(PSE_PPE2_PORT);
++
++ mtk_w32(eth, val, MTK_FE_GLO_CFG(i));
++ }
+
+ clear_bit(MTK_RESETTING, &eth->state);
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -76,9 +76,8 @@
+ #define MTK_HW_LRO_SDL_REMAIN_ROOM 1522
+
+ /* Frame Engine Global Configuration */
+-#define MTK_FE_GLO_CFG 0x00
+-#define MTK_FE_LINK_DOWN_P3 BIT(11)
+-#define MTK_FE_LINK_DOWN_P4 BIT(12)
++#define MTK_FE_GLO_CFG(x) (((x) == MTK_GMAC3_ID) ? 0x24 : 0x00)
++#define MTK_FE_LINK_DOWN_P(x) BIT(((x) + 8) % 16)
+
+ /* Frame Engine Global Reset Register */
+ #define MTK_RST_GL 0x04
+@@ -522,9 +521,15 @@
+ /* ethernet reset control register */
+ #define ETHSYS_RSTCTRL 0x34
+ #define RSTCTRL_FE BIT(6)
++#define RSTCTRL_WDMA0 BIT(24)
++#define RSTCTRL_WDMA1 BIT(25)
++#define RSTCTRL_WDMA2 BIT(26)
+ #define RSTCTRL_PPE0 BIT(31)
+ #define RSTCTRL_PPE0_V2 BIT(30)
+ #define RSTCTRL_PPE1 BIT(31)
++#define RSTCTRL_PPE0_V3 BIT(29)
++#define RSTCTRL_PPE1_V3 BIT(30)
++#define RSTCTRL_PPE2 BIT(31)
+ #define RSTCTRL_ETH BIT(23)
+
+ /* ethernet reset check idle register */
+@@ -931,6 +936,7 @@ enum mkt_eth_capabilities {
+ MTK_QDMA_BIT,
+ MTK_SOC_MT7628_BIT,
+ MTK_RSTCTRL_PPE1_BIT,
++ MTK_RSTCTRL_PPE2_BIT,
+ MTK_U3_COPHY_V2_BIT,
+
+ /* MUX BITS*/
+@@ -965,6 +971,7 @@ enum mkt_eth_capabilities {
+ #define MTK_QDMA BIT_ULL(MTK_QDMA_BIT)
+ #define MTK_SOC_MT7628 BIT_ULL(MTK_SOC_MT7628_BIT)
+ #define MTK_RSTCTRL_PPE1 BIT_ULL(MTK_RSTCTRL_PPE1_BIT)
++#define MTK_RSTCTRL_PPE2 BIT_ULL(MTK_RSTCTRL_PPE2_BIT)
+ #define MTK_U3_COPHY_V2 BIT_ULL(MTK_U3_COPHY_V2_BIT)
+
+ #define MTK_ETH_MUX_GDM1_TO_GMAC1_ESW \
+@@ -1047,7 +1054,8 @@ enum mkt_eth_capabilities {
+ MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA | \
+ MTK_RSTCTRL_PPE1)
+
+-#define MT7988_CAPS (MTK_GDM1_ESW | MTK_QDMA | MTK_RSTCTRL_PPE1)
++#define MT7988_CAPS (MTK_GDM1_ESW | MTK_QDMA | MTK_RSTCTRL_PPE1 | \
++ MTK_RSTCTRL_PPE2)
+
+ struct mtk_tx_dma_desc_info {
+ dma_addr_t addr;
diff --git a/target/linux/generic/backport-6.6/750-v6.5-18-net-ethernet-mtk_eth_soc-add-support-for-in-SoC-SRAM.patch b/target/linux/generic/backport-6.6/750-v6.5-18-net-ethernet-mtk_eth_soc-add-support-for-in-SoC-SRAM.patch
new file mode 100644
index 0000000000..74ac8dc898
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-18-net-ethernet-mtk_eth_soc-add-support-for-in-SoC-SRAM.patch
@@ -0,0 +1,254 @@
+From 25ce45fe40b574e5d7ffa407f7f2db03e7d5a910 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 22 Aug 2023 17:32:54 +0100
+Subject: [PATCH 112/250] net: ethernet: mtk_eth_soc: add support for in-SoC
+ SRAM
+
+MT7981, MT7986 and MT7988 come with in-SoC SRAM dedicated for Ethernet
+DMA rings. Support using the SRAM without breaking existing device tree
+bindings, ie. only new SoC starting from MT7988 will have the SRAM
+declared as additional resource in device tree. For MT7981 and MT7986
+an offset on top of the main I/O base is used.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/e45e0f230c63ad58869e8fe35b95a2fb8925b625.1692721443.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 88 ++++++++++++++++-----
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 12 ++-
+ 2 files changed, 78 insertions(+), 22 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -1118,10 +1118,13 @@ static int mtk_init_fq_dma(struct mtk_et
+ dma_addr_t dma_addr;
+ int i;
+
+- eth->scratch_ring = dma_alloc_coherent(eth->dma_dev,
+- cnt * soc->txrx.txd_size,
+- &eth->phy_scratch_ring,
+- GFP_KERNEL);
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_SRAM))
++ eth->scratch_ring = eth->sram_base;
++ else
++ eth->scratch_ring = dma_alloc_coherent(eth->dma_dev,
++ cnt * soc->txrx.txd_size,
++ &eth->phy_scratch_ring,
++ GFP_KERNEL);
+ if (unlikely(!eth->scratch_ring))
+ return -ENOMEM;
+
+@@ -2429,8 +2432,14 @@ static int mtk_tx_alloc(struct mtk_eth *
+ if (!ring->buf)
+ goto no_tx_mem;
+
+- ring->dma = dma_alloc_coherent(eth->dma_dev, ring_size * sz,
+- &ring->phys, GFP_KERNEL);
++ if (MTK_HAS_CAPS(soc->caps, MTK_SRAM)) {
++ ring->dma = eth->sram_base + ring_size * sz;
++ ring->phys = eth->phy_scratch_ring + ring_size * (dma_addr_t)sz;
++ } else {
++ ring->dma = dma_alloc_coherent(eth->dma_dev, ring_size * sz,
++ &ring->phys, GFP_KERNEL);
++ }
++
+ if (!ring->dma)
+ goto no_tx_mem;
+
+@@ -2529,8 +2538,7 @@ static void mtk_tx_clean(struct mtk_eth
+ kfree(ring->buf);
+ ring->buf = NULL;
+ }
+-
+- if (ring->dma) {
++ if (!MTK_HAS_CAPS(soc->caps, MTK_SRAM) && ring->dma) {
+ dma_free_coherent(eth->dma_dev,
+ ring->dma_size * soc->txrx.txd_size,
+ ring->dma, ring->phys);
+@@ -2549,9 +2557,14 @@ static int mtk_rx_alloc(struct mtk_eth *
+ {
+ const struct mtk_reg_map *reg_map = eth->soc->reg_map;
+ struct mtk_rx_ring *ring;
+- int rx_data_len, rx_dma_size;
++ int rx_data_len, rx_dma_size, tx_ring_size;
+ int i;
+
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA))
++ tx_ring_size = MTK_QDMA_RING_SIZE;
++ else
++ tx_ring_size = MTK_DMA_SIZE;
++
+ if (rx_flag == MTK_RX_FLAGS_QDMA) {
+ if (ring_no)
+ return -EINVAL;
+@@ -2586,9 +2599,20 @@ static int mtk_rx_alloc(struct mtk_eth *
+ ring->page_pool = pp;
+ }
+
+- ring->dma = dma_alloc_coherent(eth->dma_dev,
+- rx_dma_size * eth->soc->txrx.rxd_size,
+- &ring->phys, GFP_KERNEL);
++ if (!MTK_HAS_CAPS(eth->soc->caps, MTK_SRAM) ||
++ rx_flag != MTK_RX_FLAGS_NORMAL) {
++ ring->dma = dma_alloc_coherent(eth->dma_dev,
++ rx_dma_size * eth->soc->txrx.rxd_size,
++ &ring->phys, GFP_KERNEL);
++ } else {
++ struct mtk_tx_ring *tx_ring = &eth->tx_ring;
++
++ ring->dma = tx_ring->dma + tx_ring_size *
++ eth->soc->txrx.txd_size * (ring_no + 1);
++ ring->phys = tx_ring->phys + tx_ring_size *
++ eth->soc->txrx.txd_size * (ring_no + 1);
++ }
++
+ if (!ring->dma)
+ return -ENOMEM;
+
+@@ -2673,7 +2697,7 @@ static int mtk_rx_alloc(struct mtk_eth *
+ return 0;
+ }
+
+-static void mtk_rx_clean(struct mtk_eth *eth, struct mtk_rx_ring *ring)
++static void mtk_rx_clean(struct mtk_eth *eth, struct mtk_rx_ring *ring, bool in_sram)
+ {
+ int i;
+
+@@ -2696,7 +2720,7 @@ static void mtk_rx_clean(struct mtk_eth
+ ring->data = NULL;
+ }
+
+- if (ring->dma) {
++ if (!in_sram && ring->dma) {
+ dma_free_coherent(eth->dma_dev,
+ ring->dma_size * eth->soc->txrx.rxd_size,
+ ring->dma, ring->phys);
+@@ -3059,7 +3083,7 @@ static void mtk_dma_free(struct mtk_eth
+ for (i = 0; i < MTK_MAX_DEVS; i++)
+ if (eth->netdev[i])
+ netdev_reset_queue(eth->netdev[i]);
+- if (eth->scratch_ring) {
++ if (!MTK_HAS_CAPS(soc->caps, MTK_SRAM) && eth->scratch_ring) {
+ dma_free_coherent(eth->dma_dev,
+ MTK_QDMA_RING_SIZE * soc->txrx.txd_size,
+ eth->scratch_ring, eth->phy_scratch_ring);
+@@ -3067,13 +3091,13 @@ static void mtk_dma_free(struct mtk_eth
+ eth->phy_scratch_ring = 0;
+ }
+ mtk_tx_clean(eth);
+- mtk_rx_clean(eth, &eth->rx_ring[0]);
+- mtk_rx_clean(eth, &eth->rx_ring_qdma);
++ mtk_rx_clean(eth, &eth->rx_ring[0], MTK_HAS_CAPS(soc->caps, MTK_SRAM));
++ mtk_rx_clean(eth, &eth->rx_ring_qdma, false);
+
+ if (eth->hwlro) {
+ mtk_hwlro_rx_uninit(eth);
+ for (i = 1; i < MTK_MAX_RX_RING_NUM; i++)
+- mtk_rx_clean(eth, &eth->rx_ring[i]);
++ mtk_rx_clean(eth, &eth->rx_ring[i], false);
+ }
+
+ kfree(eth->scratch_head);
+@@ -4641,7 +4665,7 @@ static int mtk_sgmii_init(struct mtk_eth
+
+ static int mtk_probe(struct platform_device *pdev)
+ {
+- struct resource *res = NULL;
++ struct resource *res = NULL, *res_sram;
+ struct device_node *mac_np;
+ struct mtk_eth *eth;
+ int err, i;
+@@ -4661,6 +4685,20 @@ static int mtk_probe(struct platform_dev
+ if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628))
+ eth->ip_align = NET_IP_ALIGN;
+
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_SRAM)) {
++ /* SRAM is actual memory and supports transparent access just like DRAM.
++ * Hence we don't require __iomem being set and don't need to use accessor
++ * functions to read from or write to SRAM.
++ */
++ if (mtk_is_netsys_v3_or_greater(eth)) {
++ eth->sram_base = (void __force *)devm_platform_ioremap_resource(pdev, 1);
++ if (IS_ERR(eth->sram_base))
++ return PTR_ERR(eth->sram_base);
++ } else {
++ eth->sram_base = (void __force *)eth->base + MTK_ETH_SRAM_OFFSET;
++ }
++ }
++
+ spin_lock_init(&eth->page_lock);
+ spin_lock_init(&eth->tx_irq_lock);
+ spin_lock_init(&eth->rx_irq_lock);
+@@ -4724,6 +4762,18 @@ static int mtk_probe(struct platform_dev
+ err = -EINVAL;
+ goto err_destroy_sgmii;
+ }
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_SRAM)) {
++ if (mtk_is_netsys_v3_or_greater(eth)) {
++ res_sram = platform_get_resource(pdev, IORESOURCE_MEM, 1);
++ if (!res_sram) {
++ err = -EINVAL;
++ goto err_destroy_sgmii;
++ }
++ eth->phy_scratch_ring = res_sram->start;
++ } else {
++ eth->phy_scratch_ring = res->start + MTK_ETH_SRAM_OFFSET;
++ }
++ }
+ }
+
+ if (eth->soc->offload_version) {
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -139,6 +139,9 @@
+ #define MTK_GDMA_MAC_ADRH(x) ({ typeof(x) _x = (x); (_x == MTK_GMAC3_ID) ? \
+ 0x54C : 0x50C + (_x * 0x1000); })
+
++/* Internal SRAM offset */
++#define MTK_ETH_SRAM_OFFSET 0x40000
++
+ /* FE global misc reg*/
+ #define MTK_FE_GLO_MISC 0x124
+
+@@ -938,6 +941,7 @@ enum mkt_eth_capabilities {
+ MTK_RSTCTRL_PPE1_BIT,
+ MTK_RSTCTRL_PPE2_BIT,
+ MTK_U3_COPHY_V2_BIT,
++ MTK_SRAM_BIT,
+
+ /* MUX BITS*/
+ MTK_ETH_MUX_GDM1_TO_GMAC1_ESW_BIT,
+@@ -973,6 +977,7 @@ enum mkt_eth_capabilities {
+ #define MTK_RSTCTRL_PPE1 BIT_ULL(MTK_RSTCTRL_PPE1_BIT)
+ #define MTK_RSTCTRL_PPE2 BIT_ULL(MTK_RSTCTRL_PPE2_BIT)
+ #define MTK_U3_COPHY_V2 BIT_ULL(MTK_U3_COPHY_V2_BIT)
++#define MTK_SRAM BIT_ULL(MTK_SRAM_BIT)
+
+ #define MTK_ETH_MUX_GDM1_TO_GMAC1_ESW \
+ BIT_ULL(MTK_ETH_MUX_GDM1_TO_GMAC1_ESW_BIT)
+@@ -1048,14 +1053,14 @@ enum mkt_eth_capabilities {
+ #define MT7981_CAPS (MTK_GMAC1_SGMII | MTK_GMAC2_SGMII | MTK_GMAC2_GEPHY | \
+ MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA | \
+ MTK_MUX_U3_GMAC2_TO_QPHY | MTK_U3_COPHY_V2 | \
+- MTK_RSTCTRL_PPE1)
++ MTK_RSTCTRL_PPE1 | MTK_SRAM)
+
+ #define MT7986_CAPS (MTK_GMAC1_SGMII | MTK_GMAC2_SGMII | \
+ MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA | \
+- MTK_RSTCTRL_PPE1)
++ MTK_RSTCTRL_PPE1 | MTK_SRAM)
+
+ #define MT7988_CAPS (MTK_GDM1_ESW | MTK_QDMA | MTK_RSTCTRL_PPE1 | \
+- MTK_RSTCTRL_PPE2)
++ MTK_RSTCTRL_PPE2 | MTK_SRAM)
+
+ struct mtk_tx_dma_desc_info {
+ dma_addr_t addr;
+@@ -1215,6 +1220,7 @@ struct mtk_eth {
+ struct device *dev;
+ struct device *dma_dev;
+ void __iomem *base;
++ void *sram_base;
+ spinlock_t page_lock;
+ spinlock_t tx_irq_lock;
+ spinlock_t rx_irq_lock;
diff --git a/target/linux/generic/backport-6.6/750-v6.5-19-net-ethernet-mtk_eth_soc-support-36-bit-DMA-addressi.patch b/target/linux/generic/backport-6.6/750-v6.5-19-net-ethernet-mtk_eth_soc-support-36-bit-DMA-addressi.patch
new file mode 100644
index 0000000000..1584dfd07c
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-19-net-ethernet-mtk_eth_soc-support-36-bit-DMA-addressi.patch
@@ -0,0 +1,166 @@
+From 0b0d606eb9650fa01dd5621e072aa29a10544399 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 22 Aug 2023 17:33:12 +0100
+Subject: [PATCH 113/250] net: ethernet: mtk_eth_soc: support 36-bit DMA
+ addressing on MT7988
+
+Systems having 4 GiB of RAM and more require DMA addressing beyond the
+current 32-bit limit. Starting from MT7988 the hardware now supports
+36-bit DMA addressing, let's use that new capability in the driver to
+avoid running into swiotlb on systems with 4 GiB of RAM or more.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/95b919c98876c9e49761e44662e7c937479eecb8.1692721443.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 30 +++++++++++++++++++--
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 22 +++++++++++++--
+ 2 files changed, 48 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -1311,6 +1311,10 @@ static void mtk_tx_set_dma_desc_v2(struc
+ data = TX_DMA_PLEN0(info->size);
+ if (info->last)
+ data |= TX_DMA_LS0;
++
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_36BIT_DMA))
++ data |= TX_DMA_PREP_ADDR64(info->addr);
++
+ WRITE_ONCE(desc->txd3, data);
+
+ /* set forward port */
+@@ -1980,6 +1984,7 @@ static int mtk_poll_rx(struct napi_struc
+ bool xdp_flush = false;
+ int idx;
+ struct sk_buff *skb;
++ u64 addr64 = 0;
+ u8 *data, *new_data;
+ struct mtk_rx_dma_v2 *rxd, trxd;
+ int done = 0, bytes = 0;
+@@ -2095,7 +2100,10 @@ static int mtk_poll_rx(struct napi_struc
+ goto release_desc;
+ }
+
+- dma_unmap_single(eth->dma_dev, trxd.rxd1,
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_36BIT_DMA))
++ addr64 = RX_DMA_GET_ADDR64(trxd.rxd2);
++
++ dma_unmap_single(eth->dma_dev, ((u64)trxd.rxd1 | addr64),
+ ring->buf_size, DMA_FROM_DEVICE);
+
+ skb = build_skb(data, ring->frag_size);
+@@ -2161,6 +2169,9 @@ release_desc:
+ else
+ rxd->rxd2 = RX_DMA_PREP_PLEN0(ring->buf_size);
+
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_36BIT_DMA))
++ rxd->rxd2 |= RX_DMA_PREP_ADDR64(dma_addr);
++
+ ring->calc_idx = idx;
+ done++;
+ }
+@@ -2653,6 +2664,9 @@ static int mtk_rx_alloc(struct mtk_eth *
+ else
+ rxd->rxd2 = RX_DMA_PREP_PLEN0(ring->buf_size);
+
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_36BIT_DMA))
++ rxd->rxd2 |= RX_DMA_PREP_ADDR64(dma_addr);
++
+ rxd->rxd3 = 0;
+ rxd->rxd4 = 0;
+ if (mtk_is_netsys_v2_or_greater(eth)) {
+@@ -2699,6 +2713,7 @@ static int mtk_rx_alloc(struct mtk_eth *
+
+ static void mtk_rx_clean(struct mtk_eth *eth, struct mtk_rx_ring *ring, bool in_sram)
+ {
++ u64 addr64 = 0;
+ int i;
+
+ if (ring->data && ring->dma) {
+@@ -2712,7 +2727,10 @@ static void mtk_rx_clean(struct mtk_eth
+ if (!rxd->rxd1)
+ continue;
+
+- dma_unmap_single(eth->dma_dev, rxd->rxd1,
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_36BIT_DMA))
++ addr64 = RX_DMA_GET_ADDR64(rxd->rxd2);
++
++ dma_unmap_single(eth->dma_dev, ((u64)rxd->rxd1 | addr64),
+ ring->buf_size, DMA_FROM_DEVICE);
+ mtk_rx_put_buff(ring, ring->data[i], false);
+ }
+@@ -4699,6 +4717,14 @@ static int mtk_probe(struct platform_dev
+ }
+ }
+
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_36BIT_DMA)) {
++ err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(36));
++ if (err) {
++ dev_err(&pdev->dev, "Wrong DMA config\n");
++ return -EINVAL;
++ }
++ }
++
+ spin_lock_init(&eth->page_lock);
+ spin_lock_init(&eth->tx_irq_lock);
+ spin_lock_init(&eth->rx_irq_lock);
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -331,6 +331,14 @@
+ #define TX_DMA_PLEN1(x) ((x) & eth->soc->txrx.dma_max_len)
+ #define TX_DMA_SWC BIT(14)
+ #define TX_DMA_PQID GENMASK(3, 0)
++#define TX_DMA_ADDR64_MASK GENMASK(3, 0)
++#if IS_ENABLED(CONFIG_64BIT)
++# define TX_DMA_GET_ADDR64(x) (((u64)FIELD_GET(TX_DMA_ADDR64_MASK, (x))) << 32)
++# define TX_DMA_PREP_ADDR64(x) FIELD_PREP(TX_DMA_ADDR64_MASK, ((x) >> 32))
++#else
++# define TX_DMA_GET_ADDR64(x) (0)
++# define TX_DMA_PREP_ADDR64(x) (0)
++#endif
+
+ /* PDMA on MT7628 */
+ #define TX_DMA_DONE BIT(31)
+@@ -343,6 +351,14 @@
+ #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)
++#define RX_DMA_ADDR64_MASK GENMASK(3, 0)
++#if IS_ENABLED(CONFIG_64BIT)
++# define RX_DMA_GET_ADDR64(x) (((u64)FIELD_GET(RX_DMA_ADDR64_MASK, (x))) << 32)
++# define RX_DMA_PREP_ADDR64(x) FIELD_PREP(RX_DMA_ADDR64_MASK, ((x) >> 32))
++#else
++# define RX_DMA_GET_ADDR64(x) (0)
++# define RX_DMA_PREP_ADDR64(x) (0)
++#endif
+
+ /* QDMA descriptor rxd3 */
+ #define RX_DMA_VID(x) ((x) & VLAN_VID_MASK)
+@@ -942,6 +958,7 @@ enum mkt_eth_capabilities {
+ MTK_RSTCTRL_PPE2_BIT,
+ MTK_U3_COPHY_V2_BIT,
+ MTK_SRAM_BIT,
++ MTK_36BIT_DMA_BIT,
+
+ /* MUX BITS*/
+ MTK_ETH_MUX_GDM1_TO_GMAC1_ESW_BIT,
+@@ -978,6 +995,7 @@ enum mkt_eth_capabilities {
+ #define MTK_RSTCTRL_PPE2 BIT_ULL(MTK_RSTCTRL_PPE2_BIT)
+ #define MTK_U3_COPHY_V2 BIT_ULL(MTK_U3_COPHY_V2_BIT)
+ #define MTK_SRAM BIT_ULL(MTK_SRAM_BIT)
++#define MTK_36BIT_DMA BIT_ULL(MTK_36BIT_DMA_BIT)
+
+ #define MTK_ETH_MUX_GDM1_TO_GMAC1_ESW \
+ BIT_ULL(MTK_ETH_MUX_GDM1_TO_GMAC1_ESW_BIT)
+@@ -1059,8 +1077,8 @@ enum mkt_eth_capabilities {
+ MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA | \
+ MTK_RSTCTRL_PPE1 | MTK_SRAM)
+
+-#define MT7988_CAPS (MTK_GDM1_ESW | MTK_QDMA | MTK_RSTCTRL_PPE1 | \
+- MTK_RSTCTRL_PPE2 | MTK_SRAM)
++#define MT7988_CAPS (MTK_36BIT_DMA | MTK_GDM1_ESW | MTK_QDMA | \
++ MTK_RSTCTRL_PPE1 | MTK_RSTCTRL_PPE2 | MTK_SRAM)
+
+ struct mtk_tx_dma_desc_info {
+ dma_addr_t addr;
diff --git a/target/linux/generic/backport-6.6/750-v6.5-20-net-ethernet-mtk_eth_soc-fix-uninitialized-variable.patch b/target/linux/generic/backport-6.6/750-v6.5-20-net-ethernet-mtk_eth_soc-fix-uninitialized-variable.patch
new file mode 100644
index 0000000000..5b27458eb8
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-20-net-ethernet-mtk_eth_soc-fix-uninitialized-variable.patch
@@ -0,0 +1,44 @@
+From e10a35abb3da12b812cfb6fc6137926a0c81e39a Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sun, 10 Sep 2023 22:40:30 +0100
+Subject: [PATCH] net: ethernet: mtk_eth_soc: fix uninitialized variable
+
+Variable dma_addr in function mtk_poll_rx can be uninitialized on
+some of the error paths. In practise this doesn't matter, even random
+data present in uninitialized stack memory can safely be used in the
+way it happens in the error path.
+
+However, in order to make Smatch happy make sure the variable is
+always initialized.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -1988,11 +1988,11 @@ static int mtk_poll_rx(struct napi_struc
+ u8 *data, *new_data;
+ struct mtk_rx_dma_v2 *rxd, trxd;
+ int done = 0, bytes = 0;
++ dma_addr_t dma_addr = DMA_MAPPING_ERROR;
+
+ while (done < budget) {
+ unsigned int pktlen, *rxdcsum;
+ struct net_device *netdev;
+- dma_addr_t dma_addr;
+ u32 hash, reason;
+ int mac = 0;
+
+@@ -2169,7 +2169,8 @@ release_desc:
+ else
+ rxd->rxd2 = RX_DMA_PREP_PLEN0(ring->buf_size);
+
+- if (MTK_HAS_CAPS(eth->soc->caps, MTK_36BIT_DMA))
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_36BIT_DMA) &&
++ likely(dma_addr != DMA_MAPPING_ERROR))
+ rxd->rxd2 |= RX_DMA_PREP_ADDR64(dma_addr);
+
+ ring->calc_idx = idx;
diff --git a/target/linux/generic/backport-6.6/750-v6.5-21-net-ethernet-mtk_eth_soc-fix-pse_port-configuration-.patch b/target/linux/generic/backport-6.6/750-v6.5-21-net-ethernet-mtk_eth_soc-fix-pse_port-configuration-.patch
new file mode 100644
index 0000000000..ac3e3a3e67
--- /dev/null
+++ b/target/linux/generic/backport-6.6/750-v6.5-21-net-ethernet-mtk_eth_soc-fix-pse_port-configuration-.patch
@@ -0,0 +1,33 @@
+From 5a124b1fd3e6cb15a943f0cdfe96aa8f6d3d2f39 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 9 Sep 2023 20:41:56 +0200
+Subject: [PATCH] net: ethernet: mtk_eth_soc: fix pse_port configuration for
+ MT7988
+
+MT7988 SoC support 3 NICs. Fix pse_port configuration in
+mtk_flow_set_output_device routine if the traffic is offloaded to eth2.
+Rely on mtk_pse_port definitions.
+
+Fixes: 88efedf517e6 ("net: ethernet: mtk_eth_soc: enable nft hw flowtable_offload for MT7988 SoC")
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/ethernet/mediatek/mtk_ppe_offload.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+@@ -214,9 +214,11 @@ mtk_flow_set_output_device(struct mtk_et
+ dsa_port = mtk_flow_get_dsa_port(&dev);
+
+ if (dev == eth->netdev[0])
+- pse_port = 1;
++ pse_port = PSE_GDM1_PORT;
+ else if (dev == eth->netdev[1])
+- pse_port = 2;
++ pse_port = PSE_GDM2_PORT;
++ else if (dev == eth->netdev[2])
++ pse_port = PSE_GDM3_PORT;
+ else
+ return -EOPNOTSUPP;
+
diff --git a/target/linux/generic/backport-6.6/751-01-v6.4-net-ethernet-mtk_eth_soc-add-code-for-offloading-flo.patch b/target/linux/generic/backport-6.6/751-01-v6.4-net-ethernet-mtk_eth_soc-add-code-for-offloading-flo.patch
new file mode 100644
index 0000000000..46da5b283f
--- /dev/null
+++ b/target/linux/generic/backport-6.6/751-01-v6.4-net-ethernet-mtk_eth_soc-add-code-for-offloading-flo.patch
@@ -0,0 +1,271 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Mon, 20 Mar 2023 11:44:30 +0100
+Subject: [PATCH] net: ethernet: mtk_eth_soc: add code for offloading flows
+ from wlan devices
+
+WED version 2 (on MT7986 and later) can offload flows originating from wireless
+devices. In order to make that work, ndo_setup_tc needs to be implemented on
+the netdevs. This adds the required code to offload flows coming in from WED,
+while keeping track of the incoming wed index used for selecting the correct
+PPE device.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 3 +
+ .../net/ethernet/mediatek/mtk_ppe_offload.c | 37 ++++---
+ drivers/net/ethernet/mediatek/mtk_wed.c | 101 ++++++++++++++++++
+ include/linux/soc/mediatek/mtk_wed.h | 6 ++
+ 4 files changed, 133 insertions(+), 14 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -1435,6 +1435,9 @@ 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);
++int mtk_flow_offload_cmd(struct mtk_eth *eth, struct flow_cls_offload *cls,
++ int ppe_index);
++void mtk_flow_offload_cleanup(struct mtk_eth *eth, struct list_head *list);
+ void mtk_eth_set_dma_device(struct mtk_eth *eth, struct device *dma_dev);
+
+
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+@@ -237,7 +237,8 @@ out:
+ }
+
+ static int
+-mtk_flow_offload_replace(struct mtk_eth *eth, struct flow_cls_offload *f)
++mtk_flow_offload_replace(struct mtk_eth *eth, struct flow_cls_offload *f,
++ int ppe_index)
+ {
+ struct flow_rule *rule = flow_cls_offload_flow_rule(f);
+ struct flow_action_entry *act;
+@@ -454,6 +455,7 @@ mtk_flow_offload_replace(struct mtk_eth
+ entry->cookie = f->cookie;
+ memcpy(&entry->data, &foe, sizeof(entry->data));
+ entry->wed_index = wed_index;
++ entry->ppe_index = ppe_index;
+
+ err = mtk_foe_entry_commit(eth->ppe[entry->ppe_index], entry);
+ if (err < 0)
+@@ -522,25 +524,15 @@ mtk_flow_offload_stats(struct mtk_eth *e
+
+ static DEFINE_MUTEX(mtk_flow_offload_mutex);
+
+-static int
+-mtk_eth_setup_tc_block_cb(enum tc_setup_type type, void *type_data, void *cb_priv)
++int mtk_flow_offload_cmd(struct mtk_eth *eth, struct flow_cls_offload *cls,
++ int ppe_index)
+ {
+- struct flow_cls_offload *cls = type_data;
+- struct net_device *dev = cb_priv;
+- struct mtk_mac *mac = netdev_priv(dev);
+- struct mtk_eth *eth = mac->hw;
+ int err;
+
+- if (!tc_can_offload(dev))
+- return -EOPNOTSUPP;
+-
+- if (type != TC_SETUP_CLSFLOWER)
+- return -EOPNOTSUPP;
+-
+ mutex_lock(&mtk_flow_offload_mutex);
+ switch (cls->command) {
+ case FLOW_CLS_REPLACE:
+- err = mtk_flow_offload_replace(eth, cls);
++ err = mtk_flow_offload_replace(eth, cls, ppe_index);
+ break;
+ case FLOW_CLS_DESTROY:
+ err = mtk_flow_offload_destroy(eth, cls);
+@@ -558,6 +550,23 @@ mtk_eth_setup_tc_block_cb(enum tc_setup_
+ }
+
+ static int
++mtk_eth_setup_tc_block_cb(enum tc_setup_type type, void *type_data, void *cb_priv)
++{
++ struct flow_cls_offload *cls = type_data;
++ struct net_device *dev = cb_priv;
++ struct mtk_mac *mac = netdev_priv(dev);
++ struct mtk_eth *eth = mac->hw;
++
++ if (!tc_can_offload(dev))
++ return -EOPNOTSUPP;
++
++ if (type != TC_SETUP_CLSFLOWER)
++ return -EOPNOTSUPP;
++
++ return mtk_flow_offload_cmd(eth, cls, 0);
++}
++
++static int
+ mtk_eth_setup_tc_block(struct net_device *dev, struct flow_block_offload *f)
+ {
+ struct mtk_mac *mac = netdev_priv(dev);
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -13,6 +13,8 @@
+ #include <linux/mfd/syscon.h>
+ #include <linux/debugfs.h>
+ #include <linux/soc/mediatek/mtk_wed.h>
++#include <net/flow_offload.h>
++#include <net/pkt_cls.h>
+ #include "mtk_eth_soc.h"
+ #include "mtk_wed_regs.h"
+ #include "mtk_wed.h"
+@@ -41,6 +43,11 @@
+ static struct mtk_wed_hw *hw_list[2];
+ static DEFINE_MUTEX(hw_lock);
+
++struct mtk_wed_flow_block_priv {
++ struct mtk_wed_hw *hw;
++ struct net_device *dev;
++};
++
+ static void
+ wed_m32(struct mtk_wed_device *dev, u32 reg, u32 mask, u32 val)
+ {
+@@ -1753,6 +1760,99 @@ out:
+ mutex_unlock(&hw_lock);
+ }
+
++static int
++mtk_wed_setup_tc_block_cb(enum tc_setup_type type, void *type_data, void *cb_priv)
++{
++ struct mtk_wed_flow_block_priv *priv = cb_priv;
++ struct flow_cls_offload *cls = type_data;
++ struct mtk_wed_hw *hw = priv->hw;
++
++ if (!tc_can_offload(priv->dev))
++ return -EOPNOTSUPP;
++
++ if (type != TC_SETUP_CLSFLOWER)
++ return -EOPNOTSUPP;
++
++ return mtk_flow_offload_cmd(hw->eth, cls, hw->index);
++}
++
++static int
++mtk_wed_setup_tc_block(struct mtk_wed_hw *hw, struct net_device *dev,
++ struct flow_block_offload *f)
++{
++ struct mtk_wed_flow_block_priv *priv;
++ static LIST_HEAD(block_cb_list);
++ struct flow_block_cb *block_cb;
++ struct mtk_eth *eth = hw->eth;
++ flow_setup_cb_t *cb;
++
++ if (!eth->soc->offload_version)
++ return -EOPNOTSUPP;
++
++ if (f->binder_type != FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS)
++ return -EOPNOTSUPP;
++
++ cb = mtk_wed_setup_tc_block_cb;
++ f->driver_block_list = &block_cb_list;
++
++ switch (f->command) {
++ case FLOW_BLOCK_BIND:
++ block_cb = flow_block_cb_lookup(f->block, cb, dev);
++ if (block_cb) {
++ flow_block_cb_incref(block_cb);
++ return 0;
++ }
++
++ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
++
++ priv->hw = hw;
++ priv->dev = dev;
++ block_cb = flow_block_cb_alloc(cb, dev, priv, NULL);
++ if (IS_ERR(block_cb)) {
++ kfree(priv);
++ return PTR_ERR(block_cb);
++ }
++
++ flow_block_cb_incref(block_cb);
++ flow_block_cb_add(block_cb, f);
++ list_add_tail(&block_cb->driver_list, &block_cb_list);
++ return 0;
++ case FLOW_BLOCK_UNBIND:
++ block_cb = flow_block_cb_lookup(f->block, cb, dev);
++ if (!block_cb)
++ return -ENOENT;
++
++ if (!flow_block_cb_decref(block_cb)) {
++ flow_block_cb_remove(block_cb, f);
++ list_del(&block_cb->driver_list);
++ kfree(block_cb->cb_priv);
++ }
++ return 0;
++ default:
++ return -EOPNOTSUPP;
++ }
++}
++
++static int
++mtk_wed_setup_tc(struct mtk_wed_device *wed, struct net_device *dev,
++ enum tc_setup_type type, void *type_data)
++{
++ struct mtk_wed_hw *hw = wed->hw;
++
++ if (hw->version < 2)
++ return -EOPNOTSUPP;
++
++ switch (type) {
++ case TC_SETUP_BLOCK:
++ case TC_SETUP_FT:
++ return mtk_wed_setup_tc_block(hw, dev, type_data);
++ default:
++ return -EOPNOTSUPP;
++ }
++}
++
+ void mtk_wed_add_hw(struct device_node *np, struct mtk_eth *eth,
+ void __iomem *wdma, phys_addr_t wdma_phy,
+ int index)
+@@ -1772,6 +1872,7 @@ void mtk_wed_add_hw(struct device_node *
+ .irq_set_mask = mtk_wed_irq_set_mask,
+ .detach = mtk_wed_detach,
+ .ppe_check = mtk_wed_ppe_check,
++ .setup_tc = mtk_wed_setup_tc,
+ };
+ struct device_node *eth_np = eth->dev->of_node;
+ struct platform_device *pdev;
+--- a/include/linux/soc/mediatek/mtk_wed.h
++++ b/include/linux/soc/mediatek/mtk_wed.h
+@@ -6,6 +6,7 @@
+ #include <linux/regmap.h>
+ #include <linux/pci.h>
+ #include <linux/skbuff.h>
++#include <linux/netdevice.h>
+
+ #define MTK_WED_TX_QUEUES 2
+ #define MTK_WED_RX_QUEUES 2
+@@ -180,6 +181,8 @@ struct mtk_wed_ops {
+
+ u32 (*irq_get)(struct mtk_wed_device *dev, u32 mask);
+ void (*irq_set_mask)(struct mtk_wed_device *dev, u32 mask);
++ int (*setup_tc)(struct mtk_wed_device *wed, struct net_device *dev,
++ enum tc_setup_type type, void *type_data);
+ };
+
+ extern const struct mtk_wed_ops __rcu *mtk_soc_wed_ops;
+@@ -238,6 +241,8 @@ mtk_wed_get_rx_capa(struct mtk_wed_devic
+ (_dev)->ops->msg_update(_dev, _id, _msg, _len)
+ #define mtk_wed_device_stop(_dev) (_dev)->ops->stop(_dev)
+ #define mtk_wed_device_dma_reset(_dev) (_dev)->ops->reset_dma(_dev)
++#define mtk_wed_device_setup_tc(_dev, _netdev, _type, _type_data) \
++ (_dev)->ops->setup_tc(_dev, _netdev, _type, _type_data)
+ #else
+ static inline bool mtk_wed_device_active(struct mtk_wed_device *dev)
+ {
+@@ -256,6 +261,7 @@ static inline bool mtk_wed_device_active
+ #define mtk_wed_device_update_msg(_dev, _id, _msg, _len) -ENODEV
+ #define mtk_wed_device_stop(_dev) do {} while (0)
+ #define mtk_wed_device_dma_reset(_dev) do {} while (0)
++#define mtk_wed_device_setup_tc(_dev, _netdev, _type, _type_data) -EOPNOTSUPP
+ #endif
+
+ #endif
diff --git a/target/linux/generic/backport-6.6/751-02-v6.4-net-ethernet-mediatek-mtk_ppe-prefer-newly-added-l2-.patch b/target/linux/generic/backport-6.6/751-02-v6.4-net-ethernet-mediatek-mtk_ppe-prefer-newly-added-l2-.patch
new file mode 100644
index 0000000000..b1796641a3
--- /dev/null
+++ b/target/linux/generic/backport-6.6/751-02-v6.4-net-ethernet-mediatek-mtk_ppe-prefer-newly-added-l2-.patch
@@ -0,0 +1,37 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Mon, 20 Mar 2023 15:37:55 +0100
+Subject: [PATCH] net: ethernet: mediatek: mtk_ppe: prefer newly added l2
+ flows over existing ones
+
+When a device is roaming between interfaces and a new flow entry is created,
+we should assume that its output device is more up to date than whatever
+entry existed already.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
+@@ -656,10 +656,20 @@ void mtk_foe_entry_clear(struct mtk_ppe
+ static int
+ mtk_foe_entry_commit_l2(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
+ {
++ struct mtk_flow_entry *prev;
++
+ entry->type = MTK_FLOW_TYPE_L2;
+
+- return rhashtable_insert_fast(&ppe->l2_flows, &entry->l2_node,
+- mtk_flow_l2_ht_params);
++ prev = rhashtable_lookup_get_insert_fast(&ppe->l2_flows, &entry->l2_node,
++ mtk_flow_l2_ht_params);
++ if (likely(!prev))
++ return 0;
++
++ if (IS_ERR(prev))
++ return PTR_ERR(prev);
++
++ return rhashtable_replace_fast(&ppe->l2_flows, &prev->l2_node,
++ &entry->l2_node, mtk_flow_l2_ht_params);
+ }
+
+ int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
diff --git a/target/linux/generic/backport-6.6/751-03-v6.4-net-ethernet-mtk_eth_soc-improve-keeping-track-of-of.patch b/target/linux/generic/backport-6.6/751-03-v6.4-net-ethernet-mtk_eth_soc-improve-keeping-track-of-of.patch
new file mode 100644
index 0000000000..8b8a8e11c7
--- /dev/null
+++ b/target/linux/generic/backport-6.6/751-03-v6.4-net-ethernet-mtk_eth_soc-improve-keeping-track-of-of.patch
@@ -0,0 +1,334 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 23 Mar 2023 10:24:11 +0100
+Subject: [PATCH] net: ethernet: mtk_eth_soc: improve keeping track of
+ offloaded flows
+
+Unify tracking of L2 and L3 flows. Use the generic list field in struct
+mtk_foe_entry for tracking L2 subflows. Preparation for improving
+flow accounting support.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ drivers/net/ethernet/mediatek/mtk_ppe.c | 162 ++++++++++++------------
+ drivers/net/ethernet/mediatek/mtk_ppe.h | 15 +--
+ 2 files changed, 86 insertions(+), 91 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
+@@ -476,42 +476,43 @@ int mtk_foe_entry_set_queue(struct mtk_e
+ return 0;
+ }
+
++static int
++mtk_flow_entry_match_len(struct mtk_eth *eth, struct mtk_foe_entry *entry)
++{
++ int type = mtk_get_ib1_pkt_type(eth, entry->ib1);
++
++ if (type > MTK_PPE_PKT_TYPE_IPV4_DSLITE)
++ return offsetof(struct mtk_foe_entry, ipv6._rsv);
++ else
++ return offsetof(struct mtk_foe_entry, ipv4.ib2);
++}
++
+ static bool
+ mtk_flow_entry_match(struct mtk_eth *eth, struct mtk_flow_entry *entry,
+- struct mtk_foe_entry *data)
++ struct mtk_foe_entry *data, int len)
+ {
+- int type, len;
+-
+ if ((data->ib1 ^ entry->data.ib1) & MTK_FOE_IB1_UDP)
+ return false;
+
+- type = mtk_get_ib1_pkt_type(eth, entry->data.ib1);
+- if (type > MTK_PPE_PKT_TYPE_IPV4_DSLITE)
+- len = offsetof(struct mtk_foe_entry, ipv6._rsv);
+- else
+- len = offsetof(struct mtk_foe_entry, ipv4.ib2);
+-
+ return !memcmp(&entry->data.data, &data->data, len - 4);
+ }
+
+ static void
+-__mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
++__mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry,
++ bool set_state)
+ {
+- 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);
++ hlist_for_each_entry_safe(entry, tmp, &entry->l2_flows, l2_list)
++ __mtk_foe_entry_clear(ppe, entry, set_state);
+ return;
+ }
+
+- hlist_del_init(&entry->list);
+- if (entry->hash != 0xffff) {
++ if (entry->hash != 0xffff && set_state) {
+ struct mtk_foe_entry *hwe = mtk_foe_get_entry(ppe, entry->hash);
+
+ hwe->ib1 &= ~MTK_FOE_IB1_STATE;
+@@ -531,7 +532,8 @@ __mtk_foe_entry_clear(struct mtk_ppe *pp
+ if (entry->type != MTK_FLOW_TYPE_L2_SUBFLOW)
+ return;
+
+- hlist_del_init(&entry->l2_data.list);
++ hlist_del_init(&entry->l2_list);
++ hlist_del_init(&entry->list);
+ kfree(entry);
+ }
+
+@@ -547,66 +549,55 @@ static int __mtk_foe_entry_idle_time(str
+ return now - timestamp;
+ }
+
++static bool
++mtk_flow_entry_update(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
++{
++ struct mtk_foe_entry foe = {};
++ struct mtk_foe_entry *hwe;
++ u16 hash = entry->hash;
++ int len;
++
++ if (hash == 0xffff)
++ return false;
++
++ hwe = mtk_foe_get_entry(ppe, hash);
++ len = mtk_flow_entry_match_len(ppe->eth, &entry->data);
++ memcpy(&foe, hwe, len);
++
++ if (!mtk_flow_entry_match(ppe->eth, entry, &foe, len) ||
++ FIELD_GET(MTK_FOE_IB1_STATE, foe.ib1) != MTK_FOE_STATE_BIND)
++ return false;
++
++ entry->data.ib1 = foe.ib1;
++
++ return true;
++}
++
+ static void
+ mtk_flow_entry_update_l2(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
+ {
+ u32 ib1_ts_mask = mtk_get_ib1_ts_mask(ppe->eth);
+ struct mtk_flow_entry *cur;
+- struct mtk_foe_entry *hwe;
+ struct hlist_node *tmp;
+ 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) {
++ hlist_for_each_entry_safe(cur, tmp, &entry->l2_flows, l2_list) {
+ int cur_idle;
+- u32 ib1;
+-
+- hwe = mtk_foe_get_entry(ppe, cur->hash);
+- ib1 = READ_ONCE(hwe->ib1);
+
+- if (FIELD_GET(MTK_FOE_IB1_STATE, ib1) != MTK_FOE_STATE_BIND) {
+- cur->hash = 0xffff;
+- __mtk_foe_entry_clear(ppe, cur);
++ if (!mtk_flow_entry_update(ppe, cur)) {
++ __mtk_foe_entry_clear(ppe, entry, false);
+ continue;
+ }
+
+- cur_idle = __mtk_foe_entry_idle_time(ppe, ib1);
++ cur_idle = __mtk_foe_entry_idle_time(ppe, cur->data.ib1);
+ if (cur_idle >= idle)
+ continue;
+
+ idle = cur_idle;
+ entry->data.ib1 &= ~ib1_ts_mask;
+- entry->data.ib1 |= hwe->ib1 & ib1_ts_mask;
+- }
+-}
+-
+-static void
+-mtk_flow_entry_update(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
+-{
+- struct mtk_foe_entry foe = {};
+- struct mtk_foe_entry *hwe;
+-
+- spin_lock_bh(&ppe_lock);
+-
+- if (entry->type == MTK_FLOW_TYPE_L2) {
+- mtk_flow_entry_update_l2(ppe, entry);
+- goto out;
++ entry->data.ib1 |= cur->data.ib1 & ib1_ts_mask;
+ }
+-
+- if (entry->hash == 0xffff)
+- goto out;
+-
+- hwe = mtk_foe_get_entry(ppe, entry->hash);
+- memcpy(&foe, hwe, ppe->eth->soc->foe_entry_size);
+- if (!mtk_flow_entry_match(ppe->eth, entry, &foe)) {
+- entry->hash = 0xffff;
+- goto out;
+- }
+-
+- entry->data.ib1 = foe.ib1;
+-
+-out:
+- spin_unlock_bh(&ppe_lock);
+ }
+
+ static void
+@@ -649,7 +640,8 @@ __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);
+- __mtk_foe_entry_clear(ppe, entry);
++ __mtk_foe_entry_clear(ppe, entry, true);
++ hlist_del_init(&entry->list);
+ spin_unlock_bh(&ppe_lock);
+ }
+
+@@ -696,8 +688,8 @@ mtk_foe_entry_commit_subflow(struct mtk_
+ {
+ const struct mtk_soc_data *soc = ppe->eth->soc;
+ struct mtk_flow_entry *flow_info;
+- struct mtk_foe_entry foe = {}, *hwe;
+ struct mtk_foe_mac_info *l2;
++ struct mtk_foe_entry *hwe;
+ u32 ib1_mask = mtk_get_ib1_pkt_type_mask(ppe->eth) | MTK_FOE_IB1_UDP;
+ int type;
+
+@@ -705,30 +697,30 @@ mtk_foe_entry_commit_subflow(struct mtk_
+ 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 / soc->hash_offset]);
+- hlist_add_head(&flow_info->l2_data.list, &entry->l2_flows);
++ hlist_add_head(&flow_info->l2_list, &entry->l2_flows);
+
+ hwe = mtk_foe_get_entry(ppe, hash);
+- memcpy(&foe, hwe, soc->foe_entry_size);
+- foe.ib1 &= ib1_mask;
+- foe.ib1 |= entry->data.ib1 & ~ib1_mask;
++ memcpy(&flow_info->data, hwe, soc->foe_entry_size);
++ flow_info->data.ib1 &= ib1_mask;
++ flow_info->data.ib1 |= entry->data.ib1 & ~ib1_mask;
+
+- l2 = mtk_foe_entry_l2(ppe->eth, &foe);
++ l2 = mtk_foe_entry_l2(ppe->eth, &flow_info->data);
+ memcpy(l2, &entry->data.bridge.l2, sizeof(*l2));
+
+- type = mtk_get_ib1_pkt_type(ppe->eth, foe.ib1);
++ type = mtk_get_ib1_pkt_type(ppe->eth, flow_info->data.ib1);
+ if (type == MTK_PPE_PKT_TYPE_IPV4_HNAPT)
+- memcpy(&foe.ipv4.new, &foe.ipv4.orig, sizeof(foe.ipv4.new));
++ memcpy(&flow_info->data.ipv4.new, &flow_info->data.ipv4.orig,
++ sizeof(flow_info->data.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(ppe->eth, &foe) = entry->data.bridge.ib2;
++ *mtk_foe_entry_ib2(ppe->eth, &flow_info->data) = entry->data.bridge.ib2;
+
+- __mtk_foe_entry_commit(ppe, &foe, hash);
++ __mtk_foe_entry_commit(ppe, &flow_info->data, hash);
+ }
+
+ void __mtk_ppe_check_skb(struct mtk_ppe *ppe, struct sk_buff *skb, u16 hash)
+@@ -738,9 +730,11 @@ void __mtk_ppe_check_skb(struct mtk_ppe
+ struct mtk_foe_entry *hwe = mtk_foe_get_entry(ppe, hash);
+ struct mtk_flow_entry *entry;
+ struct mtk_foe_bridge key = {};
++ struct mtk_foe_entry foe = {};
+ struct hlist_node *n;
+ struct ethhdr *eh;
+ bool found = false;
++ int entry_len;
+ u8 *tag;
+
+ spin_lock_bh(&ppe_lock);
+@@ -748,20 +742,14 @@ 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_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))
+- continue;
+-
+- entry->hash = 0xffff;
+- __mtk_foe_entry_clear(ppe, entry);
+- continue;
+- }
++ entry_len = mtk_flow_entry_match_len(ppe->eth, hwe);
++ memcpy(&foe, hwe, entry_len);
+
+- if (found || !mtk_flow_entry_match(ppe->eth, entry, hwe)) {
++ hlist_for_each_entry_safe(entry, n, head, list) {
++ if (found ||
++ !mtk_flow_entry_match(ppe->eth, entry, &foe, entry_len)) {
+ if (entry->hash != 0xffff)
+- entry->hash = 0xffff;
++ __mtk_foe_entry_clear(ppe, entry, false);
+ continue;
+ }
+
+@@ -810,9 +798,17 @@ out:
+
+ int mtk_foe_entry_idle_time(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
+ {
+- mtk_flow_entry_update(ppe, entry);
++ int idle;
++
++ spin_lock_bh(&ppe_lock);
++ if (entry->type == MTK_FLOW_TYPE_L2)
++ mtk_flow_entry_update_l2(ppe, entry);
++ else
++ mtk_flow_entry_update(ppe, entry);
++ idle = __mtk_foe_entry_idle_time(ppe, entry->data.ib1);
++ spin_unlock_bh(&ppe_lock);
+
+- return __mtk_foe_entry_idle_time(ppe, entry->data.ib1);
++ return idle;
+ }
+
+ int mtk_ppe_prepare_reset(struct mtk_ppe *ppe)
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
+@@ -286,7 +286,12 @@ enum {
+
+ struct mtk_flow_entry {
+ union {
+- struct hlist_node list;
++ /* regular flows + L2 subflows */
++ struct {
++ struct hlist_node list;
++ struct hlist_node l2_list;
++ };
++ /* L2 flows */
+ struct {
+ struct rhash_head l2_node;
+ struct hlist_head l2_flows;
+@@ -296,13 +301,7 @@ struct mtk_flow_entry {
+ s8 wed_index;
+ u8 ppe_index;
+ u16 hash;
+- union {
+- struct mtk_foe_entry data;
+- struct {
+- struct mtk_flow_entry *base_flow;
+- struct hlist_node list;
+- } l2_data;
+- };
++ struct mtk_foe_entry data;
+ struct rhash_head node;
+ unsigned long cookie;
+ };
diff --git a/target/linux/generic/backport-6.6/751-04-v6.4-net-ethernet-mediatek-fix-ppe-flow-accounting-for-L2.patch b/target/linux/generic/backport-6.6/751-04-v6.4-net-ethernet-mediatek-fix-ppe-flow-accounting-for-L2.patch
new file mode 100644
index 0000000000..e20f94f1d4
--- /dev/null
+++ b/target/linux/generic/backport-6.6/751-04-v6.4-net-ethernet-mediatek-fix-ppe-flow-accounting-for-L2.patch
@@ -0,0 +1,342 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 23 Mar 2023 11:05:22 +0100
+Subject: [PATCH] net: ethernet: mediatek: fix ppe flow accounting for L2
+ flows
+
+For L2 flows, the packet/byte counters should report the sum of the
+counters of their subflows, both current and expired.
+In order to make this work, change the way that accounting data is tracked.
+Reset counters when a flow enters bind. Once it expires (or enters unbind),
+store the last counter value in struct mtk_flow_entry.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
+@@ -79,9 +79,9 @@ static int mtk_ppe_mib_wait_busy(struct
+ int ret;
+ u32 val;
+
+- ret = readl_poll_timeout(ppe->base + MTK_PPE_MIB_SER_CR, val,
+- !(val & MTK_PPE_MIB_SER_CR_ST),
+- 20, MTK_PPE_WAIT_TIMEOUT_US);
++ ret = readl_poll_timeout_atomic(ppe->base + MTK_PPE_MIB_SER_CR, val,
++ !(val & MTK_PPE_MIB_SER_CR_ST),
++ 20, MTK_PPE_WAIT_TIMEOUT_US);
+
+ if (ret)
+ dev_err(ppe->dev, "MIB table busy");
+@@ -89,17 +89,31 @@ static int mtk_ppe_mib_wait_busy(struct
+ return ret;
+ }
+
+-static int mtk_mib_entry_read(struct mtk_ppe *ppe, u16 index, u64 *bytes, u64 *packets)
++static inline struct mtk_foe_accounting *
++mtk_ppe_acct_data(struct mtk_ppe *ppe, u16 index)
++{
++ if (!ppe->acct_table)
++ return NULL;
++
++ return ppe->acct_table + index * sizeof(struct mtk_foe_accounting);
++}
++
++struct mtk_foe_accounting *mtk_ppe_mib_entry_read(struct mtk_ppe *ppe, u16 index)
+ {
+ u32 val, cnt_r0, cnt_r1, cnt_r2;
++ struct mtk_foe_accounting *acct;
+ int ret;
+
+ val = FIELD_PREP(MTK_PPE_MIB_SER_CR_ADDR, index) | MTK_PPE_MIB_SER_CR_ST;
+ ppe_w32(ppe, MTK_PPE_MIB_SER_CR, val);
+
++ acct = mtk_ppe_acct_data(ppe, index);
++ if (!acct)
++ return NULL;
++
+ ret = mtk_ppe_mib_wait_busy(ppe);
+ if (ret)
+- return ret;
++ return acct;
+
+ cnt_r0 = readl(ppe->base + MTK_PPE_MIB_SER_R0);
+ cnt_r1 = readl(ppe->base + MTK_PPE_MIB_SER_R1);
+@@ -108,19 +122,19 @@ static int mtk_mib_entry_read(struct mtk
+ if (mtk_is_netsys_v3_or_greater(ppe->eth)) {
+ /* 64 bit for each counter */
+ u32 cnt_r3 = readl(ppe->base + MTK_PPE_MIB_SER_R3);
+- *bytes = ((u64)cnt_r1 << 32) | cnt_r0;
+- *packets = ((u64)cnt_r3 << 32) | cnt_r2;
++ acct->bytes += ((u64)cnt_r1 << 32) | cnt_r0;
++ acct->packets += ((u64)cnt_r3 << 32) | cnt_r2;
+ } else {
+ /* 48 bit byte counter, 40 bit packet counter */
+ u32 byte_cnt_low = FIELD_GET(MTK_PPE_MIB_SER_R0_BYTE_CNT_LOW, cnt_r0);
+ u32 byte_cnt_high = FIELD_GET(MTK_PPE_MIB_SER_R1_BYTE_CNT_HIGH, cnt_r1);
+ u32 pkt_cnt_low = FIELD_GET(MTK_PPE_MIB_SER_R1_PKT_CNT_LOW, cnt_r1);
+ u32 pkt_cnt_high = FIELD_GET(MTK_PPE_MIB_SER_R2_PKT_CNT_HIGH, cnt_r2);
+- *bytes = ((u64)byte_cnt_high << 32) | byte_cnt_low;
+- *packets = (pkt_cnt_high << 16) | pkt_cnt_low;
++ acct->bytes += ((u64)byte_cnt_high << 32) | byte_cnt_low;
++ acct->packets += (pkt_cnt_high << 16) | pkt_cnt_low;
+ }
+
+- return 0;
++ return acct;
+ }
+
+ static void mtk_ppe_cache_clear(struct mtk_ppe *ppe)
+@@ -519,13 +533,6 @@ __mtk_foe_entry_clear(struct mtk_ppe *pp
+ hwe->ib1 |= FIELD_PREP(MTK_FOE_IB1_STATE, MTK_FOE_STATE_INVALID);
+ dma_wmb();
+ mtk_ppe_cache_clear(ppe);
+- if (ppe->accounting) {
+- struct mtk_foe_accounting *acct;
+-
+- acct = ppe->acct_table + entry->hash * sizeof(*acct);
+- acct->packets = 0;
+- acct->bytes = 0;
+- }
+ }
+ entry->hash = 0xffff;
+
+@@ -550,11 +557,14 @@ static int __mtk_foe_entry_idle_time(str
+ }
+
+ static bool
+-mtk_flow_entry_update(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
++mtk_flow_entry_update(struct mtk_ppe *ppe, struct mtk_flow_entry *entry,
++ u64 *packets, u64 *bytes)
+ {
++ struct mtk_foe_accounting *acct;
+ struct mtk_foe_entry foe = {};
+ struct mtk_foe_entry *hwe;
+ u16 hash = entry->hash;
++ bool ret = false;
+ int len;
+
+ if (hash == 0xffff)
+@@ -565,18 +575,35 @@ mtk_flow_entry_update(struct mtk_ppe *pp
+ memcpy(&foe, hwe, len);
+
+ if (!mtk_flow_entry_match(ppe->eth, entry, &foe, len) ||
+- FIELD_GET(MTK_FOE_IB1_STATE, foe.ib1) != MTK_FOE_STATE_BIND)
+- return false;
++ FIELD_GET(MTK_FOE_IB1_STATE, foe.ib1) != MTK_FOE_STATE_BIND) {
++ acct = mtk_ppe_acct_data(ppe, hash);
++ if (acct) {
++ entry->prev_packets += acct->packets;
++ entry->prev_bytes += acct->bytes;
++ }
++
++ goto out;
++ }
+
+ entry->data.ib1 = foe.ib1;
++ acct = mtk_ppe_mib_entry_read(ppe, hash);
++ ret = true;
++
++out:
++ if (acct) {
++ *packets += acct->packets;
++ *bytes += acct->bytes;
++ }
+
+- return true;
++ return ret;
+ }
+
+ static void
+ mtk_flow_entry_update_l2(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
+ {
+ u32 ib1_ts_mask = mtk_get_ib1_ts_mask(ppe->eth);
++ u64 *packets = &entry->packets;
++ u64 *bytes = &entry->bytes;
+ struct mtk_flow_entry *cur;
+ struct hlist_node *tmp;
+ int idle;
+@@ -585,7 +612,9 @@ mtk_flow_entry_update_l2(struct mtk_ppe
+ hlist_for_each_entry_safe(cur, tmp, &entry->l2_flows, l2_list) {
+ int cur_idle;
+
+- if (!mtk_flow_entry_update(ppe, cur)) {
++ if (!mtk_flow_entry_update(ppe, cur, packets, bytes)) {
++ entry->prev_packets += cur->prev_packets;
++ entry->prev_bytes += cur->prev_bytes;
+ __mtk_foe_entry_clear(ppe, entry, false);
+ continue;
+ }
+@@ -600,10 +629,29 @@ mtk_flow_entry_update_l2(struct mtk_ppe
+ }
+ }
+
++void mtk_foe_entry_get_stats(struct mtk_ppe *ppe, struct mtk_flow_entry *entry,
++ int *idle)
++{
++ entry->packets = entry->prev_packets;
++ entry->bytes = entry->prev_bytes;
++
++ spin_lock_bh(&ppe_lock);
++
++ if (entry->type == MTK_FLOW_TYPE_L2)
++ mtk_flow_entry_update_l2(ppe, entry);
++ else
++ mtk_flow_entry_update(ppe, entry, &entry->packets, &entry->bytes);
++
++ *idle = __mtk_foe_entry_idle_time(ppe, entry->data.ib1);
++
++ 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_accounting *acct;
+ struct mtk_eth *eth = ppe->eth;
+ u16 timestamp = mtk_eth_timestamp(eth);
+ struct mtk_foe_entry *hwe;
+@@ -634,6 +682,12 @@ __mtk_foe_entry_commit(struct mtk_ppe *p
+
+ dma_wmb();
+
++ acct = mtk_ppe_mib_entry_read(ppe, hash);
++ if (acct) {
++ acct->packets = 0;
++ acct->bytes = 0;
++ }
++
+ mtk_ppe_cache_clear(ppe);
+ }
+
+@@ -796,21 +850,6 @@ out:
+ spin_unlock_bh(&ppe_lock);
+ }
+
+-int mtk_foe_entry_idle_time(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
+-{
+- int idle;
+-
+- spin_lock_bh(&ppe_lock);
+- if (entry->type == MTK_FLOW_TYPE_L2)
+- mtk_flow_entry_update_l2(ppe, entry);
+- else
+- mtk_flow_entry_update(ppe, entry);
+- idle = __mtk_foe_entry_idle_time(ppe, entry->data.ib1);
+- spin_unlock_bh(&ppe_lock);
+-
+- return idle;
+-}
+-
+ int mtk_ppe_prepare_reset(struct mtk_ppe *ppe)
+ {
+ if (!ppe)
+@@ -838,32 +877,6 @@ int mtk_ppe_prepare_reset(struct mtk_ppe
+ return mtk_ppe_wait_busy(ppe);
+ }
+
+-struct mtk_foe_accounting *mtk_foe_entry_get_mib(struct mtk_ppe *ppe, u32 index,
+- struct mtk_foe_accounting *diff)
+-{
+- struct mtk_foe_accounting *acct;
+- int size = sizeof(struct mtk_foe_accounting);
+- u64 bytes, packets;
+-
+- if (!ppe->accounting)
+- return NULL;
+-
+- if (mtk_mib_entry_read(ppe, index, &bytes, &packets))
+- return NULL;
+-
+- acct = ppe->acct_table + index * size;
+-
+- acct->bytes += bytes;
+- acct->packets += packets;
+-
+- if (diff) {
+- diff->bytes = bytes;
+- diff->packets = packets;
+- }
+-
+- return acct;
+-}
+-
+ struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base, int index)
+ {
+ bool accounting = eth->soc->has_accounting;
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
+@@ -304,6 +304,8 @@ struct mtk_flow_entry {
+ struct mtk_foe_entry data;
+ struct rhash_head node;
+ unsigned long cookie;
++ u64 prev_packets, prev_bytes;
++ u64 packets, bytes;
+ };
+
+ struct mtk_mib_entry {
+@@ -348,6 +350,7 @@ void mtk_ppe_deinit(struct mtk_eth *eth)
+ void mtk_ppe_start(struct mtk_ppe *ppe);
+ int mtk_ppe_stop(struct mtk_ppe *ppe);
+ int mtk_ppe_prepare_reset(struct mtk_ppe *ppe);
++struct mtk_foe_accounting *mtk_ppe_mib_entry_read(struct mtk_ppe *ppe, u16 index);
+
+ void __mtk_ppe_check_skb(struct mtk_ppe *ppe, struct sk_buff *skb, u16 hash);
+
+@@ -396,9 +399,8 @@ int mtk_foe_entry_set_queue(struct mtk_e
+ unsigned int queue);
+ int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
+ void mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
+-int mtk_foe_entry_idle_time(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
+ int mtk_ppe_debugfs_init(struct mtk_ppe *ppe, int index);
+-struct mtk_foe_accounting *mtk_foe_entry_get_mib(struct mtk_ppe *ppe, u32 index,
+- struct mtk_foe_accounting *diff);
++void mtk_foe_entry_get_stats(struct mtk_ppe *ppe, struct mtk_flow_entry *entry,
++ int *idle);
+
+ #endif
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c
+@@ -96,7 +96,7 @@ mtk_ppe_debugfs_foe_show(struct seq_file
+ if (bind && state != MTK_FOE_STATE_BIND)
+ continue;
+
+- acct = mtk_foe_entry_get_mib(ppe, i, NULL);
++ acct = mtk_ppe_mib_entry_read(ppe, i);
+
+ type = mtk_get_ib1_pkt_type(ppe->eth, entry->ib1);
+ seq_printf(m, "%05x %s %7s", i,
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+@@ -501,24 +501,21 @@ static int
+ mtk_flow_offload_stats(struct mtk_eth *eth, struct flow_cls_offload *f)
+ {
+ struct mtk_flow_entry *entry;
+- struct mtk_foe_accounting diff;
+- u32 idle;
++ u64 packets, bytes;
++ int idle;
+
+ entry = rhashtable_lookup(&eth->flow_table, &f->cookie,
+ mtk_flow_ht_params);
+ if (!entry)
+ return -ENOENT;
+
+- idle = mtk_foe_entry_idle_time(eth->ppe[entry->ppe_index], entry);
++ packets = entry->packets;
++ bytes = entry->bytes;
++ mtk_foe_entry_get_stats(eth->ppe[entry->ppe_index], entry, &idle);
++ f->stats.pkts += entry->packets - packets;
++ f->stats.bytes += entry->bytes - bytes;
+ f->stats.lastused = jiffies - idle * HZ;
+
+- if (entry->hash != 0xFFFF &&
+- mtk_foe_entry_get_mib(eth->ppe[entry->ppe_index], entry->hash,
+- &diff)) {
+- f->stats.pkts += diff.packets;
+- f->stats.bytes += diff.bytes;
+- }
+-
+ return 0;
+ }
+
diff --git a/target/linux/generic/backport-6.6/752-01-v6.6-net-ethernet-mtk_wed-add-some-more-info-in-wed_txinf.patch b/target/linux/generic/backport-6.6/752-01-v6.6-net-ethernet-mtk_wed-add-some-more-info-in-wed_txinf.patch
new file mode 100644
index 0000000000..a224b62624
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-01-v6.6-net-ethernet-mtk_wed-add-some-more-info-in-wed_txinf.patch
@@ -0,0 +1,45 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sun, 27 Aug 2023 19:31:41 +0200
+Subject: [PATCH] net: ethernet: mtk_wed: add some more info in wed_txinfo_show
+ handler
+
+Add some new info in Wireless Ethernet Dispatcher wed_txinfo_show
+debugfs handler useful during debugging.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://lore.kernel.org/r/3390292655d568180b73d2a25576f61aa63310e5.1693157377.git.lorenzo@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
+@@ -127,8 +127,17 @@ wed_txinfo_show(struct seq_file *s, void
+ DUMP_WDMA_RING(WDMA_RING_RX(0)),
+ DUMP_WDMA_RING(WDMA_RING_RX(1)),
+
+- DUMP_STR("TX FREE"),
++ DUMP_STR("WED TX FREE"),
+ DUMP_WED(WED_RX_MIB(0)),
++ DUMP_WED_RING(WED_RING_RX(0)),
++ DUMP_WED(WED_WPDMA_RX_COHERENT_MIB(0)),
++ DUMP_WED(WED_RX_MIB(1)),
++ DUMP_WED_RING(WED_RING_RX(1)),
++ DUMP_WED(WED_WPDMA_RX_COHERENT_MIB(1)),
++
++ DUMP_STR("WED WPDMA TX FREE"),
++ DUMP_WED_RING(WED_WPDMA_RING_RX(0)),
++ DUMP_WED_RING(WED_WPDMA_RING_RX(1)),
+ };
+ struct mtk_wed_hw *hw = s->private;
+ struct mtk_wed_device *dev = hw->wed_dev;
+--- a/drivers/net/ethernet/mediatek/mtk_wed_regs.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
+@@ -266,6 +266,8 @@ struct mtk_wdma_desc {
+
+ #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_RX_MIB(_n) (0x5e0 + (_n) * 4)
++#define MTK_WED_WPDMA_RX_COHERENT_MIB(_n) (0x5f0 + (_n) * 4)
+
+ #define MTK_WED_WPDMA_RING_TX(_n) (0x600 + (_n) * 0x10)
+ #define MTK_WED_WPDMA_RING_RX(_n) (0x700 + (_n) * 0x10)
diff --git a/target/linux/generic/backport-6.6/752-02-v6.6-net-ethernet-mtk_wed-minor-change-in-wed_-tx-rx-info.patch b/target/linux/generic/backport-6.6/752-02-v6.6-net-ethernet-mtk_wed-minor-change-in-wed_-tx-rx-info.patch
new file mode 100644
index 0000000000..df6edfdf94
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-02-v6.6-net-ethernet-mtk_wed-minor-change-in-wed_-tx-rx-info.patch
@@ -0,0 +1,47 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sun, 27 Aug 2023 19:33:47 +0200
+Subject: [PATCH] net: ethernet: mtk_wed: minor change in wed_{tx,rx}info_show
+
+No functional changes, just cosmetic ones.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://lore.kernel.org/r/71e046c72a978745f0435af265dda610aa9bfbcf.1693157578.git.lorenzo@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
+@@ -84,7 +84,6 @@ dump_wed_regs(struct seq_file *s, struct
+ }
+ }
+
+-
+ static int
+ wed_txinfo_show(struct seq_file *s, void *data)
+ {
+@@ -142,10 +141,8 @@ wed_txinfo_show(struct seq_file *s, void
+ 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));
++ if (dev)
++ dump_wed_regs(s, dev, regs, ARRAY_SIZE(regs));
+
+ return 0;
+ }
+@@ -217,10 +214,8 @@ wed_rxinfo_show(struct seq_file *s, void
+ 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));
++ if (dev)
++ dump_wed_regs(s, dev, regs, ARRAY_SIZE(regs));
+
+ return 0;
+ }
diff --git a/target/linux/generic/backport-6.6/752-03-v6.6-net-ethernet-mtk_eth_soc-rely-on-mtk_pse_port-defini.patch b/target/linux/generic/backport-6.6/752-03-v6.6-net-ethernet-mtk_eth_soc-rely-on-mtk_pse_port-defini.patch
new file mode 100644
index 0000000000..0bf9dea24f
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-03-v6.6-net-ethernet-mtk_eth_soc-rely-on-mtk_pse_port-defini.patch
@@ -0,0 +1,29 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Tue, 12 Sep 2023 10:22:56 +0200
+Subject: [PATCH] net: ethernet: mtk_eth_soc: rely on mtk_pse_port definitions
+ in mtk_flow_set_output_device
+
+Similar to ethernet ports, rely on mtk_pse_port definitions for
+pse wdma ports as well.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://lore.kernel.org/r/b86bdb717e963e3246c1dec5f736c810703cf056.1694506814.git.lorenzo@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+@@ -196,10 +196,10 @@ mtk_flow_set_output_device(struct mtk_et
+ if (mtk_is_netsys_v2_or_greater(eth)) {
+ switch (info.wdma_idx) {
+ case 0:
+- pse_port = 8;
++ pse_port = PSE_WDMA0_PORT;
+ break;
+ case 1:
+- pse_port = 9;
++ pse_port = PSE_WDMA1_PORT;
+ break;
+ default:
+ return -EINVAL;
diff --git a/target/linux/generic/backport-6.6/752-04-v6.6-net-ethernet-mtk_wed-check-update_wo_rx_stats-in-mtk.patch b/target/linux/generic/backport-6.6/752-04-v6.6-net-ethernet-mtk_wed-check-update_wo_rx_stats-in-mtk.patch
new file mode 100644
index 0000000000..c99e1334d4
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-04-v6.6-net-ethernet-mtk_wed-check-update_wo_rx_stats-in-mtk.patch
@@ -0,0 +1,26 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Tue, 12 Sep 2023 10:28:00 +0200
+Subject: [PATCH] net: ethernet: mtk_wed: check update_wo_rx_stats in
+ mtk_wed_update_rx_stats()
+
+Check if update_wo_rx_stats function pointer is properly set in
+mtk_wed_update_rx_stats routine before accessing it.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://lore.kernel.org/r/b0d233386e059bccb59f18f69afb79a7806e5ded.1694507226.git.lorenzo@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
+@@ -68,6 +68,9 @@ mtk_wed_update_rx_stats(struct mtk_wed_d
+ struct mtk_wed_wo_rx_stats *stats;
+ int i;
+
++ if (!wed->wlan.update_wo_rx_stats)
++ return;
++
+ if (count * sizeof(*stats) > skb->len - sizeof(u32))
+ return;
+
diff --git a/target/linux/generic/backport-6.6/752-05-v6.7-net-ethernet-mtk_wed-do-not-assume-offload-callbacks.patch b/target/linux/generic/backport-6.6/752-05-v6.7-net-ethernet-mtk_wed-do-not-assume-offload-callbacks.patch
new file mode 100644
index 0000000000..cd7fb92e20
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-05-v6.7-net-ethernet-mtk_wed-do-not-assume-offload-callbacks.patch
@@ -0,0 +1,68 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Wed, 13 Sep 2023 20:42:47 +0200
+Subject: [PATCH] net: ethernet: mtk_wed: do not assume offload callbacks are
+ always set
+
+Check if wlan.offload_enable and wlan.offload_disable callbacks are set
+in mtk_wed_flow_add/mtk_wed_flow_remove since mt7996 will not rely
+on them.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Reviewed-by: Simon Horman <horms@kernel.org>
+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
+@@ -1712,19 +1712,20 @@ mtk_wed_irq_set_mask(struct mtk_wed_devi
+ int mtk_wed_flow_add(int index)
+ {
+ struct mtk_wed_hw *hw = hw_list[index];
+- int ret;
++ int ret = 0;
+
+- if (!hw || !hw->wed_dev)
+- return -ENODEV;
++ mutex_lock(&hw_lock);
+
+- if (hw->num_flows) {
+- hw->num_flows++;
+- return 0;
++ if (!hw || !hw->wed_dev) {
++ ret = -ENODEV;
++ goto out;
+ }
+
+- mutex_lock(&hw_lock);
+- if (!hw->wed_dev) {
+- ret = -ENODEV;
++ if (!hw->wed_dev->wlan.offload_enable)
++ goto out;
++
++ if (hw->num_flows) {
++ hw->num_flows++;
+ goto out;
+ }
+
+@@ -1743,14 +1744,15 @@ void mtk_wed_flow_remove(int index)
+ {
+ struct mtk_wed_hw *hw = hw_list[index];
+
+- if (!hw)
+- return;
++ mutex_lock(&hw_lock);
+
+- if (--hw->num_flows)
+- return;
++ if (!hw || !hw->wed_dev)
++ goto out;
+
+- mutex_lock(&hw_lock);
+- if (!hw->wed_dev)
++ if (!hw->wed_dev->wlan.offload_disable)
++ goto out;
++
++ if (--hw->num_flows)
+ goto out;
+
+ hw->wed_dev->wlan.offload_disable(hw->wed_dev);
diff --git a/target/linux/generic/backport-6.6/752-06-v6.7-net-ethernet-mtk_wed-introduce-versioning-utility-ro.patch b/target/linux/generic/backport-6.6/752-06-v6.7-net-ethernet-mtk_wed-introduce-versioning-utility-ro.patch
new file mode 100644
index 0000000000..2948188650
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-06-v6.7-net-ethernet-mtk_wed-introduce-versioning-utility-ro.patch
@@ -0,0 +1,232 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Mon, 18 Sep 2023 12:29:05 +0200
+Subject: [PATCH] net: ethernet: mtk_wed: introduce versioning utility routines
+
+Similar to mtk_eth_soc, introduce the following wed versioning
+utility routines:
+- mtk_wed_is_v1
+- mtk_wed_is_v2
+
+This is a preliminary patch to introduce WED support for MT7988 SoC
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -277,7 +277,7 @@ mtk_wed_assign(struct mtk_wed_device *de
+ if (!hw->wed_dev)
+ goto out;
+
+- if (hw->version == 1)
++ if (mtk_wed_is_v1(hw))
+ return NULL;
+
+ /* MT7986 WED devices do not have any pcie slot restrictions */
+@@ -358,7 +358,7 @@ mtk_wed_tx_buffer_alloc(struct mtk_wed_d
+ desc->buf0 = cpu_to_le32(buf_phys);
+ desc->buf1 = cpu_to_le32(buf_phys + txd_size);
+
+- if (dev->hw->version == 1)
++ if (mtk_wed_is_v1(dev->hw))
+ ctrl = FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN0, txd_size) |
+ FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN1,
+ MTK_WED_BUF_SIZE - txd_size) |
+@@ -497,7 +497,7 @@ mtk_wed_set_ext_int(struct mtk_wed_devic
+ {
+ u32 mask = MTK_WED_EXT_INT_STATUS_ERROR_MASK;
+
+- if (dev->hw->version == 1)
++ if (mtk_wed_is_v1(dev->hw))
+ mask |= MTK_WED_EXT_INT_STATUS_TX_DRV_R_RESP_ERR;
+ else
+ mask |= MTK_WED_EXT_INT_STATUS_RX_FBUF_LO_TH |
+@@ -576,7 +576,7 @@ mtk_wed_dma_disable(struct mtk_wed_devic
+ MTK_WDMA_GLO_CFG_RX_INFO1_PRERES |
+ MTK_WDMA_GLO_CFG_RX_INFO2_PRERES);
+
+- if (dev->hw->version == 1) {
++ if (mtk_wed_is_v1(dev->hw)) {
+ regmap_write(dev->hw->mirror, dev->hw->index * 4, 0);
+ wdma_clr(dev, MTK_WDMA_GLO_CFG,
+ MTK_WDMA_GLO_CFG_RX_INFO3_PRERES);
+@@ -605,7 +605,7 @@ mtk_wed_stop(struct mtk_wed_device *dev)
+ wdma_w32(dev, MTK_WDMA_INT_GRP2, 0);
+ wed_w32(dev, MTK_WED_WPDMA_INT_MASK, 0);
+
+- if (dev->hw->version == 1)
++ if (mtk_wed_is_v1(dev->hw))
+ return;
+
+ wed_w32(dev, MTK_WED_EXT_INT_MASK1, 0);
+@@ -624,7 +624,7 @@ mtk_wed_deinit(struct mtk_wed_device *de
+ MTK_WED_CTRL_WED_TX_BM_EN |
+ MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
+
+- if (dev->hw->version == 1)
++ if (mtk_wed_is_v1(dev->hw))
+ return;
+
+ wed_clr(dev, MTK_WED_CTRL,
+@@ -730,7 +730,7 @@ mtk_wed_bus_init(struct mtk_wed_device *
+ static void
+ mtk_wed_set_wpdma(struct mtk_wed_device *dev)
+ {
+- if (dev->hw->version == 1) {
++ if (mtk_wed_is_v1(dev->hw)) {
+ wed_w32(dev, MTK_WED_WPDMA_CFG_BASE, dev->wlan.wpdma_phys);
+ } else {
+ mtk_wed_bus_init(dev);
+@@ -761,7 +761,7 @@ mtk_wed_hw_init_early(struct mtk_wed_dev
+ MTK_WED_WDMA_GLO_CFG_IDLE_DMAD_SUPPLY;
+ wed_m32(dev, MTK_WED_WDMA_GLO_CFG, mask, set);
+
+- if (dev->hw->version == 1) {
++ if (mtk_wed_is_v1(dev->hw)) {
+ u32 offset = dev->hw->index ? 0x04000400 : 0;
+
+ wdma_set(dev, MTK_WDMA_GLO_CFG,
+@@ -934,7 +934,7 @@ mtk_wed_hw_init(struct mtk_wed_device *d
+
+ wed_w32(dev, MTK_WED_TX_BM_BUF_LEN, MTK_WED_PKT_SIZE);
+
+- if (dev->hw->version == 1) {
++ if (mtk_wed_is_v1(dev->hw)) {
+ wed_w32(dev, MTK_WED_TX_BM_TKID,
+ FIELD_PREP(MTK_WED_TX_BM_TKID_START,
+ dev->wlan.token_start) |
+@@ -967,7 +967,7 @@ mtk_wed_hw_init(struct mtk_wed_device *d
+
+ mtk_wed_reset(dev, MTK_WED_RESET_TX_BM);
+
+- if (dev->hw->version == 1) {
++ if (mtk_wed_is_v1(dev->hw)) {
+ wed_set(dev, MTK_WED_CTRL,
+ MTK_WED_CTRL_WED_TX_BM_EN |
+ MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
+@@ -1217,7 +1217,7 @@ mtk_wed_reset_dma(struct mtk_wed_device
+ }
+
+ dev->init_done = false;
+- if (dev->hw->version == 1)
++ if (mtk_wed_is_v1(dev->hw))
+ return;
+
+ if (!busy) {
+@@ -1343,7 +1343,7 @@ mtk_wed_configure_irq(struct mtk_wed_dev
+ MTK_WED_CTRL_WED_TX_BM_EN |
+ MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
+
+- if (dev->hw->version == 1) {
++ if (mtk_wed_is_v1(dev->hw)) {
+ wed_w32(dev, MTK_WED_PCIE_INT_TRIGGER,
+ MTK_WED_PCIE_INT_TRIGGER_STATUS);
+
+@@ -1416,7 +1416,7 @@ mtk_wed_dma_enable(struct mtk_wed_device
+ MTK_WDMA_GLO_CFG_RX_INFO1_PRERES |
+ MTK_WDMA_GLO_CFG_RX_INFO2_PRERES);
+
+- if (dev->hw->version == 1) {
++ if (mtk_wed_is_v1(dev->hw)) {
+ wdma_set(dev, MTK_WDMA_GLO_CFG,
+ MTK_WDMA_GLO_CFG_RX_INFO3_PRERES);
+ } else {
+@@ -1465,7 +1465,7 @@ mtk_wed_start(struct mtk_wed_device *dev
+
+ mtk_wed_set_ext_int(dev, true);
+
+- if (dev->hw->version == 1) {
++ if (mtk_wed_is_v1(dev->hw)) {
+ u32 val = dev->wlan.wpdma_phys | MTK_PCIE_MIRROR_MAP_EN |
+ FIELD_PREP(MTK_PCIE_MIRROR_MAP_WED_ID,
+ dev->hw->index);
+@@ -1550,7 +1550,7 @@ mtk_wed_attach(struct mtk_wed_device *de
+ }
+
+ mtk_wed_hw_init_early(dev);
+- if (hw->version == 1) {
++ if (mtk_wed_is_v1(hw)) {
+ regmap_update_bits(hw->hifsys, HIFSYS_DMA_AG_MAP,
+ BIT(hw->index), 0);
+ } else {
+@@ -1618,7 +1618,7 @@ 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, index = dev->hw->version == 1;
++ int i, index = mtk_wed_is_v1(dev->hw);
+
+ /*
+ * For txfree event handling, the same DMA ring is shared between WED
+@@ -1676,7 +1676,7 @@ mtk_wed_irq_get(struct mtk_wed_device *d
+ {
+ u32 val, ext_mask = MTK_WED_EXT_INT_STATUS_ERROR_MASK;
+
+- if (dev->hw->version == 1)
++ if (mtk_wed_is_v1(dev->hw))
+ ext_mask |= MTK_WED_EXT_INT_STATUS_TX_DRV_R_RESP_ERR;
+ else
+ ext_mask |= MTK_WED_EXT_INT_STATUS_RX_FBUF_LO_TH |
+@@ -1843,7 +1843,7 @@ mtk_wed_setup_tc(struct mtk_wed_device *
+ {
+ struct mtk_wed_hw *hw = wed->hw;
+
+- if (hw->version < 2)
++ if (mtk_wed_is_v1(hw))
+ return -EOPNOTSUPP;
+
+ switch (type) {
+@@ -1917,9 +1917,9 @@ void mtk_wed_add_hw(struct device_node *
+ hw->wdma = wdma;
+ hw->index = index;
+ hw->irq = irq;
+- hw->version = mtk_is_netsys_v1(eth) ? 1 : 2;
++ hw->version = eth->soc->version;
+
+- if (hw->version == 1) {
++ if (mtk_wed_is_v1(hw)) {
+ hw->mirror = syscon_regmap_lookup_by_phandle(eth_np,
+ "mediatek,pcie-mirror");
+ hw->hifsys = syscon_regmap_lookup_by_phandle(eth_np,
+--- a/drivers/net/ethernet/mediatek/mtk_wed.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed.h
+@@ -40,6 +40,16 @@ struct mtk_wdma_info {
+ };
+
+ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
++static inline bool mtk_wed_is_v1(struct mtk_wed_hw *hw)
++{
++ return hw->version == 1;
++}
++
++static inline bool mtk_wed_is_v2(struct mtk_wed_hw *hw)
++{
++ return hw->version == 2;
++}
++
+ static inline void
+ wed_w32(struct mtk_wed_device *dev, u32 reg, u32 val)
+ {
+--- a/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
+@@ -263,7 +263,7 @@ void mtk_wed_hw_add_debugfs(struct mtk_w
+ 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);
+- if (hw->version != 1)
++ if (!mtk_wed_is_v1(hw))
+ debugfs_create_file_unsafe("rxinfo", 0400, dir, hw,
+ &wed_rxinfo_fops);
+ }
+--- a/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
+@@ -207,7 +207,7 @@ int mtk_wed_mcu_msg_update(struct mtk_we
+ {
+ struct mtk_wed_wo *wo = dev->hw->wed_wo;
+
+- if (dev->hw->version == 1)
++ if (mtk_wed_is_v1(dev->hw))
+ return 0;
+
+ if (WARN_ON(!wo))
diff --git a/target/linux/generic/backport-6.6/752-07-v6.7-net-ethernet-mtk_wed-do-not-configure-rx-offload-if-.patch b/target/linux/generic/backport-6.6/752-07-v6.7-net-ethernet-mtk_wed-do-not-configure-rx-offload-if-.patch
new file mode 100644
index 0000000000..bc34aa33a9
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-07-v6.7-net-ethernet-mtk_wed-do-not-configure-rx-offload-if-.patch
@@ -0,0 +1,234 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Mon, 18 Sep 2023 12:29:06 +0200
+Subject: [PATCH] net: ethernet: mtk_wed: do not configure rx offload if not
+ supported
+
+Check if rx offload is supported running mtk_wed_get_rx_capa routine
+before configuring it. This is a preliminary patch to introduce Wireless
+Ethernet Dispatcher (WED) support for MT7988 SoC.
+
+Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -605,7 +605,7 @@ mtk_wed_stop(struct mtk_wed_device *dev)
+ wdma_w32(dev, MTK_WDMA_INT_GRP2, 0);
+ wed_w32(dev, MTK_WED_WPDMA_INT_MASK, 0);
+
+- if (mtk_wed_is_v1(dev->hw))
++ if (!mtk_wed_get_rx_capa(dev))
+ return;
+
+ wed_w32(dev, MTK_WED_EXT_INT_MASK1, 0);
+@@ -732,16 +732,21 @@ mtk_wed_set_wpdma(struct mtk_wed_device
+ {
+ if (mtk_wed_is_v1(dev->hw)) {
+ wed_w32(dev, MTK_WED_WPDMA_CFG_BASE, dev->wlan.wpdma_phys);
+- } else {
+- mtk_wed_bus_init(dev);
+-
+- wed_w32(dev, MTK_WED_WPDMA_CFG_BASE, dev->wlan.wpdma_int);
+- wed_w32(dev, MTK_WED_WPDMA_CFG_INT_MASK, dev->wlan.wpdma_mask);
+- wed_w32(dev, MTK_WED_WPDMA_CFG_TX, dev->wlan.wpdma_tx);
+- wed_w32(dev, MTK_WED_WPDMA_CFG_TX_FREE, dev->wlan.wpdma_txfree);
+- wed_w32(dev, MTK_WED_WPDMA_RX_GLO_CFG, dev->wlan.wpdma_rx_glo);
+- wed_w32(dev, MTK_WED_WPDMA_RX_RING, dev->wlan.wpdma_rx);
++ return;
+ }
++
++ mtk_wed_bus_init(dev);
++
++ wed_w32(dev, MTK_WED_WPDMA_CFG_BASE, dev->wlan.wpdma_int);
++ wed_w32(dev, MTK_WED_WPDMA_CFG_INT_MASK, dev->wlan.wpdma_mask);
++ wed_w32(dev, MTK_WED_WPDMA_CFG_TX, dev->wlan.wpdma_tx);
++ wed_w32(dev, MTK_WED_WPDMA_CFG_TX_FREE, dev->wlan.wpdma_txfree);
++
++ if (!mtk_wed_get_rx_capa(dev))
++ return;
++
++ wed_w32(dev, MTK_WED_WPDMA_RX_GLO_CFG, dev->wlan.wpdma_rx_glo);
++ wed_w32(dev, MTK_WED_WPDMA_RX_RING, dev->wlan.wpdma_rx);
+ }
+
+ static void
+@@ -973,15 +978,17 @@ mtk_wed_hw_init(struct mtk_wed_device *d
+ MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
+ } else {
+ wed_clr(dev, MTK_WED_TX_TKID_CTRL, MTK_WED_TX_TKID_CTRL_PAUSE);
+- /* rx hw init */
+- wed_w32(dev, MTK_WED_WPDMA_RX_D_RST_IDX,
+- MTK_WED_WPDMA_RX_D_RST_CRX_IDX |
+- MTK_WED_WPDMA_RX_D_RST_DRV_IDX);
+- wed_w32(dev, MTK_WED_WPDMA_RX_D_RST_IDX, 0);
+-
+- mtk_wed_rx_buffer_hw_init(dev);
+- mtk_wed_rro_hw_init(dev);
+- mtk_wed_route_qm_hw_init(dev);
++ if (mtk_wed_get_rx_capa(dev)) {
++ /* rx hw init */
++ wed_w32(dev, MTK_WED_WPDMA_RX_D_RST_IDX,
++ MTK_WED_WPDMA_RX_D_RST_CRX_IDX |
++ MTK_WED_WPDMA_RX_D_RST_DRV_IDX);
++ wed_w32(dev, MTK_WED_WPDMA_RX_D_RST_IDX, 0);
++
++ mtk_wed_rx_buffer_hw_init(dev);
++ mtk_wed_rro_hw_init(dev);
++ mtk_wed_route_qm_hw_init(dev);
++ }
+ }
+
+ wed_clr(dev, MTK_WED_TX_BM_CTRL, MTK_WED_TX_BM_CTRL_PAUSE);
+@@ -1353,8 +1360,6 @@ mtk_wed_configure_irq(struct mtk_wed_dev
+
+ wed_clr(dev, MTK_WED_WDMA_INT_CTRL, wdma_mask);
+ } else {
+- wdma_mask |= FIELD_PREP(MTK_WDMA_INT_MASK_TX_DONE,
+- GENMASK(1, 0));
+ /* initail tx interrupt trigger */
+ wed_w32(dev, MTK_WED_WPDMA_INT_CTRL_TX,
+ MTK_WED_WPDMA_INT_CTRL_TX0_DONE_EN |
+@@ -1373,15 +1378,20 @@ mtk_wed_configure_irq(struct mtk_wed_dev
+ FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_TX_FREE_DONE_TRIG,
+ dev->wlan.txfree_tbit));
+
+- wed_w32(dev, MTK_WED_WPDMA_INT_CTRL_RX,
+- MTK_WED_WPDMA_INT_CTRL_RX0_EN |
+- MTK_WED_WPDMA_INT_CTRL_RX0_CLR |
+- MTK_WED_WPDMA_INT_CTRL_RX1_EN |
+- MTK_WED_WPDMA_INT_CTRL_RX1_CLR |
+- FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_RX0_DONE_TRIG,
+- dev->wlan.rx_tbit[0]) |
+- FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_RX1_DONE_TRIG,
+- dev->wlan.rx_tbit[1]));
++ if (mtk_wed_get_rx_capa(dev)) {
++ wed_w32(dev, MTK_WED_WPDMA_INT_CTRL_RX,
++ MTK_WED_WPDMA_INT_CTRL_RX0_EN |
++ MTK_WED_WPDMA_INT_CTRL_RX0_CLR |
++ MTK_WED_WPDMA_INT_CTRL_RX1_EN |
++ MTK_WED_WPDMA_INT_CTRL_RX1_CLR |
++ FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_RX0_DONE_TRIG,
++ dev->wlan.rx_tbit[0]) |
++ FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_RX1_DONE_TRIG,
++ dev->wlan.rx_tbit[1]));
++
++ wdma_mask |= FIELD_PREP(MTK_WDMA_INT_MASK_TX_DONE,
++ GENMASK(1, 0));
++ }
+
+ wed_w32(dev, MTK_WED_WDMA_INT_CLR, wdma_mask);
+ wed_set(dev, MTK_WED_WDMA_INT_CTRL,
+@@ -1400,6 +1410,8 @@ mtk_wed_configure_irq(struct mtk_wed_dev
+ static void
+ mtk_wed_dma_enable(struct mtk_wed_device *dev)
+ {
++ int i;
++
+ wed_set(dev, MTK_WED_WPDMA_INT_CTRL, MTK_WED_WPDMA_INT_CTRL_SUBRT_ADV);
+
+ wed_set(dev, MTK_WED_GLO_CFG,
+@@ -1419,33 +1431,33 @@ mtk_wed_dma_enable(struct mtk_wed_device
+ if (mtk_wed_is_v1(dev->hw)) {
+ wdma_set(dev, MTK_WDMA_GLO_CFG,
+ MTK_WDMA_GLO_CFG_RX_INFO3_PRERES);
+- } else {
+- int i;
++ return;
++ }
+
+- wed_set(dev, MTK_WED_WPDMA_CTRL,
+- MTK_WED_WPDMA_CTRL_SDL1_FIXED);
++ wed_set(dev, MTK_WED_WPDMA_CTRL,
++ MTK_WED_WPDMA_CTRL_SDL1_FIXED);
++ wed_set(dev, MTK_WED_WPDMA_GLO_CFG,
++ MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_PKT_PROC |
++ MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_CRX_SYNC);
++ wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
++ MTK_WED_WPDMA_GLO_CFG_TX_TKID_KEEP |
++ MTK_WED_WPDMA_GLO_CFG_TX_DMAD_DW3_PREV);
+
+- wed_set(dev, MTK_WED_WDMA_GLO_CFG,
+- MTK_WED_WDMA_GLO_CFG_TX_DRV_EN |
+- MTK_WED_WDMA_GLO_CFG_TX_DDONE_CHK);
++ if (!mtk_wed_get_rx_capa(dev))
++ return;
+
+- wed_set(dev, MTK_WED_WPDMA_GLO_CFG,
+- MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_PKT_PROC |
+- MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_CRX_SYNC);
+-
+- wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
+- MTK_WED_WPDMA_GLO_CFG_TX_TKID_KEEP |
+- MTK_WED_WPDMA_GLO_CFG_TX_DMAD_DW3_PREV);
++ wed_set(dev, MTK_WED_WDMA_GLO_CFG,
++ MTK_WED_WDMA_GLO_CFG_TX_DRV_EN |
++ MTK_WED_WDMA_GLO_CFG_TX_DDONE_CHK);
+
+- wed_set(dev, MTK_WED_WPDMA_RX_D_GLO_CFG,
+- MTK_WED_WPDMA_RX_D_RX_DRV_EN |
+- FIELD_PREP(MTK_WED_WPDMA_RX_D_RXD_READ_LEN, 0x18) |
+- FIELD_PREP(MTK_WED_WPDMA_RX_D_INIT_PHASE_RXEN_SEL,
+- 0x2));
++ wed_set(dev, MTK_WED_WPDMA_RX_D_GLO_CFG,
++ MTK_WED_WPDMA_RX_D_RX_DRV_EN |
++ FIELD_PREP(MTK_WED_WPDMA_RX_D_RXD_READ_LEN, 0x18) |
++ FIELD_PREP(MTK_WED_WPDMA_RX_D_INIT_PHASE_RXEN_SEL,
++ 0x2));
+
+- for (i = 0; i < MTK_WED_RX_QUEUES; i++)
+- mtk_wed_check_wfdma_rx_fill(dev, i);
+- }
++ for (i = 0; i < MTK_WED_RX_QUEUES; i++)
++ mtk_wed_check_wfdma_rx_fill(dev, i);
+ }
+
+ static void
+@@ -1472,7 +1484,7 @@ mtk_wed_start(struct mtk_wed_device *dev
+
+ val |= BIT(0) | (BIT(1) * !!dev->hw->index);
+ regmap_write(dev->hw->mirror, dev->hw->index * 4, val);
+- } else {
++ } else if (mtk_wed_get_rx_capa(dev)) {
+ /* driver set mid ready and only once */
+ wed_w32(dev, MTK_WED_EXT_INT_MASK1,
+ MTK_WED_EXT_INT_STATUS_WPDMA_MID_RDY);
+@@ -1484,7 +1496,6 @@ mtk_wed_start(struct mtk_wed_device *dev
+
+ if (mtk_wed_rro_cfg(dev))
+ return;
+-
+ }
+
+ mtk_wed_set_512_support(dev, dev->wlan.wcid_512);
+@@ -1550,13 +1561,14 @@ mtk_wed_attach(struct mtk_wed_device *de
+ }
+
+ mtk_wed_hw_init_early(dev);
+- if (mtk_wed_is_v1(hw)) {
++ if (mtk_wed_is_v1(hw))
+ regmap_update_bits(hw->hifsys, HIFSYS_DMA_AG_MAP,
+ BIT(hw->index), 0);
+- } else {
++ else
+ dev->rev_id = wed_r32(dev, MTK_WED_REV_ID);
++
++ if (mtk_wed_get_rx_capa(dev))
+ ret = mtk_wed_wo_init(hw);
+- }
+ out:
+ if (ret) {
+ dev_err(dev->hw->dev, "failed to attach wed device\n");
+--- a/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
+@@ -207,7 +207,7 @@ int mtk_wed_mcu_msg_update(struct mtk_we
+ {
+ struct mtk_wed_wo *wo = dev->hw->wed_wo;
+
+- if (mtk_wed_is_v1(dev->hw))
++ if (!mtk_wed_get_rx_capa(dev))
+ return 0;
+
+ if (WARN_ON(!wo))
diff --git a/target/linux/generic/backport-6.6/752-08-v6.7-net-ethernet-mtk_wed-rename-mtk_rxbm_desc-in-mtk_wed.patch b/target/linux/generic/backport-6.6/752-08-v6.7-net-ethernet-mtk_wed-rename-mtk_rxbm_desc-in-mtk_wed.patch
new file mode 100644
index 0000000000..d83434fb2c
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-08-v6.7-net-ethernet-mtk_wed-rename-mtk_rxbm_desc-in-mtk_wed.patch
@@ -0,0 +1,52 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Mon, 18 Sep 2023 12:29:07 +0200
+Subject: [PATCH] net: ethernet: mtk_wed: rename mtk_rxbm_desc in
+ mtk_wed_bm_desc
+
+Rename mtk_rxbm_desc structure in mtk_wed_bm_desc since it will be used
+even on tx side by MT7988 SoC.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -421,7 +421,7 @@ free_pagelist:
+ static int
+ mtk_wed_rx_buffer_alloc(struct mtk_wed_device *dev)
+ {
+- struct mtk_rxbm_desc *desc;
++ struct mtk_wed_bm_desc *desc;
+ dma_addr_t desc_phys;
+
+ dev->rx_buf_ring.size = dev->wlan.rx_nbuf;
+@@ -441,7 +441,7 @@ mtk_wed_rx_buffer_alloc(struct mtk_wed_d
+ static void
+ mtk_wed_free_rx_buffer(struct mtk_wed_device *dev)
+ {
+- struct mtk_rxbm_desc *desc = dev->rx_buf_ring.desc;
++ struct mtk_wed_bm_desc *desc = dev->rx_buf_ring.desc;
+
+ if (!desc)
+ return;
+--- a/include/linux/soc/mediatek/mtk_wed.h
++++ b/include/linux/soc/mediatek/mtk_wed.h
+@@ -45,7 +45,7 @@ enum mtk_wed_wo_cmd {
+ MTK_WED_WO_CMD_WED_END
+ };
+
+-struct mtk_rxbm_desc {
++struct mtk_wed_bm_desc {
+ __le32 buf0;
+ __le32 token;
+ } __packed __aligned(4);
+@@ -105,7 +105,7 @@ struct mtk_wed_device {
+ struct {
+ int size;
+ struct page_frag_cache rx_page;
+- struct mtk_rxbm_desc *desc;
++ struct mtk_wed_bm_desc *desc;
+ dma_addr_t desc_phys;
+ } rx_buf_ring;
+
diff --git a/target/linux/generic/backport-6.6/752-09-v6.7-net-ethernet-mtk_wed-introduce-mtk_wed_buf-structure.patch b/target/linux/generic/backport-6.6/752-09-v6.7-net-ethernet-mtk_wed-introduce-mtk_wed_buf-structure.patch
new file mode 100644
index 0000000000..8000a8759e
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-09-v6.7-net-ethernet-mtk_wed-introduce-mtk_wed_buf-structure.patch
@@ -0,0 +1,87 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Mon, 18 Sep 2023 12:29:08 +0200
+Subject: [PATCH] net: ethernet: mtk_wed: introduce mtk_wed_buf structure
+
+Introduce mtk_wed_buf structure to store both virtual and physical
+addresses allocated in mtk_wed_tx_buffer_alloc() routine. This is a
+preliminary patch to add WED support for MT7988 SoC since it relies on a
+different dma descriptor layout not storing page dma addresses.
+
+Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -299,9 +299,9 @@ out:
+ static int
+ mtk_wed_tx_buffer_alloc(struct mtk_wed_device *dev)
+ {
++ struct mtk_wed_buf *page_list;
+ 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;
+@@ -342,7 +342,8 @@ mtk_wed_tx_buffer_alloc(struct mtk_wed_d
+ return -ENOMEM;
+ }
+
+- page_list[page_idx++] = page;
++ page_list[page_idx].p = page;
++ page_list[page_idx++].phy_addr = page_phys;
+ dma_sync_single_for_cpu(dev->hw->dev, page_phys, PAGE_SIZE,
+ DMA_BIDIRECTIONAL);
+
+@@ -386,8 +387,8 @@ mtk_wed_tx_buffer_alloc(struct mtk_wed_d
+ static void
+ mtk_wed_free_tx_buffer(struct mtk_wed_device *dev)
+ {
++ struct mtk_wed_buf *page_list = dev->tx_buf_ring.pages;
+ struct mtk_wdma_desc *desc = dev->tx_buf_ring.desc;
+- void **page_list = dev->tx_buf_ring.pages;
+ int page_idx;
+ int i;
+
+@@ -399,13 +400,12 @@ mtk_wed_free_tx_buffer(struct mtk_wed_de
+
+ for (i = 0, page_idx = 0; i < dev->tx_buf_ring.size;
+ i += MTK_WED_BUF_PER_PAGE) {
+- void *page = page_list[page_idx++];
+- dma_addr_t buf_addr;
++ dma_addr_t buf_addr = page_list[page_idx].phy_addr;
++ void *page = page_list[page_idx++].p;
+
+ if (!page)
+ break;
+
+- buf_addr = le32_to_cpu(desc[i].buf0);
+ dma_unmap_page(dev->hw->dev, buf_addr, PAGE_SIZE,
+ DMA_BIDIRECTIONAL);
+ __free_page(page);
+--- a/include/linux/soc/mediatek/mtk_wed.h
++++ b/include/linux/soc/mediatek/mtk_wed.h
+@@ -76,6 +76,11 @@ struct mtk_wed_wo_rx_stats {
+ __le32 rx_drop_cnt;
+ };
+
++struct mtk_wed_buf {
++ void *p;
++ dma_addr_t phy_addr;
++};
++
+ struct mtk_wed_device {
+ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
+ const struct mtk_wed_ops *ops;
+@@ -97,7 +102,7 @@ struct mtk_wed_device {
+
+ struct {
+ int size;
+- void **pages;
++ struct mtk_wed_buf *pages;
+ struct mtk_wdma_desc *desc;
+ dma_addr_t desc_phys;
+ } tx_buf_ring;
diff --git a/target/linux/generic/backport-6.6/752-10-v6.7-net-ethernet-mtk_wed-move-mem_region-array-out-of-mt.patch b/target/linux/generic/backport-6.6/752-10-v6.7-net-ethernet-mtk_wed-move-mem_region-array-out-of-mt.patch
new file mode 100644
index 0000000000..98d782b1d0
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-10-v6.7-net-ethernet-mtk_wed-move-mem_region-array-out-of-mt.patch
@@ -0,0 +1,88 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Mon, 18 Sep 2023 12:29:09 +0200
+Subject: [PATCH] net: ethernet: mtk_wed: move mem_region array out of
+ mtk_wed_mcu_load_firmware
+
+Remove mtk_wed_wo_memory_region boot structure in mtk_wed_wo.
+This is a preliminary patch to introduce WED support for MT7988 SoC.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
+@@ -16,14 +16,30 @@
+ #include "mtk_wed_wo.h"
+ #include "mtk_wed.h"
+
++static struct mtk_wed_wo_memory_region mem_region[] = {
++ [MTK_WED_WO_REGION_EMI] = {
++ .name = "wo-emi",
++ },
++ [MTK_WED_WO_REGION_ILM] = {
++ .name = "wo-ilm",
++ },
++ [MTK_WED_WO_REGION_DATA] = {
++ .name = "wo-data",
++ .shared = true,
++ },
++ [MTK_WED_WO_REGION_BOOT] = {
++ .name = "wo-boot",
++ },
++};
++
+ static u32 wo_r32(struct mtk_wed_wo *wo, u32 reg)
+ {
+- return readl(wo->boot.addr + reg);
++ return readl(mem_region[MTK_WED_WO_REGION_BOOT].addr + reg);
+ }
+
+ static void wo_w32(struct mtk_wed_wo *wo, u32 reg, u32 val)
+ {
+- writel(val, wo->boot.addr + reg);
++ writel(val, mem_region[MTK_WED_WO_REGION_BOOT].addr + reg);
+ }
+
+ static struct sk_buff *
+@@ -294,18 +310,6 @@ next:
+ static int
+ mtk_wed_mcu_load_firmware(struct mtk_wed_wo *wo)
+ {
+- static struct mtk_wed_wo_memory_region mem_region[] = {
+- [MTK_WED_WO_REGION_EMI] = {
+- .name = "wo-emi",
+- },
+- [MTK_WED_WO_REGION_ILM] = {
+- .name = "wo-ilm",
+- },
+- [MTK_WED_WO_REGION_DATA] = {
+- .name = "wo-data",
+- .shared = true,
+- },
+- };
+ const struct mtk_wed_fw_trailer *trailer;
+ const struct firmware *fw;
+ const char *fw_name;
+@@ -319,11 +323,6 @@ mtk_wed_mcu_load_firmware(struct mtk_wed
+ return ret;
+ }
+
+- wo->boot.name = "wo-boot";
+- ret = mtk_wed_get_memory_region(wo, &wo->boot);
+- if (ret)
+- return ret;
+-
+ /* set dummy cr */
+ wed_w32(wo->hw->wed_dev, MTK_WED_SCR0 + 4 * MTK_WED_DUMMY_CR_FWDL,
+ wo->hw->index + 1);
+--- a/drivers/net/ethernet/mediatek/mtk_wed_wo.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed_wo.h
+@@ -228,7 +228,6 @@ struct mtk_wed_wo_queue {
+
+ struct mtk_wed_wo {
+ struct mtk_wed_hw *hw;
+- struct mtk_wed_wo_memory_region boot;
+
+ struct mtk_wed_wo_queue q_tx;
+ struct mtk_wed_wo_queue q_rx;
diff --git a/target/linux/generic/backport-6.6/752-11-v6.7-net-ethernet-mtk_wed-make-memory-region-optional.patch b/target/linux/generic/backport-6.6/752-11-v6.7-net-ethernet-mtk_wed-make-memory-region-optional.patch
new file mode 100644
index 0000000000..48b0d02049
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-11-v6.7-net-ethernet-mtk_wed-make-memory-region-optional.patch
@@ -0,0 +1,71 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Mon, 18 Sep 2023 12:29:10 +0200
+Subject: [PATCH] net: ethernet: mtk_wed: make memory region optional
+
+Make mtk_wed_wo_memory_region optionals.
+This is a preliminary patch to introduce Wireless Ethernet Dispatcher
+support for MT7988 SoC since MT7988 WED fw image will have a different
+layout.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
+@@ -234,19 +234,13 @@ int mtk_wed_mcu_msg_update(struct mtk_we
+ }
+
+ static int
+-mtk_wed_get_memory_region(struct mtk_wed_wo *wo,
++mtk_wed_get_memory_region(struct mtk_wed_hw *hw, int index,
+ struct mtk_wed_wo_memory_region *region)
+ {
+ struct reserved_mem *rmem;
+ struct device_node *np;
+- int index;
+
+- index = of_property_match_string(wo->hw->node, "memory-region-names",
+- region->name);
+- if (index < 0)
+- return index;
+-
+- np = of_parse_phandle(wo->hw->node, "memory-region", index);
++ np = of_parse_phandle(hw->node, "memory-region", index);
+ if (!np)
+ return -ENODEV;
+
+@@ -258,7 +252,7 @@ mtk_wed_get_memory_region(struct mtk_wed
+
+ region->phy_addr = rmem->base;
+ region->size = rmem->size;
+- region->addr = devm_ioremap(wo->hw->dev, region->phy_addr, region->size);
++ region->addr = devm_ioremap(hw->dev, region->phy_addr, region->size);
+
+ return !region->addr ? -EINVAL : 0;
+ }
+@@ -271,6 +265,9 @@ mtk_wed_mcu_run_firmware(struct mtk_wed_
+ const struct mtk_wed_fw_trailer *trailer;
+ const struct mtk_wed_fw_region *fw_region;
+
++ if (!region->phy_addr || !region->size)
++ return 0;
++
+ trailer_ptr = fw->data + fw->size - sizeof(*trailer);
+ trailer = (const struct mtk_wed_fw_trailer *)trailer_ptr;
+ region_ptr = trailer_ptr - trailer->num_region * sizeof(*fw_region);
+@@ -318,7 +315,13 @@ mtk_wed_mcu_load_firmware(struct mtk_wed
+
+ /* load firmware region metadata */
+ for (i = 0; i < ARRAY_SIZE(mem_region); i++) {
+- ret = mtk_wed_get_memory_region(wo, &mem_region[i]);
++ int index = of_property_match_string(wo->hw->node,
++ "memory-region-names",
++ mem_region[i].name);
++ if (index < 0)
++ continue;
++
++ ret = mtk_wed_get_memory_region(wo->hw, index, &mem_region[i]);
+ if (ret)
+ return ret;
+ }
diff --git a/target/linux/generic/backport-6.6/752-13-v6.7-net-ethernet-mtk_wed-add-mtk_wed_soc_data-structure.patch b/target/linux/generic/backport-6.6/752-13-v6.7-net-ethernet-mtk_wed-add-mtk_wed_soc_data-structure.patch
new file mode 100644
index 0000000000..c43114fb5b
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-13-v6.7-net-ethernet-mtk_wed-add-mtk_wed_soc_data-structure.patch
@@ -0,0 +1,217 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Mon, 18 Sep 2023 12:29:12 +0200
+Subject: [PATCH] net: ethernet: mtk_wed: add mtk_wed_soc_data structure
+
+Introduce mtk_wed_soc_data utility structure to contain per-SoC
+definitions.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -48,6 +48,26 @@ struct mtk_wed_flow_block_priv {
+ struct net_device *dev;
+ };
+
++static const struct mtk_wed_soc_data mt7622_data = {
++ .regmap = {
++ .tx_bm_tkid = 0x088,
++ .wpdma_rx_ring0 = 0x770,
++ .reset_idx_tx_mask = GENMASK(3, 0),
++ .reset_idx_rx_mask = GENMASK(17, 16),
++ },
++ .wdma_desc_size = sizeof(struct mtk_wdma_desc),
++};
++
++static const struct mtk_wed_soc_data mt7986_data = {
++ .regmap = {
++ .tx_bm_tkid = 0x0c8,
++ .wpdma_rx_ring0 = 0x770,
++ .reset_idx_tx_mask = GENMASK(1, 0),
++ .reset_idx_rx_mask = GENMASK(7, 6),
++ },
++ .wdma_desc_size = 2 * sizeof(struct mtk_wdma_desc),
++};
++
+ static void
+ wed_m32(struct mtk_wed_device *dev, u32 reg, u32 mask, u32 val)
+ {
+@@ -746,7 +766,7 @@ mtk_wed_set_wpdma(struct mtk_wed_device
+ return;
+
+ wed_w32(dev, MTK_WED_WPDMA_RX_GLO_CFG, dev->wlan.wpdma_rx_glo);
+- wed_w32(dev, MTK_WED_WPDMA_RX_RING, dev->wlan.wpdma_rx);
++ wed_w32(dev, dev->hw->soc->regmap.wpdma_rx_ring0, dev->wlan.wpdma_rx);
+ }
+
+ static void
+@@ -940,22 +960,10 @@ mtk_wed_hw_init(struct mtk_wed_device *d
+ wed_w32(dev, MTK_WED_TX_BM_BUF_LEN, MTK_WED_PKT_SIZE);
+
+ if (mtk_wed_is_v1(dev->hw)) {
+- wed_w32(dev, MTK_WED_TX_BM_TKID,
+- FIELD_PREP(MTK_WED_TX_BM_TKID_START,
+- dev->wlan.token_start) |
+- FIELD_PREP(MTK_WED_TX_BM_TKID_END,
+- dev->wlan.token_start +
+- dev->wlan.nbuf - 1));
+ wed_w32(dev, MTK_WED_TX_BM_DYN_THR,
+ FIELD_PREP(MTK_WED_TX_BM_DYN_THR_LO, 1) |
+ MTK_WED_TX_BM_DYN_THR_HI);
+ } else {
+- wed_w32(dev, MTK_WED_TX_BM_TKID_V2,
+- FIELD_PREP(MTK_WED_TX_BM_TKID_START,
+- dev->wlan.token_start) |
+- FIELD_PREP(MTK_WED_TX_BM_TKID_END,
+- dev->wlan.token_start +
+- dev->wlan.nbuf - 1));
+ wed_w32(dev, MTK_WED_TX_BM_DYN_THR,
+ FIELD_PREP(MTK_WED_TX_BM_DYN_THR_LO_V2, 0) |
+ MTK_WED_TX_BM_DYN_THR_HI_V2);
+@@ -970,6 +978,11 @@ mtk_wed_hw_init(struct mtk_wed_device *d
+ MTK_WED_TX_TKID_DYN_THR_HI);
+ }
+
++ wed_w32(dev, dev->hw->soc->regmap.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));
++
+ mtk_wed_reset(dev, MTK_WED_RESET_TX_BM);
+
+ if (mtk_wed_is_v1(dev->hw)) {
+@@ -1104,13 +1117,8 @@ mtk_wed_rx_reset(struct mtk_wed_device *
+ if (ret) {
+ mtk_wed_reset(dev, MTK_WED_RESET_WED_RX_DMA);
+ } else {
+- struct mtk_eth *eth = dev->hw->eth;
+-
+- if (mtk_is_netsys_v2_or_greater(eth))
+- wed_set(dev, MTK_WED_RESET_IDX,
+- MTK_WED_RESET_IDX_RX_V2);
+- else
+- wed_set(dev, MTK_WED_RESET_IDX, MTK_WED_RESET_IDX_RX);
++ wed_set(dev, MTK_WED_RESET_IDX,
++ dev->hw->soc->regmap.reset_idx_rx_mask);
+ wed_w32(dev, MTK_WED_RESET_IDX, 0);
+ }
+
+@@ -1163,7 +1171,8 @@ mtk_wed_reset_dma(struct mtk_wed_device
+ 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);
++ wed_w32(dev, MTK_WED_RESET_IDX,
++ dev->hw->soc->regmap.reset_idx_tx_mask);
+ wed_w32(dev, MTK_WED_RESET_IDX, 0);
+ }
+
+@@ -1255,7 +1264,6 @@ static int
+ mtk_wed_wdma_rx_ring_setup(struct mtk_wed_device *dev, int idx, int size,
+ bool reset)
+ {
+- u32 desc_size = sizeof(struct mtk_wdma_desc) * dev->hw->version;
+ struct mtk_wed_ring *wdma;
+
+ if (idx >= ARRAY_SIZE(dev->rx_wdma))
+@@ -1263,7 +1271,7 @@ mtk_wed_wdma_rx_ring_setup(struct mtk_we
+
+ wdma = &dev->rx_wdma[idx];
+ if (!reset && mtk_wed_ring_alloc(dev, wdma, MTK_WED_WDMA_RING_SIZE,
+- desc_size, true))
++ dev->hw->soc->wdma_desc_size, true))
+ return -ENOMEM;
+
+ wdma_w32(dev, MTK_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_BASE,
+@@ -1284,7 +1292,6 @@ static int
+ mtk_wed_wdma_tx_ring_setup(struct mtk_wed_device *dev, int idx, int size,
+ bool reset)
+ {
+- u32 desc_size = sizeof(struct mtk_wdma_desc) * dev->hw->version;
+ struct mtk_wed_ring *wdma;
+
+ if (idx >= ARRAY_SIZE(dev->tx_wdma))
+@@ -1292,7 +1299,7 @@ mtk_wed_wdma_tx_ring_setup(struct mtk_we
+
+ wdma = &dev->tx_wdma[idx];
+ if (!reset && mtk_wed_ring_alloc(dev, wdma, MTK_WED_WDMA_RING_SIZE,
+- desc_size, true))
++ dev->hw->soc->wdma_desc_size, true))
+ return -ENOMEM;
+
+ wdma_w32(dev, MTK_WDMA_RING_TX(idx) + MTK_WED_RING_OFS_BASE,
+@@ -1931,7 +1938,12 @@ void mtk_wed_add_hw(struct device_node *
+ hw->irq = irq;
+ hw->version = eth->soc->version;
+
+- if (mtk_wed_is_v1(hw)) {
++ switch (hw->version) {
++ case 2:
++ hw->soc = &mt7986_data;
++ break;
++ default:
++ case 1:
+ hw->mirror = syscon_regmap_lookup_by_phandle(eth_np,
+ "mediatek,pcie-mirror");
+ hw->hifsys = syscon_regmap_lookup_by_phandle(eth_np,
+@@ -1945,6 +1957,8 @@ void mtk_wed_add_hw(struct device_node *
+ regmap_write(hw->mirror, 0, 0);
+ regmap_write(hw->mirror, 4, 0);
+ }
++ hw->soc = &mt7622_data;
++ break;
+ }
+
+ mtk_wed_hw_add_debugfs(hw);
+--- a/drivers/net/ethernet/mediatek/mtk_wed.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed.h
+@@ -12,7 +12,18 @@
+ struct mtk_eth;
+ struct mtk_wed_wo;
+
++struct mtk_wed_soc_data {
++ struct {
++ u32 tx_bm_tkid;
++ u32 wpdma_rx_ring0;
++ u32 reset_idx_tx_mask;
++ u32 reset_idx_rx_mask;
++ } regmap;
++ u32 wdma_desc_size;
++};
++
+ struct mtk_wed_hw {
++ const struct mtk_wed_soc_data *soc;
+ struct device_node *node;
+ struct mtk_eth *eth;
+ struct regmap *regs;
+--- a/drivers/net/ethernet/mediatek/mtk_wed_regs.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
+@@ -100,8 +100,6 @@ struct mtk_wdma_desc {
+
+ #define MTK_WED_TX_BM_BASE 0x084
+
+-#define MTK_WED_TX_BM_TKID 0x088
+-#define MTK_WED_TX_BM_TKID_V2 0x0c8
+ #define MTK_WED_TX_BM_TKID_START GENMASK(15, 0)
+ #define MTK_WED_TX_BM_TKID_END GENMASK(31, 16)
+
+@@ -160,9 +158,6 @@ struct mtk_wdma_desc {
+ #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_RESET_IDX_RX_V2 GENMASK(7, 6)
+ #define MTK_WED_RESET_WPDMA_IDX_RX GENMASK(31, 30)
+
+ #define MTK_WED_TX_MIB(_n) (0x2a0 + (_n) * 4)
+@@ -286,7 +281,6 @@ struct mtk_wdma_desc {
+ #define MTK_WED_WPDMA_RX_D_RST_DRV_IDX GENMASK(25, 24)
+
+ #define MTK_WED_WPDMA_RX_GLO_CFG 0x76c
+-#define MTK_WED_WPDMA_RX_RING 0x770
+
+ #define MTK_WED_WPDMA_RX_D_MIB(_n) (0x774 + (_n) * 4)
+ #define MTK_WED_WPDMA_RX_D_PROCESSED_MIB(_n) (0x784 + (_n) * 4)
diff --git a/target/linux/generic/backport-6.6/752-14-v6.7-net-ethernet-mtk_wed-introduce-WED-support-for-MT798.patch b/target/linux/generic/backport-6.6/752-14-v6.7-net-ethernet-mtk_wed-introduce-WED-support-for-MT798.patch
new file mode 100644
index 0000000000..f874899c5b
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-14-v6.7-net-ethernet-mtk_wed-introduce-WED-support-for-MT798.patch
@@ -0,0 +1,1280 @@
+From: Sujuan Chen <sujuan.chen@mediatek.com>
+Date: Mon, 18 Sep 2023 12:29:13 +0200
+Subject: [PATCH] net: ethernet: mtk_wed: introduce WED support for MT7988
+
+Similar to MT7986 and MT7622, enable Wireless Ethernet Ditpatcher for
+MT7988 in order to offload traffic forwarded from LAN/WLAN to WLAN/LAN
+
+Co-developed-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -195,6 +195,7 @@ static const struct mtk_reg_map mt7988_r
+ .wdma_base = {
+ [0] = 0x4800,
+ [1] = 0x4c00,
++ [2] = 0x5000,
+ },
+ .pse_iq_sta = 0x0180,
+ .pse_oq_sta = 0x01a0,
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -1132,7 +1132,7 @@ struct mtk_reg_map {
+ u32 gdm1_cnt;
+ u32 gdma_to_ppe;
+ u32 ppe_base;
+- u32 wdma_base[2];
++ u32 wdma_base[3];
+ u32 pse_iq_sta;
+ u32 pse_oq_sta;
+ };
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+@@ -201,6 +201,9 @@ mtk_flow_set_output_device(struct mtk_et
+ case 1:
+ pse_port = PSE_WDMA1_PORT;
+ break;
++ case 2:
++ pse_port = PSE_WDMA2_PORT;
++ break;
+ default:
+ return -EINVAL;
+ }
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -16,17 +16,19 @@
+ #include <net/flow_offload.h>
+ #include <net/pkt_cls.h>
+ #include "mtk_eth_soc.h"
+-#include "mtk_wed_regs.h"
+ #include "mtk_wed.h"
+ #include "mtk_ppe.h"
+ #include "mtk_wed_wo.h"
+
+ #define MTK_PCIE_BASE(n) (0x1a143000 + (n) * 0x2000)
+
+-#define MTK_WED_PKT_SIZE 1900
++#define MTK_WED_PKT_SIZE 1920
+ #define MTK_WED_BUF_SIZE 2048
++#define MTK_WED_PAGE_BUF_SIZE 128
+ #define MTK_WED_BUF_PER_PAGE (PAGE_SIZE / 2048)
++#define MTK_WED_RX_PAGE_BUF_PER_PAGE (PAGE_SIZE / 128)
+ #define MTK_WED_RX_RING_SIZE 1536
++#define MTK_WED_RX_PG_BM_CNT 8192
+
+ #define MTK_WED_TX_RING_SIZE 2048
+ #define MTK_WED_WDMA_RING_SIZE 1024
+@@ -40,7 +42,10 @@
+ #define MTK_WED_RRO_QUE_CNT 8192
+ #define MTK_WED_MIOD_ENTRY_CNT 128
+
+-static struct mtk_wed_hw *hw_list[2];
++#define MTK_WED_TX_BM_DMA_SIZE 65536
++#define MTK_WED_TX_BM_PKT_CNT 32768
++
++static struct mtk_wed_hw *hw_list[3];
+ static DEFINE_MUTEX(hw_lock);
+
+ struct mtk_wed_flow_block_priv {
+@@ -55,6 +60,7 @@ static const struct mtk_wed_soc_data mt7
+ .reset_idx_tx_mask = GENMASK(3, 0),
+ .reset_idx_rx_mask = GENMASK(17, 16),
+ },
++ .tx_ring_desc_size = sizeof(struct mtk_wdma_desc),
+ .wdma_desc_size = sizeof(struct mtk_wdma_desc),
+ };
+
+@@ -65,6 +71,18 @@ static const struct mtk_wed_soc_data mt7
+ .reset_idx_tx_mask = GENMASK(1, 0),
+ .reset_idx_rx_mask = GENMASK(7, 6),
+ },
++ .tx_ring_desc_size = sizeof(struct mtk_wdma_desc),
++ .wdma_desc_size = 2 * sizeof(struct mtk_wdma_desc),
++};
++
++static const struct mtk_wed_soc_data mt7988_data = {
++ .regmap = {
++ .tx_bm_tkid = 0x0c8,
++ .wpdma_rx_ring0 = 0x7d0,
++ .reset_idx_tx_mask = GENMASK(1, 0),
++ .reset_idx_rx_mask = GENMASK(7, 6),
++ },
++ .tx_ring_desc_size = sizeof(struct mtk_wed_bm_desc),
+ .wdma_desc_size = 2 * sizeof(struct mtk_wdma_desc),
+ };
+
+@@ -319,33 +337,38 @@ out:
+ static int
+ mtk_wed_tx_buffer_alloc(struct mtk_wed_device *dev)
+ {
++ u32 desc_size = dev->hw->soc->tx_ring_desc_size;
++ int i, page_idx = 0, n_pages, ring_size;
++ int token = dev->wlan.token_start;
+ struct mtk_wed_buf *page_list;
+- struct mtk_wdma_desc *desc;
+ dma_addr_t desc_phys;
+- int token = dev->wlan.token_start;
+- int ring_size;
+- int n_pages;
+- int i, page_idx;
++ void *desc_ptr;
+
+- ring_size = dev->wlan.nbuf & ~(MTK_WED_BUF_PER_PAGE - 1);
+- n_pages = ring_size / MTK_WED_BUF_PER_PAGE;
++ if (!mtk_wed_is_v3_or_greater(dev->hw)) {
++ ring_size = dev->wlan.nbuf & ~(MTK_WED_BUF_PER_PAGE - 1);
++ dev->tx_buf_ring.size = ring_size;
++ } else {
++ dev->tx_buf_ring.size = MTK_WED_TX_BM_DMA_SIZE;
++ ring_size = MTK_WED_TX_BM_PKT_CNT;
++ }
++ n_pages = dev->tx_buf_ring.size / MTK_WED_BUF_PER_PAGE;
+
+ page_list = kcalloc(n_pages, sizeof(*page_list), GFP_KERNEL);
+ if (!page_list)
+ return -ENOMEM;
+
+- dev->tx_buf_ring.size = ring_size;
+ dev->tx_buf_ring.pages = page_list;
+
+- desc = dma_alloc_coherent(dev->hw->dev, ring_size * sizeof(*desc),
+- &desc_phys, GFP_KERNEL);
+- if (!desc)
++ desc_ptr = dma_alloc_coherent(dev->hw->dev,
++ dev->tx_buf_ring.size * desc_size,
++ &desc_phys, GFP_KERNEL);
++ if (!desc_ptr)
+ return -ENOMEM;
+
+- dev->tx_buf_ring.desc = desc;
++ dev->tx_buf_ring.desc = desc_ptr;
+ dev->tx_buf_ring.desc_phys = desc_phys;
+
+- for (i = 0, page_idx = 0; i < ring_size; i += MTK_WED_BUF_PER_PAGE) {
++ for (i = 0; i < ring_size; i += MTK_WED_BUF_PER_PAGE) {
+ dma_addr_t page_phys, buf_phys;
+ struct page *page;
+ void *buf;
+@@ -371,28 +394,31 @@ mtk_wed_tx_buffer_alloc(struct mtk_wed_d
+ buf_phys = page_phys;
+
+ 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++);
++ struct mtk_wdma_desc *desc = desc_ptr;
+
+ desc->buf0 = cpu_to_le32(buf_phys);
+- desc->buf1 = cpu_to_le32(buf_phys + txd_size);
++ if (!mtk_wed_is_v3_or_greater(dev->hw)) {
++ u32 txd_size, ctrl;
+
+- if (mtk_wed_is_v1(dev->hw))
+- ctrl = FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN0, txd_size) |
+- FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN1,
+- MTK_WED_BUF_SIZE - txd_size) |
+- MTK_WDMA_DESC_CTRL_LAST_SEG1;
+- else
+- ctrl = FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN0, txd_size) |
+- FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN1_V2,
+- MTK_WED_BUF_SIZE - txd_size) |
+- MTK_WDMA_DESC_CTRL_LAST_SEG0;
+- desc->ctrl = cpu_to_le32(ctrl);
+- desc->info = 0;
+- desc++;
++ txd_size = dev->wlan.init_buf(buf, buf_phys,
++ token++);
++ desc->buf1 = cpu_to_le32(buf_phys + txd_size);
++ ctrl = FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN0, txd_size);
++ if (mtk_wed_is_v1(dev->hw))
++ ctrl |= MTK_WDMA_DESC_CTRL_LAST_SEG1 |
++ FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN1,
++ MTK_WED_BUF_SIZE - txd_size);
++ else
++ ctrl |= MTK_WDMA_DESC_CTRL_LAST_SEG0 |
++ FIELD_PREP(MTK_WDMA_DESC_CTRL_LEN1_V2,
++ MTK_WED_BUF_SIZE - txd_size);
++ desc->ctrl = cpu_to_le32(ctrl);
++ desc->info = 0;
++ } else {
++ desc->ctrl = cpu_to_le32(token << 16);
++ }
+
++ desc_ptr += desc_size;
+ buf += MTK_WED_BUF_SIZE;
+ buf_phys += MTK_WED_BUF_SIZE;
+ }
+@@ -408,31 +434,31 @@ static void
+ mtk_wed_free_tx_buffer(struct mtk_wed_device *dev)
+ {
+ struct mtk_wed_buf *page_list = dev->tx_buf_ring.pages;
+- struct mtk_wdma_desc *desc = dev->tx_buf_ring.desc;
+- int page_idx;
+- int i;
++ struct mtk_wed_hw *hw = dev->hw;
++ int i, page_idx = 0;
+
+ if (!page_list)
+ return;
+
+- if (!desc)
++ if (!dev->tx_buf_ring.desc)
+ goto free_pagelist;
+
+- for (i = 0, page_idx = 0; i < dev->tx_buf_ring.size;
+- i += MTK_WED_BUF_PER_PAGE) {
+- dma_addr_t buf_addr = page_list[page_idx].phy_addr;
++ for (i = 0; i < dev->tx_buf_ring.size; i += MTK_WED_BUF_PER_PAGE) {
++ dma_addr_t page_phy = page_list[page_idx].phy_addr;
+ void *page = page_list[page_idx++].p;
+
+ if (!page)
+ break;
+
+- dma_unmap_page(dev->hw->dev, buf_addr, PAGE_SIZE,
++ dma_unmap_page(dev->hw->dev, page_phy, PAGE_SIZE,
+ DMA_BIDIRECTIONAL);
+ __free_page(page);
+ }
+
+- dma_free_coherent(dev->hw->dev, dev->tx_buf_ring.size * sizeof(*desc),
+- desc, dev->tx_buf_ring.desc_phys);
++ dma_free_coherent(dev->hw->dev,
++ dev->tx_buf_ring.size * hw->soc->tx_ring_desc_size,
++ dev->tx_buf_ring.desc,
++ dev->tx_buf_ring.desc_phys);
+
+ free_pagelist:
+ kfree(page_list);
+@@ -517,13 +543,23 @@ mtk_wed_set_ext_int(struct mtk_wed_devic
+ {
+ u32 mask = MTK_WED_EXT_INT_STATUS_ERROR_MASK;
+
+- if (mtk_wed_is_v1(dev->hw))
++ switch (dev->hw->version) {
++ case 1:
+ mask |= MTK_WED_EXT_INT_STATUS_TX_DRV_R_RESP_ERR;
+- else
++ break;
++ case 2:
+ mask |= MTK_WED_EXT_INT_STATUS_RX_FBUF_LO_TH |
+ MTK_WED_EXT_INT_STATUS_RX_FBUF_HI_TH |
+ MTK_WED_EXT_INT_STATUS_RX_DRV_COHERENT |
+ MTK_WED_EXT_INT_STATUS_TX_DMA_W_RESP_ERR;
++ break;
++ case 3:
++ mask = MTK_WED_EXT_INT_STATUS_RX_DRV_COHERENT |
++ MTK_WED_EXT_INT_STATUS_TKID_WO_PYLD;
++ break;
++ default:
++ break;
++ }
+
+ if (!dev->hw->num_flows)
+ mask &= ~MTK_WED_EXT_INT_STATUS_TKID_WO_PYLD;
+@@ -535,6 +571,9 @@ mtk_wed_set_ext_int(struct mtk_wed_devic
+ static void
+ mtk_wed_set_512_support(struct mtk_wed_device *dev, bool enable)
+ {
++ if (!mtk_wed_is_v2(dev->hw))
++ return;
++
+ if (enable) {
+ wed_w32(dev, MTK_WED_TXDP_CTRL, MTK_WED_TXDP_DW9_OVERWR);
+ wed_w32(dev, MTK_WED_TXP_DW1,
+@@ -609,6 +648,14 @@ mtk_wed_dma_disable(struct mtk_wed_devic
+ MTK_WED_WPDMA_RX_D_RX_DRV_EN);
+ wed_clr(dev, MTK_WED_WDMA_GLO_CFG,
+ MTK_WED_WDMA_GLO_CFG_TX_DDONE_CHK);
++
++ if (mtk_wed_is_v3_or_greater(dev->hw) &&
++ mtk_wed_get_rx_capa(dev)) {
++ wdma_clr(dev, MTK_WDMA_PREF_TX_CFG,
++ MTK_WDMA_PREF_TX_CFG_PREF_EN);
++ wdma_clr(dev, MTK_WDMA_PREF_RX_CFG,
++ MTK_WDMA_PREF_RX_CFG_PREF_EN);
++ }
+ }
+
+ mtk_wed_set_512_support(dev, false);
+@@ -651,6 +698,14 @@ mtk_wed_deinit(struct mtk_wed_device *de
+ MTK_WED_CTRL_RX_ROUTE_QM_EN |
+ MTK_WED_CTRL_WED_RX_BM_EN |
+ MTK_WED_CTRL_RX_RRO_QM_EN);
++
++ if (mtk_wed_is_v3_or_greater(dev->hw)) {
++ wed_clr(dev, MTK_WED_CTRL, MTK_WED_CTRL_TX_AMSDU_EN);
++ wed_clr(dev, MTK_WED_RESET, MTK_WED_RESET_TX_AMSDU);
++ wed_clr(dev, MTK_WED_PCIE_INT_CTRL,
++ MTK_WED_PCIE_INT_CTRL_MSK_EN_POLA |
++ MTK_WED_PCIE_INT_CTRL_MSK_IRQ_FILTER);
++ }
+ }
+
+ static void
+@@ -700,21 +755,37 @@ mtk_wed_detach(struct mtk_wed_device *de
+ mutex_unlock(&hw_lock);
+ }
+
+-#define PCIE_BASE_ADDR0 0x11280000
+ static void
+ mtk_wed_bus_init(struct mtk_wed_device *dev)
+ {
+ switch (dev->wlan.bus_type) {
+ case MTK_WED_BUS_PCIE: {
+ struct device_node *np = dev->hw->eth->dev->of_node;
+- struct regmap *regs;
+
+- regs = syscon_regmap_lookup_by_phandle(np,
+- "mediatek,wed-pcie");
+- if (IS_ERR(regs))
+- break;
++ if (mtk_wed_is_v2(dev->hw)) {
++ struct regmap *regs;
++
++ regs = syscon_regmap_lookup_by_phandle(np,
++ "mediatek,wed-pcie");
++ if (IS_ERR(regs))
++ break;
+
+- regmap_update_bits(regs, 0, BIT(0), BIT(0));
++ regmap_update_bits(regs, 0, BIT(0), BIT(0));
++ }
++
++ if (dev->wlan.msi) {
++ wed_w32(dev, MTK_WED_PCIE_CFG_INTM,
++ dev->hw->pcie_base | 0xc08);
++ wed_w32(dev, MTK_WED_PCIE_CFG_BASE,
++ dev->hw->pcie_base | 0xc04);
++ wed_w32(dev, MTK_WED_PCIE_INT_TRIGGER, BIT(8));
++ } else {
++ wed_w32(dev, MTK_WED_PCIE_CFG_INTM,
++ dev->hw->pcie_base | 0x180);
++ wed_w32(dev, MTK_WED_PCIE_CFG_BASE,
++ dev->hw->pcie_base | 0x184);
++ wed_w32(dev, MTK_WED_PCIE_INT_TRIGGER, BIT(24));
++ }
+
+ wed_w32(dev, MTK_WED_PCIE_INT_CTRL,
+ FIELD_PREP(MTK_WED_PCIE_INT_CTRL_POLL_EN, 2));
+@@ -722,19 +793,9 @@ mtk_wed_bus_init(struct mtk_wed_device *
+ /* pcie interrupt control: pola/source selection */
+ wed_set(dev, MTK_WED_PCIE_INT_CTRL,
+ MTK_WED_PCIE_INT_CTRL_MSK_EN_POLA |
+- FIELD_PREP(MTK_WED_PCIE_INT_CTRL_SRC_SEL, 1));
+- wed_r32(dev, MTK_WED_PCIE_INT_CTRL);
+-
+- wed_w32(dev, MTK_WED_PCIE_CFG_INTM, PCIE_BASE_ADDR0 | 0x180);
+- wed_w32(dev, MTK_WED_PCIE_CFG_BASE, PCIE_BASE_ADDR0 | 0x184);
+-
+- /* pcie interrupt status trigger register */
+- wed_w32(dev, MTK_WED_PCIE_INT_TRIGGER, BIT(24));
+- wed_r32(dev, MTK_WED_PCIE_INT_TRIGGER);
+-
+- /* pola setting */
+- wed_set(dev, MTK_WED_PCIE_INT_CTRL,
+- MTK_WED_PCIE_INT_CTRL_MSK_EN_POLA);
++ MTK_WED_PCIE_INT_CTRL_MSK_IRQ_FILTER |
++ FIELD_PREP(MTK_WED_PCIE_INT_CTRL_SRC_SEL,
++ dev->hw->index));
+ break;
+ }
+ case MTK_WED_BUS_AXI:
+@@ -772,18 +833,19 @@ mtk_wed_set_wpdma(struct mtk_wed_device
+ static void
+ mtk_wed_hw_init_early(struct mtk_wed_device *dev)
+ {
+- u32 mask, set;
++ u32 set = FIELD_PREP(MTK_WED_WDMA_GLO_CFG_BT_SIZE, 2);
++ u32 mask = MTK_WED_WDMA_GLO_CFG_BT_SIZE;
+
+ mtk_wed_deinit(dev);
+ mtk_wed_reset(dev, MTK_WED_RESET_WED);
+ mtk_wed_set_wpdma(dev);
+
+- mask = MTK_WED_WDMA_GLO_CFG_BT_SIZE |
+- MTK_WED_WDMA_GLO_CFG_DYNAMIC_DMAD_RECYCLE |
+- 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;
++ if (!mtk_wed_is_v3_or_greater(dev->hw)) {
++ mask |= MTK_WED_WDMA_GLO_CFG_DYNAMIC_DMAD_RECYCLE |
++ MTK_WED_WDMA_GLO_CFG_RX_DIS_FSM_AUTO_IDLE;
++ set |= 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);
+
+ if (mtk_wed_is_v1(dev->hw)) {
+@@ -931,11 +993,18 @@ mtk_wed_route_qm_hw_init(struct mtk_wed_
+ }
+
+ /* configure RX_ROUTE_QM */
+- wed_clr(dev, MTK_WED_RTQM_GLO_CFG, MTK_WED_RTQM_Q_RST);
+- wed_clr(dev, MTK_WED_RTQM_GLO_CFG, MTK_WED_RTQM_TXDMAD_FPORT);
+- wed_set(dev, MTK_WED_RTQM_GLO_CFG,
+- FIELD_PREP(MTK_WED_RTQM_TXDMAD_FPORT, 0x3 + dev->hw->index));
+- wed_clr(dev, MTK_WED_RTQM_GLO_CFG, MTK_WED_RTQM_Q_RST);
++ if (mtk_wed_is_v2(dev->hw)) {
++ wed_clr(dev, MTK_WED_RTQM_GLO_CFG, MTK_WED_RTQM_Q_RST);
++ wed_clr(dev, MTK_WED_RTQM_GLO_CFG, MTK_WED_RTQM_TXDMAD_FPORT);
++ wed_set(dev, MTK_WED_RTQM_GLO_CFG,
++ FIELD_PREP(MTK_WED_RTQM_TXDMAD_FPORT,
++ 0x3 + dev->hw->index));
++ wed_clr(dev, MTK_WED_RTQM_GLO_CFG, MTK_WED_RTQM_Q_RST);
++ } else {
++ wed_set(dev, MTK_WED_RTQM_ENQ_CFG0,
++ FIELD_PREP(MTK_WED_RTQM_ENQ_CFG_TXDMAD_FPORT,
++ 0x3 + dev->hw->index));
++ }
+ /* enable RX_ROUTE_QM */
+ wed_set(dev, MTK_WED_CTRL, MTK_WED_CTRL_RX_ROUTE_QM_EN);
+ }
+@@ -948,22 +1017,30 @@ mtk_wed_hw_init(struct mtk_wed_device *d
+
+ 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->tx_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->tx_buf_ring.desc_phys);
+-
+ wed_w32(dev, MTK_WED_TX_BM_BUF_LEN, MTK_WED_PKT_SIZE);
+
+ if (mtk_wed_is_v1(dev->hw)) {
++ 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->tx_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_DYN_THR,
+ FIELD_PREP(MTK_WED_TX_BM_DYN_THR_LO, 1) |
+ MTK_WED_TX_BM_DYN_THR_HI);
+- } else {
++ } else if (mtk_wed_is_v2(dev->hw)) {
++ 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->tx_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_TKID_DYN_THR,
++ FIELD_PREP(MTK_WED_TX_TKID_DYN_THR_LO, 0) |
++ MTK_WED_TX_TKID_DYN_THR_HI);
+ wed_w32(dev, MTK_WED_TX_BM_DYN_THR,
+ FIELD_PREP(MTK_WED_TX_BM_DYN_THR_LO_V2, 0) |
+ MTK_WED_TX_BM_DYN_THR_HI_V2);
+@@ -973,9 +1050,6 @@ mtk_wed_hw_init(struct mtk_wed_device *d
+ dev->tx_buf_ring.size / 128) |
+ FIELD_PREP(MTK_WED_TX_TKID_CTRL_RSV_GRP_NUM,
+ dev->tx_buf_ring.size / 128));
+- wed_w32(dev, MTK_WED_TX_TKID_DYN_THR,
+- FIELD_PREP(MTK_WED_TX_TKID_DYN_THR_LO, 0) |
+- MTK_WED_TX_TKID_DYN_THR_HI);
+ }
+
+ wed_w32(dev, dev->hw->soc->regmap.tx_bm_tkid,
+@@ -985,26 +1059,62 @@ mtk_wed_hw_init(struct mtk_wed_device *d
+
+ mtk_wed_reset(dev, MTK_WED_RESET_TX_BM);
+
++ if (mtk_wed_is_v3_or_greater(dev->hw)) {
++ /* switch to new bm architecture */
++ wed_clr(dev, MTK_WED_TX_BM_CTRL,
++ MTK_WED_TX_BM_CTRL_LEGACY_EN);
++
++ wed_w32(dev, MTK_WED_TX_TKID_CTRL,
++ MTK_WED_TX_TKID_CTRL_PAUSE |
++ FIELD_PREP(MTK_WED_TX_TKID_CTRL_VLD_GRP_NUM_V3,
++ dev->wlan.nbuf / 128) |
++ FIELD_PREP(MTK_WED_TX_TKID_CTRL_RSV_GRP_NUM_V3,
++ dev->wlan.nbuf / 128));
++ /* return SKBID + SDP back to bm */
++ wed_set(dev, MTK_WED_TX_TKID_CTRL,
++ MTK_WED_TX_TKID_CTRL_FREE_FORMAT);
++
++ wed_w32(dev, MTK_WED_TX_BM_INIT_PTR,
++ MTK_WED_TX_BM_PKT_CNT |
++ MTK_WED_TX_BM_INIT_SW_TAIL_IDX);
++ }
++
+ if (mtk_wed_is_v1(dev->hw)) {
+ wed_set(dev, MTK_WED_CTRL,
+ MTK_WED_CTRL_WED_TX_BM_EN |
+ MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
+- } else {
+- wed_clr(dev, MTK_WED_TX_TKID_CTRL, MTK_WED_TX_TKID_CTRL_PAUSE);
+- if (mtk_wed_get_rx_capa(dev)) {
+- /* rx hw init */
+- wed_w32(dev, MTK_WED_WPDMA_RX_D_RST_IDX,
+- MTK_WED_WPDMA_RX_D_RST_CRX_IDX |
+- MTK_WED_WPDMA_RX_D_RST_DRV_IDX);
+- wed_w32(dev, MTK_WED_WPDMA_RX_D_RST_IDX, 0);
+-
+- mtk_wed_rx_buffer_hw_init(dev);
+- mtk_wed_rro_hw_init(dev);
+- mtk_wed_route_qm_hw_init(dev);
+- }
++ } else if (mtk_wed_get_rx_capa(dev)) {
++ /* rx hw init */
++ wed_w32(dev, MTK_WED_WPDMA_RX_D_RST_IDX,
++ MTK_WED_WPDMA_RX_D_RST_CRX_IDX |
++ MTK_WED_WPDMA_RX_D_RST_DRV_IDX);
++ wed_w32(dev, MTK_WED_WPDMA_RX_D_RST_IDX, 0);
++
++ /* reset prefetch index of ring */
++ wed_set(dev, MTK_WED_WPDMA_RX_D_PREF_RX0_SIDX,
++ MTK_WED_WPDMA_RX_D_PREF_SIDX_IDX_CLR);
++ wed_clr(dev, MTK_WED_WPDMA_RX_D_PREF_RX0_SIDX,
++ MTK_WED_WPDMA_RX_D_PREF_SIDX_IDX_CLR);
++
++ wed_set(dev, MTK_WED_WPDMA_RX_D_PREF_RX1_SIDX,
++ MTK_WED_WPDMA_RX_D_PREF_SIDX_IDX_CLR);
++ wed_clr(dev, MTK_WED_WPDMA_RX_D_PREF_RX1_SIDX,
++ MTK_WED_WPDMA_RX_D_PREF_SIDX_IDX_CLR);
++
++ /* reset prefetch FIFO of ring */
++ wed_set(dev, MTK_WED_WPDMA_RX_D_PREF_FIFO_CFG,
++ MTK_WED_WPDMA_RX_D_PREF_FIFO_CFG_R0_CLR |
++ MTK_WED_WPDMA_RX_D_PREF_FIFO_CFG_R1_CLR);
++ wed_w32(dev, MTK_WED_WPDMA_RX_D_PREF_FIFO_CFG, 0);
++
++ mtk_wed_rx_buffer_hw_init(dev);
++ mtk_wed_rro_hw_init(dev);
++ mtk_wed_route_qm_hw_init(dev);
+ }
+
+ wed_clr(dev, MTK_WED_TX_BM_CTRL, MTK_WED_TX_BM_CTRL_PAUSE);
++ if (!mtk_wed_is_v1(dev->hw))
++ wed_clr(dev, MTK_WED_TX_TKID_CTRL, MTK_WED_TX_TKID_CTRL_PAUSE);
+ }
+
+ static void
+@@ -1302,6 +1412,24 @@ mtk_wed_wdma_tx_ring_setup(struct mtk_we
+ dev->hw->soc->wdma_desc_size, true))
+ return -ENOMEM;
+
++ if (mtk_wed_is_v3_or_greater(dev->hw)) {
++ struct mtk_wdma_desc *desc = wdma->desc;
++ int i;
++
++ for (i = 0; i < MTK_WED_WDMA_RING_SIZE; i++) {
++ desc->buf0 = 0;
++ desc->ctrl = cpu_to_le32(MTK_WDMA_DESC_CTRL_DMA_DONE);
++ desc->buf1 = 0;
++ desc->info = cpu_to_le32(MTK_WDMA_TXD0_DESC_INFO_DMA_DONE);
++ desc++;
++ desc->buf0 = 0;
++ desc->ctrl = cpu_to_le32(MTK_WDMA_DESC_CTRL_DMA_DONE);
++ desc->buf1 = 0;
++ desc->info = cpu_to_le32(MTK_WDMA_TXD1_DESC_INFO_DMA_DONE);
++ desc++;
++ }
++ }
++
+ wdma_w32(dev, MTK_WDMA_RING_TX(idx) + MTK_WED_RING_OFS_BASE,
+ wdma->desc_phys);
+ wdma_w32(dev, MTK_WDMA_RING_TX(idx) + MTK_WED_RING_OFS_COUNT,
+@@ -1367,6 +1495,9 @@ mtk_wed_configure_irq(struct mtk_wed_dev
+
+ wed_clr(dev, MTK_WED_WDMA_INT_CTRL, wdma_mask);
+ } else {
++ if (mtk_wed_is_v3_or_greater(dev->hw))
++ wed_set(dev, MTK_WED_CTRL, MTK_WED_CTRL_TX_TKID_ALI_EN);
++
+ /* initail tx interrupt trigger */
+ wed_w32(dev, MTK_WED_WPDMA_INT_CTRL_TX,
+ MTK_WED_WPDMA_INT_CTRL_TX0_DONE_EN |
+@@ -1419,33 +1550,60 @@ mtk_wed_dma_enable(struct mtk_wed_device
+ {
+ int i;
+
+- wed_set(dev, MTK_WED_WPDMA_INT_CTRL, MTK_WED_WPDMA_INT_CTRL_SUBRT_ADV);
++ if (!mtk_wed_is_v3_or_greater(dev->hw)) {
++ wed_set(dev, MTK_WED_WPDMA_INT_CTRL,
++ MTK_WED_WPDMA_INT_CTRL_SUBRT_ADV);
++ wed_set(dev, MTK_WED_WPDMA_GLO_CFG,
++ MTK_WED_WPDMA_GLO_CFG_TX_DRV_EN |
++ MTK_WED_WPDMA_GLO_CFG_RX_DRV_EN);
++ wdma_set(dev, MTK_WDMA_GLO_CFG,
++ MTK_WDMA_GLO_CFG_TX_DMA_EN |
++ MTK_WDMA_GLO_CFG_RX_INFO1_PRERES |
++ MTK_WDMA_GLO_CFG_RX_INFO2_PRERES);
++ wed_set(dev, MTK_WED_WPDMA_CTRL, MTK_WED_WPDMA_CTRL_SDL1_FIXED);
++ } else {
++ wed_set(dev, MTK_WED_WPDMA_GLO_CFG,
++ MTK_WED_WPDMA_GLO_CFG_TX_DRV_EN |
++ MTK_WED_WPDMA_GLO_CFG_RX_DRV_EN |
++ MTK_WED_WPDMA_GLO_CFG_RX_DDONE2_WR);
++ wdma_set(dev, MTK_WDMA_GLO_CFG, MTK_WDMA_GLO_CFG_TX_DMA_EN);
++ }
+
+ 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);
+
+- wdma_set(dev, MTK_WDMA_GLO_CFG,
+- MTK_WDMA_GLO_CFG_TX_DMA_EN |
+- MTK_WDMA_GLO_CFG_RX_INFO1_PRERES |
+- MTK_WDMA_GLO_CFG_RX_INFO2_PRERES);
+-
+ if (mtk_wed_is_v1(dev->hw)) {
+ wdma_set(dev, MTK_WDMA_GLO_CFG,
+ MTK_WDMA_GLO_CFG_RX_INFO3_PRERES);
+ return;
+ }
+
+- wed_set(dev, MTK_WED_WPDMA_CTRL,
+- MTK_WED_WPDMA_CTRL_SDL1_FIXED);
+ wed_set(dev, MTK_WED_WPDMA_GLO_CFG,
+ MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_PKT_PROC |
+ MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_CRX_SYNC);
++
++ if (mtk_wed_is_v3_or_greater(dev->hw)) {
++ wed_set(dev, MTK_WED_WDMA_RX_PREF_CFG,
++ FIELD_PREP(MTK_WED_WDMA_RX_PREF_BURST_SIZE, 0x10) |
++ FIELD_PREP(MTK_WED_WDMA_RX_PREF_LOW_THRES, 0x8));
++ wed_clr(dev, MTK_WED_WDMA_RX_PREF_CFG,
++ MTK_WED_WDMA_RX_PREF_DDONE2_EN);
++ wed_set(dev, MTK_WED_WDMA_RX_PREF_CFG, MTK_WED_WDMA_RX_PREF_EN);
++
++ wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
++ MTK_WED_WPDMA_GLO_CFG_TX_DDONE_CHK_LAST);
++ wed_set(dev, MTK_WED_WPDMA_GLO_CFG,
++ MTK_WED_WPDMA_GLO_CFG_TX_DDONE_CHK |
++ MTK_WED_WPDMA_GLO_CFG_RX_DRV_EVENT_PKT_FMT_CHK |
++ MTK_WED_WPDMA_GLO_CFG_RX_DRV_UNS_VER_FORCE_4);
++
++ wdma_set(dev, MTK_WDMA_PREF_RX_CFG, MTK_WDMA_PREF_RX_CFG_PREF_EN);
++ }
++
+ wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
+ MTK_WED_WPDMA_GLO_CFG_TX_TKID_KEEP |
+ MTK_WED_WPDMA_GLO_CFG_TX_DMAD_DW3_PREV);
+@@ -1457,11 +1615,22 @@ mtk_wed_dma_enable(struct mtk_wed_device
+ MTK_WED_WDMA_GLO_CFG_TX_DRV_EN |
+ MTK_WED_WDMA_GLO_CFG_TX_DDONE_CHK);
+
++ wed_clr(dev, MTK_WED_WPDMA_RX_D_GLO_CFG, MTK_WED_WPDMA_RX_D_RXD_READ_LEN);
+ wed_set(dev, MTK_WED_WPDMA_RX_D_GLO_CFG,
+ MTK_WED_WPDMA_RX_D_RX_DRV_EN |
+ FIELD_PREP(MTK_WED_WPDMA_RX_D_RXD_READ_LEN, 0x18) |
+- FIELD_PREP(MTK_WED_WPDMA_RX_D_INIT_PHASE_RXEN_SEL,
+- 0x2));
++ FIELD_PREP(MTK_WED_WPDMA_RX_D_INIT_PHASE_RXEN_SEL, 0x2));
++
++ if (mtk_wed_is_v3_or_greater(dev->hw)) {
++ wed_set(dev, MTK_WED_WPDMA_RX_D_PREF_CFG,
++ MTK_WED_WPDMA_RX_D_PREF_EN |
++ FIELD_PREP(MTK_WED_WPDMA_RX_D_PREF_BURST_SIZE, 0x10) |
++ FIELD_PREP(MTK_WED_WPDMA_RX_D_PREF_LOW_THRES, 0x8));
++
++ wed_set(dev, MTK_WED_RRO_RX_D_CFG(2), MTK_WED_RRO_RX_D_DRV_EN);
++ wdma_set(dev, MTK_WDMA_PREF_TX_CFG, MTK_WDMA_PREF_TX_CFG_PREF_EN);
++ wdma_set(dev, MTK_WDMA_WRBK_TX_CFG, MTK_WDMA_WRBK_TX_CFG_WRBK_EN);
++ }
+
+ for (i = 0; i < MTK_WED_RX_QUEUES; i++)
+ mtk_wed_check_wfdma_rx_fill(dev, i);
+@@ -1501,6 +1670,12 @@ mtk_wed_start(struct mtk_wed_device *dev
+ wed_r32(dev, MTK_WED_EXT_INT_MASK1);
+ wed_r32(dev, MTK_WED_EXT_INT_MASK2);
+
++ if (mtk_wed_is_v3_or_greater(dev->hw)) {
++ wed_w32(dev, MTK_WED_EXT_INT_MASK3,
++ MTK_WED_EXT_INT_STATUS_WPDMA_MID_RDY);
++ wed_r32(dev, MTK_WED_EXT_INT_MASK3);
++ }
++
+ if (mtk_wed_rro_cfg(dev))
+ return;
+ }
+@@ -1552,6 +1727,7 @@ mtk_wed_attach(struct mtk_wed_device *de
+ dev->irq = hw->irq;
+ dev->wdma_idx = hw->index;
+ dev->version = hw->version;
++ dev->hw->pcie_base = mtk_wed_get_pcie_base(dev);
+
+ if (hw->eth->dma_dev == hw->eth->dev &&
+ of_dma_is_coherent(hw->eth->dev->of_node))
+@@ -1619,6 +1795,23 @@ mtk_wed_tx_ring_setup(struct mtk_wed_dev
+ ring->reg_base = MTK_WED_RING_TX(idx);
+ ring->wpdma = regs;
+
++ if (mtk_wed_is_v3_or_greater(dev->hw) && idx == 1) {
++ /* reset prefetch index */
++ wed_set(dev, MTK_WED_WDMA_RX_PREF_CFG,
++ MTK_WED_WDMA_RX_PREF_RX0_SIDX_CLR |
++ MTK_WED_WDMA_RX_PREF_RX1_SIDX_CLR);
++
++ wed_clr(dev, MTK_WED_WDMA_RX_PREF_CFG,
++ MTK_WED_WDMA_RX_PREF_RX0_SIDX_CLR |
++ MTK_WED_WDMA_RX_PREF_RX1_SIDX_CLR);
++
++ /* reset prefetch FIFO */
++ wed_w32(dev, MTK_WED_WDMA_RX_PREF_FIFO_CFG,
++ MTK_WED_WDMA_RX_PREF_FIFO_RX0_CLR |
++ MTK_WED_WDMA_RX_PREF_FIFO_RX1_CLR);
++ wed_w32(dev, MTK_WED_WDMA_RX_PREF_FIFO_CFG, 0);
++ }
++
+ /* 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);
+@@ -1693,15 +1886,13 @@ mtk_wed_rx_ring_setup(struct mtk_wed_dev
+ static u32
+ mtk_wed_irq_get(struct mtk_wed_device *dev, u32 mask)
+ {
+- u32 val, ext_mask = MTK_WED_EXT_INT_STATUS_ERROR_MASK;
++ u32 val, ext_mask;
+
+- if (mtk_wed_is_v1(dev->hw))
+- ext_mask |= MTK_WED_EXT_INT_STATUS_TX_DRV_R_RESP_ERR;
++ if (mtk_wed_is_v3_or_greater(dev->hw))
++ ext_mask = MTK_WED_EXT_INT_STATUS_RX_DRV_COHERENT |
++ MTK_WED_EXT_INT_STATUS_TKID_WO_PYLD;
+ else
+- ext_mask |= MTK_WED_EXT_INT_STATUS_RX_FBUF_LO_TH |
+- MTK_WED_EXT_INT_STATUS_RX_FBUF_HI_TH |
+- MTK_WED_EXT_INT_STATUS_RX_DRV_COHERENT |
+- MTK_WED_EXT_INT_STATUS_TX_DMA_W_RESP_ERR;
++ ext_mask = MTK_WED_EXT_INT_STATUS_ERROR_MASK;
+
+ val = wed_r32(dev, MTK_WED_EXT_INT_STATUS);
+ wed_w32(dev, MTK_WED_EXT_INT_STATUS, val);
+@@ -1942,6 +2133,9 @@ void mtk_wed_add_hw(struct device_node *
+ case 2:
+ hw->soc = &mt7986_data;
+ break;
++ case 3:
++ hw->soc = &mt7988_data;
++ break;
+ default:
+ case 1:
+ hw->mirror = syscon_regmap_lookup_by_phandle(eth_np,
+--- a/drivers/net/ethernet/mediatek/mtk_wed.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed.h
+@@ -9,6 +9,8 @@
+ #include <linux/regmap.h>
+ #include <linux/netdevice.h>
+
++#include "mtk_wed_regs.h"
++
+ struct mtk_eth;
+ struct mtk_wed_wo;
+
+@@ -19,6 +21,7 @@ struct mtk_wed_soc_data {
+ u32 reset_idx_tx_mask;
+ u32 reset_idx_rx_mask;
+ } regmap;
++ u32 tx_ring_desc_size;
+ u32 wdma_desc_size;
+ };
+
+@@ -35,6 +38,7 @@ struct mtk_wed_hw {
+ struct dentry *debugfs_dir;
+ struct mtk_wed_device *wed_dev;
+ struct mtk_wed_wo *wed_wo;
++ u32 pcie_base;
+ u32 debugfs_reg;
+ u32 num_flows;
+ u8 version;
+@@ -61,6 +65,16 @@ static inline bool mtk_wed_is_v2(struct
+ return hw->version == 2;
+ }
+
++static inline bool mtk_wed_is_v3(struct mtk_wed_hw *hw)
++{
++ return hw->version == 3;
++}
++
++static inline bool mtk_wed_is_v3_or_greater(struct mtk_wed_hw *hw)
++{
++ return hw->version > 2;
++}
++
+ static inline void
+ wed_w32(struct mtk_wed_device *dev, u32 reg, u32 val)
+ {
+@@ -143,6 +157,21 @@ wpdma_txfree_w32(struct mtk_wed_device *
+ writel(val, dev->txfree_ring.wpdma + reg);
+ }
+
++static inline u32 mtk_wed_get_pcie_base(struct mtk_wed_device *dev)
++{
++ if (!mtk_wed_is_v3_or_greater(dev->hw))
++ return MTK_WED_PCIE_BASE;
++
++ switch (dev->hw->index) {
++ case 1:
++ return MTK_WED_PCIE_BASE1;
++ case 2:
++ return MTK_WED_PCIE_BASE2;
++ default:
++ return MTK_WED_PCIE_BASE0;
++ }
++}
++
+ void mtk_wed_add_hw(struct device_node *np, struct mtk_eth *eth,
+ void __iomem *wdma, phys_addr_t wdma_phy,
+ int index);
+--- a/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
+@@ -331,10 +331,22 @@ mtk_wed_mcu_load_firmware(struct mtk_wed
+ wo->hw->index + 1);
+
+ /* load firmware */
+- if (of_device_is_compatible(wo->hw->node, "mediatek,mt7981-wed"))
+- fw_name = MT7981_FIRMWARE_WO;
+- else
+- fw_name = wo->hw->index ? MT7986_FIRMWARE_WO1 : MT7986_FIRMWARE_WO0;
++ switch (wo->hw->version) {
++ case 2:
++ if (of_device_is_compatible(wo->hw->node,
++ "mediatek,mt7981-wed"))
++ fw_name = MT7981_FIRMWARE_WO;
++ else
++ fw_name = wo->hw->index ? MT7986_FIRMWARE_WO1
++ : MT7986_FIRMWARE_WO0;
++ break;
++ case 3:
++ fw_name = wo->hw->index ? MT7988_FIRMWARE_WO1
++ : MT7988_FIRMWARE_WO0;
++ break;
++ default:
++ return -EINVAL;
++ }
+
+ ret = request_firmware(&fw, fw_name, wo->hw->dev);
+ if (ret)
+@@ -355,15 +367,16 @@ mtk_wed_mcu_load_firmware(struct mtk_wed
+ }
+
+ /* set the start address */
+- boot_cr = wo->hw->index ? MTK_WO_MCU_CFG_LS_WA_BOOT_ADDR_ADDR
+- : MTK_WO_MCU_CFG_LS_WM_BOOT_ADDR_ADDR;
++ if (!mtk_wed_is_v3_or_greater(wo->hw) && wo->hw->index)
++ boot_cr = MTK_WO_MCU_CFG_LS_WA_BOOT_ADDR_ADDR;
++ else
++ boot_cr = MTK_WO_MCU_CFG_LS_WM_BOOT_ADDR_ADDR;
+ wo_w32(wo, boot_cr, mem_region[MTK_WED_WO_REGION_EMI].phy_addr >> 16);
+ /* wo firmware reset */
+ wo_w32(wo, MTK_WO_MCU_CFG_LS_WF_MCCR_CLR_ADDR, 0xc00);
+
+- val = wo_r32(wo, MTK_WO_MCU_CFG_LS_WF_MCU_CFG_WM_WA_ADDR);
+- val |= wo->hw->index ? MTK_WO_MCU_CFG_LS_WF_WM_WA_WA_CPU_RSTB_MASK
+- : MTK_WO_MCU_CFG_LS_WF_WM_WA_WM_CPU_RSTB_MASK;
++ val = wo_r32(wo, MTK_WO_MCU_CFG_LS_WF_MCU_CFG_WM_WA_ADDR) |
++ MTK_WO_MCU_CFG_LS_WF_WM_WA_WM_CPU_RSTB_MASK;
+ wo_w32(wo, MTK_WO_MCU_CFG_LS_WF_MCU_CFG_WM_WA_ADDR, val);
+ out:
+ release_firmware(fw);
+@@ -398,3 +411,5 @@ int mtk_wed_mcu_init(struct mtk_wed_wo *
+ MODULE_FIRMWARE(MT7981_FIRMWARE_WO);
+ MODULE_FIRMWARE(MT7986_FIRMWARE_WO0);
+ MODULE_FIRMWARE(MT7986_FIRMWARE_WO1);
++MODULE_FIRMWARE(MT7988_FIRMWARE_WO0);
++MODULE_FIRMWARE(MT7988_FIRMWARE_WO1);
+--- a/drivers/net/ethernet/mediatek/mtk_wed_regs.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
+@@ -13,6 +13,9 @@
+ #define MTK_WDMA_DESC_CTRL_LAST_SEG0 BIT(30)
+ #define MTK_WDMA_DESC_CTRL_DMA_DONE BIT(31)
+
++#define MTK_WDMA_TXD0_DESC_INFO_DMA_DONE BIT(29)
++#define MTK_WDMA_TXD1_DESC_INFO_DMA_DONE BIT(31)
++
+ struct mtk_wdma_desc {
+ __le32 buf0;
+ __le32 ctrl;
+@@ -37,6 +40,7 @@ struct mtk_wdma_desc {
+ #define MTK_WED_RESET_WDMA_INT_AGENT BIT(19)
+ #define MTK_WED_RESET_RX_RRO_QM BIT(20)
+ #define MTK_WED_RESET_RX_ROUTE_QM BIT(21)
++#define MTK_WED_RESET_TX_AMSDU BIT(22)
+ #define MTK_WED_RESET_WED BIT(31)
+
+ #define MTK_WED_CTRL 0x00c
+@@ -44,6 +48,9 @@ struct mtk_wdma_desc {
+ #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_RX_IND_CMD_EN BIT(5)
++#define MTK_WED_CTRL_WED_RX_PG_BM_EN BIT(6)
++#define MTK_WED_CTRL_WED_RX_PG_BM_BUSY BIT(7)
+ #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)
+@@ -54,9 +61,14 @@ struct mtk_wdma_desc {
+ #define MTK_WED_CTRL_RX_RRO_QM_BUSY BIT(15)
+ #define MTK_WED_CTRL_RX_ROUTE_QM_EN BIT(16)
+ #define MTK_WED_CTRL_RX_ROUTE_QM_BUSY BIT(17)
++#define MTK_WED_CTRL_TX_TKID_ALI_EN BIT(20)
++#define MTK_WED_CTRL_TX_TKID_ALI_BUSY BIT(21)
++#define MTK_WED_CTRL_TX_AMSDU_EN BIT(22)
++#define MTK_WED_CTRL_TX_AMSDU_BUSY BIT(23)
+ #define MTK_WED_CTRL_FINAL_DIDX_READ BIT(24)
+ #define MTK_WED_CTRL_ETH_DMAD_FMT BIT(25)
+ #define MTK_WED_CTRL_MIB_READ_CLEAR BIT(28)
++#define MTK_WED_CTRL_FLD_MIB_RD_CLR BIT(28)
+
+ #define MTK_WED_EXT_INT_STATUS 0x020
+ #define MTK_WED_EXT_INT_STATUS_TF_LEN_ERR BIT(0)
+@@ -89,6 +101,7 @@ struct mtk_wdma_desc {
+ #define MTK_WED_EXT_INT_MASK 0x028
+ #define MTK_WED_EXT_INT_MASK1 0x02c
+ #define MTK_WED_EXT_INT_MASK2 0x030
++#define MTK_WED_EXT_INT_MASK3 0x034
+
+ #define MTK_WED_STATUS 0x060
+ #define MTK_WED_STATUS_TX GENMASK(15, 8)
+@@ -96,9 +109,14 @@ struct mtk_wdma_desc {
+ #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_LEGACY_EN BIT(26)
++#define MTK_WED_TX_TKID_CTRL_FREE_FORMAT BIT(27)
+ #define MTK_WED_TX_BM_CTRL_PAUSE BIT(28)
+
+ #define MTK_WED_TX_BM_BASE 0x084
++#define MTK_WED_TX_BM_INIT_PTR 0x088
++#define MTK_WED_TX_BM_SW_TAIL_IDX GENMASK(16, 0)
++#define MTK_WED_TX_BM_INIT_SW_TAIL_IDX BIT(16)
+
+ #define MTK_WED_TX_BM_TKID_START GENMASK(15, 0)
+ #define MTK_WED_TX_BM_TKID_END GENMASK(31, 16)
+@@ -122,6 +140,9 @@ struct mtk_wdma_desc {
+ #define MTK_WED_TX_TKID_CTRL_RSV_GRP_NUM GENMASK(22, 16)
+ #define MTK_WED_TX_TKID_CTRL_PAUSE BIT(28)
+
++#define MTK_WED_TX_TKID_CTRL_VLD_GRP_NUM_V3 GENMASK(7, 0)
++#define MTK_WED_TX_TKID_CTRL_RSV_GRP_NUM_V3 GENMASK(23, 16)
++
+ #define MTK_WED_TX_TKID_DYN_THR 0x0e0
+ #define MTK_WED_TX_TKID_DYN_THR_LO GENMASK(6, 0)
+ #define MTK_WED_TX_TKID_DYN_THR_HI GENMASK(22, 16)
+@@ -199,12 +220,15 @@ struct mtk_wdma_desc {
+ #define MTK_WED_WPDMA_GLO_CFG_RX_DRV_R1_PKT_PROC BIT(5)
+ #define MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_CRX_SYNC BIT(6)
+ #define MTK_WED_WPDMA_GLO_CFG_RX_DRV_R1_CRX_SYNC BIT(7)
+-#define MTK_WED_WPDMA_GLO_CFG_RX_DRV_EVENT_PKT_FMT_VER GENMASK(18, 16)
++#define MTK_WED_WPDMA_GLO_CFG_RX_DRV_EVENT_PKT_FMT_VER GENMASK(15, 12)
++#define MTK_WED_WPDMA_GLO_CFG_RX_DRV_UNS_VER_FORCE_4 BIT(18)
+ #define MTK_WED_WPDMA_GLO_CFG_RX_DRV_UNSUPPORT_FMT BIT(19)
+-#define MTK_WED_WPDMA_GLO_CFG_RX_DRV_UEVENT_PKT_FMT_CHK BIT(20)
++#define MTK_WED_WPDMA_GLO_CFG_RX_DRV_EVENT_PKT_FMT_CHK BIT(20)
+ #define MTK_WED_WPDMA_GLO_CFG_RX_DDONE2_WR BIT(21)
+ #define MTK_WED_WPDMA_GLO_CFG_TX_TKID_KEEP BIT(24)
++#define MTK_WED_WPDMA_GLO_CFG_TX_DDONE_CHK_LAST BIT(25)
+ #define MTK_WED_WPDMA_GLO_CFG_TX_DMAD_DW3_PREV BIT(28)
++#define MTK_WED_WPDMA_GLO_CFG_TX_DDONE_CHK BIT(30)
+
+ #define MTK_WED_WPDMA_RESET_IDX 0x50c
+ #define MTK_WED_WPDMA_RESET_IDX_TX GENMASK(3, 0)
+@@ -250,9 +274,10 @@ struct mtk_wdma_desc {
+ #define MTK_WED_PCIE_INT_TRIGGER_STATUS BIT(16)
+
+ #define MTK_WED_PCIE_INT_CTRL 0x57c
+-#define MTK_WED_PCIE_INT_CTRL_MSK_EN_POLA BIT(20)
+-#define MTK_WED_PCIE_INT_CTRL_SRC_SEL GENMASK(17, 16)
+ #define MTK_WED_PCIE_INT_CTRL_POLL_EN GENMASK(13, 12)
++#define MTK_WED_PCIE_INT_CTRL_SRC_SEL GENMASK(17, 16)
++#define MTK_WED_PCIE_INT_CTRL_MSK_EN_POLA BIT(20)
++#define MTK_WED_PCIE_INT_CTRL_MSK_IRQ_FILTER BIT(21)
+
+ #define MTK_WED_WPDMA_CFG_BASE 0x580
+ #define MTK_WED_WPDMA_CFG_INT_MASK 0x584
+@@ -286,6 +311,20 @@ struct mtk_wdma_desc {
+ #define MTK_WED_WPDMA_RX_D_PROCESSED_MIB(_n) (0x784 + (_n) * 4)
+ #define MTK_WED_WPDMA_RX_D_COHERENT_MIB 0x78c
+
++#define MTK_WED_WPDMA_RX_D_PREF_CFG 0x7b4
++#define MTK_WED_WPDMA_RX_D_PREF_EN BIT(0)
++#define MTK_WED_WPDMA_RX_D_PREF_BURST_SIZE GENMASK(12, 8)
++#define MTK_WED_WPDMA_RX_D_PREF_LOW_THRES GENMASK(21, 16)
++
++#define MTK_WED_WPDMA_RX_D_PREF_RX0_SIDX 0x7b8
++#define MTK_WED_WPDMA_RX_D_PREF_SIDX_IDX_CLR BIT(15)
++
++#define MTK_WED_WPDMA_RX_D_PREF_RX1_SIDX 0x7bc
++
++#define MTK_WED_WPDMA_RX_D_PREF_FIFO_CFG 0x7c0
++#define MTK_WED_WPDMA_RX_D_PREF_FIFO_CFG_R0_CLR BIT(0)
++#define MTK_WED_WPDMA_RX_D_PREF_FIFO_CFG_R1_CLR BIT(16)
++
+ #define MTK_WED_WDMA_RING_TX 0x800
+
+ #define MTK_WED_WDMA_TX_MIB 0x810
+@@ -293,6 +332,18 @@ struct mtk_wdma_desc {
+ #define MTK_WED_WDMA_RING_RX(_n) (0x900 + (_n) * 0x10)
+ #define MTK_WED_WDMA_RX_THRES(_n) (0x940 + (_n) * 0x4)
+
++#define MTK_WED_WDMA_RX_PREF_CFG 0x950
++#define MTK_WED_WDMA_RX_PREF_EN BIT(0)
++#define MTK_WED_WDMA_RX_PREF_BURST_SIZE GENMASK(12, 8)
++#define MTK_WED_WDMA_RX_PREF_LOW_THRES GENMASK(21, 16)
++#define MTK_WED_WDMA_RX_PREF_RX0_SIDX_CLR BIT(24)
++#define MTK_WED_WDMA_RX_PREF_RX1_SIDX_CLR BIT(25)
++#define MTK_WED_WDMA_RX_PREF_DDONE2_EN BIT(26)
++
++#define MTK_WED_WDMA_RX_PREF_FIFO_CFG 0x95C
++#define MTK_WED_WDMA_RX_PREF_FIFO_RX0_CLR BIT(0)
++#define MTK_WED_WDMA_RX_PREF_FIFO_RX1_CLR BIT(16)
++
+ #define MTK_WED_WDMA_GLO_CFG 0xa04
+ #define MTK_WED_WDMA_GLO_CFG_TX_DRV_EN BIT(0)
+ #define MTK_WED_WDMA_GLO_CFG_TX_DDONE_CHK BIT(1)
+@@ -325,6 +376,7 @@ struct mtk_wdma_desc {
+ #define MTK_WED_WDMA_INT_TRIGGER_RX_DONE GENMASK(17, 16)
+
+ #define MTK_WED_WDMA_INT_CTRL 0xa2c
++#define MTK_WED_WDMA_INT_POLL_PRD GENMASK(7, 0)
+ #define MTK_WED_WDMA_INT_CTRL_POLL_SRC_SEL GENMASK(17, 16)
+
+ #define MTK_WED_WDMA_CFG_BASE 0xaa0
+@@ -388,6 +440,18 @@ struct mtk_wdma_desc {
+ #define MTK_WDMA_INT_GRP1 0x250
+ #define MTK_WDMA_INT_GRP2 0x254
+
++#define MTK_WDMA_PREF_TX_CFG 0x2d0
++#define MTK_WDMA_PREF_TX_CFG_PREF_EN BIT(0)
++
++#define MTK_WDMA_PREF_RX_CFG 0x2dc
++#define MTK_WDMA_PREF_RX_CFG_PREF_EN BIT(0)
++
++#define MTK_WDMA_WRBK_TX_CFG 0x300
++#define MTK_WDMA_WRBK_TX_CFG_WRBK_EN BIT(30)
++
++#define MTK_WDMA_WRBK_RX_CFG 0x344
++#define MTK_WDMA_WRBK_RX_CFG_WRBK_EN BIT(30)
++
+ #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)
+@@ -401,6 +465,30 @@ struct mtk_wdma_desc {
+ #define MTK_WED_RTQM_Q_DBG_BYPASS BIT(5)
+ #define MTK_WED_RTQM_TXDMAD_FPORT GENMASK(23, 20)
+
++#define MTK_WED_RTQM_IGRS0_I2HW_DMAD_CNT 0xb1c
++#define MTK_WED_RTQM_IGRS0_I2H_DMAD_CNT(_n) (0xb20 + (_n) * 0x4)
++#define MTK_WED_RTQM_IGRS0_I2HW_PKT_CNT 0xb28
++#define MTK_WED_RTQM_IGRS0_I2H_PKT_CNT(_n) (0xb2c + (_n) * 0x4)
++#define MTK_WED_RTQM_IGRS0_FDROP_CNT 0xb34
++
++#define MTK_WED_RTQM_IGRS1_I2HW_DMAD_CNT 0xb44
++#define MTK_WED_RTQM_IGRS1_I2H_DMAD_CNT(_n) (0xb48 + (_n) * 0x4)
++#define MTK_WED_RTQM_IGRS1_I2HW_PKT_CNT 0xb50
++#define MTK_WED_RTQM_IGRS1_I2H_PKT_CNT(_n) (0xb54 + (_n) * 0x4)
++#define MTK_WED_RTQM_IGRS1_FDROP_CNT 0xb5c
++
++#define MTK_WED_RTQM_IGRS2_I2HW_DMAD_CNT 0xb6c
++#define MTK_WED_RTQM_IGRS2_I2H_DMAD_CNT(_n) (0xb70 + (_n) * 0x4)
++#define MTK_WED_RTQM_IGRS2_I2HW_PKT_CNT 0xb78
++#define MTK_WED_RTQM_IGRS2_I2H_PKT_CNT(_n) (0xb7c + (_n) * 0x4)
++#define MTK_WED_RTQM_IGRS2_FDROP_CNT 0xb84
++
++#define MTK_WED_RTQM_IGRS3_I2HW_DMAD_CNT 0xb94
++#define MTK_WED_RTQM_IGRS3_I2H_DMAD_CNT(_n) (0xb98 + (_n) * 0x4)
++#define MTK_WED_RTQM_IGRS3_I2HW_PKT_CNT 0xba0
++#define MTK_WED_RTQM_IGRS3_I2H_PKT_CNT(_n) (0xba4 + (_n) * 0x4)
++#define MTK_WED_RTQM_IGRS3_FDROP_CNT 0xbac
++
+ #define MTK_WED_RTQM_R2H_MIB(_n) (0xb70 + (_n) * 0x4)
+ #define MTK_WED_RTQM_R2Q_MIB(_n) (0xb78 + (_n) * 0x4)
+ #define MTK_WED_RTQM_Q2N_MIB 0xb80
+@@ -409,6 +497,24 @@ struct mtk_wdma_desc {
+ #define MTK_WED_RTQM_Q2B_MIB 0xb8c
+ #define MTK_WED_RTQM_PFDBK_MIB 0xb90
+
++#define MTK_WED_RTQM_ENQ_CFG0 0xbb8
++#define MTK_WED_RTQM_ENQ_CFG_TXDMAD_FPORT GENMASK(15, 12)
++
++#define MTK_WED_RTQM_FDROP_MIB 0xb84
++#define MTK_WED_RTQM_ENQ_I2Q_DMAD_CNT 0xbbc
++#define MTK_WED_RTQM_ENQ_I2N_DMAD_CNT 0xbc0
++#define MTK_WED_RTQM_ENQ_I2Q_PKT_CNT 0xbc4
++#define MTK_WED_RTQM_ENQ_I2N_PKT_CNT 0xbc8
++#define MTK_WED_RTQM_ENQ_USED_ENTRY_CNT 0xbcc
++#define MTK_WED_RTQM_ENQ_ERR_CNT 0xbd0
++
++#define MTK_WED_RTQM_DEQ_DMAD_CNT 0xbd8
++#define MTK_WED_RTQM_DEQ_Q2I_DMAD_CNT 0xbdc
++#define MTK_WED_RTQM_DEQ_PKT_CNT 0xbe0
++#define MTK_WED_RTQM_DEQ_Q2I_PKT_CNT 0xbe4
++#define MTK_WED_RTQM_DEQ_USED_PFDBK_CNT 0xbe8
++#define MTK_WED_RTQM_DEQ_ERR_CNT 0xbec
++
+ #define MTK_WED_RROQM_GLO_CFG 0xc04
+ #define MTK_WED_RROQM_RST_IDX 0xc08
+ #define MTK_WED_RROQM_RST_IDX_MIOD BIT(0)
+@@ -458,7 +564,116 @@ struct mtk_wdma_desc {
+ #define MTK_WED_RX_BM_INTF 0xd9c
+ #define MTK_WED_RX_BM_ERR_STS 0xda8
+
++#define MTK_RRO_IND_CMD_SIGNATURE 0xe00
++#define MTK_RRO_IND_CMD_DMA_IDX GENMASK(11, 0)
++#define MTK_RRO_IND_CMD_MAGIC_CNT GENMASK(30, 28)
++
++#define MTK_WED_IND_CMD_RX_CTRL0 0xe04
++#define MTK_WED_IND_CMD_PROC_IDX GENMASK(11, 0)
++#define MTK_WED_IND_CMD_PREFETCH_FREE_CNT GENMASK(19, 16)
++#define MTK_WED_IND_CMD_MAGIC_CNT GENMASK(30, 28)
++
++#define MTK_WED_IND_CMD_RX_CTRL1 0xe08
++#define MTK_WED_IND_CMD_RX_CTRL2 0xe0c
++#define MTK_WED_IND_CMD_MAX_CNT GENMASK(11, 0)
++#define MTK_WED_IND_CMD_BASE_M GENMASK(19, 16)
++
++#define MTK_WED_RRO_CFG0 0xe10
++#define MTK_WED_RRO_CFG1 0xe14
++#define MTK_WED_RRO_CFG1_MAX_WIN_SZ GENMASK(31, 29)
++#define MTK_WED_RRO_CFG1_ACK_SN_BASE_M GENMASK(19, 16)
++#define MTK_WED_RRO_CFG1_PARTICL_SE_ID GENMASK(11, 0)
++
++#define MTK_WED_ADDR_ELEM_CFG0 0xe18
++#define MTK_WED_ADDR_ELEM_CFG1 0xe1c
++#define MTK_WED_ADDR_ELEM_PREFETCH_FREE_CNT GENMASK(19, 16)
++
++#define MTK_WED_ADDR_ELEM_TBL_CFG 0xe20
++#define MTK_WED_ADDR_ELEM_TBL_OFFSET GENMASK(6, 0)
++#define MTK_WED_ADDR_ELEM_TBL_RD_RDY BIT(28)
++#define MTK_WED_ADDR_ELEM_TBL_WR_RDY BIT(29)
++#define MTK_WED_ADDR_ELEM_TBL_RD BIT(30)
++#define MTK_WED_ADDR_ELEM_TBL_WR BIT(31)
++
++#define MTK_WED_RADDR_ELEM_TBL_WDATA 0xe24
++#define MTK_WED_RADDR_ELEM_TBL_RDATA 0xe28
++
++#define MTK_WED_PN_CHECK_CFG 0xe30
++#define MTK_WED_PN_CHECK_SE_ID GENMASK(11, 0)
++#define MTK_WED_PN_CHECK_RD_RDY BIT(28)
++#define MTK_WED_PN_CHECK_WR_RDY BIT(29)
++#define MTK_WED_PN_CHECK_RD BIT(30)
++#define MTK_WED_PN_CHECK_WR BIT(31)
++
++#define MTK_WED_PN_CHECK_WDATA_M 0xe38
++#define MTK_WED_PN_CHECK_IS_FIRST BIT(17)
++
++#define MTK_WED_RRO_MSDU_PG_RING_CFG(_n) (0xe44 + (_n) * 0x8)
++
++#define MTK_WED_RRO_MSDU_PG_RING2_CFG 0xe58
++#define MTK_WED_RRO_MSDU_PG_DRV_CLR BIT(26)
++#define MTK_WED_RRO_MSDU_PG_DRV_EN BIT(31)
++
++#define MTK_WED_RRO_MSDU_PG_CTRL0(_n) (0xe5c + (_n) * 0xc)
++#define MTK_WED_RRO_MSDU_PG_CTRL1(_n) (0xe60 + (_n) * 0xc)
++#define MTK_WED_RRO_MSDU_PG_CTRL2(_n) (0xe64 + (_n) * 0xc)
++
++#define MTK_WED_RRO_RX_D_RX(_n) (0xe80 + (_n) * 0x10)
++
++#define MTK_WED_RRO_RX_MAGIC_CNT BIT(13)
++
++#define MTK_WED_RRO_RX_D_CFG(_n) (0xea0 + (_n) * 0x4)
++#define MTK_WED_RRO_RX_D_DRV_CLR BIT(26)
++#define MTK_WED_RRO_RX_D_DRV_EN BIT(31)
++
++#define MTK_WED_RRO_PG_BM_RX_DMAM 0xeb0
++#define MTK_WED_RRO_PG_BM_RX_SDL0 GENMASK(13, 0)
++
++#define MTK_WED_RRO_PG_BM_BASE 0xeb4
++#define MTK_WED_RRO_PG_BM_INIT_PTR 0xeb8
++#define MTK_WED_RRO_PG_BM_SW_TAIL_IDX GENMASK(15, 0)
++#define MTK_WED_RRO_PG_BM_INIT_SW_TAIL_IDX BIT(16)
++
++#define MTK_WED_WPDMA_INT_CTRL_RRO_RX 0xeec
++#define MTK_WED_WPDMA_INT_CTRL_RRO_RX0_EN BIT(0)
++#define MTK_WED_WPDMA_INT_CTRL_RRO_RX0_CLR BIT(1)
++#define MTK_WED_WPDMA_INT_CTRL_RRO_RX0_DONE_TRIG GENMASK(6, 2)
++#define MTK_WED_WPDMA_INT_CTRL_RRO_RX1_EN BIT(8)
++#define MTK_WED_WPDMA_INT_CTRL_RRO_RX1_CLR BIT(9)
++#define MTK_WED_WPDMA_INT_CTRL_RRO_RX1_DONE_TRIG GENMASK(14, 10)
++
++#define MTK_WED_WPDMA_INT_CTRL_RRO_MSDU_PG 0xef4
++#define MTK_WED_WPDMA_INT_CTRL_RRO_PG0_EN BIT(0)
++#define MTK_WED_WPDMA_INT_CTRL_RRO_PG0_CLR BIT(1)
++#define MTK_WED_WPDMA_INT_CTRL_RRO_PG0_DONE_TRIG GENMASK(6, 2)
++#define MTK_WED_WPDMA_INT_CTRL_RRO_PG1_EN BIT(8)
++#define MTK_WED_WPDMA_INT_CTRL_RRO_PG1_CLR BIT(9)
++#define MTK_WED_WPDMA_INT_CTRL_RRO_PG1_DONE_TRIG GENMASK(14, 10)
++#define MTK_WED_WPDMA_INT_CTRL_RRO_PG2_EN BIT(16)
++#define MTK_WED_WPDMA_INT_CTRL_RRO_PG2_CLR BIT(17)
++#define MTK_WED_WPDMA_INT_CTRL_RRO_PG2_DONE_TRIG GENMASK(22, 18)
++
++#define MTK_WED_RX_IND_CMD_CNT0 0xf20
++#define MTK_WED_RX_IND_CMD_DBG_CNT_EN BIT(31)
++
++#define MTK_WED_RX_IND_CMD_CNT(_n) (0xf20 + (_n) * 0x4)
++#define MTK_WED_IND_CMD_MAGIC_CNT_FAIL_CNT GENMASK(15, 0)
++
++#define MTK_WED_RX_ADDR_ELEM_CNT(_n) (0xf48 + (_n) * 0x4)
++#define MTK_WED_ADDR_ELEM_SIG_FAIL_CNT GENMASK(15, 0)
++#define MTK_WED_ADDR_ELEM_FIRST_SIG_FAIL_CNT GENMASK(31, 16)
++#define MTK_WED_ADDR_ELEM_ACKSN_CNT GENMASK(27, 0)
++
++#define MTK_WED_RX_MSDU_PG_CNT(_n) (0xf5c + (_n) * 0x4)
++
++#define MTK_WED_RX_PN_CHK_CNT 0xf70
++#define MTK_WED_PN_CHK_FAIL_CNT GENMASK(15, 0)
++
+ #define MTK_WED_WOCPU_VIEW_MIOD_BASE 0x8000
+ #define MTK_WED_PCIE_INT_MASK 0x0
+
++#define MTK_WED_PCIE_BASE 0x11280000
++#define MTK_WED_PCIE_BASE0 0x11300000
++#define MTK_WED_PCIE_BASE1 0x11310000
++#define MTK_WED_PCIE_BASE2 0x11290000
+ #endif
+--- a/drivers/net/ethernet/mediatek/mtk_wed_wo.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed_wo.h
+@@ -91,6 +91,8 @@ enum mtk_wed_dummy_cr_idx {
+ #define MT7981_FIRMWARE_WO "mediatek/mt7981_wo.bin"
+ #define MT7986_FIRMWARE_WO0 "mediatek/mt7986_wo_0.bin"
+ #define MT7986_FIRMWARE_WO1 "mediatek/mt7986_wo_1.bin"
++#define MT7988_FIRMWARE_WO0 "mediatek/mt7988_wo_0.bin"
++#define MT7988_FIRMWARE_WO1 "mediatek/mt7988_wo_1.bin"
+
+ #define MTK_WO_MCU_CFG_LS_BASE 0
+ #define MTK_WO_MCU_CFG_LS_HW_VER_ADDR (MTK_WO_MCU_CFG_LS_BASE + 0x000)
+--- a/include/linux/soc/mediatek/mtk_wed.h
++++ b/include/linux/soc/mediatek/mtk_wed.h
+@@ -139,6 +139,8 @@ struct mtk_wed_device {
+ u32 wpdma_rx;
+
+ bool wcid_512;
++ bool hw_rro;
++ bool msi;
+
+ u16 token_start;
+ unsigned int nbuf;
+@@ -212,10 +214,12 @@ mtk_wed_device_attach(struct mtk_wed_dev
+ return ret;
+ }
+
+-static inline bool
+-mtk_wed_get_rx_capa(struct mtk_wed_device *dev)
++static inline bool mtk_wed_get_rx_capa(struct mtk_wed_device *dev)
+ {
+ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
++ if (dev->version == 3)
++ return dev->wlan.hw_rro;
++
+ return dev->version != 1;
+ #else
+ return false;
diff --git a/target/linux/generic/backport-6.6/752-15-v6.7-net-ethernet-mtk_wed-refactor-mtk_wed_check_wfdma_rx.patch b/target/linux/generic/backport-6.6/752-15-v6.7-net-ethernet-mtk_wed-refactor-mtk_wed_check_wfdma_rx.patch
new file mode 100644
index 0000000000..e91ae69d08
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-15-v6.7-net-ethernet-mtk_wed-refactor-mtk_wed_check_wfdma_rx.patch
@@ -0,0 +1,95 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Mon, 18 Sep 2023 12:29:14 +0200
+Subject: [PATCH] net: ethernet: mtk_wed: refactor mtk_wed_check_wfdma_rx_fill
+ routine
+
+Refactor mtk_wed_check_wfdma_rx_fill() in order to be reused adding HW
+receive offload support for MT7988 SoC.
+
+Co-developed-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -585,22 +585,15 @@ mtk_wed_set_512_support(struct mtk_wed_d
+ }
+ }
+
+-#define MTK_WFMDA_RX_DMA_EN BIT(2)
+-static void
+-mtk_wed_check_wfdma_rx_fill(struct mtk_wed_device *dev, int idx)
++static int
++mtk_wed_check_wfdma_rx_fill(struct mtk_wed_device *dev,
++ struct mtk_wed_ring *ring)
+ {
+- u32 val;
+ int i;
+
+- if (!(dev->rx_ring[idx].flags & MTK_WED_RING_CONFIGURED))
+- return; /* queue is not configured by mt76 */
+-
+ for (i = 0; i < 3; i++) {
+- u32 cur_idx;
++ u32 cur_idx = readl(ring->wpdma + MTK_WED_RING_OFS_CPU_IDX);
+
+- cur_idx = wed_r32(dev,
+- MTK_WED_WPDMA_RING_RX_DATA(idx) +
+- MTK_WED_RING_OFS_CPU_IDX);
+ if (cur_idx == MTK_WED_RX_RING_SIZE - 1)
+ break;
+
+@@ -609,12 +602,10 @@ mtk_wed_check_wfdma_rx_fill(struct mtk_w
+
+ if (i == 3) {
+ dev_err(dev->hw->dev, "rx dma enable failed\n");
+- return;
++ return -ETIMEDOUT;
+ }
+
+- val = wifi_r32(dev, dev->wlan.wpdma_rx_glo - dev->wlan.phy_base) |
+- MTK_WFMDA_RX_DMA_EN;
+- wifi_w32(dev, dev->wlan.wpdma_rx_glo - dev->wlan.phy_base, val);
++ return 0;
+ }
+
+ static void
+@@ -1545,6 +1536,7 @@ mtk_wed_configure_irq(struct mtk_wed_dev
+ wed_w32(dev, MTK_WED_INT_MASK, irq_mask);
+ }
+
++#define MTK_WFMDA_RX_DMA_EN BIT(2)
+ static void
+ mtk_wed_dma_enable(struct mtk_wed_device *dev)
+ {
+@@ -1632,8 +1624,26 @@ mtk_wed_dma_enable(struct mtk_wed_device
+ wdma_set(dev, MTK_WDMA_WRBK_TX_CFG, MTK_WDMA_WRBK_TX_CFG_WRBK_EN);
+ }
+
+- for (i = 0; i < MTK_WED_RX_QUEUES; i++)
+- mtk_wed_check_wfdma_rx_fill(dev, i);
++ for (i = 0; i < MTK_WED_RX_QUEUES; i++) {
++ struct mtk_wed_ring *ring = &dev->rx_ring[i];
++ u32 val;
++
++ if (!(ring->flags & MTK_WED_RING_CONFIGURED))
++ continue; /* queue is not configured by mt76 */
++
++ if (mtk_wed_check_wfdma_rx_fill(dev, ring)) {
++ dev_err(dev->hw->dev,
++ "rx_ring(%d) dma enable failed\n", i);
++ continue;
++ }
++
++ val = wifi_r32(dev,
++ dev->wlan.wpdma_rx_glo -
++ dev->wlan.phy_base) | MTK_WFMDA_RX_DMA_EN;
++ wifi_w32(dev,
++ dev->wlan.wpdma_rx_glo - dev->wlan.phy_base,
++ val);
++ }
+ }
+
+ static void
diff --git a/target/linux/generic/backport-6.6/752-16-v6.7-net-ethernet-mtk_wed-introduce-partial-AMSDU-offload.patch b/target/linux/generic/backport-6.6/752-16-v6.7-net-ethernet-mtk_wed-introduce-partial-AMSDU-offload.patch
new file mode 100644
index 0000000000..6534d73d8e
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-16-v6.7-net-ethernet-mtk_wed-introduce-partial-AMSDU-offload.patch
@@ -0,0 +1,465 @@
+From: Sujuan Chen <sujuan.chen@mediatek.com>
+Date: Mon, 18 Sep 2023 12:29:15 +0200
+Subject: [PATCH] net: ethernet: mtk_wed: introduce partial AMSDU offload
+ support for MT7988
+
+Introduce partial AMSDU offload support for MT7988 SoC in order to merge
+in hw packets belonging to the same AMSDU before passing them to the
+WLAN nic.
+
+Co-developed-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
+@@ -438,7 +438,8 @@ int mtk_foe_entry_set_pppoe(struct mtk_e
+ }
+
+ int mtk_foe_entry_set_wdma(struct mtk_eth *eth, struct mtk_foe_entry *entry,
+- int wdma_idx, int txq, int bss, int wcid)
++ int wdma_idx, int txq, int bss, int wcid,
++ bool amsdu_en)
+ {
+ struct mtk_foe_mac_info *l2 = mtk_foe_entry_l2(eth, entry);
+ u32 *ib2 = mtk_foe_entry_ib2(eth, entry);
+@@ -450,6 +451,7 @@ int mtk_foe_entry_set_wdma(struct mtk_et
+ MTK_FOE_IB2_WDMA_WINFO_V2;
+ l2->w3info = FIELD_PREP(MTK_FOE_WINFO_WCID_V3, wcid) |
+ FIELD_PREP(MTK_FOE_WINFO_BSS_V3, bss);
++ l2->amsdu = FIELD_PREP(MTK_FOE_WINFO_AMSDU_EN, amsdu_en);
+ break;
+ case 2:
+ *ib2 &= ~MTK_FOE_IB2_PORT_MG_V2;
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
+@@ -88,13 +88,13 @@ enum {
+ #define MTK_FOE_WINFO_BSS_V3 GENMASK(23, 16)
+ #define MTK_FOE_WINFO_WCID_V3 GENMASK(15, 0)
+
+-#define MTK_FOE_WINFO_PAO_USR_INFO GENMASK(15, 0)
+-#define MTK_FOE_WINFO_PAO_TID GENMASK(19, 16)
+-#define MTK_FOE_WINFO_PAO_IS_FIXEDRATE BIT(20)
+-#define MTK_FOE_WINFO_PAO_IS_PRIOR BIT(21)
+-#define MTK_FOE_WINFO_PAO_IS_SP BIT(22)
+-#define MTK_FOE_WINFO_PAO_HF BIT(23)
+-#define MTK_FOE_WINFO_PAO_AMSDU_EN BIT(24)
++#define MTK_FOE_WINFO_AMSDU_USR_INFO GENMASK(15, 0)
++#define MTK_FOE_WINFO_AMSDU_TID GENMASK(19, 16)
++#define MTK_FOE_WINFO_AMSDU_IS_FIXEDRATE BIT(20)
++#define MTK_FOE_WINFO_AMSDU_IS_PRIOR BIT(21)
++#define MTK_FOE_WINFO_AMSDU_IS_SP BIT(22)
++#define MTK_FOE_WINFO_AMSDU_HF BIT(23)
++#define MTK_FOE_WINFO_AMSDU_EN BIT(24)
+
+ enum {
+ MTK_FOE_STATE_INVALID,
+@@ -123,7 +123,7 @@ struct mtk_foe_mac_info {
+
+ /* netsys_v3 */
+ u32 w3info;
+- u32 wpao;
++ u32 amsdu;
+ };
+
+ /* software-only entry type */
+@@ -394,7 +394,8 @@ int mtk_foe_entry_set_vlan(struct mtk_et
+ int mtk_foe_entry_set_pppoe(struct mtk_eth *eth, struct mtk_foe_entry *entry,
+ int sid);
+ int mtk_foe_entry_set_wdma(struct mtk_eth *eth, struct mtk_foe_entry *entry,
+- int wdma_idx, int txq, int bss, int wcid);
++ int wdma_idx, int txq, int bss, int wcid,
++ bool amsdu_en);
+ int mtk_foe_entry_set_queue(struct mtk_eth *eth, struct mtk_foe_entry *entry,
+ unsigned int queue);
+ int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+@@ -111,6 +111,7 @@ mtk_flow_get_wdma_info(struct net_device
+ info->queue = path->mtk_wdma.queue;
+ info->bss = path->mtk_wdma.bss;
+ info->wcid = path->mtk_wdma.wcid;
++ info->amsdu = path->mtk_wdma.amsdu;
+
+ return 0;
+ }
+@@ -192,7 +193,7 @@ mtk_flow_set_output_device(struct mtk_et
+
+ if (mtk_flow_get_wdma_info(dev, dest_mac, &info) == 0) {
+ mtk_foe_entry_set_wdma(eth, foe, info.wdma_idx, info.queue,
+- info.bss, info.wcid);
++ info.bss, info.wcid, info.amsdu);
+ if (mtk_is_netsys_v2_or_greater(eth)) {
+ switch (info.wdma_idx) {
+ case 0:
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -29,6 +29,8 @@
+ #define MTK_WED_RX_PAGE_BUF_PER_PAGE (PAGE_SIZE / 128)
+ #define MTK_WED_RX_RING_SIZE 1536
+ #define MTK_WED_RX_PG_BM_CNT 8192
++#define MTK_WED_AMSDU_BUF_SIZE (PAGE_SIZE << 4)
++#define MTK_WED_AMSDU_NPAGES 32
+
+ #define MTK_WED_TX_RING_SIZE 2048
+ #define MTK_WED_WDMA_RING_SIZE 1024
+@@ -172,6 +174,23 @@ mtk_wdma_rx_reset(struct mtk_wed_device
+ return ret;
+ }
+
++static u32
++mtk_wed_check_busy(struct mtk_wed_device *dev, u32 reg, u32 mask)
++{
++ return !!(wed_r32(dev, reg) & mask);
++}
++
++static int
++mtk_wed_poll_busy(struct mtk_wed_device *dev, u32 reg, u32 mask)
++{
++ int sleep = 15000;
++ int timeout = 100 * sleep;
++ u32 val;
++
++ return read_poll_timeout(mtk_wed_check_busy, val, !val, sleep,
++ timeout, false, dev, reg, mask);
++}
++
+ static void
+ mtk_wdma_tx_reset(struct mtk_wed_device *dev)
+ {
+@@ -335,6 +354,118 @@ out:
+ }
+
+ static int
++mtk_wed_amsdu_buffer_alloc(struct mtk_wed_device *dev)
++{
++ struct mtk_wed_hw *hw = dev->hw;
++ struct mtk_wed_amsdu *wed_amsdu;
++ int i;
++
++ if (!mtk_wed_is_v3_or_greater(hw))
++ return 0;
++
++ wed_amsdu = devm_kcalloc(hw->dev, MTK_WED_AMSDU_NPAGES,
++ sizeof(*wed_amsdu), GFP_KERNEL);
++ if (!wed_amsdu)
++ return -ENOMEM;
++
++ for (i = 0; i < MTK_WED_AMSDU_NPAGES; i++) {
++ void *ptr;
++
++ /* each segment is 64K */
++ ptr = (void *)__get_free_pages(GFP_KERNEL | __GFP_NOWARN |
++ __GFP_ZERO | __GFP_COMP |
++ GFP_DMA32,
++ get_order(MTK_WED_AMSDU_BUF_SIZE));
++ if (!ptr)
++ goto error;
++
++ wed_amsdu[i].txd = ptr;
++ wed_amsdu[i].txd_phy = dma_map_single(hw->dev, ptr,
++ MTK_WED_AMSDU_BUF_SIZE,
++ DMA_TO_DEVICE);
++ if (dma_mapping_error(hw->dev, wed_amsdu[i].txd_phy))
++ goto error;
++ }
++ dev->hw->wed_amsdu = wed_amsdu;
++
++ return 0;
++
++error:
++ for (i--; i >= 0; i--)
++ dma_unmap_single(hw->dev, wed_amsdu[i].txd_phy,
++ MTK_WED_AMSDU_BUF_SIZE, DMA_TO_DEVICE);
++ return -ENOMEM;
++}
++
++static void
++mtk_wed_amsdu_free_buffer(struct mtk_wed_device *dev)
++{
++ struct mtk_wed_amsdu *wed_amsdu = dev->hw->wed_amsdu;
++ int i;
++
++ if (!wed_amsdu)
++ return;
++
++ for (i = 0; i < MTK_WED_AMSDU_NPAGES; i++) {
++ dma_unmap_single(dev->hw->dev, wed_amsdu[i].txd_phy,
++ MTK_WED_AMSDU_BUF_SIZE, DMA_TO_DEVICE);
++ free_pages((unsigned long)wed_amsdu[i].txd,
++ get_order(MTK_WED_AMSDU_BUF_SIZE));
++ }
++}
++
++static int
++mtk_wed_amsdu_init(struct mtk_wed_device *dev)
++{
++ struct mtk_wed_amsdu *wed_amsdu = dev->hw->wed_amsdu;
++ int i, ret;
++
++ if (!wed_amsdu)
++ return 0;
++
++ for (i = 0; i < MTK_WED_AMSDU_NPAGES; i++)
++ wed_w32(dev, MTK_WED_AMSDU_HIFTXD_BASE_L(i),
++ wed_amsdu[i].txd_phy);
++
++ /* init all sta parameter */
++ wed_w32(dev, MTK_WED_AMSDU_STA_INFO_INIT, MTK_WED_AMSDU_STA_RMVL |
++ MTK_WED_AMSDU_STA_WTBL_HDRT_MODE |
++ FIELD_PREP(MTK_WED_AMSDU_STA_MAX_AMSDU_LEN,
++ dev->wlan.amsdu_max_len >> 8) |
++ FIELD_PREP(MTK_WED_AMSDU_STA_MAX_AMSDU_NUM,
++ dev->wlan.amsdu_max_subframes));
++
++ wed_w32(dev, MTK_WED_AMSDU_STA_INFO, MTK_WED_AMSDU_STA_INFO_DO_INIT);
++
++ ret = mtk_wed_poll_busy(dev, MTK_WED_AMSDU_STA_INFO,
++ MTK_WED_AMSDU_STA_INFO_DO_INIT);
++ if (ret) {
++ dev_err(dev->hw->dev, "amsdu initialization failed\n");
++ return ret;
++ }
++
++ /* init partial amsdu offload txd src */
++ wed_set(dev, MTK_WED_AMSDU_HIFTXD_CFG,
++ FIELD_PREP(MTK_WED_AMSDU_HIFTXD_SRC, dev->hw->index));
++
++ /* init qmem */
++ wed_set(dev, MTK_WED_AMSDU_PSE, MTK_WED_AMSDU_PSE_RESET);
++ ret = mtk_wed_poll_busy(dev, MTK_WED_MON_AMSDU_QMEM_STS1, BIT(29));
++ if (ret) {
++ pr_info("%s: amsdu qmem initialization failed\n", __func__);
++ return ret;
++ }
++
++ /* eagle E1 PCIE1 tx ring 22 flow control issue */
++ if (dev->wlan.id == 0x7991)
++ wed_clr(dev, MTK_WED_AMSDU_FIFO, MTK_WED_AMSDU_IS_PRIOR0_RING);
++
++ wed_set(dev, MTK_WED_CTRL, MTK_WED_CTRL_TX_AMSDU_EN);
++
++ return 0;
++}
++
++static int
+ mtk_wed_tx_buffer_alloc(struct mtk_wed_device *dev)
+ {
+ u32 desc_size = dev->hw->soc->tx_ring_desc_size;
+@@ -708,6 +839,7 @@ __mtk_wed_detach(struct mtk_wed_device *
+
+ mtk_wdma_rx_reset(dev);
+ mtk_wed_reset(dev, MTK_WED_RESET_WED);
++ mtk_wed_amsdu_free_buffer(dev);
+ mtk_wed_free_tx_buffer(dev);
+ mtk_wed_free_tx_rings(dev);
+
+@@ -1128,23 +1260,6 @@ mtk_wed_ring_reset(struct mtk_wed_ring *
+ }
+ }
+
+-static u32
+-mtk_wed_check_busy(struct mtk_wed_device *dev, u32 reg, u32 mask)
+-{
+- return !!(wed_r32(dev, reg) & mask);
+-}
+-
+-static int
+-mtk_wed_poll_busy(struct mtk_wed_device *dev, u32 reg, u32 mask)
+-{
+- int sleep = 15000;
+- int timeout = 100 * sleep;
+- u32 val;
+-
+- return read_poll_timeout(mtk_wed_check_busy, val, !val, sleep,
+- timeout, false, dev, reg, mask);
+-}
+-
+ static int
+ mtk_wed_rx_reset(struct mtk_wed_device *dev)
+ {
+@@ -1691,6 +1806,7 @@ mtk_wed_start(struct mtk_wed_device *dev
+ }
+
+ mtk_wed_set_512_support(dev, dev->wlan.wcid_512);
++ mtk_wed_amsdu_init(dev);
+
+ mtk_wed_dma_enable(dev);
+ dev->running = true;
+@@ -1747,6 +1863,10 @@ mtk_wed_attach(struct mtk_wed_device *de
+ if (ret)
+ goto out;
+
++ ret = mtk_wed_amsdu_buffer_alloc(dev);
++ if (ret)
++ goto out;
++
+ if (mtk_wed_get_rx_capa(dev)) {
+ ret = mtk_wed_rro_alloc(dev);
+ if (ret)
+--- a/drivers/net/ethernet/mediatek/mtk_wed.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed.h
+@@ -25,6 +25,11 @@ struct mtk_wed_soc_data {
+ u32 wdma_desc_size;
+ };
+
++struct mtk_wed_amsdu {
++ void *txd;
++ dma_addr_t txd_phy;
++};
++
+ struct mtk_wed_hw {
+ const struct mtk_wed_soc_data *soc;
+ struct device_node *node;
+@@ -38,6 +43,7 @@ struct mtk_wed_hw {
+ struct dentry *debugfs_dir;
+ struct mtk_wed_device *wed_dev;
+ struct mtk_wed_wo *wed_wo;
++ struct mtk_wed_amsdu *wed_amsdu;
+ u32 pcie_base;
+ u32 debugfs_reg;
+ u32 num_flows;
+@@ -52,6 +58,7 @@ struct mtk_wdma_info {
+ u8 queue;
+ u16 wcid;
+ u8 bss;
++ u8 amsdu;
+ };
+
+ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
+--- a/drivers/net/ethernet/mediatek/mtk_wed_regs.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
+@@ -672,6 +672,82 @@ struct mtk_wdma_desc {
+ #define MTK_WED_WOCPU_VIEW_MIOD_BASE 0x8000
+ #define MTK_WED_PCIE_INT_MASK 0x0
+
++#define MTK_WED_AMSDU_FIFO 0x1800
++#define MTK_WED_AMSDU_IS_PRIOR0_RING BIT(10)
++
++#define MTK_WED_AMSDU_STA_INFO 0x01810
++#define MTK_WED_AMSDU_STA_INFO_DO_INIT BIT(0)
++#define MTK_WED_AMSDU_STA_INFO_SET_INIT BIT(1)
++
++#define MTK_WED_AMSDU_STA_INFO_INIT 0x01814
++#define MTK_WED_AMSDU_STA_WTBL_HDRT_MODE BIT(0)
++#define MTK_WED_AMSDU_STA_RMVL BIT(1)
++#define MTK_WED_AMSDU_STA_MAX_AMSDU_LEN GENMASK(7, 2)
++#define MTK_WED_AMSDU_STA_MAX_AMSDU_NUM GENMASK(11, 8)
++
++#define MTK_WED_AMSDU_HIFTXD_BASE_L(_n) (0x1980 + (_n) * 0x4)
++
++#define MTK_WED_AMSDU_PSE 0x1910
++#define MTK_WED_AMSDU_PSE_RESET BIT(16)
++
++#define MTK_WED_AMSDU_HIFTXD_CFG 0x1968
++#define MTK_WED_AMSDU_HIFTXD_SRC GENMASK(16, 15)
++
++#define MTK_WED_MON_AMSDU_FIFO_DMAD 0x1a34
++
++#define MTK_WED_MON_AMSDU_ENG_DMAD(_n) (0x1a80 + (_n) * 0x50)
++#define MTK_WED_MON_AMSDU_ENG_QFPL(_n) (0x1a84 + (_n) * 0x50)
++#define MTK_WED_MON_AMSDU_ENG_QENI(_n) (0x1a88 + (_n) * 0x50)
++#define MTK_WED_MON_AMSDU_ENG_QENO(_n) (0x1a8c + (_n) * 0x50)
++#define MTK_WED_MON_AMSDU_ENG_MERG(_n) (0x1a90 + (_n) * 0x50)
++
++#define MTK_WED_MON_AMSDU_ENG_CNT8(_n) (0x1a94 + (_n) * 0x50)
++#define MTK_WED_AMSDU_ENG_MAX_QGPP_CNT GENMASK(10, 0)
++#define MTK_WED_AMSDU_ENG_MAX_PL_CNT GENMASK(27, 16)
++
++#define MTK_WED_MON_AMSDU_ENG_CNT9(_n) (0x1a98 + (_n) * 0x50)
++#define MTK_WED_AMSDU_ENG_CUR_ENTRY GENMASK(10, 0)
++#define MTK_WED_AMSDU_ENG_MAX_BUF_MERGED GENMASK(20, 16)
++#define MTK_WED_AMSDU_ENG_MAX_MSDU_MERGED GENMASK(28, 24)
++
++#define MTK_WED_MON_AMSDU_QMEM_STS1 0x1e04
++
++#define MTK_WED_MON_AMSDU_QMEM_CNT(_n) (0x1e0c + (_n) * 0x4)
++#define MTK_WED_AMSDU_QMEM_FQ_CNT GENMASK(27, 16)
++#define MTK_WED_AMSDU_QMEM_SP_QCNT GENMASK(11, 0)
++#define MTK_WED_AMSDU_QMEM_TID0_QCNT GENMASK(27, 16)
++#define MTK_WED_AMSDU_QMEM_TID1_QCNT GENMASK(11, 0)
++#define MTK_WED_AMSDU_QMEM_TID2_QCNT GENMASK(27, 16)
++#define MTK_WED_AMSDU_QMEM_TID3_QCNT GENMASK(11, 0)
++#define MTK_WED_AMSDU_QMEM_TID4_QCNT GENMASK(27, 16)
++#define MTK_WED_AMSDU_QMEM_TID5_QCNT GENMASK(11, 0)
++#define MTK_WED_AMSDU_QMEM_TID6_QCNT GENMASK(27, 16)
++#define MTK_WED_AMSDU_QMEM_TID7_QCNT GENMASK(11, 0)
++
++#define MTK_WED_MON_AMSDU_QMEM_PTR(_n) (0x1e20 + (_n) * 0x4)
++#define MTK_WED_AMSDU_QMEM_FQ_HEAD GENMASK(27, 16)
++#define MTK_WED_AMSDU_QMEM_SP_QHEAD GENMASK(11, 0)
++#define MTK_WED_AMSDU_QMEM_TID0_QHEAD GENMASK(27, 16)
++#define MTK_WED_AMSDU_QMEM_TID1_QHEAD GENMASK(11, 0)
++#define MTK_WED_AMSDU_QMEM_TID2_QHEAD GENMASK(27, 16)
++#define MTK_WED_AMSDU_QMEM_TID3_QHEAD GENMASK(11, 0)
++#define MTK_WED_AMSDU_QMEM_TID4_QHEAD GENMASK(27, 16)
++#define MTK_WED_AMSDU_QMEM_TID5_QHEAD GENMASK(11, 0)
++#define MTK_WED_AMSDU_QMEM_TID6_QHEAD GENMASK(27, 16)
++#define MTK_WED_AMSDU_QMEM_TID7_QHEAD GENMASK(11, 0)
++#define MTK_WED_AMSDU_QMEM_FQ_TAIL GENMASK(27, 16)
++#define MTK_WED_AMSDU_QMEM_SP_QTAIL GENMASK(11, 0)
++#define MTK_WED_AMSDU_QMEM_TID0_QTAIL GENMASK(27, 16)
++#define MTK_WED_AMSDU_QMEM_TID1_QTAIL GENMASK(11, 0)
++#define MTK_WED_AMSDU_QMEM_TID2_QTAIL GENMASK(27, 16)
++#define MTK_WED_AMSDU_QMEM_TID3_QTAIL GENMASK(11, 0)
++#define MTK_WED_AMSDU_QMEM_TID4_QTAIL GENMASK(27, 16)
++#define MTK_WED_AMSDU_QMEM_TID5_QTAIL GENMASK(11, 0)
++#define MTK_WED_AMSDU_QMEM_TID6_QTAIL GENMASK(27, 16)
++#define MTK_WED_AMSDU_QMEM_TID7_QTAIL GENMASK(11, 0)
++
++#define MTK_WED_MON_AMSDU_HIFTXD_FETCH_MSDU(_n) (0x1ec4 + (_n) * 0x4)
++
+ #define MTK_WED_PCIE_BASE 0x11280000
+ #define MTK_WED_PCIE_BASE0 0x11300000
+ #define MTK_WED_PCIE_BASE1 0x11310000
+--- a/include/linux/netdevice.h
++++ b/include/linux/netdevice.h
+@@ -928,6 +928,7 @@ struct net_device_path {
+ u8 queue;
+ u16 wcid;
+ u8 bss;
++ u8 amsdu;
+ } mtk_wdma;
+ };
+ };
+--- a/include/linux/soc/mediatek/mtk_wed.h
++++ b/include/linux/soc/mediatek/mtk_wed.h
+@@ -129,6 +129,7 @@ struct mtk_wed_device {
+ enum mtk_wed_bus_tye bus_type;
+ void __iomem *base;
+ u32 phy_base;
++ u32 id;
+
+ u32 wpdma_phys;
+ u32 wpdma_int;
+@@ -147,10 +148,12 @@ struct mtk_wed_device {
+ unsigned int rx_nbuf;
+ unsigned int rx_npkt;
+ unsigned int rx_size;
++ unsigned int amsdu_max_len;
+
+ u8 tx_tbit[MTK_WED_TX_QUEUES];
+ u8 rx_tbit[MTK_WED_RX_QUEUES];
+ u8 txfree_tbit;
++ u8 amsdu_max_subframes;
+
+ u32 (*init_buf)(void *ptr, dma_addr_t phys, int token_id);
+ int (*offload_enable)(struct mtk_wed_device *wed);
+@@ -224,6 +227,15 @@ static inline bool mtk_wed_get_rx_capa(s
+ #else
+ return false;
+ #endif
++}
++
++static inline bool mtk_wed_is_amsdu_supported(struct mtk_wed_device *dev)
++{
++#ifdef CONFIG_NET_MEDIATEK_SOC_WED
++ return dev->version == 3;
++#else
++ return false;
++#endif
+ }
+
+ #ifdef CONFIG_NET_MEDIATEK_SOC_WED
diff --git a/target/linux/generic/backport-6.6/752-17-v6.7-net-ethernet-mtk_wed-introduce-hw_rro-support-for-MT.patch b/target/linux/generic/backport-6.6/752-17-v6.7-net-ethernet-mtk_wed-introduce-hw_rro-support-for-MT.patch
new file mode 100644
index 0000000000..0cf4c18875
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-17-v6.7-net-ethernet-mtk_wed-introduce-hw_rro-support-for-MT.patch
@@ -0,0 +1,483 @@
+From: Sujuan Chen <sujuan.chen@mediatek.com>
+Date: Mon, 18 Sep 2023 12:29:16 +0200
+Subject: [PATCH] net: ethernet: mtk_wed: introduce hw_rro support for MT7988
+
+MT7988 SoC support 802.11 receive reordering offload in hw while
+MT7986 SoC implements it through the firmware running on the mcu.
+
+Co-developed-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -26,7 +26,7 @@
+ #define MTK_WED_BUF_SIZE 2048
+ #define MTK_WED_PAGE_BUF_SIZE 128
+ #define MTK_WED_BUF_PER_PAGE (PAGE_SIZE / 2048)
+-#define MTK_WED_RX_PAGE_BUF_PER_PAGE (PAGE_SIZE / 128)
++#define MTK_WED_RX_BUF_PER_PAGE (PAGE_SIZE / MTK_WED_PAGE_BUF_SIZE)
+ #define MTK_WED_RX_RING_SIZE 1536
+ #define MTK_WED_RX_PG_BM_CNT 8192
+ #define MTK_WED_AMSDU_BUF_SIZE (PAGE_SIZE << 4)
+@@ -596,6 +596,68 @@ free_pagelist:
+ }
+
+ static int
++mtk_wed_hwrro_buffer_alloc(struct mtk_wed_device *dev)
++{
++ int n_pages = MTK_WED_RX_PG_BM_CNT / MTK_WED_RX_BUF_PER_PAGE;
++ struct mtk_wed_buf *page_list;
++ struct mtk_wed_bm_desc *desc;
++ dma_addr_t desc_phys;
++ int i, page_idx = 0;
++
++ if (!dev->wlan.hw_rro)
++ return 0;
++
++ page_list = kcalloc(n_pages, sizeof(*page_list), GFP_KERNEL);
++ if (!page_list)
++ return -ENOMEM;
++
++ dev->hw_rro.size = dev->wlan.rx_nbuf & ~(MTK_WED_BUF_PER_PAGE - 1);
++ dev->hw_rro.pages = page_list;
++ desc = dma_alloc_coherent(dev->hw->dev,
++ dev->wlan.rx_nbuf * sizeof(*desc),
++ &desc_phys, GFP_KERNEL);
++ if (!desc)
++ return -ENOMEM;
++
++ dev->hw_rro.desc = desc;
++ dev->hw_rro.desc_phys = desc_phys;
++
++ for (i = 0; i < MTK_WED_RX_PG_BM_CNT; i += MTK_WED_RX_BUF_PER_PAGE) {
++ dma_addr_t page_phys, buf_phys;
++ struct page *page;
++ int s;
++
++ page = __dev_alloc_page(GFP_KERNEL);
++ 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].p = page;
++ page_list[page_idx++].phy_addr = page_phys;
++ dma_sync_single_for_cpu(dev->hw->dev, page_phys, PAGE_SIZE,
++ DMA_BIDIRECTIONAL);
++
++ buf_phys = page_phys;
++ for (s = 0; s < MTK_WED_RX_BUF_PER_PAGE; s++) {
++ desc->buf0 = cpu_to_le32(buf_phys);
++ buf_phys += MTK_WED_PAGE_BUF_SIZE;
++ desc++;
++ }
++
++ dma_sync_single_for_device(dev->hw->dev, page_phys, PAGE_SIZE,
++ DMA_BIDIRECTIONAL);
++ }
++
++ return 0;
++}
++
++static int
+ mtk_wed_rx_buffer_alloc(struct mtk_wed_device *dev)
+ {
+ struct mtk_wed_bm_desc *desc;
+@@ -612,7 +674,42 @@ mtk_wed_rx_buffer_alloc(struct mtk_wed_d
+ dev->rx_buf_ring.desc_phys = desc_phys;
+ dev->wlan.init_rx_buf(dev, dev->wlan.rx_npkt);
+
+- return 0;
++ return mtk_wed_hwrro_buffer_alloc(dev);
++}
++
++static void
++mtk_wed_hwrro_free_buffer(struct mtk_wed_device *dev)
++{
++ struct mtk_wed_buf *page_list = dev->hw_rro.pages;
++ struct mtk_wed_bm_desc *desc = dev->hw_rro.desc;
++ int i, page_idx = 0;
++
++ if (!dev->wlan.hw_rro)
++ return;
++
++ if (!page_list)
++ return;
++
++ if (!desc)
++ goto free_pagelist;
++
++ for (i = 0; i < MTK_WED_RX_PG_BM_CNT; i += MTK_WED_RX_BUF_PER_PAGE) {
++ dma_addr_t buf_addr = page_list[page_idx].phy_addr;
++ void *page = page_list[page_idx++].p;
++
++ if (!page)
++ break;
++
++ dma_unmap_page(dev->hw->dev, buf_addr, PAGE_SIZE,
++ DMA_BIDIRECTIONAL);
++ __free_page(page);
++ }
++
++ dma_free_coherent(dev->hw->dev, dev->hw_rro.size * sizeof(*desc),
++ desc, dev->hw_rro.desc_phys);
++
++free_pagelist:
++ kfree(page_list);
+ }
+
+ static void
+@@ -626,6 +723,28 @@ mtk_wed_free_rx_buffer(struct mtk_wed_de
+ dev->wlan.release_rx_buf(dev);
+ dma_free_coherent(dev->hw->dev, dev->rx_buf_ring.size * sizeof(*desc),
+ desc, dev->rx_buf_ring.desc_phys);
++
++ mtk_wed_hwrro_free_buffer(dev);
++}
++
++static void
++mtk_wed_hwrro_init(struct mtk_wed_device *dev)
++{
++ if (!mtk_wed_get_rx_capa(dev) || !dev->wlan.hw_rro)
++ return;
++
++ wed_set(dev, MTK_WED_RRO_PG_BM_RX_DMAM,
++ FIELD_PREP(MTK_WED_RRO_PG_BM_RX_SDL0, 128));
++
++ wed_w32(dev, MTK_WED_RRO_PG_BM_BASE, dev->hw_rro.desc_phys);
++
++ wed_w32(dev, MTK_WED_RRO_PG_BM_INIT_PTR,
++ MTK_WED_RRO_PG_BM_INIT_SW_TAIL_IDX |
++ FIELD_PREP(MTK_WED_RRO_PG_BM_SW_TAIL_IDX,
++ MTK_WED_RX_PG_BM_CNT));
++
++ /* enable rx_page_bm to fetch dmad */
++ wed_set(dev, MTK_WED_CTRL, MTK_WED_CTRL_WED_RX_PG_BM_EN);
+ }
+
+ static void
+@@ -639,6 +758,8 @@ mtk_wed_rx_buffer_hw_init(struct mtk_wed
+ wed_w32(dev, MTK_WED_RX_BM_DYN_ALLOC_TH,
+ FIELD_PREP(MTK_WED_RX_BM_DYN_ALLOC_TH_H, 0xffff));
+ wed_set(dev, MTK_WED_CTRL, MTK_WED_CTRL_WED_RX_BM_EN);
++
++ mtk_wed_hwrro_init(dev);
+ }
+
+ static void
+@@ -934,6 +1055,8 @@ mtk_wed_bus_init(struct mtk_wed_device *
+ static void
+ mtk_wed_set_wpdma(struct mtk_wed_device *dev)
+ {
++ int i;
++
+ if (mtk_wed_is_v1(dev->hw)) {
+ wed_w32(dev, MTK_WED_WPDMA_CFG_BASE, dev->wlan.wpdma_phys);
+ return;
+@@ -951,6 +1074,15 @@ mtk_wed_set_wpdma(struct mtk_wed_device
+
+ wed_w32(dev, MTK_WED_WPDMA_RX_GLO_CFG, dev->wlan.wpdma_rx_glo);
+ wed_w32(dev, dev->hw->soc->regmap.wpdma_rx_ring0, dev->wlan.wpdma_rx);
++
++ if (!dev->wlan.hw_rro)
++ return;
++
++ wed_w32(dev, MTK_WED_RRO_RX_D_CFG(0), dev->wlan.wpdma_rx_rro[0]);
++ wed_w32(dev, MTK_WED_RRO_RX_D_CFG(1), dev->wlan.wpdma_rx_rro[1]);
++ for (i = 0; i < MTK_WED_RX_PAGE_QUEUES; i++)
++ wed_w32(dev, MTK_WED_RRO_MSDU_PG_RING_CFG(i),
++ dev->wlan.wpdma_rx_pg + i * 0x10);
+ }
+
+ static void
+@@ -1762,6 +1894,165 @@ mtk_wed_dma_enable(struct mtk_wed_device
+ }
+
+ static void
++mtk_wed_start_hw_rro(struct mtk_wed_device *dev, u32 irq_mask, bool reset)
++{
++ int i;
++
++ wed_w32(dev, MTK_WED_WPDMA_INT_MASK, irq_mask);
++ wed_w32(dev, MTK_WED_INT_MASK, irq_mask);
++
++ if (!mtk_wed_get_rx_capa(dev) || !dev->wlan.hw_rro)
++ return;
++
++ wed_set(dev, MTK_WED_RRO_RX_D_CFG(2), MTK_WED_RRO_MSDU_PG_DRV_CLR);
++ wed_w32(dev, MTK_WED_RRO_MSDU_PG_RING2_CFG,
++ MTK_WED_RRO_MSDU_PG_DRV_CLR);
++
++ wed_w32(dev, MTK_WED_WPDMA_INT_CTRL_RRO_RX,
++ MTK_WED_WPDMA_INT_CTRL_RRO_RX0_EN |
++ MTK_WED_WPDMA_INT_CTRL_RRO_RX0_CLR |
++ MTK_WED_WPDMA_INT_CTRL_RRO_RX1_EN |
++ MTK_WED_WPDMA_INT_CTRL_RRO_RX1_CLR |
++ FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_RRO_RX0_DONE_TRIG,
++ dev->wlan.rro_rx_tbit[0]) |
++ FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_RRO_RX1_DONE_TRIG,
++ dev->wlan.rro_rx_tbit[1]));
++
++ wed_w32(dev, MTK_WED_WPDMA_INT_CTRL_RRO_MSDU_PG,
++ MTK_WED_WPDMA_INT_CTRL_RRO_PG0_EN |
++ MTK_WED_WPDMA_INT_CTRL_RRO_PG0_CLR |
++ MTK_WED_WPDMA_INT_CTRL_RRO_PG1_EN |
++ MTK_WED_WPDMA_INT_CTRL_RRO_PG1_CLR |
++ MTK_WED_WPDMA_INT_CTRL_RRO_PG2_EN |
++ MTK_WED_WPDMA_INT_CTRL_RRO_PG2_CLR |
++ FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_RRO_PG0_DONE_TRIG,
++ dev->wlan.rx_pg_tbit[0]) |
++ FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_RRO_PG1_DONE_TRIG,
++ dev->wlan.rx_pg_tbit[1]) |
++ FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_RRO_PG2_DONE_TRIG,
++ dev->wlan.rx_pg_tbit[2]));
++
++ /* RRO_MSDU_PG_RING2_CFG1_FLD_DRV_EN should be enabled after
++ * WM FWDL completed, otherwise RRO_MSDU_PG ring may broken
++ */
++ wed_set(dev, MTK_WED_RRO_MSDU_PG_RING2_CFG,
++ MTK_WED_RRO_MSDU_PG_DRV_EN);
++
++ for (i = 0; i < MTK_WED_RX_QUEUES; i++) {
++ struct mtk_wed_ring *ring = &dev->rx_rro_ring[i];
++
++ if (!(ring->flags & MTK_WED_RING_CONFIGURED))
++ continue;
++
++ if (mtk_wed_check_wfdma_rx_fill(dev, ring))
++ dev_err(dev->hw->dev,
++ "rx_rro_ring(%d) initialization failed\n", i);
++ }
++
++ for (i = 0; i < MTK_WED_RX_PAGE_QUEUES; i++) {
++ struct mtk_wed_ring *ring = &dev->rx_page_ring[i];
++
++ if (!(ring->flags & MTK_WED_RING_CONFIGURED))
++ continue;
++
++ if (mtk_wed_check_wfdma_rx_fill(dev, ring))
++ dev_err(dev->hw->dev,
++ "rx_page_ring(%d) initialization failed\n", i);
++ }
++}
++
++static void
++mtk_wed_rro_rx_ring_setup(struct mtk_wed_device *dev, int idx,
++ void __iomem *regs)
++{
++ struct mtk_wed_ring *ring = &dev->rx_rro_ring[idx];
++
++ ring->wpdma = regs;
++ wed_w32(dev, MTK_WED_RRO_RX_D_RX(idx) + MTK_WED_RING_OFS_BASE,
++ readl(regs));
++ wed_w32(dev, MTK_WED_RRO_RX_D_RX(idx) + MTK_WED_RING_OFS_COUNT,
++ readl(regs + MTK_WED_RING_OFS_COUNT));
++ ring->flags |= MTK_WED_RING_CONFIGURED;
++}
++
++static void
++mtk_wed_msdu_pg_rx_ring_setup(struct mtk_wed_device *dev, int idx, void __iomem *regs)
++{
++ struct mtk_wed_ring *ring = &dev->rx_page_ring[idx];
++
++ ring->wpdma = regs;
++ wed_w32(dev, MTK_WED_RRO_MSDU_PG_CTRL0(idx) + MTK_WED_RING_OFS_BASE,
++ readl(regs));
++ wed_w32(dev, MTK_WED_RRO_MSDU_PG_CTRL0(idx) + MTK_WED_RING_OFS_COUNT,
++ readl(regs + MTK_WED_RING_OFS_COUNT));
++ ring->flags |= MTK_WED_RING_CONFIGURED;
++}
++
++static int
++mtk_wed_ind_rx_ring_setup(struct mtk_wed_device *dev, void __iomem *regs)
++{
++ struct mtk_wed_ring *ring = &dev->ind_cmd_ring;
++ u32 val = readl(regs + MTK_WED_RING_OFS_COUNT);
++ int i, count = 0;
++
++ ring->wpdma = regs;
++ wed_w32(dev, MTK_WED_IND_CMD_RX_CTRL1 + MTK_WED_RING_OFS_BASE,
++ readl(regs) & 0xfffffff0);
++
++ wed_w32(dev, MTK_WED_IND_CMD_RX_CTRL1 + MTK_WED_RING_OFS_COUNT,
++ readl(regs + MTK_WED_RING_OFS_COUNT));
++
++ /* ack sn cr */
++ wed_w32(dev, MTK_WED_RRO_CFG0, dev->wlan.phy_base +
++ dev->wlan.ind_cmd.ack_sn_addr);
++ wed_w32(dev, MTK_WED_RRO_CFG1,
++ FIELD_PREP(MTK_WED_RRO_CFG1_MAX_WIN_SZ,
++ dev->wlan.ind_cmd.win_size) |
++ FIELD_PREP(MTK_WED_RRO_CFG1_PARTICL_SE_ID,
++ dev->wlan.ind_cmd.particular_sid));
++
++ /* particular session addr element */
++ wed_w32(dev, MTK_WED_ADDR_ELEM_CFG0,
++ dev->wlan.ind_cmd.particular_se_phys);
++
++ for (i = 0; i < dev->wlan.ind_cmd.se_group_nums; i++) {
++ wed_w32(dev, MTK_WED_RADDR_ELEM_TBL_WDATA,
++ dev->wlan.ind_cmd.addr_elem_phys[i] >> 4);
++ wed_w32(dev, MTK_WED_ADDR_ELEM_TBL_CFG,
++ MTK_WED_ADDR_ELEM_TBL_WR | (i & 0x7f));
++
++ val = wed_r32(dev, MTK_WED_ADDR_ELEM_TBL_CFG);
++ while (!(val & MTK_WED_ADDR_ELEM_TBL_WR_RDY) && count++ < 100)
++ val = wed_r32(dev, MTK_WED_ADDR_ELEM_TBL_CFG);
++ if (count >= 100)
++ dev_err(dev->hw->dev,
++ "write ba session base failed\n");
++ }
++
++ /* pn check init */
++ for (i = 0; i < dev->wlan.ind_cmd.particular_sid; i++) {
++ wed_w32(dev, MTK_WED_PN_CHECK_WDATA_M,
++ MTK_WED_PN_CHECK_IS_FIRST);
++
++ wed_w32(dev, MTK_WED_PN_CHECK_CFG, MTK_WED_PN_CHECK_WR |
++ FIELD_PREP(MTK_WED_PN_CHECK_SE_ID, i));
++
++ count = 0;
++ val = wed_r32(dev, MTK_WED_PN_CHECK_CFG);
++ while (!(val & MTK_WED_PN_CHECK_WR_RDY) && count++ < 100)
++ val = wed_r32(dev, MTK_WED_PN_CHECK_CFG);
++ if (count >= 100)
++ dev_err(dev->hw->dev,
++ "session(%d) initialization failed\n", i);
++ }
++
++ wed_w32(dev, MTK_WED_RX_IND_CMD_CNT0, MTK_WED_RX_IND_CMD_DBG_CNT_EN);
++ wed_set(dev, MTK_WED_CTRL, MTK_WED_CTRL_WED_RX_IND_CMD_EN);
++
++ return 0;
++}
++
++static void
+ mtk_wed_start(struct mtk_wed_device *dev, u32 irq_mask)
+ {
+ int i;
+@@ -2215,6 +2506,10 @@ void mtk_wed_add_hw(struct device_node *
+ .detach = mtk_wed_detach,
+ .ppe_check = mtk_wed_ppe_check,
+ .setup_tc = mtk_wed_setup_tc,
++ .start_hw_rro = mtk_wed_start_hw_rro,
++ .rro_rx_ring_setup = mtk_wed_rro_rx_ring_setup,
++ .msdu_pg_rx_ring_setup = mtk_wed_msdu_pg_rx_ring_setup,
++ .ind_rx_ring_setup = mtk_wed_ind_rx_ring_setup,
+ };
+ struct device_node *eth_np = eth->dev->of_node;
+ struct platform_device *pdev;
+--- a/include/linux/soc/mediatek/mtk_wed.h
++++ b/include/linux/soc/mediatek/mtk_wed.h
+@@ -10,6 +10,7 @@
+
+ #define MTK_WED_TX_QUEUES 2
+ #define MTK_WED_RX_QUEUES 2
++#define MTK_WED_RX_PAGE_QUEUES 3
+
+ #define WED_WO_STA_REC 0x6
+
+@@ -99,6 +100,9 @@ struct mtk_wed_device {
+ struct mtk_wed_ring txfree_ring;
+ struct mtk_wed_ring tx_wdma[MTK_WED_TX_QUEUES];
+ struct mtk_wed_ring rx_wdma[MTK_WED_RX_QUEUES];
++ struct mtk_wed_ring rx_rro_ring[MTK_WED_RX_QUEUES];
++ struct mtk_wed_ring rx_page_ring[MTK_WED_RX_PAGE_QUEUES];
++ struct mtk_wed_ring ind_cmd_ring;
+
+ struct {
+ int size;
+@@ -120,6 +124,13 @@ struct mtk_wed_device {
+ dma_addr_t fdbk_phys;
+ } rro;
+
++ struct {
++ int size;
++ struct mtk_wed_buf *pages;
++ struct mtk_wed_bm_desc *desc;
++ dma_addr_t desc_phys;
++ } hw_rro;
++
+ /* filled by driver: */
+ struct {
+ union {
+@@ -138,6 +149,8 @@ struct mtk_wed_device {
+ u32 wpdma_txfree;
+ u32 wpdma_rx_glo;
+ u32 wpdma_rx;
++ u32 wpdma_rx_rro[MTK_WED_RX_QUEUES];
++ u32 wpdma_rx_pg;
+
+ bool wcid_512;
+ bool hw_rro;
+@@ -152,9 +165,20 @@ struct mtk_wed_device {
+
+ u8 tx_tbit[MTK_WED_TX_QUEUES];
+ u8 rx_tbit[MTK_WED_RX_QUEUES];
++ u8 rro_rx_tbit[MTK_WED_RX_QUEUES];
++ u8 rx_pg_tbit[MTK_WED_RX_PAGE_QUEUES];
+ u8 txfree_tbit;
+ u8 amsdu_max_subframes;
+
++ struct {
++ u8 se_group_nums;
++ u16 win_size;
++ u16 particular_sid;
++ u32 ack_sn_addr;
++ dma_addr_t particular_se_phys;
++ dma_addr_t addr_elem_phys[1024];
++ } ind_cmd;
++
+ 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);
+@@ -193,6 +217,14 @@ struct mtk_wed_ops {
+ void (*irq_set_mask)(struct mtk_wed_device *dev, u32 mask);
+ int (*setup_tc)(struct mtk_wed_device *wed, struct net_device *dev,
+ enum tc_setup_type type, void *type_data);
++ void (*start_hw_rro)(struct mtk_wed_device *dev, u32 irq_mask,
++ bool reset);
++ void (*rro_rx_ring_setup)(struct mtk_wed_device *dev, int ring,
++ void __iomem *regs);
++ void (*msdu_pg_rx_ring_setup)(struct mtk_wed_device *dev, int ring,
++ void __iomem *regs);
++ int (*ind_rx_ring_setup)(struct mtk_wed_device *dev,
++ void __iomem *regs);
+ };
+
+ extern const struct mtk_wed_ops __rcu *mtk_soc_wed_ops;
+@@ -264,6 +296,15 @@ static inline bool mtk_wed_is_amsdu_supp
+ #define mtk_wed_device_dma_reset(_dev) (_dev)->ops->reset_dma(_dev)
+ #define mtk_wed_device_setup_tc(_dev, _netdev, _type, _type_data) \
+ (_dev)->ops->setup_tc(_dev, _netdev, _type, _type_data)
++#define mtk_wed_device_start_hw_rro(_dev, _mask, _reset) \
++ (_dev)->ops->start_hw_rro(_dev, _mask, _reset)
++#define mtk_wed_device_rro_rx_ring_setup(_dev, _ring, _regs) \
++ (_dev)->ops->rro_rx_ring_setup(_dev, _ring, _regs)
++#define mtk_wed_device_msdu_pg_rx_ring_setup(_dev, _ring, _regs) \
++ (_dev)->ops->msdu_pg_rx_ring_setup(_dev, _ring, _regs)
++#define mtk_wed_device_ind_rx_ring_setup(_dev, _regs) \
++ (_dev)->ops->ind_rx_ring_setup(_dev, _regs)
++
+ #else
+ static inline bool mtk_wed_device_active(struct mtk_wed_device *dev)
+ {
+@@ -283,6 +324,10 @@ static inline bool mtk_wed_device_active
+ #define mtk_wed_device_stop(_dev) do {} while (0)
+ #define mtk_wed_device_dma_reset(_dev) do {} while (0)
+ #define mtk_wed_device_setup_tc(_dev, _netdev, _type, _type_data) -EOPNOTSUPP
++#define mtk_wed_device_start_hw_rro(_dev, _mask, _reset) do {} while (0)
++#define mtk_wed_device_rro_rx_ring_setup(_dev, _ring, _regs) -ENODEV
++#define mtk_wed_device_msdu_pg_rx_ring_setup(_dev, _ring, _regs) -ENODEV
++#define mtk_wed_device_ind_rx_ring_setup(_dev, _regs) -ENODEV
+ #endif
+
+ #endif
diff --git a/target/linux/generic/backport-6.6/752-18-v6.7-net-ethernet-mtk_wed-debugfs-move-wed_v2-specific-re.patch b/target/linux/generic/backport-6.6/752-18-v6.7-net-ethernet-mtk_wed-debugfs-move-wed_v2-specific-re.patch
new file mode 100644
index 0000000000..5ea43a4445
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-18-v6.7-net-ethernet-mtk_wed-debugfs-move-wed_v2-specific-re.patch
@@ -0,0 +1,78 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Mon, 18 Sep 2023 12:29:17 +0200
+Subject: [PATCH] net: ethernet: mtk_wed: debugfs: move wed_v2 specific regs
+ out of regs array
+
+Move specific WED2.0 debugfs entries out of regs array. This is a
+preliminary patch to introduce WED 3.0 debugfs info.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
+@@ -151,7 +151,7 @@ DEFINE_SHOW_ATTRIBUTE(wed_txinfo);
+ static int
+ wed_rxinfo_show(struct seq_file *s, void *data)
+ {
+- static const struct reg_dump regs[] = {
++ static const struct reg_dump regs_common[] = {
+ DUMP_STR("WPDMA RX"),
+ DUMP_WPDMA_RX_RING(0),
+ DUMP_WPDMA_RX_RING(1),
+@@ -169,7 +169,7 @@ wed_rxinfo_show(struct seq_file *s, void
+ DUMP_WED_RING(WED_RING_RX_DATA(0)),
+ DUMP_WED_RING(WED_RING_RX_DATA(1)),
+
+- DUMP_STR("WED RRO"),
++ DUMP_STR("WED WO RRO"),
+ DUMP_WED_RRO_RING(WED_RROQM_MIOD_CTRL0),
+ DUMP_WED(WED_RROQM_MID_MIB),
+ DUMP_WED(WED_RROQM_MOD_MIB),
+@@ -180,17 +180,6 @@ wed_rxinfo_show(struct seq_file *s, void
+ DUMP_WED(WED_RROQM_FDBK_ANC_MIB),
+ DUMP_WED(WED_RROQM_FDBK_ANC2H_MIB),
+
+- DUMP_STR("WED Route QM"),
+- DUMP_WED(WED_RTQM_R2H_MIB(0)),
+- DUMP_WED(WED_RTQM_R2Q_MIB(0)),
+- DUMP_WED(WED_RTQM_Q2H_MIB(0)),
+- DUMP_WED(WED_RTQM_R2H_MIB(1)),
+- DUMP_WED(WED_RTQM_R2Q_MIB(1)),
+- DUMP_WED(WED_RTQM_Q2H_MIB(1)),
+- DUMP_WED(WED_RTQM_Q2N_MIB),
+- DUMP_WED(WED_RTQM_Q2B_MIB),
+- DUMP_WED(WED_RTQM_PFDBK_MIB),
+-
+ DUMP_STR("WED WDMA TX"),
+ DUMP_WED(WED_WDMA_TX_MIB),
+ DUMP_WED_RING(WED_WDMA_RING_TX),
+@@ -211,11 +200,25 @@ wed_rxinfo_show(struct seq_file *s, void
+ DUMP_WED(WED_RX_BM_INTF),
+ DUMP_WED(WED_RX_BM_ERR_STS),
+ };
++ static const struct reg_dump regs_wed_v2[] = {
++ DUMP_STR("WED Route QM"),
++ DUMP_WED(WED_RTQM_R2H_MIB(0)),
++ DUMP_WED(WED_RTQM_R2Q_MIB(0)),
++ DUMP_WED(WED_RTQM_Q2H_MIB(0)),
++ DUMP_WED(WED_RTQM_R2H_MIB(1)),
++ DUMP_WED(WED_RTQM_R2Q_MIB(1)),
++ DUMP_WED(WED_RTQM_Q2H_MIB(1)),
++ DUMP_WED(WED_RTQM_Q2N_MIB),
++ DUMP_WED(WED_RTQM_Q2B_MIB),
++ DUMP_WED(WED_RTQM_PFDBK_MIB),
++ };
+ struct mtk_wed_hw *hw = s->private;
+ struct mtk_wed_device *dev = hw->wed_dev;
+
+- if (dev)
+- dump_wed_regs(s, dev, regs, ARRAY_SIZE(regs));
++ if (dev) {
++ dump_wed_regs(s, dev, regs_common, ARRAY_SIZE(regs_common));
++ dump_wed_regs(s, dev, regs_wed_v2, ARRAY_SIZE(regs_wed_v2));
++ }
+
+ return 0;
+ }
diff --git a/target/linux/generic/backport-6.6/752-19-v6.7-net-ethernet-mtk_wed-debugfs-add-WED-3.0-debugfs-ent.patch b/target/linux/generic/backport-6.6/752-19-v6.7-net-ethernet-mtk_wed-debugfs-add-WED-3.0-debugfs-ent.patch
new file mode 100644
index 0000000000..f491d2fd80
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-19-v6.7-net-ethernet-mtk_wed-debugfs-add-WED-3.0-debugfs-ent.patch
@@ -0,0 +1,432 @@
+From: Sujuan Chen <sujuan.chen@mediatek.com>
+Date: Mon, 18 Sep 2023 12:29:18 +0200
+Subject: [PATCH] net: ethernet: mtk_wed: debugfs: add WED 3.0 debugfs entries
+
+Introduce WED3.0 debugfs entries useful for debugging.
+
+Co-developed-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
+@@ -11,6 +11,7 @@ struct reg_dump {
+ u16 offset;
+ u8 type;
+ u8 base;
++ u32 mask;
+ };
+
+ enum {
+@@ -25,6 +26,8 @@ enum {
+
+ #define DUMP_STR(_str) { _str, 0, DUMP_TYPE_STRING }
+ #define DUMP_REG(_reg, ...) { #_reg, MTK_##_reg, __VA_ARGS__ }
++#define DUMP_REG_MASK(_reg, _mask) \
++ { #_mask, MTK_##_reg, DUMP_TYPE_WED, 0, MTK_##_mask }
+ #define DUMP_RING(_prefix, _base, ...) \
+ { _prefix " BASE", _base, __VA_ARGS__ }, \
+ { _prefix " CNT", _base + 0x4, __VA_ARGS__ }, \
+@@ -32,6 +35,7 @@ enum {
+ { _prefix " DIDX", _base + 0xc, __VA_ARGS__ }
+
+ #define DUMP_WED(_reg) DUMP_REG(_reg, DUMP_TYPE_WED)
++#define DUMP_WED_MASK(_reg, _mask) DUMP_REG_MASK(_reg, _mask)
+ #define DUMP_WED_RING(_base) DUMP_RING(#_base, MTK_##_base, DUMP_TYPE_WED)
+
+ #define DUMP_WDMA(_reg) DUMP_REG(_reg, DUMP_TYPE_WDMA)
+@@ -212,12 +216,58 @@ wed_rxinfo_show(struct seq_file *s, void
+ DUMP_WED(WED_RTQM_Q2B_MIB),
+ DUMP_WED(WED_RTQM_PFDBK_MIB),
+ };
++ static const struct reg_dump regs_wed_v3[] = {
++ DUMP_STR("WED RX RRO DATA"),
++ DUMP_WED_RING(WED_RRO_RX_D_RX(0)),
++ DUMP_WED_RING(WED_RRO_RX_D_RX(1)),
++
++ DUMP_STR("WED RX MSDU PAGE"),
++ DUMP_WED_RING(WED_RRO_MSDU_PG_CTRL0(0)),
++ DUMP_WED_RING(WED_RRO_MSDU_PG_CTRL0(1)),
++ DUMP_WED_RING(WED_RRO_MSDU_PG_CTRL0(2)),
++
++ DUMP_STR("WED RX IND CMD"),
++ DUMP_WED(WED_IND_CMD_RX_CTRL1),
++ DUMP_WED_MASK(WED_IND_CMD_RX_CTRL2, WED_IND_CMD_MAX_CNT),
++ DUMP_WED_MASK(WED_IND_CMD_RX_CTRL0, WED_IND_CMD_PROC_IDX),
++ DUMP_WED_MASK(RRO_IND_CMD_SIGNATURE, RRO_IND_CMD_DMA_IDX),
++ DUMP_WED_MASK(WED_IND_CMD_RX_CTRL0, WED_IND_CMD_MAGIC_CNT),
++ DUMP_WED_MASK(RRO_IND_CMD_SIGNATURE, RRO_IND_CMD_MAGIC_CNT),
++ DUMP_WED_MASK(WED_IND_CMD_RX_CTRL0,
++ WED_IND_CMD_PREFETCH_FREE_CNT),
++ DUMP_WED_MASK(WED_RRO_CFG1, WED_RRO_CFG1_PARTICL_SE_ID),
++
++ DUMP_STR("WED ADDR ELEM"),
++ DUMP_WED(WED_ADDR_ELEM_CFG0),
++ DUMP_WED_MASK(WED_ADDR_ELEM_CFG1,
++ WED_ADDR_ELEM_PREFETCH_FREE_CNT),
++
++ DUMP_STR("WED Route QM"),
++ DUMP_WED(WED_RTQM_ENQ_I2Q_DMAD_CNT),
++ DUMP_WED(WED_RTQM_ENQ_I2N_DMAD_CNT),
++ DUMP_WED(WED_RTQM_ENQ_I2Q_PKT_CNT),
++ DUMP_WED(WED_RTQM_ENQ_I2N_PKT_CNT),
++ DUMP_WED(WED_RTQM_ENQ_USED_ENTRY_CNT),
++ DUMP_WED(WED_RTQM_ENQ_ERR_CNT),
++
++ DUMP_WED(WED_RTQM_DEQ_DMAD_CNT),
++ DUMP_WED(WED_RTQM_DEQ_Q2I_DMAD_CNT),
++ DUMP_WED(WED_RTQM_DEQ_PKT_CNT),
++ DUMP_WED(WED_RTQM_DEQ_Q2I_PKT_CNT),
++ DUMP_WED(WED_RTQM_DEQ_USED_PFDBK_CNT),
++ DUMP_WED(WED_RTQM_DEQ_ERR_CNT),
++ };
+ struct mtk_wed_hw *hw = s->private;
+ struct mtk_wed_device *dev = hw->wed_dev;
+
+ if (dev) {
+ dump_wed_regs(s, dev, regs_common, ARRAY_SIZE(regs_common));
+- dump_wed_regs(s, dev, regs_wed_v2, ARRAY_SIZE(regs_wed_v2));
++ if (mtk_wed_is_v2(hw))
++ dump_wed_regs(s, dev,
++ regs_wed_v2, ARRAY_SIZE(regs_wed_v2));
++ else
++ dump_wed_regs(s, dev,
++ regs_wed_v3, ARRAY_SIZE(regs_wed_v3));
+ }
+
+ return 0;
+@@ -225,6 +275,314 @@ wed_rxinfo_show(struct seq_file *s, void
+ DEFINE_SHOW_ATTRIBUTE(wed_rxinfo);
+
+ static int
++wed_amsdu_show(struct seq_file *s, void *data)
++{
++ static const struct reg_dump regs[] = {
++ DUMP_STR("WED AMDSU INFO"),
++ DUMP_WED(WED_MON_AMSDU_FIFO_DMAD),
++
++ DUMP_STR("WED AMDSU ENG0 INFO"),
++ DUMP_WED(WED_MON_AMSDU_ENG_DMAD(0)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QFPL(0)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QENI(0)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QENO(0)),
++ DUMP_WED(WED_MON_AMSDU_ENG_MERG(0)),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT8(0),
++ WED_AMSDU_ENG_MAX_PL_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT8(0),
++ WED_AMSDU_ENG_MAX_QGPP_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(0),
++ WED_AMSDU_ENG_CUR_ENTRY),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(0),
++ WED_AMSDU_ENG_MAX_BUF_MERGED),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(0),
++ WED_AMSDU_ENG_MAX_MSDU_MERGED),
++
++ DUMP_STR("WED AMDSU ENG1 INFO"),
++ DUMP_WED(WED_MON_AMSDU_ENG_DMAD(1)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QFPL(1)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QENI(1)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QENO(1)),
++ DUMP_WED(WED_MON_AMSDU_ENG_MERG(1)),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT8(1),
++ WED_AMSDU_ENG_MAX_PL_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT8(1),
++ WED_AMSDU_ENG_MAX_QGPP_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(1),
++ WED_AMSDU_ENG_CUR_ENTRY),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(2),
++ WED_AMSDU_ENG_MAX_BUF_MERGED),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(2),
++ WED_AMSDU_ENG_MAX_MSDU_MERGED),
++
++ DUMP_STR("WED AMDSU ENG2 INFO"),
++ DUMP_WED(WED_MON_AMSDU_ENG_DMAD(2)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QFPL(2)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QENI(2)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QENO(2)),
++ DUMP_WED(WED_MON_AMSDU_ENG_MERG(2)),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT8(2),
++ WED_AMSDU_ENG_MAX_PL_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT8(2),
++ WED_AMSDU_ENG_MAX_QGPP_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(2),
++ WED_AMSDU_ENG_CUR_ENTRY),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(2),
++ WED_AMSDU_ENG_MAX_BUF_MERGED),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(2),
++ WED_AMSDU_ENG_MAX_MSDU_MERGED),
++
++ DUMP_STR("WED AMDSU ENG3 INFO"),
++ DUMP_WED(WED_MON_AMSDU_ENG_DMAD(3)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QFPL(3)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QENI(3)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QENO(3)),
++ DUMP_WED(WED_MON_AMSDU_ENG_MERG(3)),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT8(3),
++ WED_AMSDU_ENG_MAX_PL_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT8(3),
++ WED_AMSDU_ENG_MAX_QGPP_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(3),
++ WED_AMSDU_ENG_CUR_ENTRY),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(3),
++ WED_AMSDU_ENG_MAX_BUF_MERGED),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(3),
++ WED_AMSDU_ENG_MAX_MSDU_MERGED),
++
++ DUMP_STR("WED AMDSU ENG4 INFO"),
++ DUMP_WED(WED_MON_AMSDU_ENG_DMAD(4)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QFPL(4)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QENI(4)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QENO(4)),
++ DUMP_WED(WED_MON_AMSDU_ENG_MERG(4)),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT8(4),
++ WED_AMSDU_ENG_MAX_PL_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT8(4),
++ WED_AMSDU_ENG_MAX_QGPP_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(4),
++ WED_AMSDU_ENG_CUR_ENTRY),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(4),
++ WED_AMSDU_ENG_MAX_BUF_MERGED),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(4),
++ WED_AMSDU_ENG_MAX_MSDU_MERGED),
++
++ DUMP_STR("WED AMDSU ENG5 INFO"),
++ DUMP_WED(WED_MON_AMSDU_ENG_DMAD(5)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QFPL(5)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QENI(5)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QENO(5)),
++ DUMP_WED(WED_MON_AMSDU_ENG_MERG(5)),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT8(5),
++ WED_AMSDU_ENG_MAX_PL_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT8(5),
++ WED_AMSDU_ENG_MAX_QGPP_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(5),
++ WED_AMSDU_ENG_CUR_ENTRY),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(5),
++ WED_AMSDU_ENG_MAX_BUF_MERGED),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(5),
++ WED_AMSDU_ENG_MAX_MSDU_MERGED),
++
++ DUMP_STR("WED AMDSU ENG6 INFO"),
++ DUMP_WED(WED_MON_AMSDU_ENG_DMAD(6)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QFPL(6)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QENI(6)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QENO(6)),
++ DUMP_WED(WED_MON_AMSDU_ENG_MERG(6)),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT8(6),
++ WED_AMSDU_ENG_MAX_PL_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT8(6),
++ WED_AMSDU_ENG_MAX_QGPP_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(6),
++ WED_AMSDU_ENG_CUR_ENTRY),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(6),
++ WED_AMSDU_ENG_MAX_BUF_MERGED),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(6),
++ WED_AMSDU_ENG_MAX_MSDU_MERGED),
++
++ DUMP_STR("WED AMDSU ENG7 INFO"),
++ DUMP_WED(WED_MON_AMSDU_ENG_DMAD(7)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QFPL(7)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QENI(7)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QENO(7)),
++ DUMP_WED(WED_MON_AMSDU_ENG_MERG(7)),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT8(7),
++ WED_AMSDU_ENG_MAX_PL_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT8(7),
++ WED_AMSDU_ENG_MAX_QGPP_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(7),
++ WED_AMSDU_ENG_CUR_ENTRY),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(7),
++ WED_AMSDU_ENG_MAX_BUF_MERGED),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(4),
++ WED_AMSDU_ENG_MAX_MSDU_MERGED),
++
++ DUMP_STR("WED AMDSU ENG8 INFO"),
++ DUMP_WED(WED_MON_AMSDU_ENG_DMAD(8)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QFPL(8)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QENI(8)),
++ DUMP_WED(WED_MON_AMSDU_ENG_QENO(8)),
++ DUMP_WED(WED_MON_AMSDU_ENG_MERG(8)),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT8(8),
++ WED_AMSDU_ENG_MAX_PL_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT8(8),
++ WED_AMSDU_ENG_MAX_QGPP_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(8),
++ WED_AMSDU_ENG_CUR_ENTRY),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(8),
++ WED_AMSDU_ENG_MAX_BUF_MERGED),
++ DUMP_WED_MASK(WED_MON_AMSDU_ENG_CNT9(8),
++ WED_AMSDU_ENG_MAX_MSDU_MERGED),
++
++ DUMP_STR("WED QMEM INFO"),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_CNT(0), WED_AMSDU_QMEM_FQ_CNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_CNT(0), WED_AMSDU_QMEM_SP_QCNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_CNT(1), WED_AMSDU_QMEM_TID0_QCNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_CNT(1), WED_AMSDU_QMEM_TID1_QCNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_CNT(2), WED_AMSDU_QMEM_TID2_QCNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_CNT(2), WED_AMSDU_QMEM_TID3_QCNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_CNT(3), WED_AMSDU_QMEM_TID4_QCNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_CNT(3), WED_AMSDU_QMEM_TID5_QCNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_CNT(4), WED_AMSDU_QMEM_TID6_QCNT),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_CNT(4), WED_AMSDU_QMEM_TID7_QCNT),
++
++ DUMP_STR("WED QMEM HEAD INFO"),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(0), WED_AMSDU_QMEM_FQ_HEAD),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(0), WED_AMSDU_QMEM_SP_QHEAD),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(1), WED_AMSDU_QMEM_TID0_QHEAD),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(1), WED_AMSDU_QMEM_TID1_QHEAD),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(2), WED_AMSDU_QMEM_TID2_QHEAD),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(2), WED_AMSDU_QMEM_TID3_QHEAD),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(3), WED_AMSDU_QMEM_TID4_QHEAD),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(3), WED_AMSDU_QMEM_TID5_QHEAD),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(4), WED_AMSDU_QMEM_TID6_QHEAD),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(4), WED_AMSDU_QMEM_TID7_QHEAD),
++
++ DUMP_STR("WED QMEM TAIL INFO"),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(5), WED_AMSDU_QMEM_FQ_TAIL),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(5), WED_AMSDU_QMEM_SP_QTAIL),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(6), WED_AMSDU_QMEM_TID0_QTAIL),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(6), WED_AMSDU_QMEM_TID1_QTAIL),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(7), WED_AMSDU_QMEM_TID2_QTAIL),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(7), WED_AMSDU_QMEM_TID3_QTAIL),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(8), WED_AMSDU_QMEM_TID4_QTAIL),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(8), WED_AMSDU_QMEM_TID5_QTAIL),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(9), WED_AMSDU_QMEM_TID6_QTAIL),
++ DUMP_WED_MASK(WED_MON_AMSDU_QMEM_PTR(9), WED_AMSDU_QMEM_TID7_QTAIL),
++
++ DUMP_STR("WED HIFTXD MSDU INFO"),
++ DUMP_WED(WED_MON_AMSDU_HIFTXD_FETCH_MSDU(1)),
++ DUMP_WED(WED_MON_AMSDU_HIFTXD_FETCH_MSDU(2)),
++ DUMP_WED(WED_MON_AMSDU_HIFTXD_FETCH_MSDU(3)),
++ DUMP_WED(WED_MON_AMSDU_HIFTXD_FETCH_MSDU(4)),
++ DUMP_WED(WED_MON_AMSDU_HIFTXD_FETCH_MSDU(5)),
++ DUMP_WED(WED_MON_AMSDU_HIFTXD_FETCH_MSDU(6)),
++ DUMP_WED(WED_MON_AMSDU_HIFTXD_FETCH_MSDU(7)),
++ DUMP_WED(WED_MON_AMSDU_HIFTXD_FETCH_MSDU(8)),
++ DUMP_WED(WED_MON_AMSDU_HIFTXD_FETCH_MSDU(9)),
++ DUMP_WED(WED_MON_AMSDU_HIFTXD_FETCH_MSDU(10)),
++ DUMP_WED(WED_MON_AMSDU_HIFTXD_FETCH_MSDU(11)),
++ DUMP_WED(WED_MON_AMSDU_HIFTXD_FETCH_MSDU(12)),
++ DUMP_WED(WED_MON_AMSDU_HIFTXD_FETCH_MSDU(13)),
++ };
++ struct mtk_wed_hw *hw = s->private;
++ struct mtk_wed_device *dev = hw->wed_dev;
++
++ if (dev)
++ dump_wed_regs(s, dev, regs, ARRAY_SIZE(regs));
++
++ return 0;
++}
++DEFINE_SHOW_ATTRIBUTE(wed_amsdu);
++
++static int
++wed_rtqm_show(struct seq_file *s, void *data)
++{
++ static const struct reg_dump regs[] = {
++ DUMP_STR("WED Route QM IGRS0(N2H + Recycle)"),
++ DUMP_WED(WED_RTQM_IGRS0_I2HW_DMAD_CNT),
++ DUMP_WED(WED_RTQM_IGRS0_I2H_DMAD_CNT(0)),
++ DUMP_WED(WED_RTQM_IGRS0_I2H_DMAD_CNT(1)),
++ DUMP_WED(WED_RTQM_IGRS0_I2HW_PKT_CNT),
++ DUMP_WED(WED_RTQM_IGRS0_I2H_PKT_CNT(0)),
++ DUMP_WED(WED_RTQM_IGRS0_I2H_PKT_CNT(0)),
++ DUMP_WED(WED_RTQM_IGRS0_FDROP_CNT),
++
++ DUMP_STR("WED Route QM IGRS1(Legacy)"),
++ DUMP_WED(WED_RTQM_IGRS1_I2HW_DMAD_CNT),
++ DUMP_WED(WED_RTQM_IGRS1_I2H_DMAD_CNT(0)),
++ DUMP_WED(WED_RTQM_IGRS1_I2H_DMAD_CNT(1)),
++ DUMP_WED(WED_RTQM_IGRS1_I2HW_PKT_CNT),
++ DUMP_WED(WED_RTQM_IGRS1_I2H_PKT_CNT(0)),
++ DUMP_WED(WED_RTQM_IGRS1_I2H_PKT_CNT(1)),
++ DUMP_WED(WED_RTQM_IGRS1_FDROP_CNT),
++
++ DUMP_STR("WED Route QM IGRS2(RRO3.0)"),
++ DUMP_WED(WED_RTQM_IGRS2_I2HW_DMAD_CNT),
++ DUMP_WED(WED_RTQM_IGRS2_I2H_DMAD_CNT(0)),
++ DUMP_WED(WED_RTQM_IGRS2_I2H_DMAD_CNT(1)),
++ DUMP_WED(WED_RTQM_IGRS2_I2HW_PKT_CNT),
++ DUMP_WED(WED_RTQM_IGRS2_I2H_PKT_CNT(0)),
++ DUMP_WED(WED_RTQM_IGRS2_I2H_PKT_CNT(1)),
++ DUMP_WED(WED_RTQM_IGRS2_FDROP_CNT),
++
++ DUMP_STR("WED Route QM IGRS3(DEBUG)"),
++ DUMP_WED(WED_RTQM_IGRS2_I2HW_DMAD_CNT),
++ DUMP_WED(WED_RTQM_IGRS3_I2H_DMAD_CNT(0)),
++ DUMP_WED(WED_RTQM_IGRS3_I2H_DMAD_CNT(1)),
++ DUMP_WED(WED_RTQM_IGRS3_I2HW_PKT_CNT),
++ DUMP_WED(WED_RTQM_IGRS3_I2H_PKT_CNT(0)),
++ DUMP_WED(WED_RTQM_IGRS3_I2H_PKT_CNT(1)),
++ DUMP_WED(WED_RTQM_IGRS3_FDROP_CNT),
++ };
++ struct mtk_wed_hw *hw = s->private;
++ struct mtk_wed_device *dev = hw->wed_dev;
++
++ if (dev)
++ dump_wed_regs(s, dev, regs, ARRAY_SIZE(regs));
++
++ return 0;
++}
++DEFINE_SHOW_ATTRIBUTE(wed_rtqm);
++
++static int
++wed_rro_show(struct seq_file *s, void *data)
++{
++ static const struct reg_dump regs[] = {
++ DUMP_STR("RRO/IND CMD CNT"),
++ DUMP_WED(WED_RX_IND_CMD_CNT(1)),
++ DUMP_WED(WED_RX_IND_CMD_CNT(2)),
++ DUMP_WED(WED_RX_IND_CMD_CNT(3)),
++ DUMP_WED(WED_RX_IND_CMD_CNT(4)),
++ DUMP_WED(WED_RX_IND_CMD_CNT(5)),
++ DUMP_WED(WED_RX_IND_CMD_CNT(6)),
++ DUMP_WED(WED_RX_IND_CMD_CNT(7)),
++ DUMP_WED(WED_RX_IND_CMD_CNT(8)),
++ DUMP_WED_MASK(WED_RX_IND_CMD_CNT(9),
++ WED_IND_CMD_MAGIC_CNT_FAIL_CNT),
++
++ DUMP_WED(WED_RX_ADDR_ELEM_CNT(0)),
++ DUMP_WED_MASK(WED_RX_ADDR_ELEM_CNT(1),
++ WED_ADDR_ELEM_SIG_FAIL_CNT),
++ DUMP_WED(WED_RX_MSDU_PG_CNT(1)),
++ DUMP_WED(WED_RX_MSDU_PG_CNT(2)),
++ DUMP_WED(WED_RX_MSDU_PG_CNT(3)),
++ DUMP_WED(WED_RX_MSDU_PG_CNT(4)),
++ DUMP_WED(WED_RX_MSDU_PG_CNT(5)),
++ DUMP_WED_MASK(WED_RX_PN_CHK_CNT,
++ WED_PN_CHK_FAIL_CNT),
++ };
++ struct mtk_wed_hw *hw = s->private;
++ struct mtk_wed_device *dev = hw->wed_dev;
++
++ if (dev)
++ dump_wed_regs(s, dev, regs, ARRAY_SIZE(regs));
++
++ return 0;
++}
++DEFINE_SHOW_ATTRIBUTE(wed_rro);
++
++static int
+ mtk_wed_reg_set(void *data, u64 val)
+ {
+ struct mtk_wed_hw *hw = data;
+@@ -266,7 +624,16 @@ void mtk_wed_hw_add_debugfs(struct mtk_w
+ 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);
+- if (!mtk_wed_is_v1(hw))
++ if (!mtk_wed_is_v1(hw)) {
+ debugfs_create_file_unsafe("rxinfo", 0400, dir, hw,
+ &wed_rxinfo_fops);
++ if (mtk_wed_is_v3_or_greater(hw)) {
++ debugfs_create_file_unsafe("amsdu", 0400, dir, hw,
++ &wed_amsdu_fops);
++ debugfs_create_file_unsafe("rtqm", 0400, dir, hw,
++ &wed_rtqm_fops);
++ debugfs_create_file_unsafe("rro", 0400, dir, hw,
++ &wed_rro_fops);
++ }
++ }
+ }
diff --git a/target/linux/generic/backport-6.6/752-20-v6.7-net-ethernet-mtk_wed-add-wed-3.0-reset-support.patch b/target/linux/generic/backport-6.6/752-20-v6.7-net-ethernet-mtk_wed-add-wed-3.0-reset-support.patch
new file mode 100644
index 0000000000..aaaabf05e8
--- /dev/null
+++ b/target/linux/generic/backport-6.6/752-20-v6.7-net-ethernet-mtk_wed-add-wed-3.0-reset-support.patch
@@ -0,0 +1,587 @@
+From: Sujuan Chen <sujuan.chen@mediatek.com>
+Date: Mon, 18 Sep 2023 12:29:19 +0200
+Subject: [PATCH] net: ethernet: mtk_wed: add wed 3.0 reset support
+
+Introduce support for resetting Wireless Ethernet Dispatcher 3.0
+available on MT988 SoC.
+
+Co-developed-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_wed.c
++++ b/drivers/net/ethernet/mediatek/mtk_wed.c
+@@ -148,6 +148,90 @@ mtk_wdma_read_reset(struct mtk_wed_devic
+ return wdma_r32(dev, MTK_WDMA_GLO_CFG);
+ }
+
++static void
++mtk_wdma_v3_rx_reset(struct mtk_wed_device *dev)
++{
++ u32 status;
++
++ if (!mtk_wed_is_v3_or_greater(dev->hw))
++ return;
++
++ wdma_clr(dev, MTK_WDMA_PREF_TX_CFG, MTK_WDMA_PREF_TX_CFG_PREF_EN);
++ wdma_clr(dev, MTK_WDMA_PREF_RX_CFG, MTK_WDMA_PREF_RX_CFG_PREF_EN);
++
++ if (read_poll_timeout(wdma_r32, status,
++ !(status & MTK_WDMA_PREF_TX_CFG_PREF_BUSY),
++ 0, 10000, false, dev, MTK_WDMA_PREF_TX_CFG))
++ dev_err(dev->hw->dev, "rx reset failed\n");
++
++ if (read_poll_timeout(wdma_r32, status,
++ !(status & MTK_WDMA_PREF_RX_CFG_PREF_BUSY),
++ 0, 10000, false, dev, MTK_WDMA_PREF_RX_CFG))
++ dev_err(dev->hw->dev, "rx reset failed\n");
++
++ wdma_clr(dev, MTK_WDMA_WRBK_TX_CFG, MTK_WDMA_WRBK_TX_CFG_WRBK_EN);
++ wdma_clr(dev, MTK_WDMA_WRBK_RX_CFG, MTK_WDMA_WRBK_RX_CFG_WRBK_EN);
++
++ if (read_poll_timeout(wdma_r32, status,
++ !(status & MTK_WDMA_WRBK_TX_CFG_WRBK_BUSY),
++ 0, 10000, false, dev, MTK_WDMA_WRBK_TX_CFG))
++ dev_err(dev->hw->dev, "rx reset failed\n");
++
++ if (read_poll_timeout(wdma_r32, status,
++ !(status & MTK_WDMA_WRBK_RX_CFG_WRBK_BUSY),
++ 0, 10000, false, dev, MTK_WDMA_WRBK_RX_CFG))
++ dev_err(dev->hw->dev, "rx reset failed\n");
++
++ /* prefetch FIFO */
++ wdma_w32(dev, MTK_WDMA_PREF_RX_FIFO_CFG,
++ MTK_WDMA_PREF_RX_FIFO_CFG_RING0_CLEAR |
++ MTK_WDMA_PREF_RX_FIFO_CFG_RING1_CLEAR);
++ wdma_clr(dev, MTK_WDMA_PREF_RX_FIFO_CFG,
++ MTK_WDMA_PREF_RX_FIFO_CFG_RING0_CLEAR |
++ MTK_WDMA_PREF_RX_FIFO_CFG_RING1_CLEAR);
++
++ /* core FIFO */
++ wdma_w32(dev, MTK_WDMA_XDMA_RX_FIFO_CFG,
++ MTK_WDMA_XDMA_RX_FIFO_CFG_RX_PAR_FIFO_CLEAR |
++ MTK_WDMA_XDMA_RX_FIFO_CFG_RX_CMD_FIFO_CLEAR |
++ MTK_WDMA_XDMA_RX_FIFO_CFG_RX_DMAD_FIFO_CLEAR |
++ MTK_WDMA_XDMA_RX_FIFO_CFG_RX_ARR_FIFO_CLEAR |
++ MTK_WDMA_XDMA_RX_FIFO_CFG_RX_LEN_FIFO_CLEAR |
++ MTK_WDMA_XDMA_RX_FIFO_CFG_RX_WID_FIFO_CLEAR |
++ MTK_WDMA_XDMA_RX_FIFO_CFG_RX_BID_FIFO_CLEAR);
++ wdma_clr(dev, MTK_WDMA_XDMA_RX_FIFO_CFG,
++ MTK_WDMA_XDMA_RX_FIFO_CFG_RX_PAR_FIFO_CLEAR |
++ MTK_WDMA_XDMA_RX_FIFO_CFG_RX_CMD_FIFO_CLEAR |
++ MTK_WDMA_XDMA_RX_FIFO_CFG_RX_DMAD_FIFO_CLEAR |
++ MTK_WDMA_XDMA_RX_FIFO_CFG_RX_ARR_FIFO_CLEAR |
++ MTK_WDMA_XDMA_RX_FIFO_CFG_RX_LEN_FIFO_CLEAR |
++ MTK_WDMA_XDMA_RX_FIFO_CFG_RX_WID_FIFO_CLEAR |
++ MTK_WDMA_XDMA_RX_FIFO_CFG_RX_BID_FIFO_CLEAR);
++
++ /* writeback FIFO */
++ wdma_w32(dev, MTK_WDMA_WRBK_RX_FIFO_CFG(0),
++ MTK_WDMA_WRBK_RX_FIFO_CFG_RING_CLEAR);
++ wdma_w32(dev, MTK_WDMA_WRBK_RX_FIFO_CFG(1),
++ MTK_WDMA_WRBK_RX_FIFO_CFG_RING_CLEAR);
++
++ wdma_clr(dev, MTK_WDMA_WRBK_RX_FIFO_CFG(0),
++ MTK_WDMA_WRBK_RX_FIFO_CFG_RING_CLEAR);
++ wdma_clr(dev, MTK_WDMA_WRBK_RX_FIFO_CFG(1),
++ MTK_WDMA_WRBK_RX_FIFO_CFG_RING_CLEAR);
++
++ /* prefetch ring status */
++ wdma_w32(dev, MTK_WDMA_PREF_SIDX_CFG,
++ MTK_WDMA_PREF_SIDX_CFG_RX_RING_CLEAR);
++ wdma_clr(dev, MTK_WDMA_PREF_SIDX_CFG,
++ MTK_WDMA_PREF_SIDX_CFG_RX_RING_CLEAR);
++
++ /* writeback ring status */
++ wdma_w32(dev, MTK_WDMA_WRBK_SIDX_CFG,
++ MTK_WDMA_WRBK_SIDX_CFG_RX_RING_CLEAR);
++ wdma_clr(dev, MTK_WDMA_WRBK_SIDX_CFG,
++ MTK_WDMA_WRBK_SIDX_CFG_RX_RING_CLEAR);
++}
++
+ static int
+ mtk_wdma_rx_reset(struct mtk_wed_device *dev)
+ {
+@@ -160,6 +244,7 @@ mtk_wdma_rx_reset(struct mtk_wed_device
+ if (ret)
+ dev_err(dev->hw->dev, "rx reset failed\n");
+
++ mtk_wdma_v3_rx_reset(dev);
+ wdma_w32(dev, MTK_WDMA_RESET_IDX, MTK_WDMA_RESET_IDX_RX);
+ wdma_w32(dev, MTK_WDMA_RESET_IDX, 0);
+
+@@ -192,6 +277,84 @@ mtk_wed_poll_busy(struct mtk_wed_device
+ }
+
+ static void
++mtk_wdma_v3_tx_reset(struct mtk_wed_device *dev)
++{
++ u32 status;
++
++ if (!mtk_wed_is_v3_or_greater(dev->hw))
++ return;
++
++ wdma_clr(dev, MTK_WDMA_PREF_TX_CFG, MTK_WDMA_PREF_TX_CFG_PREF_EN);
++ wdma_clr(dev, MTK_WDMA_PREF_RX_CFG, MTK_WDMA_PREF_RX_CFG_PREF_EN);
++
++ if (read_poll_timeout(wdma_r32, status,
++ !(status & MTK_WDMA_PREF_TX_CFG_PREF_BUSY),
++ 0, 10000, false, dev, MTK_WDMA_PREF_TX_CFG))
++ dev_err(dev->hw->dev, "tx reset failed\n");
++
++ if (read_poll_timeout(wdma_r32, status,
++ !(status & MTK_WDMA_PREF_RX_CFG_PREF_BUSY),
++ 0, 10000, false, dev, MTK_WDMA_PREF_RX_CFG))
++ dev_err(dev->hw->dev, "tx reset failed\n");
++
++ wdma_clr(dev, MTK_WDMA_WRBK_TX_CFG, MTK_WDMA_WRBK_TX_CFG_WRBK_EN);
++ wdma_clr(dev, MTK_WDMA_WRBK_RX_CFG, MTK_WDMA_WRBK_RX_CFG_WRBK_EN);
++
++ if (read_poll_timeout(wdma_r32, status,
++ !(status & MTK_WDMA_WRBK_TX_CFG_WRBK_BUSY),
++ 0, 10000, false, dev, MTK_WDMA_WRBK_TX_CFG))
++ dev_err(dev->hw->dev, "tx reset failed\n");
++
++ if (read_poll_timeout(wdma_r32, status,
++ !(status & MTK_WDMA_WRBK_RX_CFG_WRBK_BUSY),
++ 0, 10000, false, dev, MTK_WDMA_WRBK_RX_CFG))
++ dev_err(dev->hw->dev, "tx reset failed\n");
++
++ /* prefetch FIFO */
++ wdma_w32(dev, MTK_WDMA_PREF_TX_FIFO_CFG,
++ MTK_WDMA_PREF_TX_FIFO_CFG_RING0_CLEAR |
++ MTK_WDMA_PREF_TX_FIFO_CFG_RING1_CLEAR);
++ wdma_clr(dev, MTK_WDMA_PREF_TX_FIFO_CFG,
++ MTK_WDMA_PREF_TX_FIFO_CFG_RING0_CLEAR |
++ MTK_WDMA_PREF_TX_FIFO_CFG_RING1_CLEAR);
++
++ /* core FIFO */
++ wdma_w32(dev, MTK_WDMA_XDMA_TX_FIFO_CFG,
++ MTK_WDMA_XDMA_TX_FIFO_CFG_TX_PAR_FIFO_CLEAR |
++ MTK_WDMA_XDMA_TX_FIFO_CFG_TX_CMD_FIFO_CLEAR |
++ MTK_WDMA_XDMA_TX_FIFO_CFG_TX_DMAD_FIFO_CLEAR |
++ MTK_WDMA_XDMA_TX_FIFO_CFG_TX_ARR_FIFO_CLEAR);
++ wdma_clr(dev, MTK_WDMA_XDMA_TX_FIFO_CFG,
++ MTK_WDMA_XDMA_TX_FIFO_CFG_TX_PAR_FIFO_CLEAR |
++ MTK_WDMA_XDMA_TX_FIFO_CFG_TX_CMD_FIFO_CLEAR |
++ MTK_WDMA_XDMA_TX_FIFO_CFG_TX_DMAD_FIFO_CLEAR |
++ MTK_WDMA_XDMA_TX_FIFO_CFG_TX_ARR_FIFO_CLEAR);
++
++ /* writeback FIFO */
++ wdma_w32(dev, MTK_WDMA_WRBK_TX_FIFO_CFG(0),
++ MTK_WDMA_WRBK_TX_FIFO_CFG_RING_CLEAR);
++ wdma_w32(dev, MTK_WDMA_WRBK_TX_FIFO_CFG(1),
++ MTK_WDMA_WRBK_TX_FIFO_CFG_RING_CLEAR);
++
++ wdma_clr(dev, MTK_WDMA_WRBK_TX_FIFO_CFG(0),
++ MTK_WDMA_WRBK_TX_FIFO_CFG_RING_CLEAR);
++ wdma_clr(dev, MTK_WDMA_WRBK_TX_FIFO_CFG(1),
++ MTK_WDMA_WRBK_TX_FIFO_CFG_RING_CLEAR);
++
++ /* prefetch ring status */
++ wdma_w32(dev, MTK_WDMA_PREF_SIDX_CFG,
++ MTK_WDMA_PREF_SIDX_CFG_TX_RING_CLEAR);
++ wdma_clr(dev, MTK_WDMA_PREF_SIDX_CFG,
++ MTK_WDMA_PREF_SIDX_CFG_TX_RING_CLEAR);
++
++ /* writeback ring status */
++ wdma_w32(dev, MTK_WDMA_WRBK_SIDX_CFG,
++ MTK_WDMA_WRBK_SIDX_CFG_TX_RING_CLEAR);
++ wdma_clr(dev, MTK_WDMA_WRBK_SIDX_CFG,
++ MTK_WDMA_WRBK_SIDX_CFG_TX_RING_CLEAR);
++}
++
++static void
+ mtk_wdma_tx_reset(struct mtk_wed_device *dev)
+ {
+ u32 status, mask = MTK_WDMA_GLO_CFG_TX_DMA_BUSY;
+@@ -202,6 +365,7 @@ mtk_wdma_tx_reset(struct mtk_wed_device
+ !(status & mask), 0, 10000))
+ dev_err(dev->hw->dev, "tx reset failed\n");
+
++ mtk_wdma_v3_tx_reset(dev);
+ wdma_w32(dev, MTK_WDMA_RESET_IDX, MTK_WDMA_RESET_IDX_TX);
+ wdma_w32(dev, MTK_WDMA_RESET_IDX, 0);
+
+@@ -1405,13 +1569,33 @@ mtk_wed_rx_reset(struct mtk_wed_device *
+ if (ret)
+ return ret;
+
++ if (dev->wlan.hw_rro) {
++ wed_clr(dev, MTK_WED_CTRL, MTK_WED_CTRL_WED_RX_IND_CMD_EN);
++ mtk_wed_poll_busy(dev, MTK_WED_RRO_RX_HW_STS,
++ MTK_WED_RX_IND_CMD_BUSY);
++ mtk_wed_reset(dev, MTK_WED_RESET_RRO_RX_TO_PG);
++ }
++
+ wed_clr(dev, MTK_WED_WPDMA_RX_D_GLO_CFG, MTK_WED_WPDMA_RX_D_RX_DRV_EN);
+ ret = mtk_wed_poll_busy(dev, MTK_WED_WPDMA_RX_D_GLO_CFG,
+ MTK_WED_WPDMA_RX_D_RX_DRV_BUSY);
++ if (!ret && mtk_wed_is_v3_or_greater(dev->hw))
++ ret = mtk_wed_poll_busy(dev, MTK_WED_WPDMA_RX_D_PREF_CFG,
++ MTK_WED_WPDMA_RX_D_PREF_BUSY);
+ if (ret) {
+ mtk_wed_reset(dev, MTK_WED_RESET_WPDMA_INT_AGENT);
+ mtk_wed_reset(dev, MTK_WED_RESET_WPDMA_RX_D_DRV);
+ } else {
++ if (mtk_wed_is_v3_or_greater(dev->hw)) {
++ /* 1.a. disable prefetch HW */
++ wed_clr(dev, MTK_WED_WPDMA_RX_D_PREF_CFG,
++ MTK_WED_WPDMA_RX_D_PREF_EN);
++ mtk_wed_poll_busy(dev, MTK_WED_WPDMA_RX_D_PREF_CFG,
++ MTK_WED_WPDMA_RX_D_PREF_BUSY);
++ wed_w32(dev, MTK_WED_WPDMA_RX_D_RST_IDX,
++ MTK_WED_WPDMA_RX_D_RST_DRV_IDX_ALL);
++ }
++
+ wed_w32(dev, MTK_WED_WPDMA_RX_D_RST_IDX,
+ MTK_WED_WPDMA_RX_D_RST_CRX_IDX |
+ MTK_WED_WPDMA_RX_D_RST_DRV_IDX);
+@@ -1439,23 +1623,52 @@ mtk_wed_rx_reset(struct mtk_wed_device *
+ wed_w32(dev, MTK_WED_RROQM_RST_IDX, 0);
+ }
+
++ if (dev->wlan.hw_rro) {
++ /* disable rro msdu page drv */
++ wed_clr(dev, MTK_WED_RRO_MSDU_PG_RING2_CFG,
++ MTK_WED_RRO_MSDU_PG_DRV_EN);
++
++ /* disable rro data drv */
++ wed_clr(dev, MTK_WED_RRO_RX_D_CFG(2), MTK_WED_RRO_RX_D_DRV_EN);
++
++ /* rro msdu page drv reset */
++ wed_w32(dev, MTK_WED_RRO_MSDU_PG_RING2_CFG,
++ MTK_WED_RRO_MSDU_PG_DRV_CLR);
++ mtk_wed_poll_busy(dev, MTK_WED_RRO_MSDU_PG_RING2_CFG,
++ MTK_WED_RRO_MSDU_PG_DRV_CLR);
++
++ /* rro data drv reset */
++ wed_w32(dev, MTK_WED_RRO_RX_D_CFG(2),
++ MTK_WED_RRO_RX_D_DRV_CLR);
++ mtk_wed_poll_busy(dev, MTK_WED_RRO_RX_D_CFG(2),
++ MTK_WED_RRO_RX_D_DRV_CLR);
++ }
++
+ /* reset route qm */
+ wed_clr(dev, MTK_WED_CTRL, MTK_WED_CTRL_RX_ROUTE_QM_EN);
+ ret = mtk_wed_poll_busy(dev, MTK_WED_CTRL,
+ MTK_WED_CTRL_RX_ROUTE_QM_BUSY);
+- if (ret)
++ if (ret) {
+ mtk_wed_reset(dev, MTK_WED_RESET_RX_ROUTE_QM);
+- else
+- wed_set(dev, MTK_WED_RTQM_GLO_CFG,
+- MTK_WED_RTQM_Q_RST);
++ } else if (mtk_wed_is_v3_or_greater(dev->hw)) {
++ wed_set(dev, MTK_WED_RTQM_RST, BIT(0));
++ wed_clr(dev, MTK_WED_RTQM_RST, BIT(0));
++ mtk_wed_reset(dev, MTK_WED_RESET_RX_ROUTE_QM);
++ } else {
++ wed_set(dev, MTK_WED_RTQM_GLO_CFG, MTK_WED_RTQM_Q_RST);
++ }
+
+ /* reset tx wdma */
+ mtk_wdma_tx_reset(dev);
+
+ /* reset tx wdma drv */
+ wed_clr(dev, MTK_WED_WDMA_GLO_CFG, MTK_WED_WDMA_GLO_CFG_TX_DRV_EN);
+- mtk_wed_poll_busy(dev, MTK_WED_CTRL,
+- MTK_WED_CTRL_WDMA_INT_AGENT_BUSY);
++ if (mtk_wed_is_v3_or_greater(dev->hw))
++ mtk_wed_poll_busy(dev, MTK_WED_WPDMA_STATUS,
++ MTK_WED_WPDMA_STATUS_TX_DRV);
++ else
++ mtk_wed_poll_busy(dev, MTK_WED_CTRL,
++ MTK_WED_CTRL_WDMA_INT_AGENT_BUSY);
+ mtk_wed_reset(dev, MTK_WED_RESET_WDMA_TX_DRV);
+
+ /* reset wed rx dma */
+@@ -1476,6 +1689,14 @@ mtk_wed_rx_reset(struct mtk_wed_device *
+ MTK_WED_CTRL_WED_RX_BM_BUSY);
+ mtk_wed_reset(dev, MTK_WED_RESET_RX_BM);
+
++ if (dev->wlan.hw_rro) {
++ wed_clr(dev, MTK_WED_CTRL, MTK_WED_CTRL_WED_RX_PG_BM_EN);
++ mtk_wed_poll_busy(dev, MTK_WED_CTRL,
++ MTK_WED_CTRL_WED_RX_PG_BM_BUSY);
++ wed_set(dev, MTK_WED_RESET, MTK_WED_RESET_RX_PG_BM);
++ wed_clr(dev, MTK_WED_RESET, MTK_WED_RESET_RX_PG_BM);
++ }
++
+ /* wo change to enable state */
+ val = MTK_WED_WO_STATE_ENABLE;
+ ret = mtk_wed_mcu_send_msg(wo, MTK_WED_MODULE_ID_WO,
+@@ -1493,6 +1714,7 @@ mtk_wed_rx_reset(struct mtk_wed_device *
+ false);
+ }
+ mtk_wed_free_rx_buffer(dev);
++ mtk_wed_hwrro_free_buffer(dev);
+
+ return 0;
+ }
+@@ -1526,15 +1748,41 @@ mtk_wed_reset_dma(struct mtk_wed_device
+
+ /* 2. reset WDMA rx DMA */
+ busy = !!mtk_wdma_rx_reset(dev);
+- wed_clr(dev, MTK_WED_WDMA_GLO_CFG, MTK_WED_WDMA_GLO_CFG_RX_DRV_EN);
++ if (mtk_wed_is_v3_or_greater(dev->hw)) {
++ val = MTK_WED_WDMA_GLO_CFG_RX_DIS_FSM_AUTO_IDLE |
++ wed_r32(dev, MTK_WED_WDMA_GLO_CFG);
++ val &= ~MTK_WED_WDMA_GLO_CFG_RX_DRV_EN;
++ wed_w32(dev, MTK_WED_WDMA_GLO_CFG, val);
++ } else {
++ wed_clr(dev, MTK_WED_WDMA_GLO_CFG,
++ MTK_WED_WDMA_GLO_CFG_RX_DRV_EN);
++ }
++
+ if (!busy)
+ busy = mtk_wed_poll_busy(dev, MTK_WED_WDMA_GLO_CFG,
+ MTK_WED_WDMA_GLO_CFG_RX_DRV_BUSY);
++ if (!busy && mtk_wed_is_v3_or_greater(dev->hw))
++ busy = mtk_wed_poll_busy(dev, MTK_WED_WDMA_RX_PREF_CFG,
++ MTK_WED_WDMA_RX_PREF_BUSY);
+
+ if (busy) {
+ mtk_wed_reset(dev, MTK_WED_RESET_WDMA_INT_AGENT);
+ mtk_wed_reset(dev, MTK_WED_RESET_WDMA_RX_DRV);
+ } else {
++ if (mtk_wed_is_v3_or_greater(dev->hw)) {
++ /* 1.a. disable prefetch HW */
++ wed_clr(dev, MTK_WED_WDMA_RX_PREF_CFG,
++ MTK_WED_WDMA_RX_PREF_EN);
++ mtk_wed_poll_busy(dev, MTK_WED_WDMA_RX_PREF_CFG,
++ MTK_WED_WDMA_RX_PREF_BUSY);
++ wed_clr(dev, MTK_WED_WDMA_RX_PREF_CFG,
++ MTK_WED_WDMA_RX_PREF_DDONE2_EN);
++
++ /* 2. Reset dma index */
++ wed_w32(dev, MTK_WED_WDMA_RESET_IDX,
++ MTK_WED_WDMA_RESET_IDX_RX_ALL);
++ }
++
+ 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);
+@@ -1550,8 +1798,13 @@ mtk_wed_reset_dma(struct mtk_wed_device
+ wed_clr(dev, MTK_WED_CTRL, MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
+
+ 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)
++ if (mtk_wed_is_v1(dev->hw))
++ val = FIELD_GET(MTK_WED_TX_BM_INTF_TKFIFO_FDEP,
++ wed_r32(dev, MTK_WED_TX_BM_INTF));
++ else
++ val = FIELD_GET(MTK_WED_TX_TKID_INTF_TKFIFO_FDEP,
++ wed_r32(dev, MTK_WED_TX_TKID_INTF));
++ if (val == 0x40)
+ break;
+ }
+
+@@ -1573,6 +1826,8 @@ mtk_wed_reset_dma(struct mtk_wed_device
+ 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);
++ if (mtk_wed_is_v3_or_greater(dev->hw))
++ wed_w32(dev, MTK_WED_RX1_CTRL2, 0);
+ } else {
+ wed_w32(dev, MTK_WED_WPDMA_RESET_IDX,
+ MTK_WED_WPDMA_RESET_IDX_TX |
+@@ -1589,7 +1844,14 @@ mtk_wed_reset_dma(struct mtk_wed_device
+ wed_w32(dev, MTK_WED_RESET_IDX, 0);
+ }
+
+- mtk_wed_rx_reset(dev);
++ if (mtk_wed_is_v3_or_greater(dev->hw)) {
++ /* reset amsdu engine */
++ wed_clr(dev, MTK_WED_CTRL, MTK_WED_CTRL_TX_AMSDU_EN);
++ mtk_wed_reset(dev, MTK_WED_RESET_TX_AMSDU);
++ }
++
++ if (mtk_wed_get_rx_capa(dev))
++ mtk_wed_rx_reset(dev);
+ }
+
+ static int
+@@ -1841,6 +2103,7 @@ mtk_wed_dma_enable(struct mtk_wed_device
+ MTK_WED_WPDMA_GLO_CFG_RX_DRV_UNS_VER_FORCE_4);
+
+ wdma_set(dev, MTK_WDMA_PREF_RX_CFG, MTK_WDMA_PREF_RX_CFG_PREF_EN);
++ wdma_set(dev, MTK_WDMA_WRBK_RX_CFG, MTK_WDMA_WRBK_RX_CFG_WRBK_EN);
+ }
+
+ wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
+@@ -1904,6 +2167,12 @@ mtk_wed_start_hw_rro(struct mtk_wed_devi
+ if (!mtk_wed_get_rx_capa(dev) || !dev->wlan.hw_rro)
+ return;
+
++ if (reset) {
++ wed_set(dev, MTK_WED_RRO_MSDU_PG_RING2_CFG,
++ MTK_WED_RRO_MSDU_PG_DRV_EN);
++ return;
++ }
++
+ wed_set(dev, MTK_WED_RRO_RX_D_CFG(2), MTK_WED_RRO_MSDU_PG_DRV_CLR);
+ wed_w32(dev, MTK_WED_RRO_MSDU_PG_RING2_CFG,
+ MTK_WED_RRO_MSDU_PG_DRV_CLR);
+--- a/drivers/net/ethernet/mediatek/mtk_wed_regs.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
+@@ -28,6 +28,8 @@ struct mtk_wdma_desc {
+ #define MTK_WED_RESET 0x008
+ #define MTK_WED_RESET_TX_BM BIT(0)
+ #define MTK_WED_RESET_RX_BM BIT(1)
++#define MTK_WED_RESET_RX_PG_BM BIT(2)
++#define MTK_WED_RESET_RRO_RX_TO_PG BIT(3)
+ #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)
+@@ -106,6 +108,9 @@ struct mtk_wdma_desc {
+ #define MTK_WED_STATUS 0x060
+ #define MTK_WED_STATUS_TX GENMASK(15, 8)
+
++#define MTK_WED_WPDMA_STATUS 0x068
++#define MTK_WED_WPDMA_STATUS_TX_DRV 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)
+@@ -140,6 +145,9 @@ struct mtk_wdma_desc {
+ #define MTK_WED_TX_TKID_CTRL_RSV_GRP_NUM GENMASK(22, 16)
+ #define MTK_WED_TX_TKID_CTRL_PAUSE BIT(28)
+
++#define MTK_WED_TX_TKID_INTF 0x0dc
++#define MTK_WED_TX_TKID_INTF_TKFIFO_FDEP GENMASK(25, 16)
++
+ #define MTK_WED_TX_TKID_CTRL_VLD_GRP_NUM_V3 GENMASK(7, 0)
+ #define MTK_WED_TX_TKID_CTRL_RSV_GRP_NUM_V3 GENMASK(23, 16)
+
+@@ -190,6 +198,7 @@ struct mtk_wdma_desc {
+ #define MTK_WED_RING_RX_DATA(_n) (0x420 + (_n) * 0x10)
+
+ #define MTK_WED_SCR0 0x3c0
++#define MTK_WED_RX1_CTRL2 0x418
+ #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)
+@@ -303,6 +312,7 @@ struct mtk_wdma_desc {
+
+ #define MTK_WED_WPDMA_RX_D_RST_IDX 0x760
+ #define MTK_WED_WPDMA_RX_D_RST_CRX_IDX GENMASK(17, 16)
++#define MTK_WED_WPDMA_RX_D_RST_DRV_IDX_ALL BIT(20)
+ #define MTK_WED_WPDMA_RX_D_RST_DRV_IDX GENMASK(25, 24)
+
+ #define MTK_WED_WPDMA_RX_GLO_CFG 0x76c
+@@ -313,6 +323,7 @@ struct mtk_wdma_desc {
+
+ #define MTK_WED_WPDMA_RX_D_PREF_CFG 0x7b4
+ #define MTK_WED_WPDMA_RX_D_PREF_EN BIT(0)
++#define MTK_WED_WPDMA_RX_D_PREF_BUSY BIT(1)
+ #define MTK_WED_WPDMA_RX_D_PREF_BURST_SIZE GENMASK(12, 8)
+ #define MTK_WED_WPDMA_RX_D_PREF_LOW_THRES GENMASK(21, 16)
+
+@@ -334,11 +345,13 @@ struct mtk_wdma_desc {
+
+ #define MTK_WED_WDMA_RX_PREF_CFG 0x950
+ #define MTK_WED_WDMA_RX_PREF_EN BIT(0)
++#define MTK_WED_WDMA_RX_PREF_BUSY BIT(1)
+ #define MTK_WED_WDMA_RX_PREF_BURST_SIZE GENMASK(12, 8)
+ #define MTK_WED_WDMA_RX_PREF_LOW_THRES GENMASK(21, 16)
+ #define MTK_WED_WDMA_RX_PREF_RX0_SIDX_CLR BIT(24)
+ #define MTK_WED_WDMA_RX_PREF_RX1_SIDX_CLR BIT(25)
+ #define MTK_WED_WDMA_RX_PREF_DDONE2_EN BIT(26)
++#define MTK_WED_WDMA_RX_PREF_DDONE2_BUSY BIT(27)
+
+ #define MTK_WED_WDMA_RX_PREF_FIFO_CFG 0x95C
+ #define MTK_WED_WDMA_RX_PREF_FIFO_RX0_CLR BIT(0)
+@@ -367,6 +380,7 @@ struct mtk_wdma_desc {
+
+ #define MTK_WED_WDMA_RESET_IDX 0xa08
+ #define MTK_WED_WDMA_RESET_IDX_RX GENMASK(17, 16)
++#define MTK_WED_WDMA_RESET_IDX_RX_ALL BIT(20)
+ #define MTK_WED_WDMA_RESET_IDX_DRV GENMASK(25, 24)
+
+ #define MTK_WED_WDMA_INT_CLR 0xa24
+@@ -437,21 +451,62 @@ struct mtk_wdma_desc {
+ #define MTK_WDMA_INT_MASK_RX_DELAY BIT(30)
+ #define MTK_WDMA_INT_MASK_RX_COHERENT BIT(31)
+
++#define MTK_WDMA_XDMA_TX_FIFO_CFG 0x238
++#define MTK_WDMA_XDMA_TX_FIFO_CFG_TX_PAR_FIFO_CLEAR BIT(0)
++#define MTK_WDMA_XDMA_TX_FIFO_CFG_TX_CMD_FIFO_CLEAR BIT(4)
++#define MTK_WDMA_XDMA_TX_FIFO_CFG_TX_DMAD_FIFO_CLEAR BIT(8)
++#define MTK_WDMA_XDMA_TX_FIFO_CFG_TX_ARR_FIFO_CLEAR BIT(12)
++
++#define MTK_WDMA_XDMA_RX_FIFO_CFG 0x23c
++#define MTK_WDMA_XDMA_RX_FIFO_CFG_RX_PAR_FIFO_CLEAR BIT(0)
++#define MTK_WDMA_XDMA_RX_FIFO_CFG_RX_CMD_FIFO_CLEAR BIT(4)
++#define MTK_WDMA_XDMA_RX_FIFO_CFG_RX_DMAD_FIFO_CLEAR BIT(8)
++#define MTK_WDMA_XDMA_RX_FIFO_CFG_RX_ARR_FIFO_CLEAR BIT(12)
++#define MTK_WDMA_XDMA_RX_FIFO_CFG_RX_LEN_FIFO_CLEAR BIT(15)
++#define MTK_WDMA_XDMA_RX_FIFO_CFG_RX_WID_FIFO_CLEAR BIT(18)
++#define MTK_WDMA_XDMA_RX_FIFO_CFG_RX_BID_FIFO_CLEAR BIT(21)
++
+ #define MTK_WDMA_INT_GRP1 0x250
+ #define MTK_WDMA_INT_GRP2 0x254
+
+ #define MTK_WDMA_PREF_TX_CFG 0x2d0
+ #define MTK_WDMA_PREF_TX_CFG_PREF_EN BIT(0)
++#define MTK_WDMA_PREF_TX_CFG_PREF_BUSY BIT(1)
+
+ #define MTK_WDMA_PREF_RX_CFG 0x2dc
+ #define MTK_WDMA_PREF_RX_CFG_PREF_EN BIT(0)
++#define MTK_WDMA_PREF_RX_CFG_PREF_BUSY BIT(1)
++
++#define MTK_WDMA_PREF_RX_FIFO_CFG 0x2e0
++#define MTK_WDMA_PREF_RX_FIFO_CFG_RING0_CLEAR BIT(0)
++#define MTK_WDMA_PREF_RX_FIFO_CFG_RING1_CLEAR BIT(16)
++
++#define MTK_WDMA_PREF_TX_FIFO_CFG 0x2d4
++#define MTK_WDMA_PREF_TX_FIFO_CFG_RING0_CLEAR BIT(0)
++#define MTK_WDMA_PREF_TX_FIFO_CFG_RING1_CLEAR BIT(16)
++
++#define MTK_WDMA_PREF_SIDX_CFG 0x2e4
++#define MTK_WDMA_PREF_SIDX_CFG_TX_RING_CLEAR GENMASK(3, 0)
++#define MTK_WDMA_PREF_SIDX_CFG_RX_RING_CLEAR GENMASK(5, 4)
+
+ #define MTK_WDMA_WRBK_TX_CFG 0x300
++#define MTK_WDMA_WRBK_TX_CFG_WRBK_BUSY BIT(0)
+ #define MTK_WDMA_WRBK_TX_CFG_WRBK_EN BIT(30)
+
++#define MTK_WDMA_WRBK_TX_FIFO_CFG(_n) (0x304 + (_n) * 0x4)
++#define MTK_WDMA_WRBK_TX_FIFO_CFG_RING_CLEAR BIT(0)
++
+ #define MTK_WDMA_WRBK_RX_CFG 0x344
++#define MTK_WDMA_WRBK_RX_CFG_WRBK_BUSY BIT(0)
+ #define MTK_WDMA_WRBK_RX_CFG_WRBK_EN BIT(30)
+
++#define MTK_WDMA_WRBK_RX_FIFO_CFG(_n) (0x348 + (_n) * 0x4)
++#define MTK_WDMA_WRBK_RX_FIFO_CFG_RING_CLEAR BIT(0)
++
++#define MTK_WDMA_WRBK_SIDX_CFG 0x388
++#define MTK_WDMA_WRBK_SIDX_CFG_TX_RING_CLEAR GENMASK(3, 0)
++#define MTK_WDMA_WRBK_SIDX_CFG_RX_RING_CLEAR GENMASK(5, 4)
++
+ #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)
+@@ -465,6 +520,8 @@ struct mtk_wdma_desc {
+ #define MTK_WED_RTQM_Q_DBG_BYPASS BIT(5)
+ #define MTK_WED_RTQM_TXDMAD_FPORT GENMASK(23, 20)
+
++#define MTK_WED_RTQM_RST 0xb04
++
+ #define MTK_WED_RTQM_IGRS0_I2HW_DMAD_CNT 0xb1c
+ #define MTK_WED_RTQM_IGRS0_I2H_DMAD_CNT(_n) (0xb20 + (_n) * 0x4)
+ #define MTK_WED_RTQM_IGRS0_I2HW_PKT_CNT 0xb28
+@@ -653,6 +710,9 @@ struct mtk_wdma_desc {
+ #define MTK_WED_WPDMA_INT_CTRL_RRO_PG2_CLR BIT(17)
+ #define MTK_WED_WPDMA_INT_CTRL_RRO_PG2_DONE_TRIG GENMASK(22, 18)
+
++#define MTK_WED_RRO_RX_HW_STS 0xf00
++#define MTK_WED_RX_IND_CMD_BUSY GENMASK(31, 0)
++
+ #define MTK_WED_RX_IND_CMD_CNT0 0xf20
+ #define MTK_WED_RX_IND_CMD_DBG_CNT_EN BIT(31)
+
diff --git a/target/linux/generic/backport-6.6/760-v6.9-net-phy-aquantia-add-AQR111-and-AQR111B0-PHY-ID.patch b/target/linux/generic/backport-6.6/760-v6.9-net-phy-aquantia-add-AQR111-and-AQR111B0-PHY-ID.patch
new file mode 100644
index 0000000000..32ac37e8d0
--- /dev/null
+++ b/target/linux/generic/backport-6.6/760-v6.9-net-phy-aquantia-add-AQR111-and-AQR111B0-PHY-ID.patch
@@ -0,0 +1,102 @@
+From 038ba1dc4e54d51d953f5618d8eb5dd39bd9de25 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 13 Feb 2024 14:35:51 +0100
+Subject: [PATCH] net: phy: aquantia: add AQR111 and AQR111B0 PHY ID
+
+Add Aquantia AQR111 and AQR111B0 PHY ID. These PHY advertise 10G speed
+but actually supports up to 5G speed, hence some manual fixup is needed.
+
+The Aquantia AQR111B0 PHY is just a variant of the AQR111 with smaller
+chip size.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/20240213133558.1836-1-ansuelsmth@gmail.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/aquantia/aquantia_main.c | 52 ++++++++++++++++++++++++
+ 1 file changed, 52 insertions(+)
+
+--- a/drivers/net/phy/aquantia/aquantia_main.c
++++ b/drivers/net/phy/aquantia/aquantia_main.c
+@@ -22,6 +22,8 @@
+ #define PHY_ID_AQR107 0x03a1b4e0
+ #define PHY_ID_AQCS109 0x03a1b5c2
+ #define PHY_ID_AQR405 0x03a1b4b0
++#define PHY_ID_AQR111 0x03a1b610
++#define PHY_ID_AQR111B0 0x03a1b612
+ #define PHY_ID_AQR113C 0x31c31c12
+
+ #define MDIO_PHYXS_VEND_IF_STATUS 0xe812
+@@ -670,6 +672,16 @@ static int aqr107_probe(struct phy_devic
+ return aqr_hwmon_probe(phydev);
+ }
+
++static int aqr111_config_init(struct phy_device *phydev)
++{
++ /* AQR111 reports supporting speed up to 10G,
++ * however only speeds up to 5G are supported.
++ */
++ phy_set_max_speed(phydev, SPEED_5000);
++
++ return aqr107_config_init(phydev);
++}
++
+ static struct phy_driver aqr_driver[] = {
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_AQ1202),
+@@ -744,6 +756,44 @@ static struct phy_driver aqr_driver[] =
+ .link_change_notify = aqr107_link_change_notify,
+ },
+ {
++ PHY_ID_MATCH_MODEL(PHY_ID_AQR111),
++ .name = "Aquantia AQR111",
++ .probe = aqr107_probe,
++ .get_rate_matching = aqr107_get_rate_matching,
++ .config_init = aqr111_config_init,
++ .config_aneg = aqr_config_aneg,
++ .config_intr = aqr_config_intr,
++ .handle_interrupt = aqr_handle_interrupt,
++ .read_status = aqr107_read_status,
++ .get_tunable = aqr107_get_tunable,
++ .set_tunable = aqr107_set_tunable,
++ .suspend = aqr107_suspend,
++ .resume = aqr107_resume,
++ .get_sset_count = aqr107_get_sset_count,
++ .get_strings = aqr107_get_strings,
++ .get_stats = aqr107_get_stats,
++ .link_change_notify = aqr107_link_change_notify,
++},
++{
++ PHY_ID_MATCH_MODEL(PHY_ID_AQR111B0),
++ .name = "Aquantia AQR111B0",
++ .probe = aqr107_probe,
++ .get_rate_matching = aqr107_get_rate_matching,
++ .config_init = aqr111_config_init,
++ .config_aneg = aqr_config_aneg,
++ .config_intr = aqr_config_intr,
++ .handle_interrupt = aqr_handle_interrupt,
++ .read_status = aqr107_read_status,
++ .get_tunable = aqr107_get_tunable,
++ .set_tunable = aqr107_set_tunable,
++ .suspend = aqr107_suspend,
++ .resume = aqr107_resume,
++ .get_sset_count = aqr107_get_sset_count,
++ .get_strings = aqr107_get_strings,
++ .get_stats = aqr107_get_stats,
++ .link_change_notify = aqr107_link_change_notify,
++},
++{
+ PHY_ID_MATCH_MODEL(PHY_ID_AQR405),
+ .name = "Aquantia AQR405",
+ .config_aneg = aqr_config_aneg,
+@@ -782,6 +832,8 @@ static struct mdio_device_id __maybe_unu
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR107) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQCS109) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR405) },
++ { PHY_ID_MATCH_MODEL(PHY_ID_AQR111) },
++ { PHY_ID_MATCH_MODEL(PHY_ID_AQR111B0) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR113C) },
+ { }
+ };
diff --git a/target/linux/generic/backport-6.6/761-v6.9-net-phy-aquantia-add-AQR113-PHY-ID.patch b/target/linux/generic/backport-6.6/761-v6.9-net-phy-aquantia-add-AQR113-PHY-ID.patch
new file mode 100644
index 0000000000..073bbbd49d
--- /dev/null
+++ b/target/linux/generic/backport-6.6/761-v6.9-net-phy-aquantia-add-AQR113-PHY-ID.patch
@@ -0,0 +1,60 @@
+From 71b605d32017e5b8d257db7344bc2f8e8fcc973e Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 15 Feb 2024 16:30:05 +0100
+Subject: [PATCH] net: phy: aquantia: add AQR113 PHY ID
+
+Add Aquantia AQR113 PHY ID. Aquantia AQR113 is just a chip size variant of
+the already supported AQR133C where the only difference is the PHY ID
+and the hw chip size.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/aquantia/aquantia_main.c | 21 +++++++++++++++++++++
+ 1 file changed, 21 insertions(+)
+
+--- a/drivers/net/phy/aquantia/aquantia_main.c
++++ b/drivers/net/phy/aquantia/aquantia_main.c
+@@ -24,6 +24,7 @@
+ #define PHY_ID_AQR405 0x03a1b4b0
+ #define PHY_ID_AQR111 0x03a1b610
+ #define PHY_ID_AQR111B0 0x03a1b612
++#define PHY_ID_AQR113 0x31c31c40
+ #define PHY_ID_AQR113C 0x31c31c12
+
+ #define MDIO_PHYXS_VEND_IF_STATUS 0xe812
+@@ -802,6 +803,25 @@ static struct phy_driver aqr_driver[] =
+ .read_status = aqr_read_status,
+ },
+ {
++ PHY_ID_MATCH_MODEL(PHY_ID_AQR113),
++ .name = "Aquantia AQR113",
++ .probe = aqr107_probe,
++ .get_rate_matching = aqr107_get_rate_matching,
++ .config_init = aqr107_config_init,
++ .config_aneg = aqr_config_aneg,
++ .config_intr = aqr_config_intr,
++ .handle_interrupt = aqr_handle_interrupt,
++ .read_status = aqr107_read_status,
++ .get_tunable = aqr107_get_tunable,
++ .set_tunable = aqr107_set_tunable,
++ .suspend = aqr107_suspend,
++ .resume = aqr107_resume,
++ .get_sset_count = aqr107_get_sset_count,
++ .get_strings = aqr107_get_strings,
++ .get_stats = aqr107_get_stats,
++ .link_change_notify = aqr107_link_change_notify,
++},
++{
+ PHY_ID_MATCH_MODEL(PHY_ID_AQR113C),
+ .name = "Aquantia AQR113C",
+ .probe = aqr107_probe,
+@@ -834,6 +854,7 @@ static struct mdio_device_id __maybe_unu
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR405) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR111) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR111B0) },
++ { PHY_ID_MATCH_MODEL(PHY_ID_AQR113) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR113C) },
+ { }
+ };
diff --git a/target/linux/generic/backport-6.6/762-v6.9-net-phy-aquantia-add-AQR813-PHY-ID.patch b/target/linux/generic/backport-6.6/762-v6.9-net-phy-aquantia-add-AQR813-PHY-ID.patch
new file mode 100644
index 0000000000..965b8c91b2
--- /dev/null
+++ b/target/linux/generic/backport-6.6/762-v6.9-net-phy-aquantia-add-AQR813-PHY-ID.patch
@@ -0,0 +1,59 @@
+From 6d47302a3f0ba31445478d518d98bd55918bc8ab Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 15 Feb 2024 22:43:30 +0100
+Subject: [PATCH] net: phy: aquantia: add AQR813 PHY ID
+
+Aquantia AQR813 is the Octal Port variant of the AQR113. Add PHY ID for
+it to provide support for it.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/aquantia/aquantia_main.c | 21 +++++++++++++++++++++
+ 1 file changed, 21 insertions(+)
+
+--- a/drivers/net/phy/aquantia/aquantia_main.c
++++ b/drivers/net/phy/aquantia/aquantia_main.c
+@@ -26,6 +26,7 @@
+ #define PHY_ID_AQR111B0 0x03a1b612
+ #define PHY_ID_AQR113 0x31c31c40
+ #define PHY_ID_AQR113C 0x31c31c12
++#define PHY_ID_AQR813 0x31c31cb2
+
+ #define MDIO_PHYXS_VEND_IF_STATUS 0xe812
+ #define MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK GENMASK(7, 3)
+@@ -840,6 +841,25 @@ static struct phy_driver aqr_driver[] =
+ .get_stats = aqr107_get_stats,
+ .link_change_notify = aqr107_link_change_notify,
+ },
++{
++ PHY_ID_MATCH_MODEL(PHY_ID_AQR813),
++ .name = "Aquantia AQR813",
++ .probe = aqr107_probe,
++ .get_rate_matching = aqr107_get_rate_matching,
++ .config_init = aqr107_config_init,
++ .config_aneg = aqr_config_aneg,
++ .config_intr = aqr_config_intr,
++ .handle_interrupt = aqr_handle_interrupt,
++ .read_status = aqr107_read_status,
++ .get_tunable = aqr107_get_tunable,
++ .set_tunable = aqr107_set_tunable,
++ .suspend = aqr107_suspend,
++ .resume = aqr107_resume,
++ .get_sset_count = aqr107_get_sset_count,
++ .get_strings = aqr107_get_strings,
++ .get_stats = aqr107_get_stats,
++ .link_change_notify = aqr107_link_change_notify,
++},
+ };
+
+ module_phy_driver(aqr_driver);
+@@ -856,6 +876,7 @@ static struct mdio_device_id __maybe_unu
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR111B0) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR113) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR113C) },
++ { PHY_ID_MATCH_MODEL(PHY_ID_AQR813) },
+ { }
+ };
+
diff --git a/target/linux/generic/backport-6.6/770-net-introduce-napi_is_scheduled-helper.patch b/target/linux/generic/backport-6.6/770-net-introduce-napi_is_scheduled-helper.patch
new file mode 100644
index 0000000000..789b93e9f9
--- /dev/null
+++ b/target/linux/generic/backport-6.6/770-net-introduce-napi_is_scheduled-helper.patch
@@ -0,0 +1,96 @@
+From 7f3eb2174512fe6c9c0f062e96eccb0d3cc6d5cd Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 18 Oct 2023 14:35:47 +0200
+Subject: [PATCH] net: introduce napi_is_scheduled helper
+
+We currently have napi_if_scheduled_mark_missed that can be used to
+check if napi is scheduled but that does more thing than simply checking
+it and return a bool. Some driver already implement custom function to
+check if napi is scheduled.
+
+Drop these custom function and introduce napi_is_scheduled that simply
+check if napi is scheduled atomically.
+
+Update any driver and code that implement a similar check and instead
+use this new helper.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/chelsio/cxgb3/sge.c | 8 --------
+ drivers/net/wireless/realtek/rtw89/core.c | 2 +-
+ include/linux/netdevice.h | 23 +++++++++++++++++++++++
+ net/core/dev.c | 2 +-
+ 4 files changed, 25 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/ethernet/chelsio/cxgb3/sge.c
++++ b/drivers/net/ethernet/chelsio/cxgb3/sge.c
+@@ -2507,14 +2507,6 @@ static int napi_rx_handler(struct napi_s
+ return work_done;
+ }
+
+-/*
+- * Returns true if the device is already scheduled for polling.
+- */
+-static inline int napi_is_scheduled(struct napi_struct *napi)
+-{
+- return test_bit(NAPI_STATE_SCHED, &napi->state);
+-}
+-
+ /**
+ * process_pure_responses - process pure responses from a response queue
+ * @adap: the adapter
+--- a/drivers/net/wireless/realtek/rtw89/core.c
++++ b/drivers/net/wireless/realtek/rtw89/core.c
+@@ -1479,7 +1479,7 @@ static void rtw89_core_rx_to_mac80211(st
+ struct napi_struct *napi = &rtwdev->napi;
+
+ /* In low power mode, napi isn't scheduled. Receive it to netif. */
+- if (unlikely(!test_bit(NAPI_STATE_SCHED, &napi->state)))
++ if (unlikely(!napi_is_scheduled(napi)))
+ napi = NULL;
+
+ rtw89_core_hw_to_sband_rate(rx_status);
+--- a/include/linux/netdevice.h
++++ b/include/linux/netdevice.h
+@@ -468,6 +468,29 @@ static inline bool napi_prefer_busy_poll
+ return test_bit(NAPI_STATE_PREFER_BUSY_POLL, &n->state);
+ }
+
++/**
++ * napi_is_scheduled - test if NAPI is scheduled
++ * @n: NAPI context
++ *
++ * This check is "best-effort". With no locking implemented,
++ * a NAPI can be scheduled or terminate right after this check
++ * and produce not precise results.
++ *
++ * NAPI_STATE_SCHED is an internal state, napi_is_scheduled
++ * should not be used normally and napi_schedule should be
++ * used instead.
++ *
++ * Use only if the driver really needs to check if a NAPI
++ * is scheduled for example in the context of delayed timer
++ * that can be skipped if a NAPI is already scheduled.
++ *
++ * Return True if NAPI is scheduled, False otherwise.
++ */
++static inline bool napi_is_scheduled(struct napi_struct *n)
++{
++ return test_bit(NAPI_STATE_SCHED, &n->state);
++}
++
+ bool napi_schedule_prep(struct napi_struct *n);
+
+ /**
+--- a/net/core/dev.c
++++ b/net/core/dev.c
+@@ -6533,7 +6533,7 @@ static int __napi_poll(struct napi_struc
+ * accidentally calling ->poll() when NAPI is not scheduled.
+ */
+ work = 0;
+- if (test_bit(NAPI_STATE_SCHED, &n->state)) {
++ if (napi_is_scheduled(n)) {
+ work = n->poll(n, weight);
+ trace_napi_poll(n, work, weight);
+ }
diff --git a/target/linux/generic/backport-6.6/771-v6.7-01-net-stmmac-improve-TX-timer-arm-logic.patch b/target/linux/generic/backport-6.6/771-v6.7-01-net-stmmac-improve-TX-timer-arm-logic.patch
new file mode 100644
index 0000000000..aa0d730bc8
--- /dev/null
+++ b/target/linux/generic/backport-6.6/771-v6.7-01-net-stmmac-improve-TX-timer-arm-logic.patch
@@ -0,0 +1,77 @@
+From 2d1a42cf7f77cda54dbbee18d00b1200e7bc22aa Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 18 Oct 2023 14:35:48 +0200
+Subject: [PATCH 1/3] net: stmmac: improve TX timer arm logic
+
+There is currently a problem with the TX timer getting armed multiple
+unnecessary times causing big performance regression on some device that
+suffer from heavy handling of hrtimer rearm.
+
+The use of the TX timer is an old implementation that predates the napi
+implementation and the interrupt enable/disable handling.
+
+Due to stmmac being a very old code, the TX timer was never evaluated
+again with this new implementation and was kept there causing
+performance regression. The performance regression started to appear
+with kernel version 4.19 with 8fce33317023 ("net: stmmac: Rework coalesce
+timer and fix multi-queue races") where the timer was reduced to 1ms
+causing it to be armed 40 times more than before.
+
+Decreasing the timer made the problem more present and caused the
+regression in the other of 600-700mbps on some device (regression where
+this was notice is ipq806x).
+
+The problem is in the fact that handling the hrtimer on some target is
+expensive and recent kernel made the timer armed much more times.
+A solution that was proposed was reverting the hrtimer change and use
+mod_timer but such solution would still hide the real problem in the
+current implementation.
+
+To fix the regression, apply some additional logic and skip arming the
+timer when not needed.
+
+Arm the timer ONLY if a napi is not already scheduled. Running the timer
+is redundant since the same function (stmmac_tx_clean) will run in the
+napi TX poll. Also try to cancel any timer if a napi is scheduled to
+prevent redundant run of TX call.
+
+With the following new logic the original performance are restored while
+keeping using the hrtimer.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ .../net/ethernet/stmicro/stmmac/stmmac_main.c | 18 +++++++++++++++---
+ 1 file changed, 15 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+@@ -2974,13 +2974,25 @@ static void stmmac_tx_timer_arm(struct s
+ {
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ u32 tx_coal_timer = priv->tx_coal_timer[queue];
++ struct stmmac_channel *ch;
++ struct napi_struct *napi;
+
+ if (!tx_coal_timer)
+ return;
+
+- hrtimer_start(&tx_q->txtimer,
+- STMMAC_COAL_TIMER(tx_coal_timer),
+- HRTIMER_MODE_REL);
++ ch = &priv->channel[tx_q->queue_index];
++ napi = tx_q->xsk_pool ? &ch->rxtx_napi : &ch->tx_napi;
++
++ /* Arm timer only if napi is not already scheduled.
++ * Try to cancel any timer if napi is scheduled, timer will be armed
++ * again in the next scheduled napi.
++ */
++ if (unlikely(!napi_is_scheduled(napi)))
++ hrtimer_start(&tx_q->txtimer,
++ STMMAC_COAL_TIMER(tx_coal_timer),
++ HRTIMER_MODE_REL);
++ else
++ hrtimer_try_to_cancel(&tx_q->txtimer);
+ }
+
+ /**
diff --git a/target/linux/generic/backport-6.6/771-v6.7-02-net-stmmac-move-TX-timer-arm-after-DMA-enable.patch b/target/linux/generic/backport-6.6/771-v6.7-02-net-stmmac-move-TX-timer-arm-after-DMA-enable.patch
new file mode 100644
index 0000000000..95dd24881b
--- /dev/null
+++ b/target/linux/generic/backport-6.6/771-v6.7-02-net-stmmac-move-TX-timer-arm-after-DMA-enable.patch
@@ -0,0 +1,96 @@
+From a594166387fe08e6f5a32130c400249a35b298f9 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 18 Oct 2023 14:35:49 +0200
+Subject: [PATCH 2/3] net: stmmac: move TX timer arm after DMA enable
+
+Move TX timer arm call after DMA interrupt is enabled again.
+
+The TX timer arm function changed logic and now is skipped if a napi is
+already scheduled. By moving the TX timer arm call after DMA is enabled,
+we permit to correctly skip if a DMA interrupt has been fired and a napi
+has been scheduled again.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ .../net/ethernet/stmicro/stmmac/stmmac_main.c | 22 +++++++++++++++----
+ 1 file changed, 18 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+@@ -2527,9 +2527,13 @@ static void stmmac_bump_dma_threshold(st
+ * @priv: driver private structure
+ * @budget: napi budget limiting this functions packet handling
+ * @queue: TX queue index
++ * @pending_packets: signal to arm the TX coal timer
+ * Description: it reclaims the transmit resources after transmission completes.
++ * If some packets still needs to be handled, due to TX coalesce, set
++ * pending_packets to true to make NAPI arm the TX coal timer.
+ */
+-static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue)
++static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue,
++ bool *pending_packets)
+ {
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ unsigned int bytes_compl = 0, pkts_compl = 0;
+@@ -2692,7 +2696,7 @@ static int stmmac_tx_clean(struct stmmac
+
+ /* We still have pending packets, let's call for a new scheduling */
+ if (tx_q->dirty_tx != tx_q->cur_tx)
+- stmmac_tx_timer_arm(priv, queue);
++ *pending_packets = true;
+
+ __netif_tx_unlock_bh(netdev_get_tx_queue(priv->dev, queue));
+
+@@ -5488,12 +5492,13 @@ static int stmmac_napi_poll_tx(struct na
+ struct stmmac_channel *ch =
+ container_of(napi, struct stmmac_channel, tx_napi);
+ struct stmmac_priv *priv = ch->priv_data;
++ bool pending_packets = false;
+ u32 chan = ch->index;
+ int work_done;
+
+ priv->xstats.napi_poll++;
+
+- work_done = stmmac_tx_clean(priv, budget, chan);
++ work_done = stmmac_tx_clean(priv, budget, chan, &pending_packets);
+ work_done = min(work_done, budget);
+
+ if (work_done < budget && napi_complete_done(napi, work_done)) {
+@@ -5504,6 +5509,10 @@ static int stmmac_napi_poll_tx(struct na
+ spin_unlock_irqrestore(&ch->lock, flags);
+ }
+
++ /* TX still have packet to handle, check if we need to arm tx timer */
++ if (pending_packets)
++ stmmac_tx_timer_arm(priv, chan);
++
+ return work_done;
+ }
+
+@@ -5512,12 +5521,13 @@ static int stmmac_napi_poll_rxtx(struct
+ struct stmmac_channel *ch =
+ container_of(napi, struct stmmac_channel, rxtx_napi);
+ struct stmmac_priv *priv = ch->priv_data;
++ bool tx_pending_packets = false;
+ int rx_done, tx_done, rxtx_done;
+ u32 chan = ch->index;
+
+ priv->xstats.napi_poll++;
+
+- tx_done = stmmac_tx_clean(priv, budget, chan);
++ tx_done = stmmac_tx_clean(priv, budget, chan, &tx_pending_packets);
+ tx_done = min(tx_done, budget);
+
+ rx_done = stmmac_rx_zc(priv, budget, chan);
+@@ -5542,6 +5552,10 @@ static int stmmac_napi_poll_rxtx(struct
+ spin_unlock_irqrestore(&ch->lock, flags);
+ }
+
++ /* TX still have packet to handle, check if we need to arm tx timer */
++ if (tx_pending_packets)
++ stmmac_tx_timer_arm(priv, chan);
++
+ return min(rxtx_done, budget - 1);
+ }
+
diff --git a/target/linux/generic/backport-6.6/771-v6.7-03-net-stmmac-increase-TX-coalesce-timer-to-5ms.patch b/target/linux/generic/backport-6.6/771-v6.7-03-net-stmmac-increase-TX-coalesce-timer-to-5ms.patch
new file mode 100644
index 0000000000..bce54eba4f
--- /dev/null
+++ b/target/linux/generic/backport-6.6/771-v6.7-03-net-stmmac-increase-TX-coalesce-timer-to-5ms.patch
@@ -0,0 +1,38 @@
+From 039550960a2235cfe2dfaa773df9f98f8da31a0c Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 18 Oct 2023 14:35:50 +0200
+Subject: [PATCH 3/3] net: stmmac: increase TX coalesce timer to 5ms
+
+Commit 8fce33317023 ("net: stmmac: Rework coalesce timer and fix
+multi-queue races") decreased the TX coalesce timer from 40ms to 1ms.
+
+This caused some performance regression on some target (regression was
+reported at least on ipq806x) in the order of 600mbps dropping from
+gigabit handling to only 200mbps.
+
+The problem was identified in the TX timer getting armed too much time.
+While this was fixed and improved in another commit, performance can be
+improved even further by increasing the timer delay a bit moving from
+1ms to 5ms.
+
+The value is a good balance between battery saving by prevending too
+much interrupt to be generated and permitting good performance for
+internet oriented devices.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/stmicro/stmmac/common.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/stmicro/stmmac/common.h
++++ b/drivers/net/ethernet/stmicro/stmmac/common.h
+@@ -287,7 +287,7 @@ struct stmmac_safety_stats {
+ #define MIN_DMA_RIWT 0x10
+ #define DEF_DMA_RIWT 0xa0
+ /* Tx coalesce parameters */
+-#define STMMAC_COAL_TX_TIMER 1000
++#define STMMAC_COAL_TX_TIMER 5000
+ #define STMMAC_MAX_COAL_TX_TICK 100000
+ #define STMMAC_TX_MAX_FRAMES 256
+ #define STMMAC_TX_FRAMES 25
diff --git a/target/linux/generic/backport-6.6/777-v6.2-04-net-dsa-qca8k-introduce-single-mii-read-write-lo-hi.patch b/target/linux/generic/backport-6.6/777-v6.2-04-net-dsa-qca8k-introduce-single-mii-read-write-lo-hi.patch
new file mode 100644
index 0000000000..c0320ad6f9
--- /dev/null
+++ b/target/linux/generic/backport-6.6/777-v6.2-04-net-dsa-qca8k-introduce-single-mii-read-write-lo-hi.patch
@@ -0,0 +1,150 @@
+From cfbd6de588ef659c198083205dc954a6d3ed2aec Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 29 Dec 2022 17:33:35 +0100
+Subject: [PATCH 4/5] net: dsa: qca8k: introduce single mii read/write lo/hi
+
+It may be useful to read/write just the lo or hi half of a reg.
+
+This is especially useful for phy poll with the use of mdio master.
+The mdio master reg is composed by the first 16 bit related to setup and
+the other half with the returned data or data to write.
+
+Refactor the mii function to permit single mii read/write of lo or hi
+half of the reg.
+
+Tested-by: Ronald Wahl <ronald.wahl@raritan.com>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/qca8k-8xxx.c | 106 ++++++++++++++++++++++++-------
+ 1 file changed, 84 insertions(+), 22 deletions(-)
+
+--- a/drivers/net/dsa/qca/qca8k-8xxx.c
++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
+@@ -37,42 +37,104 @@ qca8k_split_addr(u32 regaddr, u16 *r1, u
+ }
+
+ static int
+-qca8k_mii_read32(struct mii_bus *bus, int phy_id, u32 regnum, u32 *val)
++qca8k_mii_write_lo(struct mii_bus *bus, int phy_id, u32 regnum, u32 val)
+ {
+ int ret;
++ u16 lo;
+
+- ret = bus->read(bus, phy_id, regnum);
+- if (ret >= 0) {
+- *val = ret;
+- ret = bus->read(bus, phy_id, regnum + 1);
+- *val |= ret << 16;
+- }
++ lo = val & 0xffff;
++ ret = bus->write(bus, phy_id, regnum, lo);
++ if (ret < 0)
++ dev_err_ratelimited(&bus->dev,
++ "failed to write qca8k 32bit lo register\n");
++
++ return ret;
++}
+
+- if (ret < 0) {
++static int
++qca8k_mii_write_hi(struct mii_bus *bus, int phy_id, u32 regnum, u32 val)
++{
++ int ret;
++ u16 hi;
++
++ hi = (u16)(val >> 16);
++ ret = bus->write(bus, phy_id, regnum, hi);
++ if (ret < 0)
+ dev_err_ratelimited(&bus->dev,
+- "failed to read qca8k 32bit register\n");
+- *val = 0;
+- return ret;
+- }
++ "failed to write qca8k 32bit hi register\n");
+
++ return ret;
++}
++
++static int
++qca8k_mii_read_lo(struct mii_bus *bus, int phy_id, u32 regnum, u32 *val)
++{
++ int ret;
++
++ ret = bus->read(bus, phy_id, regnum);
++ if (ret < 0)
++ goto err;
++
++ *val = ret & 0xffff;
+ return 0;
++
++err:
++ dev_err_ratelimited(&bus->dev,
++ "failed to read qca8k 32bit lo register\n");
++ *val = 0;
++
++ return ret;
+ }
+
+-static void
+-qca8k_mii_write32(struct mii_bus *bus, int phy_id, u32 regnum, u32 val)
++static int
++qca8k_mii_read_hi(struct mii_bus *bus, int phy_id, u32 regnum, u32 *val)
+ {
+- u16 lo, hi;
+ int ret;
+
+- lo = val & 0xffff;
+- hi = (u16)(val >> 16);
++ ret = bus->read(bus, phy_id, regnum);
++ if (ret < 0)
++ goto err;
+
+- ret = bus->write(bus, phy_id, regnum, lo);
+- if (ret >= 0)
+- ret = bus->write(bus, phy_id, regnum + 1, hi);
++ *val = ret << 16;
++ return 0;
++
++err:
++ dev_err_ratelimited(&bus->dev,
++ "failed to read qca8k 32bit hi register\n");
++ *val = 0;
++
++ return ret;
++}
++
++static int
++qca8k_mii_read32(struct mii_bus *bus, int phy_id, u32 regnum, u32 *val)
++{
++ u32 hi, lo;
++ int ret;
++
++ *val = 0;
++
++ ret = qca8k_mii_read_lo(bus, phy_id, regnum, &lo);
+ if (ret < 0)
+- dev_err_ratelimited(&bus->dev,
+- "failed to write qca8k 32bit register\n");
++ goto err;
++
++ ret = qca8k_mii_read_hi(bus, phy_id, regnum + 1, &hi);
++ if (ret < 0)
++ goto err;
++
++ *val = lo | hi;
++
++err:
++ return ret;
++}
++
++static void
++qca8k_mii_write32(struct mii_bus *bus, int phy_id, u32 regnum, u32 val)
++{
++ if (qca8k_mii_write_lo(bus, phy_id, regnum, val) < 0)
++ return;
++
++ qca8k_mii_write_hi(bus, phy_id, regnum + 1, val);
+ }
+
+ static int
diff --git a/target/linux/generic/backport-6.6/777-v6.2-05-net-dsa-qca8k-improve-mdio-master-read-write-by-usin.patch b/target/linux/generic/backport-6.6/777-v6.2-05-net-dsa-qca8k-improve-mdio-master-read-write-by-usin.patch
new file mode 100644
index 0000000000..dcafad0fd5
--- /dev/null
+++ b/target/linux/generic/backport-6.6/777-v6.2-05-net-dsa-qca8k-improve-mdio-master-read-write-by-usin.patch
@@ -0,0 +1,73 @@
+From a4165830ca237f2b3318faf62562bce8ce12a389 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 29 Dec 2022 17:33:36 +0100
+Subject: [PATCH 5/5] net: dsa: qca8k: improve mdio master read/write by using
+ single lo/hi
+
+Improve mdio master read/write by using singe mii read/write lo/hi.
+
+In a read and write we need to poll the mdio master regs in a busy loop
+to check for a specific bit present in the upper half of the reg. We can
+ignore the other half since it won't contain useful data. This will save
+an additional useless read for each read and write operation.
+
+In a read operation the returned data is present in the mdio master reg
+lower half. We can ignore the other half since it won't contain useful
+data. This will save an additional useless read for each read operation.
+
+In a read operation it's needed to just set the hi half of the mdio
+master reg as the lo half will be replaced by the result. This will save
+an additional useless write for each read operation.
+
+Tested-by: Ronald Wahl <ronald.wahl@raritan.com>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/qca8k-8xxx.c | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/dsa/qca/qca8k-8xxx.c
++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
+@@ -754,9 +754,9 @@ qca8k_mdio_busy_wait(struct mii_bus *bus
+
+ qca8k_split_addr(reg, &r1, &r2, &page);
+
+- ret = read_poll_timeout(qca8k_mii_read32, ret1, !(val & mask), 0,
++ ret = read_poll_timeout(qca8k_mii_read_hi, ret1, !(val & mask), 0,
+ QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
+- bus, 0x10 | r2, r1, &val);
++ bus, 0x10 | r2, r1 + 1, &val);
+
+ /* Check if qca8k_read has failed for a different reason
+ * before returnting -ETIMEDOUT
+@@ -798,7 +798,7 @@ qca8k_mdio_write(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_write_hi(bus, 0x10 | r2, r1 + 1, 0);
+
+ mutex_unlock(&bus->mdio_lock);
+
+@@ -828,18 +828,18 @@ qca8k_mdio_read(struct qca8k_priv *priv,
+ if (ret)
+ goto exit;
+
+- qca8k_mii_write32(bus, 0x10 | r2, r1, val);
++ qca8k_mii_write_hi(bus, 0x10 | r2, r1 + 1, val);
+
+ ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
+ QCA8K_MDIO_MASTER_BUSY);
+ if (ret)
+ goto exit;
+
+- ret = qca8k_mii_read32(bus, 0x10 | r2, r1, &val);
++ ret = qca8k_mii_read_lo(bus, 0x10 | r2, r1, &val);
+
+ exit:
+ /* even if the busy_wait timeouts try to clear the MASTER_EN */
+- qca8k_mii_write32(bus, 0x10 | r2, r1, 0);
++ qca8k_mii_write_hi(bus, 0x10 | r2, r1 + 1, 0);
+
+ mutex_unlock(&bus->mdio_lock);
+
diff --git a/target/linux/generic/backport-6.6/778-v6.3-01-net-dsa-qca8k-add-QCA8K_ATU_TABLE_SIZE-define-for-fd.patch b/target/linux/generic/backport-6.6/778-v6.3-01-net-dsa-qca8k-add-QCA8K_ATU_TABLE_SIZE-define-for-fd.patch
new file mode 100644
index 0000000000..9331fd536d
--- /dev/null
+++ b/target/linux/generic/backport-6.6/778-v6.3-01-net-dsa-qca8k-add-QCA8K_ATU_TABLE_SIZE-define-for-fd.patch
@@ -0,0 +1,63 @@
+From e03cea60c3db8c6b011cc36ecef9281dff8377f3 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 25 Jan 2023 21:35:16 +0100
+Subject: [PATCH] net: dsa: qca8k: add QCA8K_ATU_TABLE_SIZE define for fdb
+ access
+
+Add and use QCA8K_ATU_TABLE_SIZE instead of hardcoding the ATU size with
+a pure number and using sizeof on the array.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/qca8k-common.c | 10 ++++++----
+ drivers/net/dsa/qca/qca8k.h | 2 ++
+ 2 files changed, 8 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/dsa/qca/qca8k-common.c
++++ b/drivers/net/dsa/qca/qca8k-common.c
+@@ -150,11 +150,12 @@ static int qca8k_busy_wait(struct qca8k_
+
+ static int qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb)
+ {
+- u32 reg[3];
++ u32 reg[QCA8K_ATU_TABLE_SIZE];
+ int ret;
+
+ /* load the ARL table into an array */
+- ret = qca8k_bulk_read(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
++ ret = qca8k_bulk_read(priv, QCA8K_REG_ATU_DATA0, reg,
++ QCA8K_ATU_TABLE_SIZE * sizeof(u32));
+ if (ret)
+ return ret;
+
+@@ -178,7 +179,7 @@ static int qca8k_fdb_read(struct qca8k_p
+ static void qca8k_fdb_write(struct qca8k_priv *priv, u16 vid, u8 port_mask,
+ const u8 *mac, u8 aging)
+ {
+- u32 reg[3] = { 0 };
++ u32 reg[QCA8K_ATU_TABLE_SIZE] = { 0 };
+
+ /* vid - 83:72 */
+ reg[2] = FIELD_PREP(QCA8K_ATU_VID_MASK, vid);
+@@ -195,7 +196,8 @@ static void qca8k_fdb_write(struct qca8k
+ reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR5_MASK, mac[5]);
+
+ /* load the array into the ARL table */
+- qca8k_bulk_write(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
++ qca8k_bulk_write(priv, QCA8K_REG_ATU_DATA0, reg,
++ QCA8K_ATU_TABLE_SIZE * sizeof(u32));
+ }
+
+ static int qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd,
+--- a/drivers/net/dsa/qca/qca8k.h
++++ b/drivers/net/dsa/qca/qca8k.h
+@@ -148,6 +148,8 @@
+ #define QCA8K_REG_IPV4_PRI_ADDR_MASK 0x474
+
+ /* Lookup registers */
++#define QCA8K_ATU_TABLE_SIZE 3 /* 12 bytes wide table / sizeof(u32) */
++
+ #define QCA8K_REG_ATU_DATA0 0x600
+ #define QCA8K_ATU_ADDR2_MASK GENMASK(31, 24)
+ #define QCA8K_ATU_ADDR3_MASK GENMASK(23, 16)
diff --git a/target/linux/generic/backport-6.6/778-v6.3-02-net-dsa-qca8k-convert-to-regmap-read-write-API.patch b/target/linux/generic/backport-6.6/778-v6.3-02-net-dsa-qca8k-convert-to-regmap-read-write-API.patch
new file mode 100644
index 0000000000..b8f8071b0a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/778-v6.3-02-net-dsa-qca8k-convert-to-regmap-read-write-API.patch
@@ -0,0 +1,261 @@
+From c766e077d927e1775902c18827205ea2ade3a35d Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 25 Jan 2023 21:35:17 +0100
+Subject: [PATCH] net: dsa: qca8k: convert to regmap read/write API
+
+Convert qca8k to regmap read/write bulk API. The mgmt eth can write up
+to 32 bytes of data at times. Currently we use a custom function to do
+it but regmap now supports declaration of read/write bulk even without a
+bus.
+
+Drop the custom function and rework the regmap function to this new
+implementation.
+
+Rework the qca8k_fdb_read/write function to use the new
+regmap_bulk_read/write as the old qca8k_bulk_read/write are now dropped.
+
+Cc: Mark Brown <broonie@kernel.org>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/qca8k-8xxx.c | 92 ++++++++++++++++++++++++------
+ drivers/net/dsa/qca/qca8k-common.c | 47 ++-------------
+ drivers/net/dsa/qca/qca8k.h | 3 -
+ 3 files changed, 77 insertions(+), 65 deletions(-)
+
+--- a/drivers/net/dsa/qca/qca8k-8xxx.c
++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
+@@ -425,16 +425,12 @@ qca8k_regmap_update_bits_eth(struct qca8
+ }
+
+ static int
+-qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
++qca8k_read_mii(struct qca8k_priv *priv, uint32_t reg, uint32_t *val)
+ {
+- struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
+ struct mii_bus *bus = priv->bus;
+ u16 r1, r2, page;
+ int ret;
+
+- if (!qca8k_read_eth(priv, reg, val, sizeof(*val)))
+- return 0;
+-
+ qca8k_split_addr(reg, &r1, &r2, &page);
+
+ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+@@ -451,16 +447,12 @@ exit:
+ }
+
+ static int
+-qca8k_regmap_write(void *ctx, uint32_t reg, uint32_t val)
++qca8k_write_mii(struct qca8k_priv *priv, uint32_t reg, uint32_t val)
+ {
+- struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
+ struct mii_bus *bus = priv->bus;
+ u16 r1, r2, page;
+ int ret;
+
+- if (!qca8k_write_eth(priv, reg, &val, sizeof(val)))
+- return 0;
+-
+ qca8k_split_addr(reg, &r1, &r2, &page);
+
+ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+@@ -477,17 +469,14 @@ exit:
+ }
+
+ static int
+-qca8k_regmap_update_bits(void *ctx, uint32_t reg, uint32_t mask, uint32_t write_val)
++qca8k_regmap_update_bits_mii(struct qca8k_priv *priv, uint32_t reg,
++ uint32_t mask, uint32_t write_val)
+ {
+- struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
+ struct mii_bus *bus = priv->bus;
+ u16 r1, r2, page;
+ u32 val;
+ int ret;
+
+- if (!qca8k_regmap_update_bits_eth(priv, reg, mask, write_val))
+- return 0;
+-
+ qca8k_split_addr(reg, &r1, &r2, &page);
+
+ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+@@ -510,17 +499,84 @@ exit:
+ return ret;
+ }
+
++static int
++qca8k_bulk_read(void *ctx, const void *reg_buf, size_t reg_len,
++ void *val_buf, size_t val_len)
++{
++ int i, count = val_len / sizeof(u32), ret;
++ u32 reg = *(u32 *)reg_buf & U16_MAX;
++ struct qca8k_priv *priv = ctx;
++
++ if (priv->mgmt_master &&
++ !qca8k_read_eth(priv, reg, val_buf, val_len))
++ return 0;
++
++ /* loop count times and increment reg of 4 */
++ for (i = 0; i < count; i++, reg += sizeof(u32)) {
++ ret = qca8k_read_mii(priv, reg, val_buf + i);
++ if (ret < 0)
++ return ret;
++ }
++
++ return 0;
++}
++
++static int
++qca8k_bulk_gather_write(void *ctx, const void *reg_buf, size_t reg_len,
++ const void *val_buf, size_t val_len)
++{
++ int i, count = val_len / sizeof(u32), ret;
++ u32 reg = *(u32 *)reg_buf & U16_MAX;
++ struct qca8k_priv *priv = ctx;
++ u32 *val = (u32 *)val_buf;
++
++ if (priv->mgmt_master &&
++ !qca8k_write_eth(priv, reg, val, val_len))
++ return 0;
++
++ /* loop count times, increment reg of 4 and increment val ptr to
++ * the next value
++ */
++ for (i = 0; i < count; i++, reg += sizeof(u32), val++) {
++ ret = qca8k_write_mii(priv, reg, *val);
++ if (ret < 0)
++ return ret;
++ }
++
++ return 0;
++}
++
++static int
++qca8k_bulk_write(void *ctx, const void *data, size_t bytes)
++{
++ return qca8k_bulk_gather_write(ctx, data, sizeof(u16), data + sizeof(u16),
++ bytes - sizeof(u16));
++}
++
++static int
++qca8k_regmap_update_bits(void *ctx, uint32_t reg, uint32_t mask, uint32_t write_val)
++{
++ struct qca8k_priv *priv = ctx;
++
++ if (!qca8k_regmap_update_bits_eth(priv, reg, mask, write_val))
++ return 0;
++
++ return qca8k_regmap_update_bits_mii(priv, reg, mask, write_val);
++}
++
+ static struct regmap_config qca8k_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x16ac, /* end MIB - Port6 range */
+- .reg_read = qca8k_regmap_read,
+- .reg_write = qca8k_regmap_write,
++ .read = qca8k_bulk_read,
++ .write = qca8k_bulk_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 */
++ .max_raw_read = 32, /* mgmt eth can read/write up to 8 registers at time */
++ .max_raw_write = 32,
+ };
+
+ static int
+@@ -2112,8 +2168,6 @@ static SIMPLE_DEV_PM_OPS(qca8k_pm_ops,
+
+ static const struct qca8k_info_ops qca8xxx_ops = {
+ .autocast_mib = qca8k_get_ethtool_stats_eth,
+- .read_eth = qca8k_read_eth,
+- .write_eth = qca8k_write_eth,
+ };
+
+ static const struct qca8k_match_data qca8327 = {
+--- a/drivers/net/dsa/qca/qca8k-common.c
++++ b/drivers/net/dsa/qca/qca8k-common.c
+@@ -101,45 +101,6 @@ const struct regmap_access_table qca8k_r
+ .n_yes_ranges = ARRAY_SIZE(qca8k_readable_ranges),
+ };
+
+-/* TODO: remove these extra ops when we can support regmap bulk read/write */
+-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 && priv->info->ops->read_eth &&
+- !priv->info->ops->read_eth(priv, reg, val, len))
+- return 0;
+-
+- for (i = 0; i < count; i++) {
+- ret = regmap_read(priv->regmap, reg + (i * 4), val + i);
+- if (ret < 0)
+- return ret;
+- }
+-
+- return 0;
+-}
+-
+-/* TODO: remove these extra ops when we can support regmap bulk read/write */
+-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 && priv->info->ops->write_eth &&
+- !priv->info->ops->write_eth(priv, reg, val, len))
+- return 0;
+-
+- for (i = 0; i < count; i++) {
+- tmp = val[i];
+-
+- ret = regmap_write(priv->regmap, reg + (i * 4), tmp);
+- if (ret < 0)
+- return ret;
+- }
+-
+- return 0;
+-}
+-
+ static int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask)
+ {
+ u32 val;
+@@ -154,8 +115,8 @@ static int qca8k_fdb_read(struct qca8k_p
+ int ret;
+
+ /* load the ARL table into an array */
+- ret = qca8k_bulk_read(priv, QCA8K_REG_ATU_DATA0, reg,
+- QCA8K_ATU_TABLE_SIZE * sizeof(u32));
++ ret = regmap_bulk_read(priv->regmap, QCA8K_REG_ATU_DATA0, reg,
++ QCA8K_ATU_TABLE_SIZE);
+ if (ret)
+ return ret;
+
+@@ -196,8 +157,8 @@ static void qca8k_fdb_write(struct qca8k
+ reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR5_MASK, mac[5]);
+
+ /* load the array into the ARL table */
+- qca8k_bulk_write(priv, QCA8K_REG_ATU_DATA0, reg,
+- QCA8K_ATU_TABLE_SIZE * sizeof(u32));
++ regmap_bulk_write(priv->regmap, QCA8K_REG_ATU_DATA0, reg,
++ QCA8K_ATU_TABLE_SIZE);
+ }
+
+ static int qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd,
+--- a/drivers/net/dsa/qca/qca8k.h
++++ b/drivers/net/dsa/qca/qca8k.h
+@@ -330,9 +330,6 @@ struct qca8k_priv;
+
+ struct qca8k_info_ops {
+ int (*autocast_mib)(struct dsa_switch *ds, int port, u64 *data);
+- /* TODO: remove these extra ops when we can support regmap bulk read/write */
+- int (*read_eth)(struct qca8k_priv *priv, u32 reg, u32 *val, int len);
+- int (*write_eth)(struct qca8k_priv *priv, u32 reg, u32 *val, int len);
+ };
+
+ struct qca8k_match_data {
diff --git a/target/linux/generic/backport-6.6/779-v6.5-net-dsa-qca8k-enable-use_single_write-for-qca8xxx.patch b/target/linux/generic/backport-6.6/779-v6.5-net-dsa-qca8k-enable-use_single_write-for-qca8xxx.patch
new file mode 100644
index 0000000000..3619440f07
--- /dev/null
+++ b/target/linux/generic/backport-6.6/779-v6.5-net-dsa-qca8k-enable-use_single_write-for-qca8xxx.patch
@@ -0,0 +1,78 @@
+From 2c39dd025da489cf87d26469d9f5ff19715324a0 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 24 Jul 2023 05:25:28 +0200
+Subject: [PATCH 1/4] net: dsa: qca8k: enable use_single_write for qca8xxx
+
+The qca8xxx switch supports 2 way to write reg values, a slow way using
+mdio and a fast way by sending specially crafted mgmt packet to
+read/write reg.
+
+The fast way can support up to 32 bytes of data as eth packet are used
+to send/receive.
+
+This correctly works for almost the entire regmap of the switch but with
+the use of some kernel selftests for dsa drivers it was found a funny
+and interesting hw defect/limitation.
+
+For some specific reg, bulk write won't work and will result in writing
+only part of the requested regs resulting in half data written. This was
+especially hard to track and discover due to the total strangeness of
+the problem and also by the specific regs where this occurs.
+
+This occurs in the specific regs of the ATU table, where multiple entry
+needs to be written to compose the entire entry.
+It was discovered that with a bulk write of 12 bytes on
+QCA8K_REG_ATU_DATA0 only QCA8K_REG_ATU_DATA0 and QCA8K_REG_ATU_DATA2
+were written, but QCA8K_REG_ATU_DATA1 was always zero.
+Tcpdump was used to make sure the specially crafted packet was correct
+and this was confirmed.
+
+The problem was hard to track as the lack of QCA8K_REG_ATU_DATA1
+resulted in an entry somehow possible as the first bytes of the mac
+address are set in QCA8K_REG_ATU_DATA0 and the entry type is set in
+QCA8K_REG_ATU_DATA2.
+
+Funlly enough writing QCA8K_REG_ATU_DATA1 results in the same problem
+with QCA8K_REG_ATU_DATA2 empty and QCA8K_REG_ATU_DATA1 and
+QCA8K_REG_ATU_FUNC correctly written.
+A speculation on the problem might be that there are some kind of
+indirection internally when accessing these regs and they can't be
+accessed all together, due to the fact that it's really a table mapped
+somewhere in the switch SRAM.
+
+Even more funny is the fact that every other reg was tested with all
+kind of combination and they are not affected by this problem. Read
+operation was also tested and always worked so it's not affected by this
+problem.
+
+The problem is not present if we limit writing a single reg at times.
+
+To handle this hardware defect, enable use_single_write so that bulk
+api can correctly split the write in multiple different operation
+effectively reverting to a non-bulk write.
+
+Cc: Mark Brown <broonie@kernel.org>
+Fixes: c766e077d927 ("net: dsa: qca8k: convert to regmap read/write API")
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Cc: stable@vger.kernel.org
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/qca8k-8xxx.c | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/dsa/qca/qca8k-8xxx.c
++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
+@@ -575,8 +575,11 @@ static struct regmap_config qca8k_regmap
+ .rd_table = &qca8k_readable_table,
+ .disable_locking = true, /* Locking is handled by qca8k read/write */
+ .cache_type = REGCACHE_NONE, /* Explicitly disable CACHE */
+- .max_raw_read = 32, /* mgmt eth can read/write up to 8 registers at time */
+- .max_raw_write = 32,
++ .max_raw_read = 32, /* mgmt eth can read up to 8 registers at time */
++ /* ATU regs suffer from a bug where some data are not correctly
++ * written. Disable bulk write to correctly write ATU entry.
++ */
++ .use_single_write = true,
+ };
+
+ static int
diff --git a/target/linux/generic/backport-6.6/780-v6.6-01-net-dsa-qca8k-make-learning-configurable-and-keep-of.patch b/target/linux/generic/backport-6.6/780-v6.6-01-net-dsa-qca8k-make-learning-configurable-and-keep-of.patch
new file mode 100644
index 0000000000..d31789370e
--- /dev/null
+++ b/target/linux/generic/backport-6.6/780-v6.6-01-net-dsa-qca8k-make-learning-configurable-and-keep-of.patch
@@ -0,0 +1,146 @@
+From 23cfc7172e5297d0bee49ac6f6f8248d1cf0820d Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Sun, 30 Jul 2023 09:41:10 +0200
+Subject: [PATCH 1/4] net: dsa: qca8k: make learning configurable and keep off
+ if standalone
+
+Address learning should initially be turned off by the driver for port
+operation in standalone mode, then the DSA core handles changes to it
+via ds->ops->port_bridge_flags().
+
+Currently this is not the case for qca8k where learning is enabled
+unconditionally in qca8k_setup for every user port.
+
+Handle ports configured in standalone mode by making the learning
+configurable and not enabling it by default.
+
+Implement .port_pre_bridge_flags and .port_bridge_flags dsa ops to
+enable learning for bridge that request it and tweak
+.port_stp_state_set to correctly disable learning when port is
+configured in standalone mode.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://lore.kernel.org/r/20230730074113.21889-2-ansuelsmth@gmail.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/dsa/qca/qca8k-8xxx.c | 7 +++--
+ drivers/net/dsa/qca/qca8k-common.c | 48 ++++++++++++++++++++++++++++++
+ drivers/net/dsa/qca/qca8k.h | 6 ++++
+ 3 files changed, 58 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/dsa/qca/qca8k-8xxx.c
++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
+@@ -1905,9 +1905,8 @@ qca8k_setup(struct dsa_switch *ds)
+ if (ret)
+ return ret;
+
+- /* Enable ARP Auto-learning by default */
+- ret = regmap_set_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(i),
+- QCA8K_PORT_LOOKUP_LEARN);
++ ret = regmap_clear_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(i),
++ QCA8K_PORT_LOOKUP_LEARN);
+ if (ret)
+ return ret;
+
+@@ -2013,6 +2012,8 @@ static const struct dsa_switch_ops qca8k
+ .port_change_mtu = qca8k_port_change_mtu,
+ .port_max_mtu = qca8k_port_max_mtu,
+ .port_stp_state_set = qca8k_port_stp_state_set,
++ .port_pre_bridge_flags = qca8k_port_pre_bridge_flags,
++ .port_bridge_flags = qca8k_port_bridge_flags,
+ .port_bridge_join = qca8k_port_bridge_join,
+ .port_bridge_leave = qca8k_port_bridge_leave,
+ .port_fast_age = qca8k_port_fast_age,
+--- a/drivers/net/dsa/qca/qca8k-common.c
++++ b/drivers/net/dsa/qca/qca8k-common.c
+@@ -565,9 +565,26 @@ int qca8k_get_mac_eee(struct dsa_switch
+ return 0;
+ }
+
++static int qca8k_port_configure_learning(struct dsa_switch *ds, int port,
++ bool learning)
++{
++ struct qca8k_priv *priv = ds->priv;
++
++ if (learning)
++ return regmap_set_bits(priv->regmap,
++ QCA8K_PORT_LOOKUP_CTRL(port),
++ QCA8K_PORT_LOOKUP_LEARN);
++ else
++ return regmap_clear_bits(priv->regmap,
++ QCA8K_PORT_LOOKUP_CTRL(port),
++ QCA8K_PORT_LOOKUP_LEARN);
++}
++
+ void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
+ {
++ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct qca8k_priv *priv = ds->priv;
++ bool learning = false;
+ u32 stp_state;
+
+ switch (state) {
+@@ -582,8 +599,11 @@ void qca8k_port_stp_state_set(struct dsa
+ break;
+ case BR_STATE_LEARNING:
+ stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING;
++ learning = dp->learning;
+ break;
+ case BR_STATE_FORWARDING:
++ learning = dp->learning;
++ fallthrough;
+ default:
+ stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD;
+ break;
+@@ -591,6 +611,34 @@ void qca8k_port_stp_state_set(struct dsa
+
+ qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+ QCA8K_PORT_LOOKUP_STATE_MASK, stp_state);
++
++ qca8k_port_configure_learning(ds, port, learning);
++}
++
++int qca8k_port_pre_bridge_flags(struct dsa_switch *ds, int port,
++ struct switchdev_brport_flags flags,
++ struct netlink_ext_ack *extack)
++{
++ if (flags.mask & ~BR_LEARNING)
++ return -EINVAL;
++
++ return 0;
++}
++
++int qca8k_port_bridge_flags(struct dsa_switch *ds, int port,
++ struct switchdev_brport_flags flags,
++ struct netlink_ext_ack *extack)
++{
++ int ret;
++
++ if (flags.mask & BR_LEARNING) {
++ ret = qca8k_port_configure_learning(ds, port,
++ flags.val & BR_LEARNING);
++ if (ret)
++ return ret;
++ }
++
++ return 0;
+ }
+
+ int qca8k_port_bridge_join(struct dsa_switch *ds, int port,
+--- a/drivers/net/dsa/qca/qca8k.h
++++ b/drivers/net/dsa/qca/qca8k.h
+@@ -448,6 +448,12 @@ int qca8k_get_mac_eee(struct dsa_switch
+
+ /* Common bridge function */
+ void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state);
++int qca8k_port_pre_bridge_flags(struct dsa_switch *ds, int port,
++ struct switchdev_brport_flags flags,
++ struct netlink_ext_ack *extack);
++int qca8k_port_bridge_flags(struct dsa_switch *ds, int port,
++ struct switchdev_brport_flags flags,
++ struct netlink_ext_ack *extack);
+ int qca8k_port_bridge_join(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge,
+ bool *tx_fwd_offload,
diff --git a/target/linux/generic/backport-6.6/780-v6.6-02-net-dsa-qca8k-limit-user-ports-access-to-the-first-C.patch b/target/linux/generic/backport-6.6/780-v6.6-02-net-dsa-qca8k-limit-user-ports-access-to-the-first-C.patch
new file mode 100644
index 0000000000..4b457f67de
--- /dev/null
+++ b/target/linux/generic/backport-6.6/780-v6.6-02-net-dsa-qca8k-limit-user-ports-access-to-the-first-C.patch
@@ -0,0 +1,53 @@
+From 18e8feae4a807994e4906d659116d249bfecd4c5 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Sun, 30 Jul 2023 09:41:11 +0200
+Subject: [PATCH 2/4] net: dsa: qca8k: limit user ports access to the first CPU
+ port on setup
+
+In preparation for multi-CPU support, set CPU port LOOKUP MEMBER outside
+the port loop and setup the LOOKUP MEMBER mask for user ports only to
+the first CPU port.
+
+This is to handle flooding condition where every CPU port is set as
+target and prevent packet duplication for unknown frames from user ports.
+
+Secondary CPU port LOOKUP MEMBER mask will be setup later when
+port_change_master will be implemented.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Link: https://lore.kernel.org/r/20230730074113.21889-3-ansuelsmth@gmail.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/dsa/qca/qca8k-8xxx.c | 14 ++++++--------
+ 1 file changed, 6 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/dsa/qca/qca8k-8xxx.c
++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
+@@ -1885,18 +1885,16 @@ qca8k_setup(struct dsa_switch *ds)
+ if (ret)
+ return ret;
+
++ /* CPU port gets connected to all user ports of the switch */
++ ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(cpu_port),
++ QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds));
++ if (ret)
++ return ret;
++
+ /* Setup connection between CPU port & user ports
+ * Configure specific switch configuration for ports
+ */
+ for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+- /* CPU port gets connected to all user ports of the switch */
+- if (dsa_is_cpu_port(ds, i)) {
+- ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
+- QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds));
+- if (ret)
+- return ret;
+- }
+-
+ /* Individual user ports get connected to CPU port only */
+ if (dsa_is_user_port(ds, i)) {
+ ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
diff --git a/target/linux/generic/backport-6.6/780-v6.6-03-net-dsa-qca8k-move-qca8xxx-hol-fixup-to-separate-fun.patch b/target/linux/generic/backport-6.6/780-v6.6-03-net-dsa-qca8k-move-qca8xxx-hol-fixup-to-separate-fun.patch
new file mode 100644
index 0000000000..f556628b5b
--- /dev/null
+++ b/target/linux/generic/backport-6.6/780-v6.6-03-net-dsa-qca8k-move-qca8xxx-hol-fixup-to-separate-fun.patch
@@ -0,0 +1,111 @@
+From a9108b0712bf018dc69020864b21485b71b17dfc Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Sun, 30 Jul 2023 09:41:12 +0200
+Subject: [PATCH 3/4] net: dsa: qca8k: move qca8xxx hol fixup to separate
+ function
+
+Move qca8xxx hol fixup to separate function to tidy things up and to
+permit using a more efficent loop in future patch.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://lore.kernel.org/r/20230730074113.21889-4-ansuelsmth@gmail.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/dsa/qca/qca8k-8xxx.c | 78 +++++++++++++++++---------------
+ 1 file changed, 42 insertions(+), 36 deletions(-)
+
+--- a/drivers/net/dsa/qca/qca8k-8xxx.c
++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
+@@ -1795,6 +1795,46 @@ static int qca8k_connect_tag_protocol(st
+ return 0;
+ }
+
++static void qca8k_setup_hol_fixup(struct qca8k_priv *priv, int port)
++{
++ u32 mask;
++
++ switch (port) {
++ /* The 2 CPU port and port 5 requires some different
++ * priority than any other ports.
++ */
++ case 0:
++ case 5:
++ case 6:
++ mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) |
++ QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) |
++ QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x4) |
++ QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x4) |
++ QCA8K_PORT_HOL_CTRL0_EG_PRI4(0x6) |
++ QCA8K_PORT_HOL_CTRL0_EG_PRI5(0x8) |
++ QCA8K_PORT_HOL_CTRL0_EG_PORT(0x1e);
++ break;
++ default:
++ mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) |
++ QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) |
++ QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x6) |
++ QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x8) |
++ QCA8K_PORT_HOL_CTRL0_EG_PORT(0x19);
++ }
++ regmap_write(priv->regmap, QCA8K_REG_PORT_HOL_CTRL0(port), mask);
++
++ mask = QCA8K_PORT_HOL_CTRL1_ING(0x6) |
++ QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
++ QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
++ QCA8K_PORT_HOL_CTRL1_WRED_EN;
++ regmap_update_bits(priv->regmap, QCA8K_REG_PORT_HOL_CTRL1(port),
++ QCA8K_PORT_HOL_CTRL1_ING_BUF_MASK |
++ QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
++ QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
++ QCA8K_PORT_HOL_CTRL1_WRED_EN,
++ mask);
++}
++
+ static int
+ qca8k_setup(struct dsa_switch *ds)
+ {
+@@ -1930,42 +1970,8 @@ qca8k_setup(struct dsa_switch *ds)
+ * missing settings to improve switch stability under load condition.
+ * This problem is limited to qca8337 and other qca8k switch are not affected.
+ */
+- if (priv->switch_id == QCA8K_ID_QCA8337) {
+- switch (i) {
+- /* The 2 CPU port and port 5 requires some different
+- * priority than any other ports.
+- */
+- case 0:
+- case 5:
+- case 6:
+- mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) |
+- QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) |
+- QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x4) |
+- QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x4) |
+- QCA8K_PORT_HOL_CTRL0_EG_PRI4(0x6) |
+- QCA8K_PORT_HOL_CTRL0_EG_PRI5(0x8) |
+- QCA8K_PORT_HOL_CTRL0_EG_PORT(0x1e);
+- break;
+- default:
+- mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) |
+- QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) |
+- QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x6) |
+- QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x8) |
+- QCA8K_PORT_HOL_CTRL0_EG_PORT(0x19);
+- }
+- qca8k_write(priv, QCA8K_REG_PORT_HOL_CTRL0(i), mask);
+-
+- mask = QCA8K_PORT_HOL_CTRL1_ING(0x6) |
+- QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
+- QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
+- QCA8K_PORT_HOL_CTRL1_WRED_EN;
+- qca8k_rmw(priv, QCA8K_REG_PORT_HOL_CTRL1(i),
+- QCA8K_PORT_HOL_CTRL1_ING_BUF_MASK |
+- QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
+- QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
+- QCA8K_PORT_HOL_CTRL1_WRED_EN,
+- mask);
+- }
++ if (priv->switch_id == QCA8K_ID_QCA8337)
++ qca8k_setup_hol_fixup(priv, i);
+ }
+
+ /* Special GLOBAL_FC_THRESH value are needed for ar8327 switch */
diff --git a/target/linux/generic/backport-6.6/780-v6.6-04-net-dsa-qca8k-use-dsa_for_each-macro-instead-of-for-.patch b/target/linux/generic/backport-6.6/780-v6.6-04-net-dsa-qca8k-use-dsa_for_each-macro-instead-of-for-.patch
new file mode 100644
index 0000000000..faa0142ca9
--- /dev/null
+++ b/target/linux/generic/backport-6.6/780-v6.6-04-net-dsa-qca8k-use-dsa_for_each-macro-instead-of-for-.patch
@@ -0,0 +1,158 @@
+From 01e6f8ad8d26ced14b0cf288c42e55d03a7c5070 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Sun, 30 Jul 2023 09:41:13 +0200
+Subject: [PATCH 4/4] net: dsa: qca8k: use dsa_for_each macro instead of for
+ loop
+
+Convert for loop to dsa_for_each macro to save some redundant write on
+unconnected/unused port and tidy things up.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://lore.kernel.org/r/20230730074113.21889-5-ansuelsmth@gmail.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/dsa/qca/qca8k-8xxx.c | 107 ++++++++++++++++---------------
+ 1 file changed, 54 insertions(+), 53 deletions(-)
+
+--- a/drivers/net/dsa/qca/qca8k-8xxx.c
++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
+@@ -1839,7 +1839,8 @@ static int
+ qca8k_setup(struct dsa_switch *ds)
+ {
+ struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+- int cpu_port, ret, i;
++ struct dsa_port *dp;
++ int cpu_port, ret;
+ u32 mask;
+
+ cpu_port = qca8k_find_cpu_port(ds);
+@@ -1890,27 +1891,27 @@ qca8k_setup(struct dsa_switch *ds)
+ dev_warn(priv->dev, "mib init failed");
+
+ /* Initial setup of all ports */
+- for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++ dsa_switch_for_each_port(dp, ds) {
+ /* Disable forwarding by default on all ports */
+- ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
++ ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(dp->index),
+ QCA8K_PORT_LOOKUP_MEMBER, 0);
+ if (ret)
+ return ret;
++ }
+
+- /* Enable QCA header mode on all cpu ports */
+- if (dsa_is_cpu_port(ds, i)) {
+- ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(i),
+- FIELD_PREP(QCA8K_PORT_HDR_CTRL_TX_MASK, QCA8K_PORT_HDR_CTRL_ALL) |
+- FIELD_PREP(QCA8K_PORT_HDR_CTRL_RX_MASK, QCA8K_PORT_HDR_CTRL_ALL));
+- if (ret) {
+- dev_err(priv->dev, "failed enabling QCA header mode");
+- return ret;
+- }
++ /* Disable MAC by default on all user ports */
++ dsa_switch_for_each_user_port(dp, ds)
++ qca8k_port_set_status(priv, dp->index, 0);
++
++ /* Enable QCA header mode on all cpu ports */
++ dsa_switch_for_each_cpu_port(dp, ds) {
++ ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(dp->index),
++ 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 on port %d", dp->index);
++ 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
+@@ -1932,48 +1933,48 @@ qca8k_setup(struct dsa_switch *ds)
+ return ret;
+
+ /* Setup connection between CPU port & user ports
+- * Configure specific switch configuration for ports
++ * Individual user ports get connected to CPU port only
+ */
+- for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+- /* Individual user ports get connected to CPU port only */
+- if (dsa_is_user_port(ds, i)) {
+- ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
+- QCA8K_PORT_LOOKUP_MEMBER,
+- BIT(cpu_port));
+- if (ret)
+- return ret;
+-
+- ret = regmap_clear_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(i),
+- QCA8K_PORT_LOOKUP_LEARN);
+- if (ret)
+- return ret;
+-
+- /* For port based vlans to work we need to set the
+- * default egress vid
+- */
+- ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(i),
+- QCA8K_EGREES_VLAN_PORT_MASK(i),
+- QCA8K_EGREES_VLAN_PORT(i, QCA8K_PORT_VID_DEF));
+- if (ret)
+- return ret;
+-
+- ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(i),
+- QCA8K_PORT_VLAN_CVID(QCA8K_PORT_VID_DEF) |
+- QCA8K_PORT_VLAN_SVID(QCA8K_PORT_VID_DEF));
+- if (ret)
+- return ret;
+- }
++ dsa_switch_for_each_user_port(dp, ds) {
++ u8 port = dp->index;
+
+- /* 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.
++ ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++ QCA8K_PORT_LOOKUP_MEMBER,
++ BIT(cpu_port));
++ if (ret)
++ return ret;
++
++ ret = regmap_clear_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(port),
++ QCA8K_PORT_LOOKUP_LEARN);
++ if (ret)
++ return ret;
++
++ /* For port based vlans to work we need to set the
++ * default egress vid
+ */
+- if (priv->switch_id == QCA8K_ID_QCA8337)
+- qca8k_setup_hol_fixup(priv, i);
++ ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port),
++ QCA8K_EGREES_VLAN_PORT_MASK(port),
++ QCA8K_EGREES_VLAN_PORT(port, QCA8K_PORT_VID_DEF));
++ if (ret)
++ return ret;
++
++ ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port),
++ QCA8K_PORT_VLAN_CVID(QCA8K_PORT_VID_DEF) |
++ QCA8K_PORT_VLAN_SVID(QCA8K_PORT_VID_DEF));
++ if (ret)
++ return ret;
+ }
+
++ /* The port 5 of the qca8337 have some problem in flood condition. The
++ * original legacy driver had some specific buffer and priority settings
++ * for the different port suggested by the QCA switch team. Add this
++ * missing settings to improve switch stability under load condition.
++ * This problem is limited to qca8337 and other qca8k switch are not affected.
++ */
++ if (priv->switch_id == QCA8K_ID_QCA8337)
++ dsa_switch_for_each_available_port(dp, ds)
++ qca8k_setup_hol_fixup(priv, dp->index);
++
+ /* Special GLOBAL_FC_THRESH value are needed for ar8327 switch */
+ if (priv->switch_id == QCA8K_ID_QCA8327) {
+ mask = QCA8K_GLOBAL_FC_GOL_XON_THRES(288) |
diff --git a/target/linux/generic/backport-6.6/781-v6.6-01-net-dsa-qca8k-fix-regmap-bulk-read-write-methods-on-.patch b/target/linux/generic/backport-6.6/781-v6.6-01-net-dsa-qca8k-fix-regmap-bulk-read-write-methods-on-.patch
new file mode 100644
index 0000000000..632f422d1f
--- /dev/null
+++ b/target/linux/generic/backport-6.6/781-v6.6-01-net-dsa-qca8k-fix-regmap-bulk-read-write-methods-on-.patch
@@ -0,0 +1,61 @@
+From 5652d1741574eb89cc02576e50ee3e348bd6dd77 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
+Date: Wed, 4 Oct 2023 11:19:03 +0200
+Subject: [PATCH 1/2] net: dsa: qca8k: fix regmap bulk read/write methods on
+ big endian systems
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Commit c766e077d927 ("net: dsa: qca8k: convert to regmap read/write
+API") introduced bulk read/write methods to qca8k's regmap.
+
+The regmap bulk read/write methods get the register address in a buffer
+passed as a void pointer parameter (the same buffer contains also the
+read/written values). The register address occupies only as many bytes
+as it requires at the beginning of this buffer. For example if the
+.reg_bits member in regmap_config is 16 (as is the case for this
+driver), the register address occupies only the first 2 bytes in this
+buffer, so it can be cast to u16.
+
+But the original commit implementing these bulk read/write methods cast
+the buffer to u32:
+ u32 reg = *(u32 *)reg_buf & U16_MAX;
+taking the first 4 bytes. This works on little endian systems where the
+first 2 bytes of the buffer correspond to the low 16-bits, but it
+obviously cannot work on big endian systems.
+
+Fix this by casting the beginning of the buffer to u16 as
+ u32 reg = *(u16 *)reg_buf;
+
+Fixes: c766e077d927 ("net: dsa: qca8k: convert to regmap read/write API")
+Signed-off-by: Marek Behún <kabel@kernel.org>
+Tested-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/qca8k-8xxx.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/dsa/qca/qca8k-8xxx.c
++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
+@@ -504,8 +504,8 @@ qca8k_bulk_read(void *ctx, const void *r
+ void *val_buf, size_t val_len)
+ {
+ int i, count = val_len / sizeof(u32), ret;
+- u32 reg = *(u32 *)reg_buf & U16_MAX;
+ struct qca8k_priv *priv = ctx;
++ u32 reg = *(u16 *)reg_buf;
+
+ if (priv->mgmt_master &&
+ !qca8k_read_eth(priv, reg, val_buf, val_len))
+@@ -526,8 +526,8 @@ qca8k_bulk_gather_write(void *ctx, const
+ const void *val_buf, size_t val_len)
+ {
+ int i, count = val_len / sizeof(u32), ret;
+- u32 reg = *(u32 *)reg_buf & U16_MAX;
+ struct qca8k_priv *priv = ctx;
++ u32 reg = *(u16 *)reg_buf;
+ u32 *val = (u32 *)val_buf;
+
+ if (priv->mgmt_master &&
diff --git a/target/linux/generic/backport-6.6/788-v6.3-net-dsa-mt7530-use-external-PCS-driver.patch b/target/linux/generic/backport-6.6/788-v6.3-net-dsa-mt7530-use-external-PCS-driver.patch
new file mode 100644
index 0000000000..8010076fc0
--- /dev/null
+++ b/target/linux/generic/backport-6.6/788-v6.3-net-dsa-mt7530-use-external-PCS-driver.patch
@@ -0,0 +1,514 @@
+From patchwork Thu Mar 9 10:57:44 2023
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 8bit
+X-Patchwork-Submitter: Daniel Golle <daniel@makrotopia.org>
+X-Patchwork-Id: 13167235
+X-Patchwork-Delegate: kuba@kernel.org
+Return-Path: <netdev-owner@vger.kernel.org>
+Date: Thu, 9 Mar 2023 10:57:44 +0000
+From: Daniel Golle <daniel@makrotopia.org>
+To: netdev@vger.kernel.org, linux-mediatek@lists.infradead.org,
+ linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org,
+ Russell King <linux@armlinux.org.uk>,
+ Heiner Kallweit <hkallweit1@gmail.com>,
+ Lorenzo Bianconi <lorenzo@kernel.org>,
+ Mark Lee <Mark-MC.Lee@mediatek.com>,
+ John Crispin <john@phrozen.org>, Felix Fietkau <nbd@nbd.name>,
+ AngeloGioacchino Del Regno
+ <angelogioacchino.delregno@collabora.com>,
+ Matthias Brugger <matthias.bgg@gmail.com>,
+ DENG Qingfang <dqfext@gmail.com>,
+ Landen Chao <Landen.Chao@mediatek.com>,
+ Sean Wang <sean.wang@mediatek.com>,
+ Paolo Abeni <pabeni@redhat.com>,
+ Jakub Kicinski <kuba@kernel.org>,
+ Eric Dumazet <edumazet@google.com>,
+ "David S. Miller" <davem@davemloft.net>,
+ Vladimir Oltean <olteanv@gmail.com>,
+ Florian Fainelli <f.fainelli@gmail.com>,
+ Andrew Lunn <andrew@lunn.ch>,
+ Vladimir Oltean <vladimir.oltean@nxp.com>
+Cc: =?iso-8859-1?q?Bj=F8rn?= Mork <bjorn@mork.no>,
+ Frank Wunderlich <frank-w@public-files.de>,
+ Alexander Couzens <lynxis@fe80.eu>
+Subject: [PATCH net-next v13 11/16] net: dsa: mt7530: use external PCS driver
+Message-ID:
+ <2ac2ee40d3b0e705461b50613fda6a7edfdbc4b3.1678357225.git.daniel@makrotopia.org>
+References: <cover.1678357225.git.daniel@makrotopia.org>
+MIME-Version: 1.0
+Content-Disposition: inline
+In-Reply-To: <cover.1678357225.git.daniel@makrotopia.org>
+Precedence: bulk
+List-ID: <netdev.vger.kernel.org>
+X-Mailing-List: netdev@vger.kernel.org
+X-Patchwork-Delegate: kuba@kernel.org
+
+Implement regmap access wrappers, for now only to be used by the
+pcs-mtk driver.
+Make use of external PCS driver and drop the reduntant implementation
+in mt7530.c.
+As a nice side effect the SGMII registers can now also more easily be
+inspected for debugging via /sys/kernel/debug/regmap.
+
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Tested-by: Bjørn Mork <bjorn@mork.no>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Tested-by: Frank Wunderlich <frank-w@public-files.de>
+---
+ drivers/net/dsa/Kconfig | 1 +
+ drivers/net/dsa/mt7530.c | 277 ++++++++++-----------------------------
+ drivers/net/dsa/mt7530.h | 47 +------
+ 3 files changed, 71 insertions(+), 254 deletions(-)
+
+--- a/drivers/net/dsa/Kconfig
++++ b/drivers/net/dsa/Kconfig
+@@ -37,6 +37,7 @@ config NET_DSA_MT7530
+ tristate "MediaTek MT753x and MT7621 Ethernet switch support"
+ select NET_DSA_TAG_MTK
+ select MEDIATEK_GE_PHY
++ select PCS_MTK_LYNXI
+ help
+ This enables support for the MediaTek MT7530, MT7531, and MT7621
+ Ethernet switch chips.
+--- a/drivers/net/dsa/mt7530.c
++++ b/drivers/net/dsa/mt7530.c
+@@ -14,6 +14,7 @@
+ #include <linux/of_mdio.h>
+ #include <linux/of_net.h>
+ #include <linux/of_platform.h>
++#include <linux/pcs/pcs-mtk-lynxi.h>
+ #include <linux/phylink.h>
+ #include <linux/regmap.h>
+ #include <linux/regulator/consumer.h>
+@@ -2615,128 +2616,11 @@ static int mt7531_rgmii_setup(struct mt7
+ return 0;
+ }
+
+-static void mt7531_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode,
+- phy_interface_t interface, int speed, int duplex)
+-{
+- struct mt7530_priv *priv = pcs_to_mt753x_pcs(pcs)->priv;
+- int port = pcs_to_mt753x_pcs(pcs)->port;
+- unsigned int val;
+-
+- /* For adjusting speed and duplex of SGMII force mode. */
+- if (interface != PHY_INTERFACE_MODE_SGMII ||
+- phylink_autoneg_inband(mode))
+- return;
+-
+- /* SGMII force mode setting */
+- val = mt7530_read(priv, MT7531_SGMII_MODE(port));
+- val &= ~MT7531_SGMII_IF_MODE_MASK;
+-
+- switch (speed) {
+- case SPEED_10:
+- val |= MT7531_SGMII_FORCE_SPEED_10;
+- break;
+- case SPEED_100:
+- val |= MT7531_SGMII_FORCE_SPEED_100;
+- break;
+- case SPEED_1000:
+- val |= MT7531_SGMII_FORCE_SPEED_1000;
+- break;
+- }
+-
+- /* MT7531 SGMII 1G force mode can only work in full duplex mode,
+- * no matter MT7531_SGMII_FORCE_HALF_DUPLEX is set or not.
+- *
+- * The speed check is unnecessary as the MAC capabilities apply
+- * this restriction. --rmk
+- */
+- if ((speed == SPEED_10 || speed == SPEED_100) &&
+- duplex != DUPLEX_FULL)
+- val |= MT7531_SGMII_FORCE_HALF_DUPLEX;
+-
+- mt7530_write(priv, MT7531_SGMII_MODE(port), val);
+-}
+-
+ static bool mt753x_is_mac_port(u32 port)
+ {
+ return (port == 5 || port == 6);
+ }
+
+-static int mt7531_sgmii_setup_mode_force(struct mt7530_priv *priv, u32 port,
+- phy_interface_t interface)
+-{
+- u32 val;
+-
+- if (!mt753x_is_mac_port(port))
+- return -EINVAL;
+-
+- mt7530_set(priv, MT7531_QPHY_PWR_STATE_CTRL(port),
+- MT7531_SGMII_PHYA_PWD);
+-
+- val = mt7530_read(priv, MT7531_PHYA_CTRL_SIGNAL3(port));
+- val &= ~MT7531_RG_TPHY_SPEED_MASK;
+- /* Setup 2.5 times faster clock for 2.5Gbps data speeds with 10B/8B
+- * encoding.
+- */
+- val |= (interface == PHY_INTERFACE_MODE_2500BASEX) ?
+- MT7531_RG_TPHY_SPEED_3_125G : MT7531_RG_TPHY_SPEED_1_25G;
+- mt7530_write(priv, MT7531_PHYA_CTRL_SIGNAL3(port), val);
+-
+- mt7530_clear(priv, MT7531_PCS_CONTROL_1(port), MT7531_SGMII_AN_ENABLE);
+-
+- /* MT7531 SGMII 1G and 2.5G force mode can only work in full duplex
+- * mode, no matter MT7531_SGMII_FORCE_HALF_DUPLEX is set or not.
+- */
+- mt7530_rmw(priv, MT7531_SGMII_MODE(port),
+- MT7531_SGMII_IF_MODE_MASK | MT7531_SGMII_REMOTE_FAULT_DIS,
+- MT7531_SGMII_FORCE_SPEED_1000);
+-
+- mt7530_write(priv, MT7531_QPHY_PWR_STATE_CTRL(port), 0);
+-
+- return 0;
+-}
+-
+-static int mt7531_sgmii_setup_mode_an(struct mt7530_priv *priv, int port,
+- phy_interface_t interface)
+-{
+- if (!mt753x_is_mac_port(port))
+- return -EINVAL;
+-
+- mt7530_set(priv, MT7531_QPHY_PWR_STATE_CTRL(port),
+- MT7531_SGMII_PHYA_PWD);
+-
+- mt7530_rmw(priv, MT7531_PHYA_CTRL_SIGNAL3(port),
+- MT7531_RG_TPHY_SPEED_MASK, MT7531_RG_TPHY_SPEED_1_25G);
+-
+- mt7530_set(priv, MT7531_SGMII_MODE(port),
+- MT7531_SGMII_REMOTE_FAULT_DIS |
+- MT7531_SGMII_SPEED_DUPLEX_AN);
+-
+- mt7530_rmw(priv, MT7531_PCS_SPEED_ABILITY(port),
+- MT7531_SGMII_TX_CONFIG_MASK, 1);
+-
+- mt7530_set(priv, MT7531_PCS_CONTROL_1(port), MT7531_SGMII_AN_ENABLE);
+-
+- mt7530_set(priv, MT7531_PCS_CONTROL_1(port), MT7531_SGMII_AN_RESTART);
+-
+- mt7530_write(priv, MT7531_QPHY_PWR_STATE_CTRL(port), 0);
+-
+- return 0;
+-}
+-
+-static void mt7531_pcs_an_restart(struct phylink_pcs *pcs)
+-{
+- struct mt7530_priv *priv = pcs_to_mt753x_pcs(pcs)->priv;
+- int port = pcs_to_mt753x_pcs(pcs)->port;
+- u32 val;
+-
+- /* Only restart AN when AN is enabled */
+- val = mt7530_read(priv, MT7531_PCS_CONTROL_1(port));
+- if (val & MT7531_SGMII_AN_ENABLE) {
+- val |= MT7531_SGMII_AN_RESTART;
+- mt7530_write(priv, MT7531_PCS_CONTROL_1(port), val);
+- }
+-}
+-
+ static int
+ mt7531_mac_config(struct dsa_switch *ds, int port, unsigned int mode,
+ phy_interface_t interface)
+@@ -2759,11 +2643,11 @@ mt7531_mac_config(struct dsa_switch *ds,
+ phydev = dp->slave->phydev;
+ return mt7531_rgmii_setup(priv, port, interface, phydev);
+ case PHY_INTERFACE_MODE_SGMII:
+- return mt7531_sgmii_setup_mode_an(priv, port, interface);
+ case PHY_INTERFACE_MODE_NA:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_2500BASEX:
+- return mt7531_sgmii_setup_mode_force(priv, port, interface);
++ /* handled in SGMII PCS driver */
++ return 0;
+ default:
+ return -EINVAL;
+ }
+@@ -2788,11 +2672,11 @@ mt753x_phylink_mac_select_pcs(struct dsa
+
+ switch (interface) {
+ case PHY_INTERFACE_MODE_TRGMII:
++ return &priv->pcs[port].pcs;
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_2500BASEX:
+- return &priv->pcs[port].pcs;
+-
++ return priv->ports[port].sgmii_pcs;
+ default:
+ return NULL;
+ }
+@@ -3033,86 +2917,6 @@ static void mt7530_pcs_get_state(struct
+ state->pause |= MLO_PAUSE_TX;
+ }
+
+-static int
+-mt7531_sgmii_pcs_get_state_an(struct mt7530_priv *priv, int port,
+- struct phylink_link_state *state)
+-{
+- u32 status, val;
+- u16 config_reg;
+-
+- status = mt7530_read(priv, MT7531_PCS_CONTROL_1(port));
+- state->link = !!(status & MT7531_SGMII_LINK_STATUS);
+- state->an_complete = !!(status & MT7531_SGMII_AN_COMPLETE);
+- if (state->interface == PHY_INTERFACE_MODE_SGMII &&
+- (status & MT7531_SGMII_AN_ENABLE)) {
+- val = mt7530_read(priv, MT7531_PCS_SPEED_ABILITY(port));
+- config_reg = val >> 16;
+-
+- switch (config_reg & LPA_SGMII_SPD_MASK) {
+- case LPA_SGMII_1000:
+- state->speed = SPEED_1000;
+- break;
+- case LPA_SGMII_100:
+- state->speed = SPEED_100;
+- break;
+- case LPA_SGMII_10:
+- state->speed = SPEED_10;
+- break;
+- default:
+- dev_err(priv->dev, "invalid sgmii PHY speed\n");
+- state->link = false;
+- return -EINVAL;
+- }
+-
+- if (config_reg & LPA_SGMII_FULL_DUPLEX)
+- state->duplex = DUPLEX_FULL;
+- else
+- state->duplex = DUPLEX_HALF;
+- }
+-
+- return 0;
+-}
+-
+-static void
+-mt7531_sgmii_pcs_get_state_inband(struct mt7530_priv *priv, int port,
+- struct phylink_link_state *state)
+-{
+- unsigned int val;
+-
+- val = mt7530_read(priv, MT7531_PCS_CONTROL_1(port));
+- state->link = !!(val & MT7531_SGMII_LINK_STATUS);
+- if (!state->link)
+- return;
+-
+- state->an_complete = state->link;
+-
+- if (state->interface == PHY_INTERFACE_MODE_2500BASEX)
+- state->speed = SPEED_2500;
+- else
+- state->speed = SPEED_1000;
+-
+- state->duplex = DUPLEX_FULL;
+- state->pause = MLO_PAUSE_NONE;
+-}
+-
+-static void mt7531_pcs_get_state(struct phylink_pcs *pcs,
+- struct phylink_link_state *state)
+-{
+- struct mt7530_priv *priv = pcs_to_mt753x_pcs(pcs)->priv;
+- int port = pcs_to_mt753x_pcs(pcs)->port;
+-
+- if (state->interface == PHY_INTERFACE_MODE_SGMII) {
+- mt7531_sgmii_pcs_get_state_an(priv, port, state);
+- return;
+- } else if ((state->interface == PHY_INTERFACE_MODE_1000BASEX) ||
+- (state->interface == PHY_INTERFACE_MODE_2500BASEX)) {
+- mt7531_sgmii_pcs_get_state_inband(priv, port, state);
+- return;
+- }
+-
+- state->link = false;
+-}
+-
+ static int mt753x_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+@@ -3132,18 +2936,57 @@ static const struct phylink_pcs_ops mt75
+ .pcs_an_restart = mt7530_pcs_an_restart,
+ };
+
+-static const struct phylink_pcs_ops mt7531_pcs_ops = {
+- .pcs_validate = mt753x_pcs_validate,
+- .pcs_get_state = mt7531_pcs_get_state,
+- .pcs_config = mt753x_pcs_config,
+- .pcs_an_restart = mt7531_pcs_an_restart,
+- .pcs_link_up = mt7531_pcs_link_up,
++static int mt7530_regmap_read(void *context, unsigned int reg, unsigned int *val)
++{
++ struct mt7530_priv *priv = context;
++
++ *val = mt7530_read(priv, reg);
++ return 0;
++};
++
++static int mt7530_regmap_write(void *context, unsigned int reg, unsigned int val)
++{
++ struct mt7530_priv *priv = context;
++
++ mt7530_write(priv, reg, val);
++ return 0;
++};
++
++static int mt7530_regmap_update_bits(void *context, unsigned int reg,
++ unsigned int mask, unsigned int val)
++{
++ struct mt7530_priv *priv = context;
++
++ mt7530_rmw(priv, reg, mask, val);
++ return 0;
++};
++
++static const struct regmap_bus mt7531_regmap_bus = {
++ .reg_write = mt7530_regmap_write,
++ .reg_read = mt7530_regmap_read,
++ .reg_update_bits = mt7530_regmap_update_bits,
++};
++
++#define MT7531_PCS_REGMAP_CONFIG(_name, _reg_base) \
++ { \
++ .name = _name, \
++ .reg_bits = 16, \
++ .val_bits = 32, \
++ .reg_stride = 4, \
++ .reg_base = _reg_base, \
++ .max_register = 0x17c, \
++ }
++
++static const struct regmap_config mt7531_pcs_config[] = {
++ MT7531_PCS_REGMAP_CONFIG("port5", MT7531_SGMII_REG_BASE(5)),
++ MT7531_PCS_REGMAP_CONFIG("port6", MT7531_SGMII_REG_BASE(6)),
+ };
+
+ static int
+ mt753x_setup(struct dsa_switch *ds)
+ {
+ struct mt7530_priv *priv = ds->priv;
++ struct regmap *regmap;
+ int i, ret;
+
+ /* Initialise the PCS devices */
+@@ -3151,8 +2994,6 @@ mt753x_setup(struct dsa_switch *ds)
+ priv->pcs[i].pcs.ops = priv->info->pcs_ops;
+ priv->pcs[i].priv = priv;
+ priv->pcs[i].port = i;
+- if (mt753x_is_mac_port(i))
+- priv->pcs[i].pcs.poll = 1;
+ }
+
+ ret = priv->info->sw_setup(ds);
+@@ -3167,6 +3008,16 @@ mt753x_setup(struct dsa_switch *ds)
+ if (ret && priv->irq)
+ mt7530_free_irq_common(priv);
+
++ if (priv->id == ID_MT7531)
++ for (i = 0; i < 2; i++) {
++ regmap = devm_regmap_init(ds->dev,
++ &mt7531_regmap_bus, priv,
++ &mt7531_pcs_config[i]);
++ priv->ports[5 + i].sgmii_pcs =
++ mtk_pcs_lynxi_create(ds->dev, regmap,
++ MT7531_PHYA_CTRL_SIGNAL3, 0);
++ }
++
+ return ret;
+ }
+
+@@ -3258,7 +3109,7 @@ static const struct mt753x_info mt753x_t
+ },
+ [ID_MT7531] = {
+ .id = ID_MT7531,
+- .pcs_ops = &mt7531_pcs_ops,
++ .pcs_ops = &mt7530_pcs_ops,
+ .sw_setup = mt7531_setup,
+ .phy_read = mt7531_ind_phy_read,
+ .phy_write = mt7531_ind_phy_write,
+@@ -3366,7 +3217,7 @@ static void
+ mt7530_remove(struct mdio_device *mdiodev)
+ {
+ struct mt7530_priv *priv = dev_get_drvdata(&mdiodev->dev);
+- int ret = 0;
++ int ret = 0, i;
+
+ if (!priv)
+ return;
+@@ -3385,6 +3236,10 @@ mt7530_remove(struct mdio_device *mdiode
+ mt7530_free_irq(priv);
+
+ dsa_unregister_switch(priv->ds);
++
++ for (i = 0; i < 2; ++i)
++ mtk_pcs_lynxi_destroy(priv->ports[5 + i].sgmii_pcs);
++
+ mutex_destroy(&priv->reg_mutex);
+ }
+
+--- a/drivers/net/dsa/mt7530.h
++++ b/drivers/net/dsa/mt7530.h
+@@ -371,47 +371,8 @@ enum mt7530_vlan_port_acc_frm {
+ CCR_TX_OCT_CNT_BAD)
+
+ /* MT7531 SGMII register group */
+-#define MT7531_SGMII_REG_BASE 0x5000
+-#define MT7531_SGMII_REG(p, r) (MT7531_SGMII_REG_BASE + \
+- ((p) - 5) * 0x1000 + (r))
+-
+-/* Register forSGMII PCS_CONTROL_1 */
+-#define MT7531_PCS_CONTROL_1(p) MT7531_SGMII_REG(p, 0x00)
+-#define MT7531_SGMII_LINK_STATUS BIT(18)
+-#define MT7531_SGMII_AN_ENABLE BIT(12)
+-#define MT7531_SGMII_AN_RESTART BIT(9)
+-#define MT7531_SGMII_AN_COMPLETE BIT(21)
+-
+-/* Register for SGMII PCS_SPPED_ABILITY */
+-#define MT7531_PCS_SPEED_ABILITY(p) MT7531_SGMII_REG(p, 0x08)
+-#define MT7531_SGMII_TX_CONFIG_MASK GENMASK(15, 0)
+-#define MT7531_SGMII_TX_CONFIG BIT(0)
+-
+-/* Register for SGMII_MODE */
+-#define MT7531_SGMII_MODE(p) MT7531_SGMII_REG(p, 0x20)
+-#define MT7531_SGMII_REMOTE_FAULT_DIS BIT(8)
+-#define MT7531_SGMII_IF_MODE_MASK GENMASK(5, 1)
+-#define MT7531_SGMII_FORCE_DUPLEX BIT(4)
+-#define MT7531_SGMII_FORCE_SPEED_MASK GENMASK(3, 2)
+-#define MT7531_SGMII_FORCE_SPEED_1000 BIT(3)
+-#define MT7531_SGMII_FORCE_SPEED_100 BIT(2)
+-#define MT7531_SGMII_FORCE_SPEED_10 0
+-#define MT7531_SGMII_SPEED_DUPLEX_AN BIT(1)
+-
+-enum mt7531_sgmii_force_duplex {
+- MT7531_SGMII_FORCE_FULL_DUPLEX = 0,
+- MT7531_SGMII_FORCE_HALF_DUPLEX = 0x10,
+-};
+-
+-/* Fields of QPHY_PWR_STATE_CTRL */
+-#define MT7531_QPHY_PWR_STATE_CTRL(p) MT7531_SGMII_REG(p, 0xe8)
+-#define MT7531_SGMII_PHYA_PWD BIT(4)
+-
+-/* Values of SGMII SPEED */
+-#define MT7531_PHYA_CTRL_SIGNAL3(p) MT7531_SGMII_REG(p, 0x128)
+-#define MT7531_RG_TPHY_SPEED_MASK (BIT(2) | BIT(3))
+-#define MT7531_RG_TPHY_SPEED_1_25G 0x0
+-#define MT7531_RG_TPHY_SPEED_3_125G BIT(2)
++#define MT7531_SGMII_REG_BASE(p) (0x5000 + ((p) - 5) * 0x1000)
++#define MT7531_PHYA_CTRL_SIGNAL3 0x128
+
+ /* Register for system reset */
+ #define MT7530_SYS_CTRL 0x7000
+@@ -710,13 +671,13 @@ struct mt7530_fdb {
+ * @pm: The matrix used to show all connections with the port.
+ * @pvid: The VLAN specified is to be considered a PVID at ingress. Any
+ * untagged frames will be assigned to the related VLAN.
+- * @vlan_filtering: The flags indicating whether the port that can recognize
+- * VLAN-tagged frames.
++ * @sgmii_pcs: Pointer to PCS instance for SerDes ports
+ */
+ struct mt7530_port {
+ bool enable;
+ u32 pm;
+ u16 pvid;
++ struct phylink_pcs *sgmii_pcs;
+ };
+
+ /* Port 5 interface select definitions */
diff --git a/target/linux/generic/backport-6.6/790-v6.4-0001-net-dsa-mt7530-make-some-noise-if-register-read-fail.patch b/target/linux/generic/backport-6.6/790-v6.4-0001-net-dsa-mt7530-make-some-noise-if-register-read-fail.patch
new file mode 100644
index 0000000000..4d024b063a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/790-v6.4-0001-net-dsa-mt7530-make-some-noise-if-register-read-fail.patch
@@ -0,0 +1,32 @@
+From c3552d3f85f06cf4b4818bd84c4fcc09d8d45165 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 3 Apr 2023 02:17:19 +0100
+Subject: [PATCH 01/13] net: dsa: mt7530: make some noise if register read
+ fails
+
+Simply returning the negative error value instead of the read value
+doesn't seem like a good idea. Return 0 instead and add WARN_ON_ONCE(1)
+so this kind of error will not go unnoticed.
+
+Suggested-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/mt7530.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/dsa/mt7530.c
++++ b/drivers/net/dsa/mt7530.c
+@@ -224,9 +224,10 @@ mt7530_mii_read(struct mt7530_priv *priv
+ /* MT7530 uses 31 as the pseudo port */
+ ret = bus->write(bus, 0x1f, 0x1f, page);
+ if (ret < 0) {
++ WARN_ON_ONCE(1);
+ dev_err(&bus->dev,
+ "failed to read mt7530 register\n");
+- return ret;
++ return 0;
+ }
+
+ lo = bus->read(bus, 0x1f, r);
diff --git a/target/linux/generic/backport-6.6/790-v6.4-0002-net-dsa-mt7530-refactor-SGMII-PCS-creation.patch b/target/linux/generic/backport-6.6/790-v6.4-0002-net-dsa-mt7530-refactor-SGMII-PCS-creation.patch
new file mode 100644
index 0000000000..5667449296
--- /dev/null
+++ b/target/linux/generic/backport-6.6/790-v6.4-0002-net-dsa-mt7530-refactor-SGMII-PCS-creation.patch
@@ -0,0 +1,111 @@
+From b896355fc4988216d4f38582d07add9252a795ae Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 3 Apr 2023 02:17:30 +0100
+Subject: [PATCH 02/13] net: dsa: mt7530: refactor SGMII PCS creation
+
+Instead of macro templates use a dedidated function and allocated
+regmap_config when creating the regmaps for the pcs-mtk-lynxi
+instances.
+This is in preparation to switching to use unlocked regmap accessors
+and have regmap's locking API handle locking for us.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/mt7530.c | 74 +++++++++++++++++++++++++++-------------
+ 1 file changed, 50 insertions(+), 24 deletions(-)
+
+--- a/drivers/net/dsa/mt7530.c
++++ b/drivers/net/dsa/mt7530.c
+@@ -2968,26 +2968,56 @@ static const struct regmap_bus mt7531_re
+ .reg_update_bits = mt7530_regmap_update_bits,
+ };
+
+-#define MT7531_PCS_REGMAP_CONFIG(_name, _reg_base) \
+- { \
+- .name = _name, \
+- .reg_bits = 16, \
+- .val_bits = 32, \
+- .reg_stride = 4, \
+- .reg_base = _reg_base, \
+- .max_register = 0x17c, \
++static int
++mt7531_create_sgmii(struct mt7530_priv *priv)
++{
++ struct regmap_config *mt7531_pcs_config[2];
++ struct phylink_pcs *pcs;
++ struct regmap *regmap;
++ int i, ret = 0;
++
++ for (i = 0; i < 2; i++) {
++ mt7531_pcs_config[i] = devm_kzalloc(priv->dev,
++ sizeof(struct regmap_config),
++ GFP_KERNEL);
++ if (!mt7531_pcs_config[i]) {
++ ret = -ENOMEM;
++ break;
++ }
++
++ mt7531_pcs_config[i]->name = i ? "port6" : "port5";
++ mt7531_pcs_config[i]->reg_bits = 16;
++ mt7531_pcs_config[i]->val_bits = 32;
++ mt7531_pcs_config[i]->reg_stride = 4;
++ mt7531_pcs_config[i]->reg_base = MT7531_SGMII_REG_BASE(5 + i);
++ mt7531_pcs_config[i]->max_register = 0x17c;
++
++ regmap = devm_regmap_init(priv->dev,
++ &mt7531_regmap_bus, priv,
++ mt7531_pcs_config[i]);
++ if (IS_ERR(regmap)) {
++ ret = PTR_ERR(regmap);
++ break;
++ }
++ pcs = mtk_pcs_lynxi_create(priv->dev, regmap,
++ MT7531_PHYA_CTRL_SIGNAL3, 0);
++ if (!pcs) {
++ ret = -ENXIO;
++ break;
++ }
++ priv->ports[5 + i].sgmii_pcs = pcs;
+ }
+
+-static const struct regmap_config mt7531_pcs_config[] = {
+- MT7531_PCS_REGMAP_CONFIG("port5", MT7531_SGMII_REG_BASE(5)),
+- MT7531_PCS_REGMAP_CONFIG("port6", MT7531_SGMII_REG_BASE(6)),
+-};
++ if (ret && i)
++ mtk_pcs_lynxi_destroy(priv->ports[5].sgmii_pcs);
++
++ return ret;
++}
+
+ static int
+ mt753x_setup(struct dsa_switch *ds)
+ {
+ struct mt7530_priv *priv = ds->priv;
+- struct regmap *regmap;
+ int i, ret;
+
+ /* Initialise the PCS devices */
+@@ -3009,15 +3039,11 @@ mt753x_setup(struct dsa_switch *ds)
+ if (ret && priv->irq)
+ mt7530_free_irq_common(priv);
+
+- if (priv->id == ID_MT7531)
+- for (i = 0; i < 2; i++) {
+- regmap = devm_regmap_init(ds->dev,
+- &mt7531_regmap_bus, priv,
+- &mt7531_pcs_config[i]);
+- priv->ports[5 + i].sgmii_pcs =
+- mtk_pcs_lynxi_create(ds->dev, regmap,
+- MT7531_PHYA_CTRL_SIGNAL3, 0);
+- }
++ if (priv->id == ID_MT7531) {
++ ret = mt7531_create_sgmii(priv);
++ if (ret && priv->irq)
++ mt7530_free_irq_common(priv);
++ }
+
+ return ret;
+ }
diff --git a/target/linux/generic/backport-6.6/790-v6.4-0003-net-dsa-mt7530-use-unlocked-regmap-accessors.patch b/target/linux/generic/backport-6.6/790-v6.4-0003-net-dsa-mt7530-use-unlocked-regmap-accessors.patch
new file mode 100644
index 0000000000..3b4689fb19
--- /dev/null
+++ b/target/linux/generic/backport-6.6/790-v6.4-0003-net-dsa-mt7530-use-unlocked-regmap-accessors.patch
@@ -0,0 +1,74 @@
+From 33396408776385f3d2f6069646169a6b5b28e3b3 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 3 Apr 2023 02:17:40 +0100
+Subject: [PATCH 03/13] net: dsa: mt7530: use unlocked regmap accessors
+
+Instead of wrapping the locked register accessor functions, use the
+unlocked variants and add locking wrapper functions to let regmap
+handle the locking.
+
+This is a preparation towards being able to always use regmap to
+access switch registers instead of open-coded accessor functions.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/mt7530.c | 23 ++++++++++++++---------
+ 1 file changed, 14 insertions(+), 9 deletions(-)
+
+--- a/drivers/net/dsa/mt7530.c
++++ b/drivers/net/dsa/mt7530.c
+@@ -2941,7 +2941,7 @@ static int mt7530_regmap_read(void *cont
+ {
+ struct mt7530_priv *priv = context;
+
+- *val = mt7530_read(priv, reg);
++ *val = mt7530_mii_read(priv, reg);
+ return 0;
+ };
+
+@@ -2949,23 +2949,25 @@ static int mt7530_regmap_write(void *con
+ {
+ struct mt7530_priv *priv = context;
+
+- mt7530_write(priv, reg, val);
++ mt7530_mii_write(priv, reg, val);
+ return 0;
+ };
+
+-static int mt7530_regmap_update_bits(void *context, unsigned int reg,
+- unsigned int mask, unsigned int val)
++static void
++mt7530_mdio_regmap_lock(void *mdio_lock)
+ {
+- struct mt7530_priv *priv = context;
++ mutex_lock_nested(mdio_lock, MDIO_MUTEX_NESTED);
++}
+
+- mt7530_rmw(priv, reg, mask, val);
+- return 0;
+-};
++static void
++mt7530_mdio_regmap_unlock(void *mdio_lock)
++{
++ mutex_unlock(mdio_lock);
++}
+
+ static const struct regmap_bus mt7531_regmap_bus = {
+ .reg_write = mt7530_regmap_write,
+ .reg_read = mt7530_regmap_read,
+- .reg_update_bits = mt7530_regmap_update_bits,
+ };
+
+ static int
+@@ -2991,6 +2993,9 @@ mt7531_create_sgmii(struct mt7530_priv *
+ mt7531_pcs_config[i]->reg_stride = 4;
+ mt7531_pcs_config[i]->reg_base = MT7531_SGMII_REG_BASE(5 + i);
+ mt7531_pcs_config[i]->max_register = 0x17c;
++ mt7531_pcs_config[i]->lock = mt7530_mdio_regmap_lock;
++ mt7531_pcs_config[i]->unlock = mt7530_mdio_regmap_unlock;
++ mt7531_pcs_config[i]->lock_arg = &priv->bus->mdio_lock;
+
+ regmap = devm_regmap_init(priv->dev,
+ &mt7531_regmap_bus, priv,
diff --git a/target/linux/generic/backport-6.6/790-v6.4-0004-net-dsa-mt7530-use-regmap-to-access-switch-register-.patch b/target/linux/generic/backport-6.6/790-v6.4-0004-net-dsa-mt7530-use-regmap-to-access-switch-register-.patch
new file mode 100644
index 0000000000..04033f14f4
--- /dev/null
+++ b/target/linux/generic/backport-6.6/790-v6.4-0004-net-dsa-mt7530-use-regmap-to-access-switch-register-.patch
@@ -0,0 +1,224 @@
+From 743cba4345cb366248f9d375c6a9e50243dc0677 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 3 Apr 2023 02:17:52 +0100
+Subject: [PATCH 04/13] net: dsa: mt7530: use regmap to access switch register
+ space
+
+Use regmap API to access the switch register space.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/mt7530.c | 99 ++++++++++++++++++++++++----------------
+ drivers/net/dsa/mt7530.h | 2 +
+ 2 files changed, 62 insertions(+), 39 deletions(-)
+
+--- a/drivers/net/dsa/mt7530.c
++++ b/drivers/net/dsa/mt7530.c
+@@ -183,9 +183,9 @@ core_clear(struct mt7530_priv *priv, u32
+ }
+
+ static int
+-mt7530_mii_write(struct mt7530_priv *priv, u32 reg, u32 val)
++mt7530_regmap_write(void *context, unsigned int reg, unsigned int val)
+ {
+- struct mii_bus *bus = priv->bus;
++ struct mii_bus *bus = context;
+ u16 page, r, lo, hi;
+ int ret;
+
+@@ -197,24 +197,34 @@ mt7530_mii_write(struct mt7530_priv *pri
+ /* MT7530 uses 31 as the pseudo port */
+ ret = bus->write(bus, 0x1f, 0x1f, page);
+ if (ret < 0)
+- goto err;
++ return ret;
+
+ ret = bus->write(bus, 0x1f, r, lo);
+ if (ret < 0)
+- goto err;
++ return ret;
+
+ ret = bus->write(bus, 0x1f, 0x10, hi);
+-err:
++ return ret;
++}
++
++static int
++mt7530_mii_write(struct mt7530_priv *priv, u32 reg, u32 val)
++{
++ int ret;
++
++ ret = regmap_write(priv->regmap, reg, val);
++
+ if (ret < 0)
+- dev_err(&bus->dev,
++ dev_err(priv->dev,
+ "failed to write mt7530 register\n");
++
+ return ret;
+ }
+
+-static u32
+-mt7530_mii_read(struct mt7530_priv *priv, u32 reg)
++static int
++mt7530_regmap_read(void *context, unsigned int reg, unsigned int *val)
+ {
+- struct mii_bus *bus = priv->bus;
++ struct mii_bus *bus = context;
+ u16 page, r, lo, hi;
+ int ret;
+
+@@ -223,17 +233,32 @@ mt7530_mii_read(struct mt7530_priv *priv
+
+ /* MT7530 uses 31 as the pseudo port */
+ ret = bus->write(bus, 0x1f, 0x1f, page);
+- if (ret < 0) {
++ if (ret < 0)
++ return ret;
++
++ lo = bus->read(bus, 0x1f, r);
++ hi = bus->read(bus, 0x1f, 0x10);
++
++ *val = (hi << 16) | (lo & 0xffff);
++
++ return 0;
++}
++
++static u32
++mt7530_mii_read(struct mt7530_priv *priv, u32 reg)
++{
++ int ret;
++ u32 val;
++
++ ret = regmap_read(priv->regmap, reg, &val);
++ if (ret) {
+ WARN_ON_ONCE(1);
+- dev_err(&bus->dev,
++ dev_err(priv->dev,
+ "failed to read mt7530 register\n");
+ return 0;
+ }
+
+- lo = bus->read(bus, 0x1f, r);
+- hi = bus->read(bus, 0x1f, 0x10);
+-
+- return (hi << 16) | (lo & 0xffff);
++ return val;
+ }
+
+ static void
+@@ -283,14 +308,10 @@ mt7530_rmw(struct mt7530_priv *priv, u32
+ u32 mask, u32 set)
+ {
+ struct mii_bus *bus = priv->bus;
+- u32 val;
+
+ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+- val = mt7530_mii_read(priv, reg);
+- val &= ~mask;
+- val |= set;
+- mt7530_mii_write(priv, reg, val);
++ regmap_update_bits(priv->regmap, reg, mask, set);
+
+ mutex_unlock(&bus->mdio_lock);
+ }
+@@ -298,7 +319,7 @@ mt7530_rmw(struct mt7530_priv *priv, u32
+ static void
+ mt7530_set(struct mt7530_priv *priv, u32 reg, u32 val)
+ {
+- mt7530_rmw(priv, reg, 0, val);
++ mt7530_rmw(priv, reg, val, val);
+ }
+
+ static void
+@@ -2937,22 +2958,6 @@ static const struct phylink_pcs_ops mt75
+ .pcs_an_restart = mt7530_pcs_an_restart,
+ };
+
+-static int mt7530_regmap_read(void *context, unsigned int reg, unsigned int *val)
+-{
+- struct mt7530_priv *priv = context;
+-
+- *val = mt7530_mii_read(priv, reg);
+- return 0;
+-};
+-
+-static int mt7530_regmap_write(void *context, unsigned int reg, unsigned int val)
+-{
+- struct mt7530_priv *priv = context;
+-
+- mt7530_mii_write(priv, reg, val);
+- return 0;
+-};
+-
+ static void
+ mt7530_mdio_regmap_lock(void *mdio_lock)
+ {
+@@ -2965,7 +2970,7 @@ mt7530_mdio_regmap_unlock(void *mdio_loc
+ mutex_unlock(mdio_lock);
+ }
+
+-static const struct regmap_bus mt7531_regmap_bus = {
++static const struct regmap_bus mt7530_regmap_bus = {
+ .reg_write = mt7530_regmap_write,
+ .reg_read = mt7530_regmap_read,
+ };
+@@ -2998,7 +3003,7 @@ mt7531_create_sgmii(struct mt7530_priv *
+ mt7531_pcs_config[i]->lock_arg = &priv->bus->mdio_lock;
+
+ regmap = devm_regmap_init(priv->dev,
+- &mt7531_regmap_bus, priv,
++ &mt7530_regmap_bus, priv->bus,
+ mt7531_pcs_config[i]);
+ if (IS_ERR(regmap)) {
+ ret = PTR_ERR(regmap);
+@@ -3163,6 +3168,7 @@ MODULE_DEVICE_TABLE(of, mt7530_of_match)
+ static int
+ mt7530_probe(struct mdio_device *mdiodev)
+ {
++ static struct regmap_config *regmap_config;
+ struct mt7530_priv *priv;
+ struct device_node *dn;
+
+@@ -3242,6 +3248,21 @@ mt7530_probe(struct mdio_device *mdiodev
+ mutex_init(&priv->reg_mutex);
+ dev_set_drvdata(&mdiodev->dev, priv);
+
++ regmap_config = devm_kzalloc(&mdiodev->dev, sizeof(*regmap_config),
++ GFP_KERNEL);
++ if (!regmap_config)
++ return -ENOMEM;
++
++ regmap_config->reg_bits = 16;
++ regmap_config->val_bits = 32;
++ regmap_config->reg_stride = 4;
++ regmap_config->max_register = MT7530_CREV;
++ regmap_config->disable_locking = true;
++ priv->regmap = devm_regmap_init(priv->dev, &mt7530_regmap_bus,
++ priv->bus, regmap_config);
++ if (IS_ERR(priv->regmap))
++ return PTR_ERR(priv->regmap);
++
+ return dsa_register_switch(priv->ds);
+ }
+
+--- a/drivers/net/dsa/mt7530.h
++++ b/drivers/net/dsa/mt7530.h
+@@ -754,6 +754,7 @@ struct mt753x_info {
+ * @dev: The device pointer
+ * @ds: The pointer to the dsa core structure
+ * @bus: The bus used for the device and built-in PHY
++ * @regmap: The regmap instance representing all switch registers
+ * @rstc: The pointer to reset control used by MCM
+ * @core_pwr: The power supplied into the core
+ * @io_pwr: The power supplied into the I/O
+@@ -774,6 +775,7 @@ struct mt7530_priv {
+ struct device *dev;
+ struct dsa_switch *ds;
+ struct mii_bus *bus;
++ struct regmap *regmap;
+ struct reset_control *rstc;
+ struct regulator *core_pwr;
+ struct regulator *io_pwr;
diff --git a/target/linux/generic/backport-6.6/790-v6.4-0005-net-dsa-mt7530-move-SGMII-PCS-creation-to-mt7530_pro.patch b/target/linux/generic/backport-6.6/790-v6.4-0005-net-dsa-mt7530-move-SGMII-PCS-creation-to-mt7530_pro.patch
new file mode 100644
index 0000000000..6c5bebdd80
--- /dev/null
+++ b/target/linux/generic/backport-6.6/790-v6.4-0005-net-dsa-mt7530-move-SGMII-PCS-creation-to-mt7530_pro.patch
@@ -0,0 +1,54 @@
+From f3cf1d06e2aef644b426c23b4bb570780b1f8d47 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 3 Apr 2023 02:18:04 +0100
+Subject: [PATCH 05/13] net: dsa: mt7530: move SGMII PCS creation to
+ mt7530_probe function
+
+Move creating the SGMII PCS from mt753x_setup() to the more appropriate
+mt7530_probe() function.
+This is done also in preparation of moving all functions related to
+MDIO-connected MT753x switches to a separate module.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/mt7530.c | 13 +++++++------
+ 1 file changed, 7 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/dsa/mt7530.c
++++ b/drivers/net/dsa/mt7530.c
+@@ -3049,12 +3049,6 @@ mt753x_setup(struct dsa_switch *ds)
+ if (ret && priv->irq)
+ mt7530_free_irq_common(priv);
+
+- if (priv->id == ID_MT7531) {
+- ret = mt7531_create_sgmii(priv);
+- if (ret && priv->irq)
+- mt7530_free_irq_common(priv);
+- }
+-
+ return ret;
+ }
+
+@@ -3171,6 +3165,7 @@ mt7530_probe(struct mdio_device *mdiodev
+ static struct regmap_config *regmap_config;
+ struct mt7530_priv *priv;
+ struct device_node *dn;
++ int ret;
+
+ dn = mdiodev->dev.of_node;
+
+@@ -3263,6 +3258,12 @@ mt7530_probe(struct mdio_device *mdiodev
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
++ if (priv->id == ID_MT7531) {
++ ret = mt7531_create_sgmii(priv);
++ if (ret)
++ return ret;
++ }
++
+ return dsa_register_switch(priv->ds);
+ }
+
diff --git a/target/linux/generic/backport-6.6/790-v6.4-0006-net-dsa-mt7530-introduce-mutex-helpers.patch b/target/linux/generic/backport-6.6/790-v6.4-0006-net-dsa-mt7530-introduce-mutex-helpers.patch
new file mode 100644
index 0000000000..a8933d2cf4
--- /dev/null
+++ b/target/linux/generic/backport-6.6/790-v6.4-0006-net-dsa-mt7530-introduce-mutex-helpers.patch
@@ -0,0 +1,273 @@
+From e4729ae7c095c0c87794bff47ea43e35d69de986 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 3 Apr 2023 02:18:16 +0100
+Subject: [PATCH 06/13] net: dsa: mt7530: introduce mutex helpers
+
+As the MDIO bus lock only needs to be involved if actually operating
+on an MDIO-connected switch we will need to skip locking for built-in
+switches which are accessed via MMIO.
+Create helper functions which simplify that upcoming change.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/mt7530.c | 73 ++++++++++++++++++++--------------------
+ 1 file changed, 36 insertions(+), 37 deletions(-)
+
+--- a/drivers/net/dsa/mt7530.c
++++ b/drivers/net/dsa/mt7530.c
+@@ -143,31 +143,40 @@ err:
+ }
+
+ static void
+-core_write(struct mt7530_priv *priv, u32 reg, u32 val)
++mt7530_mutex_lock(struct mt7530_priv *priv)
+ {
+- struct mii_bus *bus = priv->bus;
++ mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
++}
+
+- mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++static void
++mt7530_mutex_unlock(struct mt7530_priv *priv)
++{
++ mutex_unlock(&priv->bus->mdio_lock);
++}
++
++static void
++core_write(struct mt7530_priv *priv, u32 reg, u32 val)
++{
++ mt7530_mutex_lock(priv);
+
+ core_write_mmd_indirect(priv, reg, MDIO_MMD_VEND2, val);
+
+- mutex_unlock(&bus->mdio_lock);
++ mt7530_mutex_unlock(priv);
+ }
+
+ static void
+ core_rmw(struct mt7530_priv *priv, u32 reg, u32 mask, u32 set)
+ {
+- struct mii_bus *bus = priv->bus;
+ u32 val;
+
+- mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ mt7530_mutex_lock(priv);
+
+ val = core_read_mmd_indirect(priv, reg, MDIO_MMD_VEND2);
+ val &= ~mask;
+ val |= set;
+ core_write_mmd_indirect(priv, reg, MDIO_MMD_VEND2, val);
+
+- mutex_unlock(&bus->mdio_lock);
++ mt7530_mutex_unlock(priv);
+ }
+
+ static void
+@@ -264,13 +273,11 @@ mt7530_mii_read(struct mt7530_priv *priv
+ static void
+ mt7530_write(struct mt7530_priv *priv, u32 reg, u32 val)
+ {
+- struct mii_bus *bus = priv->bus;
+-
+- mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ mt7530_mutex_lock(priv);
+
+ mt7530_mii_write(priv, reg, val);
+
+- mutex_unlock(&bus->mdio_lock);
++ mt7530_mutex_unlock(priv);
+ }
+
+ static u32
+@@ -282,14 +289,13 @@ _mt7530_unlocked_read(struct mt7530_dumm
+ static u32
+ _mt7530_read(struct mt7530_dummy_poll *p)
+ {
+- struct mii_bus *bus = p->priv->bus;
+ u32 val;
+
+- mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ mt7530_mutex_lock(p->priv);
+
+ val = mt7530_mii_read(p->priv, p->reg);
+
+- mutex_unlock(&bus->mdio_lock);
++ mt7530_mutex_unlock(p->priv);
+
+ return val;
+ }
+@@ -307,13 +313,11 @@ static void
+ mt7530_rmw(struct mt7530_priv *priv, u32 reg,
+ u32 mask, u32 set)
+ {
+- struct mii_bus *bus = priv->bus;
+-
+- mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ mt7530_mutex_lock(priv);
+
+ regmap_update_bits(priv->regmap, reg, mask, set);
+
+- mutex_unlock(&bus->mdio_lock);
++ mt7530_mutex_unlock(priv);
+ }
+
+ static void
+@@ -645,14 +649,13 @@ static int
+ mt7531_ind_c45_phy_read(struct mt7530_priv *priv, int port, int devad,
+ int regnum)
+ {
+- struct mii_bus *bus = priv->bus;
+ struct mt7530_dummy_poll p;
+ u32 reg, val;
+ int ret;
+
+ INIT_MT7530_DUMMY_POLL(&p, priv, MT7531_PHY_IAC);
+
+- mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ mt7530_mutex_lock(priv);
+
+ ret = readx_poll_timeout(_mt7530_unlocked_read, &p, val,
+ !(val & MT7531_PHY_ACS_ST), 20, 100000);
+@@ -685,7 +688,7 @@ mt7531_ind_c45_phy_read(struct mt7530_pr
+
+ ret = val & MT7531_MDIO_RW_DATA_MASK;
+ out:
+- mutex_unlock(&bus->mdio_lock);
++ mt7530_mutex_unlock(priv);
+
+ return ret;
+ }
+@@ -694,14 +697,13 @@ static int
+ mt7531_ind_c45_phy_write(struct mt7530_priv *priv, int port, int devad,
+ int regnum, u32 data)
+ {
+- struct mii_bus *bus = priv->bus;
+ struct mt7530_dummy_poll p;
+ u32 val, reg;
+ int ret;
+
+ INIT_MT7530_DUMMY_POLL(&p, priv, MT7531_PHY_IAC);
+
+- mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ mt7530_mutex_lock(priv);
+
+ ret = readx_poll_timeout(_mt7530_unlocked_read, &p, val,
+ !(val & MT7531_PHY_ACS_ST), 20, 100000);
+@@ -733,7 +735,7 @@ mt7531_ind_c45_phy_write(struct mt7530_p
+ }
+
+ out:
+- mutex_unlock(&bus->mdio_lock);
++ mt7530_mutex_unlock(priv);
+
+ return ret;
+ }
+@@ -741,14 +743,13 @@ out:
+ static int
+ mt7531_ind_c22_phy_read(struct mt7530_priv *priv, int port, int regnum)
+ {
+- struct mii_bus *bus = priv->bus;
+ struct mt7530_dummy_poll p;
+ int ret;
+ u32 val;
+
+ INIT_MT7530_DUMMY_POLL(&p, priv, MT7531_PHY_IAC);
+
+- mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ mt7530_mutex_lock(priv);
+
+ ret = readx_poll_timeout(_mt7530_unlocked_read, &p, val,
+ !(val & MT7531_PHY_ACS_ST), 20, 100000);
+@@ -771,7 +772,7 @@ mt7531_ind_c22_phy_read(struct mt7530_pr
+
+ ret = val & MT7531_MDIO_RW_DATA_MASK;
+ out:
+- mutex_unlock(&bus->mdio_lock);
++ mt7530_mutex_unlock(priv);
+
+ return ret;
+ }
+@@ -780,14 +781,13 @@ static int
+ mt7531_ind_c22_phy_write(struct mt7530_priv *priv, int port, int regnum,
+ u16 data)
+ {
+- struct mii_bus *bus = priv->bus;
+ struct mt7530_dummy_poll p;
+ int ret;
+ u32 reg;
+
+ INIT_MT7530_DUMMY_POLL(&p, priv, MT7531_PHY_IAC);
+
+- mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ mt7530_mutex_lock(priv);
+
+ ret = readx_poll_timeout(_mt7530_unlocked_read, &p, reg,
+ !(reg & MT7531_PHY_ACS_ST), 20, 100000);
+@@ -809,7 +809,7 @@ mt7531_ind_c22_phy_write(struct mt7530_p
+ }
+
+ out:
+- mutex_unlock(&bus->mdio_lock);
++ mt7530_mutex_unlock(priv);
+
+ return ret;
+ }
+@@ -1125,7 +1125,6 @@ static int
+ mt7530_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
+ {
+ struct mt7530_priv *priv = ds->priv;
+- struct mii_bus *bus = priv->bus;
+ int length;
+ u32 val;
+
+@@ -1136,7 +1135,7 @@ mt7530_port_change_mtu(struct dsa_switch
+ if (!dsa_is_cpu_port(ds, port))
+ return 0;
+
+- mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ mt7530_mutex_lock(priv);
+
+ val = mt7530_mii_read(priv, MT7530_GMACCR);
+ val &= ~MAX_RX_PKT_LEN_MASK;
+@@ -1157,7 +1156,7 @@ mt7530_port_change_mtu(struct dsa_switch
+
+ mt7530_mii_write(priv, MT7530_GMACCR, val);
+
+- mutex_unlock(&bus->mdio_lock);
++ mt7530_mutex_unlock(priv);
+
+ return 0;
+ }
+@@ -1958,10 +1957,10 @@ mt7530_irq_thread_fn(int irq, void *dev_
+ u32 val;
+ int p;
+
+- mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
++ mt7530_mutex_lock(priv);
+ val = mt7530_mii_read(priv, MT7530_SYS_INT_STS);
+ mt7530_mii_write(priv, MT7530_SYS_INT_STS, val);
+- mutex_unlock(&priv->bus->mdio_lock);
++ mt7530_mutex_unlock(priv);
+
+ for (p = 0; p < MT7530_NUM_PHYS; p++) {
+ if (BIT(p) & val) {
+@@ -1997,7 +1996,7 @@ mt7530_irq_bus_lock(struct irq_data *d)
+ {
+ struct mt7530_priv *priv = irq_data_get_irq_chip_data(d);
+
+- mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
++ mt7530_mutex_lock(priv);
+ }
+
+ static void
+@@ -2006,7 +2005,7 @@ mt7530_irq_bus_sync_unlock(struct irq_da
+ struct mt7530_priv *priv = irq_data_get_irq_chip_data(d);
+
+ mt7530_mii_write(priv, MT7530_SYS_INT_EN, priv->irq_enable);
+- mutex_unlock(&priv->bus->mdio_lock);
++ mt7530_mutex_unlock(priv);
+ }
+
+ static struct irq_chip mt7530_irq_chip = {
diff --git a/target/linux/generic/backport-6.6/790-v6.4-0007-net-dsa-mt7530-move-p5_intf_modes-function-to-mt7530.patch b/target/linux/generic/backport-6.6/790-v6.4-0007-net-dsa-mt7530-move-p5_intf_modes-function-to-mt7530.patch
new file mode 100644
index 0000000000..6c68dc0c4f
--- /dev/null
+++ b/target/linux/generic/backport-6.6/790-v6.4-0007-net-dsa-mt7530-move-p5_intf_modes-function-to-mt7530.patch
@@ -0,0 +1,75 @@
+From 0d7ae94a0c581f86939bebec0b6ccd66e640d1d8 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 3 Apr 2023 02:18:28 +0100
+Subject: [PATCH 07/13] net: dsa: mt7530: move p5_intf_modes() function to
+ mt7530.c
+
+In preparation of splitting mt7530.c into a driver for MDIO-connected
+as well as MDIO-accessed built-in switches on one hand and MMIO-accessed
+built-in switches move the p5_inft_modes() function from mt7530.h to
+mt7530.c. The function is only needed there and will trigger a compiler
+warning about a defined but unused function otherwise when including
+mt7530.h in the to-be-introduced bus-specific drivers.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/mt7530.c | 18 ++++++++++++++++++
+ drivers/net/dsa/mt7530.h | 18 ------------------
+ 2 files changed, 18 insertions(+), 18 deletions(-)
+
+--- a/drivers/net/dsa/mt7530.c
++++ b/drivers/net/dsa/mt7530.c
+@@ -950,6 +950,24 @@ mt7530_set_ageing_time(struct dsa_switch
+ return 0;
+ }
+
++static const char *p5_intf_modes(unsigned int p5_interface)
++{
++ switch (p5_interface) {
++ case P5_DISABLED:
++ return "DISABLED";
++ case P5_INTF_SEL_PHY_P0:
++ return "PHY P0";
++ case P5_INTF_SEL_PHY_P4:
++ return "PHY P4";
++ case P5_INTF_SEL_GMAC5:
++ return "GMAC5";
++ case P5_INTF_SEL_GMAC5_SGMII:
++ return "GMAC5_SGMII";
++ default:
++ return "unknown";
++ }
++}
++
+ static void mt7530_setup_port5(struct dsa_switch *ds, phy_interface_t interface)
+ {
+ struct mt7530_priv *priv = ds->priv;
+--- a/drivers/net/dsa/mt7530.h
++++ b/drivers/net/dsa/mt7530.h
+@@ -689,24 +689,6 @@ enum p5_interface_select {
+ P5_INTF_SEL_GMAC5_SGMII,
+ };
+
+-static const char *p5_intf_modes(unsigned int p5_interface)
+-{
+- switch (p5_interface) {
+- case P5_DISABLED:
+- return "DISABLED";
+- case P5_INTF_SEL_PHY_P0:
+- return "PHY P0";
+- case P5_INTF_SEL_PHY_P4:
+- return "PHY P4";
+- case P5_INTF_SEL_GMAC5:
+- return "GMAC5";
+- case P5_INTF_SEL_GMAC5_SGMII:
+- return "GMAC5_SGMII";
+- default:
+- return "unknown";
+- }
+-}
+-
+ struct mt7530_priv;
+
+ struct mt753x_pcs {
diff --git a/target/linux/generic/backport-6.6/790-v6.4-0008-net-dsa-mt7530-introduce-mt7530_probe_common-helper-.patch b/target/linux/generic/backport-6.6/790-v6.4-0008-net-dsa-mt7530-introduce-mt7530_probe_common-helper-.patch
new file mode 100644
index 0000000000..dc4fcb6aa1
--- /dev/null
+++ b/target/linux/generic/backport-6.6/790-v6.4-0008-net-dsa-mt7530-introduce-mt7530_probe_common-helper-.patch
@@ -0,0 +1,155 @@
+From 4d632005c90e253c000d0db73b7cdb9d8dc2e2dd Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 3 Apr 2023 02:18:39 +0100
+Subject: [PATCH 08/13] net: dsa: mt7530: introduce mt7530_probe_common helper
+ function
+
+Move commonly used parts from mt7530_probe into new mt7530_probe_common
+helper function which will be used by both, mt7530_probe and the
+to-be-introduced mt7988_probe.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/mt7530.c | 98 ++++++++++++++++++++++------------------
+ 1 file changed, 54 insertions(+), 44 deletions(-)
+
+--- a/drivers/net/dsa/mt7530.c
++++ b/drivers/net/dsa/mt7530.c
+@@ -3177,44 +3177,21 @@ static const struct of_device_id mt7530_
+ MODULE_DEVICE_TABLE(of, mt7530_of_match);
+
+ static int
+-mt7530_probe(struct mdio_device *mdiodev)
++mt7530_probe_common(struct mt7530_priv *priv)
+ {
+- static struct regmap_config *regmap_config;
+- struct mt7530_priv *priv;
+- struct device_node *dn;
+- int ret;
++ struct device *dev = priv->dev;
+
+- dn = mdiodev->dev.of_node;
+-
+- priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL);
+- if (!priv)
+- return -ENOMEM;
+-
+- priv->ds = devm_kzalloc(&mdiodev->dev, sizeof(*priv->ds), GFP_KERNEL);
++ priv->ds = devm_kzalloc(dev, sizeof(*priv->ds), GFP_KERNEL);
+ if (!priv->ds)
+ return -ENOMEM;
+
+- priv->ds->dev = &mdiodev->dev;
++ priv->ds->dev = dev;
+ priv->ds->num_ports = MT7530_NUM_PORTS;
+
+- /* Use medatek,mcm property to distinguish hardware type that would
+- * casues a little bit differences on power-on sequence.
+- */
+- priv->mcm = of_property_read_bool(dn, "mediatek,mcm");
+- if (priv->mcm) {
+- dev_info(&mdiodev->dev, "MT7530 adapts as multi-chip module\n");
+-
+- priv->rstc = devm_reset_control_get(&mdiodev->dev, "mcm");
+- if (IS_ERR(priv->rstc)) {
+- dev_err(&mdiodev->dev, "Couldn't get our reset line\n");
+- return PTR_ERR(priv->rstc);
+- }
+- }
+-
+ /* Get the hardware identifier from the devicetree node.
+ * We will need it for some of the clock and regulator setup.
+ */
+- priv->info = of_device_get_match_data(&mdiodev->dev);
++ priv->info = of_device_get_match_data(dev);
+ if (!priv->info)
+ return -EINVAL;
+
+@@ -3228,23 +3205,53 @@ mt7530_probe(struct mdio_device *mdiodev
+ return -EINVAL;
+
+ priv->id = priv->info->id;
++ priv->dev = dev;
++ priv->ds->priv = priv;
++ priv->ds->ops = &mt7530_switch_ops;
++ mutex_init(&priv->reg_mutex);
++ dev_set_drvdata(dev, priv);
+
+- if (priv->id == ID_MT7530) {
+- priv->core_pwr = devm_regulator_get(&mdiodev->dev, "core");
+- if (IS_ERR(priv->core_pwr))
+- return PTR_ERR(priv->core_pwr);
++ return 0;
++}
+
+- priv->io_pwr = devm_regulator_get(&mdiodev->dev, "io");
+- if (IS_ERR(priv->io_pwr))
+- return PTR_ERR(priv->io_pwr);
+- }
++static int
++mt7530_probe(struct mdio_device *mdiodev)
++{
++ static struct regmap_config *regmap_config;
++ struct mt7530_priv *priv;
++ struct device_node *dn;
++ int ret;
++
++ dn = mdiodev->dev.of_node;
++
++ priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
+
+- /* Not MCM that indicates switch works as the remote standalone
++ priv->bus = mdiodev->bus;
++ priv->dev = &mdiodev->dev;
++
++ ret = mt7530_probe_common(priv);
++ if (ret)
++ return ret;
++
++ /* Use medatek,mcm property to distinguish hardware type that would
++ * cause a little bit differences on power-on sequence.
++ * Not MCM that indicates switch works as the remote standalone
+ * integrated circuit so the GPIO pin would be used to complete
+ * the reset, otherwise memory-mapped register accessing used
+ * through syscon provides in the case of MCM.
+ */
+- if (!priv->mcm) {
++ priv->mcm = of_property_read_bool(dn, "mediatek,mcm");
++ if (priv->mcm) {
++ dev_info(&mdiodev->dev, "MT7530 adapts as multi-chip module\n");
++
++ priv->rstc = devm_reset_control_get(&mdiodev->dev, "mcm");
++ if (IS_ERR(priv->rstc)) {
++ dev_err(&mdiodev->dev, "Couldn't get our reset line\n");
++ return PTR_ERR(priv->rstc);
++ }
++ } else {
+ priv->reset = devm_gpiod_get_optional(&mdiodev->dev, "reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(priv->reset)) {
+@@ -3253,12 +3260,15 @@ mt7530_probe(struct mdio_device *mdiodev
+ }
+ }
+
+- priv->bus = mdiodev->bus;
+- priv->dev = &mdiodev->dev;
+- priv->ds->priv = priv;
+- priv->ds->ops = &mt7530_switch_ops;
+- mutex_init(&priv->reg_mutex);
+- dev_set_drvdata(&mdiodev->dev, priv);
++ if (priv->id == ID_MT7530) {
++ priv->core_pwr = devm_regulator_get(&mdiodev->dev, "core");
++ if (IS_ERR(priv->core_pwr))
++ return PTR_ERR(priv->core_pwr);
++
++ priv->io_pwr = devm_regulator_get(&mdiodev->dev, "io");
++ if (IS_ERR(priv->io_pwr))
++ return PTR_ERR(priv->io_pwr);
++ }
+
+ regmap_config = devm_kzalloc(&mdiodev->dev, sizeof(*regmap_config),
+ GFP_KERNEL);
diff --git a/target/linux/generic/backport-6.6/790-v6.4-0009-net-dsa-mt7530-introduce-mt7530_remove_common-helper.patch b/target/linux/generic/backport-6.6/790-v6.4-0009-net-dsa-mt7530-introduce-mt7530_remove_common-helper.patch
new file mode 100644
index 0000000000..5df859d2df
--- /dev/null
+++ b/target/linux/generic/backport-6.6/790-v6.4-0009-net-dsa-mt7530-introduce-mt7530_remove_common-helper.patch
@@ -0,0 +1,54 @@
+From 69b838d2629e6b82bcd9e0ab3c1c03f46e5e01d3 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 3 Apr 2023 02:18:50 +0100
+Subject: [PATCH 09/13] net: dsa: mt7530: introduce mt7530_remove_common helper
+ function
+
+Move commonly used parts from mt7530_remove into new
+mt7530_remove_common helper function which will be used by both,
+mt7530_remove and the to-be-introduced mt7988_remove.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/mt7530.c | 18 ++++++++++++------
+ 1 file changed, 12 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/dsa/mt7530.c
++++ b/drivers/net/dsa/mt7530.c
+@@ -3295,6 +3295,17 @@ mt7530_probe(struct mdio_device *mdiodev
+ }
+
+ static void
++mt7530_remove_common(struct mt7530_priv *priv)
++{
++ if (priv->irq)
++ mt7530_free_irq(priv);
++
++ dsa_unregister_switch(priv->ds);
++
++ mutex_destroy(&priv->reg_mutex);
++}
++
++static void
+ mt7530_remove(struct mdio_device *mdiodev)
+ {
+ struct mt7530_priv *priv = dev_get_drvdata(&mdiodev->dev);
+@@ -3313,15 +3324,10 @@ mt7530_remove(struct mdio_device *mdiode
+ dev_err(priv->dev, "Failed to disable io pwr: %d\n",
+ ret);
+
+- if (priv->irq)
+- mt7530_free_irq(priv);
+-
+- dsa_unregister_switch(priv->ds);
++ mt7530_remove_common(priv);
+
+ for (i = 0; i < 2; ++i)
+ mtk_pcs_lynxi_destroy(priv->ports[5 + i].sgmii_pcs);
+-
+- mutex_destroy(&priv->reg_mutex);
+ }
+
+ static void mt7530_shutdown(struct mdio_device *mdiodev)
diff --git a/target/linux/generic/backport-6.6/790-v6.4-0010-net-dsa-mt7530-introduce-separate-MDIO-driver.patch b/target/linux/generic/backport-6.6/790-v6.4-0010-net-dsa-mt7530-introduce-separate-MDIO-driver.patch
new file mode 100644
index 0000000000..d203711884
--- /dev/null
+++ b/target/linux/generic/backport-6.6/790-v6.4-0010-net-dsa-mt7530-introduce-separate-MDIO-driver.patch
@@ -0,0 +1,691 @@
+From 8eceed6dbd74067dbf4d8e39f14734f4d2f35176 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 3 Apr 2023 02:19:13 +0100
+Subject: [PATCH 10/13] net: dsa: mt7530: introduce separate MDIO driver
+
+Split MT7530 switch driver into a common part and a part specific
+for MDIO connected switches and multi-chip modules.
+Move MDIO-specific functions to newly introduced mt7530-mdio.c while
+keeping the common parts in mt7530.c.
+Introduce new Kconfig symbol CONFIG_NET_DSA_MT7530_MDIO which is
+implied by CONFIG_NET_DSA_MT7530.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ MAINTAINERS | 1 +
+ drivers/net/dsa/Kconfig | 16 +-
+ drivers/net/dsa/Makefile | 1 +
+ drivers/net/dsa/mt7530-mdio.c | 271 ++++++++++++++++++++++++++++++++++
+ drivers/net/dsa/mt7530.c | 264 +--------------------------------
+ drivers/net/dsa/mt7530.h | 6 +
+ 6 files changed, 301 insertions(+), 258 deletions(-)
+ create mode 100644 drivers/net/dsa/mt7530-mdio.c
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -13062,6 +13062,7 @@ M: Landen Chao <Landen.Chao@mediatek.com
+ M: DENG Qingfang <dqfext@gmail.com>
+ L: netdev@vger.kernel.org
+ S: Maintained
++F: drivers/net/dsa/mt7530-mdio.c
+ F: drivers/net/dsa/mt7530.*
+ F: net/dsa/tag_mtk.c
+
+--- a/drivers/net/dsa/Kconfig
++++ b/drivers/net/dsa/Kconfig
+@@ -37,10 +37,22 @@ config NET_DSA_MT7530
+ tristate "MediaTek MT753x and MT7621 Ethernet switch support"
+ select NET_DSA_TAG_MTK
+ select MEDIATEK_GE_PHY
++ imply NET_DSA_MT7530_MDIO
++ help
++ This enables support for the MediaTek MT7530 and MT7531 Ethernet
++ switch chips. Multi-chip module MT7530 in MT7621AT, MT7621DAT,
++ MT7621ST and MT7623AI SoCs, and built-in switch in MT7988 SoC are
++ supported as well.
++
++config NET_DSA_MT7530_MDIO
++ tristate "MediaTek MT7530 MDIO interface driver"
++ depends on NET_DSA_MT7530
+ select PCS_MTK_LYNXI
+ help
+- This enables support for the MediaTek MT7530, MT7531, and MT7621
+- Ethernet switch chips.
++ This enables support for the MediaTek MT7530 and MT7531 switch
++ chips which are connected via MDIO, as well as multi-chip
++ module MT7530 which can be found in the MT7621AT, MT7621DAT,
++ MT7621ST and MT7623AI SoCs.
+
+ config NET_DSA_MV88E6060
+ tristate "Marvell 88E6060 ethernet switch chip support"
+--- a/drivers/net/dsa/Makefile
++++ b/drivers/net/dsa/Makefile
+@@ -7,6 +7,7 @@ obj-$(CONFIG_FIXED_PHY) += dsa_loop_bdi
+ endif
+ obj-$(CONFIG_NET_DSA_LANTIQ_GSWIP) += lantiq_gswip.o
+ obj-$(CONFIG_NET_DSA_MT7530) += mt7530.o
++obj-$(CONFIG_NET_DSA_MT7530_MDIO) += mt7530-mdio.o
+ obj-$(CONFIG_NET_DSA_MV88E6060) += mv88e6060.o
+ obj-$(CONFIG_NET_DSA_RZN1_A5PSW) += rzn1_a5psw.o
+ obj-$(CONFIG_NET_DSA_SMSC_LAN9303) += lan9303-core.o
+--- /dev/null
++++ b/drivers/net/dsa/mt7530-mdio.c
+@@ -0,0 +1,271 @@
++// SPDX-License-Identifier: GPL-2.0-only
++
++#include <linux/gpio/consumer.h>
++#include <linux/mdio.h>
++#include <linux/module.h>
++#include <linux/pcs/pcs-mtk-lynxi.h>
++#include <linux/of_irq.h>
++#include <linux/of_mdio.h>
++#include <linux/of_net.h>
++#include <linux/of_platform.h>
++#include <linux/regmap.h>
++#include <linux/reset.h>
++#include <linux/regulator/consumer.h>
++#include <net/dsa.h>
++
++#include "mt7530.h"
++
++static int
++mt7530_regmap_write(void *context, unsigned int reg, unsigned int val)
++{
++ struct mii_bus *bus = context;
++ u16 page, r, lo, hi;
++ int ret;
++
++ page = (reg >> 6) & 0x3ff;
++ r = (reg >> 2) & 0xf;
++ lo = val & 0xffff;
++ hi = val >> 16;
++
++ /* MT7530 uses 31 as the pseudo port */
++ ret = bus->write(bus, 0x1f, 0x1f, page);
++ if (ret < 0)
++ return ret;
++
++ ret = bus->write(bus, 0x1f, r, lo);
++ if (ret < 0)
++ return ret;
++
++ ret = bus->write(bus, 0x1f, 0x10, hi);
++ return ret;
++}
++
++static int
++mt7530_regmap_read(void *context, unsigned int reg, unsigned int *val)
++{
++ struct mii_bus *bus = context;
++ u16 page, r, lo, hi;
++ int ret;
++
++ page = (reg >> 6) & 0x3ff;
++ r = (reg >> 2) & 0xf;
++
++ /* MT7530 uses 31 as the pseudo port */
++ ret = bus->write(bus, 0x1f, 0x1f, page);
++ if (ret < 0)
++ return ret;
++
++ lo = bus->read(bus, 0x1f, r);
++ hi = bus->read(bus, 0x1f, 0x10);
++
++ *val = (hi << 16) | (lo & 0xffff);
++
++ return 0;
++}
++
++static void
++mt7530_mdio_regmap_lock(void *mdio_lock)
++{
++ mutex_lock_nested(mdio_lock, MDIO_MUTEX_NESTED);
++}
++
++static void
++mt7530_mdio_regmap_unlock(void *mdio_lock)
++{
++ mutex_unlock(mdio_lock);
++}
++
++static const struct regmap_bus mt7530_regmap_bus = {
++ .reg_write = mt7530_regmap_write,
++ .reg_read = mt7530_regmap_read,
++};
++
++static int
++mt7531_create_sgmii(struct mt7530_priv *priv)
++{
++ struct regmap_config *mt7531_pcs_config[2];
++ struct phylink_pcs *pcs;
++ struct regmap *regmap;
++ int i, ret = 0;
++
++ for (i = 0; i < 2; i++) {
++ mt7531_pcs_config[i] = devm_kzalloc(priv->dev,
++ sizeof(struct regmap_config),
++ GFP_KERNEL);
++ if (!mt7531_pcs_config[i]) {
++ ret = -ENOMEM;
++ break;
++ }
++
++ mt7531_pcs_config[i]->name = i ? "port6" : "port5";
++ mt7531_pcs_config[i]->reg_bits = 16;
++ mt7531_pcs_config[i]->val_bits = 32;
++ mt7531_pcs_config[i]->reg_stride = 4;
++ mt7531_pcs_config[i]->reg_base = MT7531_SGMII_REG_BASE(5 + i);
++ mt7531_pcs_config[i]->max_register = 0x17c;
++ mt7531_pcs_config[i]->lock = mt7530_mdio_regmap_lock;
++ mt7531_pcs_config[i]->unlock = mt7530_mdio_regmap_unlock;
++ mt7531_pcs_config[i]->lock_arg = &priv->bus->mdio_lock;
++
++ regmap = devm_regmap_init(priv->dev,
++ &mt7530_regmap_bus, priv->bus,
++ mt7531_pcs_config[i]);
++ if (IS_ERR(regmap)) {
++ ret = PTR_ERR(regmap);
++ break;
++ }
++ pcs = mtk_pcs_lynxi_create(priv->dev, regmap,
++ MT7531_PHYA_CTRL_SIGNAL3, 0);
++ if (!pcs) {
++ ret = -ENXIO;
++ break;
++ }
++ priv->ports[5 + i].sgmii_pcs = pcs;
++ }
++
++ if (ret && i)
++ mtk_pcs_lynxi_destroy(priv->ports[5].sgmii_pcs);
++
++ return ret;
++}
++
++static const struct of_device_id mt7530_of_match[] = {
++ { .compatible = "mediatek,mt7621", .data = &mt753x_table[ID_MT7621], },
++ { .compatible = "mediatek,mt7530", .data = &mt753x_table[ID_MT7530], },
++ { .compatible = "mediatek,mt7531", .data = &mt753x_table[ID_MT7531], },
++ { /* sentinel */ },
++};
++MODULE_DEVICE_TABLE(of, mt7530_of_match);
++
++static int
++mt7530_probe(struct mdio_device *mdiodev)
++{
++ static struct regmap_config *regmap_config;
++ struct mt7530_priv *priv;
++ struct device_node *dn;
++ int ret;
++
++ dn = mdiodev->dev.of_node;
++
++ priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
++
++ priv->bus = mdiodev->bus;
++ priv->dev = &mdiodev->dev;
++
++ ret = mt7530_probe_common(priv);
++ if (ret)
++ return ret;
++
++ /* Use medatek,mcm property to distinguish hardware type that would
++ * cause a little bit differences on power-on sequence.
++ * Not MCM that indicates switch works as the remote standalone
++ * integrated circuit so the GPIO pin would be used to complete
++ * the reset, otherwise memory-mapped register accessing used
++ * through syscon provides in the case of MCM.
++ */
++ priv->mcm = of_property_read_bool(dn, "mediatek,mcm");
++ if (priv->mcm) {
++ dev_info(&mdiodev->dev, "MT7530 adapts as multi-chip module\n");
++
++ priv->rstc = devm_reset_control_get(&mdiodev->dev, "mcm");
++ if (IS_ERR(priv->rstc)) {
++ dev_err(&mdiodev->dev, "Couldn't get our reset line\n");
++ return PTR_ERR(priv->rstc);
++ }
++ } else {
++ priv->reset = devm_gpiod_get_optional(&mdiodev->dev, "reset",
++ GPIOD_OUT_LOW);
++ if (IS_ERR(priv->reset)) {
++ dev_err(&mdiodev->dev, "Couldn't get our reset line\n");
++ return PTR_ERR(priv->reset);
++ }
++ }
++
++ if (priv->id == ID_MT7530) {
++ priv->core_pwr = devm_regulator_get(&mdiodev->dev, "core");
++ if (IS_ERR(priv->core_pwr))
++ return PTR_ERR(priv->core_pwr);
++
++ priv->io_pwr = devm_regulator_get(&mdiodev->dev, "io");
++ if (IS_ERR(priv->io_pwr))
++ return PTR_ERR(priv->io_pwr);
++ }
++
++ regmap_config = devm_kzalloc(&mdiodev->dev, sizeof(*regmap_config),
++ GFP_KERNEL);
++ if (!regmap_config)
++ return -ENOMEM;
++
++ regmap_config->reg_bits = 16;
++ regmap_config->val_bits = 32;
++ regmap_config->reg_stride = 4;
++ regmap_config->max_register = MT7530_CREV;
++ regmap_config->disable_locking = true;
++ priv->regmap = devm_regmap_init(priv->dev, &mt7530_regmap_bus,
++ priv->bus, regmap_config);
++ if (IS_ERR(priv->regmap))
++ return PTR_ERR(priv->regmap);
++
++ if (priv->id == ID_MT7531) {
++ ret = mt7531_create_sgmii(priv);
++ if (ret)
++ return ret;
++ }
++
++ return dsa_register_switch(priv->ds);
++}
++
++static void
++mt7530_remove(struct mdio_device *mdiodev)
++{
++ struct mt7530_priv *priv = dev_get_drvdata(&mdiodev->dev);
++ int ret = 0, i;
++
++ if (!priv)
++ return;
++
++ ret = regulator_disable(priv->core_pwr);
++ if (ret < 0)
++ dev_err(priv->dev,
++ "Failed to disable core power: %d\n", ret);
++
++ ret = regulator_disable(priv->io_pwr);
++ if (ret < 0)
++ dev_err(priv->dev, "Failed to disable io pwr: %d\n",
++ ret);
++
++ mt7530_remove_common(priv);
++
++ for (i = 0; i < 2; ++i)
++ mtk_pcs_lynxi_destroy(priv->ports[5 + i].sgmii_pcs);
++}
++
++static void mt7530_shutdown(struct mdio_device *mdiodev)
++{
++ struct mt7530_priv *priv = dev_get_drvdata(&mdiodev->dev);
++
++ if (!priv)
++ return;
++
++ dsa_switch_shutdown(priv->ds);
++
++ dev_set_drvdata(&mdiodev->dev, NULL);
++}
++
++static struct mdio_driver mt7530_mdio_driver = {
++ .probe = mt7530_probe,
++ .remove = mt7530_remove,
++ .shutdown = mt7530_shutdown,
++ .mdiodrv.driver = {
++ .name = "mt7530-mdio",
++ .of_match_table = mt7530_of_match,
++ },
++};
++
++mdio_module_driver(mt7530_mdio_driver);
++
++MODULE_AUTHOR("Sean Wang <sean.wang@mediatek.com>");
++MODULE_DESCRIPTION("Driver for Mediatek MT7530 Switch (MDIO)");
++MODULE_LICENSE("GPL");
+--- a/drivers/net/dsa/mt7530.c
++++ b/drivers/net/dsa/mt7530.c
+@@ -14,7 +14,6 @@
+ #include <linux/of_mdio.h>
+ #include <linux/of_net.h>
+ #include <linux/of_platform.h>
+-#include <linux/pcs/pcs-mtk-lynxi.h>
+ #include <linux/phylink.h>
+ #include <linux/regmap.h>
+ #include <linux/regulator/consumer.h>
+@@ -192,31 +191,6 @@ core_clear(struct mt7530_priv *priv, u32
+ }
+
+ static int
+-mt7530_regmap_write(void *context, unsigned int reg, unsigned int val)
+-{
+- struct mii_bus *bus = context;
+- u16 page, r, lo, hi;
+- int ret;
+-
+- page = (reg >> 6) & 0x3ff;
+- r = (reg >> 2) & 0xf;
+- lo = val & 0xffff;
+- hi = val >> 16;
+-
+- /* MT7530 uses 31 as the pseudo port */
+- ret = bus->write(bus, 0x1f, 0x1f, page);
+- if (ret < 0)
+- return ret;
+-
+- ret = bus->write(bus, 0x1f, r, lo);
+- if (ret < 0)
+- return ret;
+-
+- ret = bus->write(bus, 0x1f, 0x10, hi);
+- return ret;
+-}
+-
+-static int
+ mt7530_mii_write(struct mt7530_priv *priv, u32 reg, u32 val)
+ {
+ int ret;
+@@ -230,29 +204,6 @@ mt7530_mii_write(struct mt7530_priv *pri
+ return ret;
+ }
+
+-static int
+-mt7530_regmap_read(void *context, unsigned int reg, unsigned int *val)
+-{
+- struct mii_bus *bus = context;
+- u16 page, r, lo, hi;
+- int ret;
+-
+- page = (reg >> 6) & 0x3ff;
+- r = (reg >> 2) & 0xf;
+-
+- /* MT7530 uses 31 as the pseudo port */
+- ret = bus->write(bus, 0x1f, 0x1f, page);
+- if (ret < 0)
+- return ret;
+-
+- lo = bus->read(bus, 0x1f, r);
+- hi = bus->read(bus, 0x1f, 0x10);
+-
+- *val = (hi << 16) | (lo & 0xffff);
+-
+- return 0;
+-}
+-
+ static u32
+ mt7530_mii_read(struct mt7530_priv *priv, u32 reg)
+ {
+@@ -2975,72 +2926,6 @@ static const struct phylink_pcs_ops mt75
+ .pcs_an_restart = mt7530_pcs_an_restart,
+ };
+
+-static void
+-mt7530_mdio_regmap_lock(void *mdio_lock)
+-{
+- mutex_lock_nested(mdio_lock, MDIO_MUTEX_NESTED);
+-}
+-
+-static void
+-mt7530_mdio_regmap_unlock(void *mdio_lock)
+-{
+- mutex_unlock(mdio_lock);
+-}
+-
+-static const struct regmap_bus mt7530_regmap_bus = {
+- .reg_write = mt7530_regmap_write,
+- .reg_read = mt7530_regmap_read,
+-};
+-
+-static int
+-mt7531_create_sgmii(struct mt7530_priv *priv)
+-{
+- struct regmap_config *mt7531_pcs_config[2];
+- struct phylink_pcs *pcs;
+- struct regmap *regmap;
+- int i, ret = 0;
+-
+- for (i = 0; i < 2; i++) {
+- mt7531_pcs_config[i] = devm_kzalloc(priv->dev,
+- sizeof(struct regmap_config),
+- GFP_KERNEL);
+- if (!mt7531_pcs_config[i]) {
+- ret = -ENOMEM;
+- break;
+- }
+-
+- mt7531_pcs_config[i]->name = i ? "port6" : "port5";
+- mt7531_pcs_config[i]->reg_bits = 16;
+- mt7531_pcs_config[i]->val_bits = 32;
+- mt7531_pcs_config[i]->reg_stride = 4;
+- mt7531_pcs_config[i]->reg_base = MT7531_SGMII_REG_BASE(5 + i);
+- mt7531_pcs_config[i]->max_register = 0x17c;
+- mt7531_pcs_config[i]->lock = mt7530_mdio_regmap_lock;
+- mt7531_pcs_config[i]->unlock = mt7530_mdio_regmap_unlock;
+- mt7531_pcs_config[i]->lock_arg = &priv->bus->mdio_lock;
+-
+- regmap = devm_regmap_init(priv->dev,
+- &mt7530_regmap_bus, priv->bus,
+- mt7531_pcs_config[i]);
+- if (IS_ERR(regmap)) {
+- ret = PTR_ERR(regmap);
+- break;
+- }
+- pcs = mtk_pcs_lynxi_create(priv->dev, regmap,
+- MT7531_PHYA_CTRL_SIGNAL3, 0);
+- if (!pcs) {
+- ret = -ENXIO;
+- break;
+- }
+- priv->ports[5 + i].sgmii_pcs = pcs;
+- }
+-
+- if (ret && i)
+- mtk_pcs_lynxi_destroy(priv->ports[5].sgmii_pcs);
+-
+- return ret;
+-}
+-
+ static int
+ mt753x_setup(struct dsa_switch *ds)
+ {
+@@ -3099,7 +2984,7 @@ static int mt753x_set_mac_eee(struct dsa
+ return 0;
+ }
+
+-static const struct dsa_switch_ops mt7530_switch_ops = {
++const struct dsa_switch_ops mt7530_switch_ops = {
+ .get_tag_protocol = mtk_get_tag_protocol,
+ .setup = mt753x_setup,
+ .get_strings = mt7530_get_strings,
+@@ -3133,8 +3018,9 @@ static const struct dsa_switch_ops mt753
+ .get_mac_eee = mt753x_get_mac_eee,
+ .set_mac_eee = mt753x_set_mac_eee,
+ };
++EXPORT_SYMBOL_GPL(mt7530_switch_ops);
+
+-static const struct mt753x_info mt753x_table[] = {
++const struct mt753x_info mt753x_table[] = {
+ [ID_MT7621] = {
+ .id = ID_MT7621,
+ .pcs_ops = &mt7530_pcs_ops,
+@@ -3167,16 +3053,9 @@ static const struct mt753x_info mt753x_t
+ .mac_port_config = mt7531_mac_config,
+ },
+ };
++EXPORT_SYMBOL_GPL(mt753x_table);
+
+-static const struct of_device_id mt7530_of_match[] = {
+- { .compatible = "mediatek,mt7621", .data = &mt753x_table[ID_MT7621], },
+- { .compatible = "mediatek,mt7530", .data = &mt753x_table[ID_MT7530], },
+- { .compatible = "mediatek,mt7531", .data = &mt753x_table[ID_MT7531], },
+- { /* sentinel */ },
+-};
+-MODULE_DEVICE_TABLE(of, mt7530_of_match);
+-
+-static int
++int
+ mt7530_probe_common(struct mt7530_priv *priv)
+ {
+ struct device *dev = priv->dev;
+@@ -3213,88 +3092,9 @@ mt7530_probe_common(struct mt7530_priv *
+
+ return 0;
+ }
++EXPORT_SYMBOL_GPL(mt7530_probe_common);
+
+-static int
+-mt7530_probe(struct mdio_device *mdiodev)
+-{
+- static struct regmap_config *regmap_config;
+- struct mt7530_priv *priv;
+- struct device_node *dn;
+- int ret;
+-
+- dn = mdiodev->dev.of_node;
+-
+- priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL);
+- if (!priv)
+- return -ENOMEM;
+-
+- priv->bus = mdiodev->bus;
+- priv->dev = &mdiodev->dev;
+-
+- ret = mt7530_probe_common(priv);
+- if (ret)
+- return ret;
+-
+- /* Use medatek,mcm property to distinguish hardware type that would
+- * cause a little bit differences on power-on sequence.
+- * Not MCM that indicates switch works as the remote standalone
+- * integrated circuit so the GPIO pin would be used to complete
+- * the reset, otherwise memory-mapped register accessing used
+- * through syscon provides in the case of MCM.
+- */
+- priv->mcm = of_property_read_bool(dn, "mediatek,mcm");
+- if (priv->mcm) {
+- dev_info(&mdiodev->dev, "MT7530 adapts as multi-chip module\n");
+-
+- priv->rstc = devm_reset_control_get(&mdiodev->dev, "mcm");
+- if (IS_ERR(priv->rstc)) {
+- dev_err(&mdiodev->dev, "Couldn't get our reset line\n");
+- return PTR_ERR(priv->rstc);
+- }
+- } else {
+- priv->reset = devm_gpiod_get_optional(&mdiodev->dev, "reset",
+- GPIOD_OUT_LOW);
+- if (IS_ERR(priv->reset)) {
+- dev_err(&mdiodev->dev, "Couldn't get our reset line\n");
+- return PTR_ERR(priv->reset);
+- }
+- }
+-
+- if (priv->id == ID_MT7530) {
+- priv->core_pwr = devm_regulator_get(&mdiodev->dev, "core");
+- if (IS_ERR(priv->core_pwr))
+- return PTR_ERR(priv->core_pwr);
+-
+- priv->io_pwr = devm_regulator_get(&mdiodev->dev, "io");
+- if (IS_ERR(priv->io_pwr))
+- return PTR_ERR(priv->io_pwr);
+- }
+-
+- regmap_config = devm_kzalloc(&mdiodev->dev, sizeof(*regmap_config),
+- GFP_KERNEL);
+- if (!regmap_config)
+- return -ENOMEM;
+-
+- regmap_config->reg_bits = 16;
+- regmap_config->val_bits = 32;
+- regmap_config->reg_stride = 4;
+- regmap_config->max_register = MT7530_CREV;
+- regmap_config->disable_locking = true;
+- priv->regmap = devm_regmap_init(priv->dev, &mt7530_regmap_bus,
+- priv->bus, regmap_config);
+- if (IS_ERR(priv->regmap))
+- return PTR_ERR(priv->regmap);
+-
+- if (priv->id == ID_MT7531) {
+- ret = mt7531_create_sgmii(priv);
+- if (ret)
+- return ret;
+- }
+-
+- return dsa_register_switch(priv->ds);
+-}
+-
+-static void
++void
+ mt7530_remove_common(struct mt7530_priv *priv)
+ {
+ if (priv->irq)
+@@ -3304,55 +3104,7 @@ mt7530_remove_common(struct mt7530_priv
+
+ mutex_destroy(&priv->reg_mutex);
+ }
+-
+-static void
+-mt7530_remove(struct mdio_device *mdiodev)
+-{
+- struct mt7530_priv *priv = dev_get_drvdata(&mdiodev->dev);
+- int ret = 0, i;
+-
+- if (!priv)
+- return;
+-
+- ret = regulator_disable(priv->core_pwr);
+- if (ret < 0)
+- dev_err(priv->dev,
+- "Failed to disable core power: %d\n", ret);
+-
+- ret = regulator_disable(priv->io_pwr);
+- if (ret < 0)
+- dev_err(priv->dev, "Failed to disable io pwr: %d\n",
+- ret);
+-
+- mt7530_remove_common(priv);
+-
+- for (i = 0; i < 2; ++i)
+- mtk_pcs_lynxi_destroy(priv->ports[5 + i].sgmii_pcs);
+-}
+-
+-static void mt7530_shutdown(struct mdio_device *mdiodev)
+-{
+- struct mt7530_priv *priv = dev_get_drvdata(&mdiodev->dev);
+-
+- if (!priv)
+- return;
+-
+- dsa_switch_shutdown(priv->ds);
+-
+- dev_set_drvdata(&mdiodev->dev, NULL);
+-}
+-
+-static struct mdio_driver mt7530_mdio_driver = {
+- .probe = mt7530_probe,
+- .remove = mt7530_remove,
+- .shutdown = mt7530_shutdown,
+- .mdiodrv.driver = {
+- .name = "mt7530",
+- .of_match_table = mt7530_of_match,
+- },
+-};
+-
+-mdio_module_driver(mt7530_mdio_driver);
++EXPORT_SYMBOL_GPL(mt7530_remove_common);
+
+ MODULE_AUTHOR("Sean Wang <sean.wang@mediatek.com>");
+ MODULE_DESCRIPTION("Driver for Mediatek MT7530 Switch");
+--- a/drivers/net/dsa/mt7530.h
++++ b/drivers/net/dsa/mt7530.h
+@@ -814,4 +814,10 @@ static inline void INIT_MT7530_DUMMY_POL
+ p->reg = reg;
+ }
+
++int mt7530_probe_common(struct mt7530_priv *priv);
++void mt7530_remove_common(struct mt7530_priv *priv);
++
++extern const struct dsa_switch_ops mt7530_switch_ops;
++extern const struct mt753x_info mt753x_table[];
++
+ #endif /* __MT7530_H */
diff --git a/target/linux/generic/backport-6.6/790-v6.4-0011-net-dsa-mt7530-skip-locking-if-MDIO-bus-isn-t-presen.patch b/target/linux/generic/backport-6.6/790-v6.4-0011-net-dsa-mt7530-skip-locking-if-MDIO-bus-isn-t-presen.patch
new file mode 100644
index 0000000000..01011ed1a0
--- /dev/null
+++ b/target/linux/generic/backport-6.6/790-v6.4-0011-net-dsa-mt7530-skip-locking-if-MDIO-bus-isn-t-presen.patch
@@ -0,0 +1,47 @@
+From a52cadbf76593f8fcb2f4f62cb006e3f2a22ad06 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 3 Apr 2023 02:19:28 +0100
+Subject: [PATCH 11/13] net: dsa: mt7530: skip locking if MDIO bus isn't
+ present
+
+As MT7530 and MT7531 internally use 32-bit wide registers, each access
+to any register of the switch requires several operations on the MDIO
+bus. Hence if there is congruent access, e.g. due to PCS or PHY
+polling, this can mess up and interfere with another ongoing register
+access sequence.
+
+However, the MDIO bus mutex is only relevant for MDIO-connected
+switches. Prepare switches which have there registers directly mapped
+into the SoCs register space via MMIO which do not require such
+locking. There we can simply use regmap's default locking mechanism.
+
+Hence guard mutex operations to only be performed in case of MDIO
+connected switches.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/mt7530.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/dsa/mt7530.c
++++ b/drivers/net/dsa/mt7530.c
+@@ -144,13 +144,15 @@ err:
+ static void
+ mt7530_mutex_lock(struct mt7530_priv *priv)
+ {
+- mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
++ if (priv->bus)
++ mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
+ }
+
+ static void
+ mt7530_mutex_unlock(struct mt7530_priv *priv)
+ {
+- mutex_unlock(&priv->bus->mdio_lock);
++ if (priv->bus)
++ mutex_unlock(&priv->bus->mdio_lock);
+ }
+
+ static void
diff --git a/target/linux/generic/backport-6.6/790-v6.4-0012-net-dsa-mt7530-introduce-driver-for-MT7988-built-in-.patch b/target/linux/generic/backport-6.6/790-v6.4-0012-net-dsa-mt7530-introduce-driver-for-MT7988-built-in-.patch
new file mode 100644
index 0000000000..63bf0e77fa
--- /dev/null
+++ b/target/linux/generic/backport-6.6/790-v6.4-0012-net-dsa-mt7530-introduce-driver-for-MT7988-built-in-.patch
@@ -0,0 +1,421 @@
+From b361015763fedea439f13b336b15ef7bdf1f7d4f Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 3 Apr 2023 02:19:40 +0100
+Subject: [PATCH 12/13] net: dsa: mt7530: introduce driver for MT7988 built-in
+ switch
+
+Add driver for the built-in Gigabit Ethernet switch which can be found
+in the MediaTek MT7988 SoC.
+
+The switch shares most of its design with MT7530 and MT7531, but has
+it's registers mapped into the SoCs register space rather than being
+connected externally or internally via MDIO.
+
+Introduce a new platform driver to support that.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ MAINTAINERS | 2 +
+ drivers/net/dsa/Kconfig | 12 +++
+ drivers/net/dsa/Makefile | 1 +
+ drivers/net/dsa/mt7530-mmio.c | 101 +++++++++++++++++++++++++
+ drivers/net/dsa/mt7530.c | 135 +++++++++++++++++++++++++++++++++-
+ drivers/net/dsa/mt7530.h | 12 +--
+ 6 files changed, 253 insertions(+), 10 deletions(-)
+ create mode 100644 drivers/net/dsa/mt7530-mmio.c
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -13060,9 +13060,11 @@ MEDIATEK SWITCH DRIVER
+ M: Sean Wang <sean.wang@mediatek.com>
+ M: Landen Chao <Landen.Chao@mediatek.com>
+ M: DENG Qingfang <dqfext@gmail.com>
++M: Daniel Golle <daniel@makrotopia.org>
+ L: netdev@vger.kernel.org
+ S: Maintained
+ F: drivers/net/dsa/mt7530-mdio.c
++F: drivers/net/dsa/mt7530-mmio.c
+ F: drivers/net/dsa/mt7530.*
+ F: net/dsa/tag_mtk.c
+
+--- a/drivers/net/dsa/Kconfig
++++ b/drivers/net/dsa/Kconfig
+@@ -38,6 +38,7 @@ config NET_DSA_MT7530
+ select NET_DSA_TAG_MTK
+ select MEDIATEK_GE_PHY
+ imply NET_DSA_MT7530_MDIO
++ imply NET_DSA_MT7530_MMIO
+ help
+ This enables support for the MediaTek MT7530 and MT7531 Ethernet
+ switch chips. Multi-chip module MT7530 in MT7621AT, MT7621DAT,
+@@ -54,6 +55,17 @@ config NET_DSA_MT7530_MDIO
+ module MT7530 which can be found in the MT7621AT, MT7621DAT,
+ MT7621ST and MT7623AI SoCs.
+
++config NET_DSA_MT7530_MMIO
++ tristate "MediaTek MT7530 MMIO interface driver"
++ depends on NET_DSA_MT7530
++ depends on HAS_IOMEM
++ help
++ This enables support for the built-in Ethernet switch found
++ in the MediaTek MT7988 SoC.
++ The switch is a similar design as MT7531, but the switch registers
++ are directly mapped into the SoCs register space rather than being
++ accessible via MDIO.
++
+ config NET_DSA_MV88E6060
+ tristate "Marvell 88E6060 ethernet switch chip support"
+ select NET_DSA_TAG_TRAILER
+--- a/drivers/net/dsa/Makefile
++++ b/drivers/net/dsa/Makefile
+@@ -8,6 +8,7 @@ endif
+ obj-$(CONFIG_NET_DSA_LANTIQ_GSWIP) += lantiq_gswip.o
+ obj-$(CONFIG_NET_DSA_MT7530) += mt7530.o
+ obj-$(CONFIG_NET_DSA_MT7530_MDIO) += mt7530-mdio.o
++obj-$(CONFIG_NET_DSA_MT7530_MMIO) += mt7530-mmio.o
+ obj-$(CONFIG_NET_DSA_MV88E6060) += mv88e6060.o
+ obj-$(CONFIG_NET_DSA_RZN1_A5PSW) += rzn1_a5psw.o
+ obj-$(CONFIG_NET_DSA_SMSC_LAN9303) += lan9303-core.o
+--- /dev/null
++++ b/drivers/net/dsa/mt7530-mmio.c
+@@ -0,0 +1,101 @@
++// SPDX-License-Identifier: GPL-2.0-only
++
++#include <linux/module.h>
++#include <linux/of_platform.h>
++#include <linux/regmap.h>
++#include <linux/regulator/consumer.h>
++#include <linux/reset.h>
++#include <net/dsa.h>
++
++#include "mt7530.h"
++
++static const struct of_device_id mt7988_of_match[] = {
++ { .compatible = "mediatek,mt7988-switch", .data = &mt753x_table[ID_MT7988], },
++ { /* sentinel */ },
++};
++MODULE_DEVICE_TABLE(of, mt7988_of_match);
++
++static int
++mt7988_probe(struct platform_device *pdev)
++{
++ static struct regmap_config *sw_regmap_config;
++ struct mt7530_priv *priv;
++ void __iomem *base_addr;
++ int ret;
++
++ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
++
++ priv->bus = NULL;
++ priv->dev = &pdev->dev;
++
++ ret = mt7530_probe_common(priv);
++ if (ret)
++ return ret;
++
++ priv->rstc = devm_reset_control_get(&pdev->dev, NULL);
++ if (IS_ERR(priv->rstc)) {
++ dev_err(&pdev->dev, "Couldn't get our reset line\n");
++ return PTR_ERR(priv->rstc);
++ }
++
++ base_addr = devm_platform_ioremap_resource(pdev, 0);
++ if (IS_ERR(base_addr)) {
++ dev_err(&pdev->dev, "cannot request I/O memory space\n");
++ return -ENXIO;
++ }
++
++ sw_regmap_config = devm_kzalloc(&pdev->dev, sizeof(*sw_regmap_config), GFP_KERNEL);
++ if (!sw_regmap_config)
++ return -ENOMEM;
++
++ sw_regmap_config->name = "switch";
++ sw_regmap_config->reg_bits = 16;
++ sw_regmap_config->val_bits = 32;
++ sw_regmap_config->reg_stride = 4;
++ sw_regmap_config->max_register = MT7530_CREV;
++ priv->regmap = devm_regmap_init_mmio(&pdev->dev, base_addr, sw_regmap_config);
++ if (IS_ERR(priv->regmap))
++ return PTR_ERR(priv->regmap);
++
++ return dsa_register_switch(priv->ds);
++}
++
++static int
++mt7988_remove(struct platform_device *pdev)
++{
++ struct mt7530_priv *priv = platform_get_drvdata(pdev);
++
++ if (priv)
++ mt7530_remove_common(priv);
++
++ return 0;
++}
++
++static void mt7988_shutdown(struct platform_device *pdev)
++{
++ struct mt7530_priv *priv = platform_get_drvdata(pdev);
++
++ if (!priv)
++ return;
++
++ dsa_switch_shutdown(priv->ds);
++
++ dev_set_drvdata(&pdev->dev, NULL);
++}
++
++static struct platform_driver mt7988_platform_driver = {
++ .probe = mt7988_probe,
++ .remove = mt7988_remove,
++ .shutdown = mt7988_shutdown,
++ .driver = {
++ .name = "mt7530-mmio",
++ .of_match_table = mt7988_of_match,
++ },
++};
++module_platform_driver(mt7988_platform_driver);
++
++MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>");
++MODULE_DESCRIPTION("Driver for Mediatek MT7530 Switch (MMIO)");
++MODULE_LICENSE("GPL");
+--- a/drivers/net/dsa/mt7530.c
++++ b/drivers/net/dsa/mt7530.c
+@@ -2005,6 +2005,47 @@ static const struct irq_domain_ops mt753
+ };
+
+ static void
++mt7988_irq_mask(struct irq_data *d)
++{
++ struct mt7530_priv *priv = irq_data_get_irq_chip_data(d);
++
++ priv->irq_enable &= ~BIT(d->hwirq);
++ mt7530_mii_write(priv, MT7530_SYS_INT_EN, priv->irq_enable);
++}
++
++static void
++mt7988_irq_unmask(struct irq_data *d)
++{
++ struct mt7530_priv *priv = irq_data_get_irq_chip_data(d);
++
++ priv->irq_enable |= BIT(d->hwirq);
++ mt7530_mii_write(priv, MT7530_SYS_INT_EN, priv->irq_enable);
++}
++
++static struct irq_chip mt7988_irq_chip = {
++ .name = KBUILD_MODNAME,
++ .irq_mask = mt7988_irq_mask,
++ .irq_unmask = mt7988_irq_unmask,
++};
++
++static int
++mt7988_irq_map(struct irq_domain *domain, unsigned int irq,
++ irq_hw_number_t hwirq)
++{
++ irq_set_chip_data(irq, domain->host_data);
++ irq_set_chip_and_handler(irq, &mt7988_irq_chip, handle_simple_irq);
++ irq_set_nested_thread(irq, true);
++ irq_set_noprobe(irq);
++
++ return 0;
++}
++
++static const struct irq_domain_ops mt7988_irq_domain_ops = {
++ .map = mt7988_irq_map,
++ .xlate = irq_domain_xlate_onecell,
++};
++
++static void
+ mt7530_setup_mdio_irq(struct mt7530_priv *priv)
+ {
+ struct dsa_switch *ds = priv->ds;
+@@ -2038,8 +2079,15 @@ mt7530_setup_irq(struct mt7530_priv *pri
+ return priv->irq ? : -EINVAL;
+ }
+
+- priv->irq_domain = irq_domain_add_linear(np, MT7530_NUM_PHYS,
+- &mt7530_irq_domain_ops, priv);
++ if (priv->id == ID_MT7988)
++ priv->irq_domain = irq_domain_add_linear(np, MT7530_NUM_PHYS,
++ &mt7988_irq_domain_ops,
++ priv);
++ else
++ priv->irq_domain = irq_domain_add_linear(np, MT7530_NUM_PHYS,
++ &mt7530_irq_domain_ops,
++ priv);
++
+ if (!priv->irq_domain) {
+ dev_err(dev, "failed to create IRQ domain\n");
+ return -ENOMEM;
+@@ -2538,6 +2586,25 @@ static void mt7531_mac_port_get_caps(str
+ }
+ }
+
++static void mt7988_mac_port_get_caps(struct dsa_switch *ds, int port,
++ struct phylink_config *config)
++{
++ phy_interface_zero(config->supported_interfaces);
++
++ switch (port) {
++ case 0 ... 4: /* Internal phy */
++ __set_bit(PHY_INTERFACE_MODE_INTERNAL,
++ config->supported_interfaces);
++ break;
++
++ case 6:
++ __set_bit(PHY_INTERFACE_MODE_INTERNAL,
++ config->supported_interfaces);
++ config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
++ MAC_10000FD;
++ }
++}
++
+ static int
+ mt753x_pad_setup(struct dsa_switch *ds, const struct phylink_link_state *state)
+ {
+@@ -2614,6 +2681,17 @@ static bool mt753x_is_mac_port(u32 port)
+ }
+
+ static int
++mt7988_mac_config(struct dsa_switch *ds, int port, unsigned int mode,
++ phy_interface_t interface)
++{
++ if (dsa_is_cpu_port(ds, port) &&
++ interface == PHY_INTERFACE_MODE_INTERNAL)
++ return 0;
++
++ return -EINVAL;
++}
++
++static int
+ mt7531_mac_config(struct dsa_switch *ds, int port, unsigned int mode,
+ phy_interface_t interface)
+ {
+@@ -2683,7 +2761,8 @@ mt753x_phylink_mac_config(struct dsa_swi
+
+ switch (port) {
+ case 0 ... 4: /* Internal phy */
+- if (state->interface != PHY_INTERFACE_MODE_GMII)
++ if (state->interface != PHY_INTERFACE_MODE_GMII &&
++ state->interface != PHY_INTERFACE_MODE_INTERNAL)
+ goto unsupported;
+ break;
+ case 5: /* 2nd cpu port with phy of port 0 or 4 / external phy */
+@@ -2761,7 +2840,8 @@ static void mt753x_phylink_mac_link_up(s
+ /* MT753x MAC works in 1G full duplex mode for all up-clocked
+ * variants.
+ */
+- if (interface == PHY_INTERFACE_MODE_TRGMII ||
++ if (interface == PHY_INTERFACE_MODE_INTERNAL ||
++ interface == PHY_INTERFACE_MODE_TRGMII ||
+ (phy_interface_mode_is_8023z(interface))) {
+ speed = SPEED_1000;
+ duplex = DUPLEX_FULL;
+@@ -2841,6 +2921,21 @@ mt7531_cpu_port_config(struct dsa_switch
+ return 0;
+ }
+
++static int
++mt7988_cpu_port_config(struct dsa_switch *ds, int port)
++{
++ struct mt7530_priv *priv = ds->priv;
++
++ mt7530_write(priv, MT7530_PMCR_P(port),
++ PMCR_CPU_PORT_SETTING(priv->id));
++
++ mt753x_phylink_mac_link_up(ds, port, MLO_AN_FIXED,
++ PHY_INTERFACE_MODE_INTERNAL, NULL,
++ SPEED_10000, DUPLEX_FULL, true, true);
++
++ return 0;
++}
++
+ static void mt753x_phylink_get_caps(struct dsa_switch *ds, int port,
+ struct phylink_config *config)
+ {
+@@ -2986,6 +3081,27 @@ static int mt753x_set_mac_eee(struct dsa
+ return 0;
+ }
+
++static int mt7988_pad_setup(struct dsa_switch *ds, phy_interface_t interface)
++{
++ return 0;
++}
++
++static int mt7988_setup(struct dsa_switch *ds)
++{
++ struct mt7530_priv *priv = ds->priv;
++
++ /* Reset the switch */
++ reset_control_assert(priv->rstc);
++ usleep_range(20, 50);
++ reset_control_deassert(priv->rstc);
++ usleep_range(20, 50);
++
++ /* Reset the switch PHYs */
++ mt7530_write(priv, MT7530_SYS_CTRL, SYS_CTRL_PHY_RST);
++
++ return mt7531_setup_common(ds);
++}
++
+ const struct dsa_switch_ops mt7530_switch_ops = {
+ .get_tag_protocol = mtk_get_tag_protocol,
+ .setup = mt753x_setup,
+@@ -3054,6 +3170,17 @@ const struct mt753x_info mt753x_table[]
+ .mac_port_get_caps = mt7531_mac_port_get_caps,
+ .mac_port_config = mt7531_mac_config,
+ },
++ [ID_MT7988] = {
++ .id = ID_MT7988,
++ .pcs_ops = &mt7530_pcs_ops,
++ .sw_setup = mt7988_setup,
++ .phy_read = mt7531_ind_phy_read,
++ .phy_write = mt7531_ind_phy_write,
++ .pad_setup = mt7988_pad_setup,
++ .cpu_port_config = mt7988_cpu_port_config,
++ .mac_port_get_caps = mt7988_mac_port_get_caps,
++ .mac_port_config = mt7988_mac_config,
++ },
+ };
+ EXPORT_SYMBOL_GPL(mt753x_table);
+
+--- a/drivers/net/dsa/mt7530.h
++++ b/drivers/net/dsa/mt7530.h
+@@ -18,6 +18,7 @@ enum mt753x_id {
+ ID_MT7530 = 0,
+ ID_MT7621 = 1,
+ ID_MT7531 = 2,
++ ID_MT7988 = 3,
+ };
+
+ #define NUM_TRGMII_CTRL 5
+@@ -54,11 +55,11 @@ enum mt753x_id {
+ #define MT7531_MIRROR_PORT_SET(x) (((x) & MIRROR_MASK) << 16)
+ #define MT7531_CPU_PMAP_MASK GENMASK(7, 0)
+
+-#define MT753X_MIRROR_REG(id) (((id) == ID_MT7531) ? \
++#define MT753X_MIRROR_REG(id) ((((id) == ID_MT7531) || ((id) == ID_MT7988)) ? \
+ MT7531_CFC : MT7530_MFC)
+-#define MT753X_MIRROR_EN(id) (((id) == ID_MT7531) ? \
++#define MT753X_MIRROR_EN(id) ((((id) == ID_MT7531) || ((id) == ID_MT7988)) ? \
+ MT7531_MIRROR_EN : MIRROR_EN)
+-#define MT753X_MIRROR_MASK(id) (((id) == ID_MT7531) ? \
++#define MT753X_MIRROR_MASK(id) ((((id) == ID_MT7531) || ((id) == ID_MT7988)) ? \
+ MT7531_MIRROR_MASK : MIRROR_MASK)
+
+ /* Registers for BPDU and PAE frame control*/
+@@ -302,9 +303,8 @@ enum mt7530_vlan_port_acc_frm {
+ MT7531_FORCE_DPX | \
+ MT7531_FORCE_RX_FC | \
+ MT7531_FORCE_TX_FC)
+-#define PMCR_FORCE_MODE_ID(id) (((id) == ID_MT7531) ? \
+- MT7531_FORCE_MODE : \
+- PMCR_FORCE_MODE)
++#define PMCR_FORCE_MODE_ID(id) ((((id) == ID_MT7531) || ((id) == ID_MT7988)) ? \
++ MT7531_FORCE_MODE : PMCR_FORCE_MODE)
+ #define PMCR_LINK_SETTINGS_MASK (PMCR_TX_EN | PMCR_FORCE_SPEED_1000 | \
+ PMCR_RX_EN | PMCR_FORCE_SPEED_100 | \
+ PMCR_TX_FC_EN | PMCR_RX_FC_EN | \
diff --git a/target/linux/generic/backport-6.6/790-v6.4-0013-net-dsa-mt7530-fix-support-for-MT7531BE.patch b/target/linux/generic/backport-6.6/790-v6.4-0013-net-dsa-mt7530-fix-support-for-MT7531BE.patch
new file mode 100644
index 0000000000..5b5f25e7af
--- /dev/null
+++ b/target/linux/generic/backport-6.6/790-v6.4-0013-net-dsa-mt7530-fix-support-for-MT7531BE.patch
@@ -0,0 +1,118 @@
+From eb1dd407b4be7ca38166a38c56c8edf52c6a399f Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sun, 16 Apr 2023 13:08:14 +0100
+Subject: [PATCH 13/13] net: dsa: mt7530: fix support for MT7531BE
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+There are two variants of the MT7531 switch IC which got different
+features (and pins) regarding port 5:
+ * MT7531AE: SGMII/1000Base-X/2500Base-X SerDes PCS
+ * MT7531BE: RGMII
+
+Moving the creation of the SerDes PCS from mt753x_setup to mt7530_probe
+with commit 6de285229773 ("net: dsa: mt7530: move SGMII PCS creation
+to mt7530_probe function") works fine for MT7531AE which got two
+instances of mtk-pcs-lynxi, however, MT7531BE requires mt7531_pll_setup
+to setup clocks before the single PCS on port 6 (usually used as CPU
+port) starts to work and hence the PCS creation failed on MT7531BE.
+
+Fix this by introducing a pointer to mt7531_create_sgmii function in
+struct mt7530_priv and call it again at the end of mt753x_setup like it
+was before commit 6de285229773 ("net: dsa: mt7530: move SGMII PCS
+creation to mt7530_probe function").
+
+Fixes: 6de285229773 ("net: dsa: mt7530: move SGMII PCS creation to mt7530_probe function")
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Acked-by: Arınç ÜNAL <arinc.unal@arinc9.com>
+Link: https://lore.kernel.org/r/ZDvlLhhqheobUvOK@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/mt7530-mdio.c | 16 ++++++++--------
+ drivers/net/dsa/mt7530.c | 6 ++++++
+ drivers/net/dsa/mt7530.h | 4 ++--
+ 3 files changed, 16 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/dsa/mt7530-mdio.c
++++ b/drivers/net/dsa/mt7530-mdio.c
+@@ -81,14 +81,17 @@ static const struct regmap_bus mt7530_re
+ };
+
+ static int
+-mt7531_create_sgmii(struct mt7530_priv *priv)
++mt7531_create_sgmii(struct mt7530_priv *priv, bool dual_sgmii)
+ {
+- struct regmap_config *mt7531_pcs_config[2];
++ struct regmap_config *mt7531_pcs_config[2] = {};
+ struct phylink_pcs *pcs;
+ struct regmap *regmap;
+ int i, ret = 0;
+
+- for (i = 0; i < 2; i++) {
++ /* MT7531AE has two SGMII units for port 5 and port 6
++ * MT7531BE has only one SGMII unit for port 6
++ */
++ for (i = dual_sgmii ? 0 : 1; i < 2; i++) {
+ mt7531_pcs_config[i] = devm_kzalloc(priv->dev,
+ sizeof(struct regmap_config),
+ GFP_KERNEL);
+@@ -208,11 +211,8 @@ mt7530_probe(struct mdio_device *mdiodev
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
+- if (priv->id == ID_MT7531) {
+- ret = mt7531_create_sgmii(priv);
+- if (ret)
+- return ret;
+- }
++ if (priv->id == ID_MT7531)
++ priv->create_sgmii = mt7531_create_sgmii;
+
+ return dsa_register_switch(priv->ds);
+ }
+--- a/drivers/net/dsa/mt7530.c
++++ b/drivers/net/dsa/mt7530.c
+@@ -3048,6 +3048,12 @@ mt753x_setup(struct dsa_switch *ds)
+ if (ret && priv->irq)
+ mt7530_free_irq_common(priv);
+
++ if (priv->create_sgmii) {
++ ret = priv->create_sgmii(priv, mt7531_dual_sgmii_supported(priv));
++ if (ret && priv->irq)
++ mt7530_free_irq(priv);
++ }
++
+ return ret;
+ }
+
+--- a/drivers/net/dsa/mt7530.h
++++ b/drivers/net/dsa/mt7530.h
+@@ -748,10 +748,10 @@ struct mt753x_info {
+ * registers
+ * @p6_interface Holding the current port 6 interface
+ * @p5_intf_sel: Holding the current port 5 interface select
+- *
+ * @irq: IRQ number of the switch
+ * @irq_domain: IRQ domain of the switch irq_chip
+ * @irq_enable: IRQ enable bits, synced to SYS_INT_EN
++ * @create_sgmii: Pointer to function creating SGMII PCS instance(s)
+ */
+ struct mt7530_priv {
+ struct device *dev;
+@@ -770,7 +770,6 @@ struct mt7530_priv {
+ unsigned int p5_intf_sel;
+ u8 mirror_rx;
+ u8 mirror_tx;
+-
+ struct mt7530_port ports[MT7530_NUM_PORTS];
+ struct mt753x_pcs pcs[MT7530_NUM_PORTS];
+ /* protect among processes for registers access*/
+@@ -778,6 +777,7 @@ struct mt7530_priv {
+ int irq;
+ struct irq_domain *irq_domain;
+ u32 irq_enable;
++ int (*create_sgmii)(struct mt7530_priv *priv, bool dual_sgmii);
+ };
+
+ struct mt7530_hw_vlan_entry {
diff --git a/target/linux/generic/backport-6.6/791-v6.2-01-net-phy-Add-driver-for-Motorcomm-yt8521-gigabit-ethernet.patch b/target/linux/generic/backport-6.6/791-v6.2-01-net-phy-Add-driver-for-Motorcomm-yt8521-gigabit-ethernet.patch
new file mode 100644
index 0000000000..4cdd67cdcc
--- /dev/null
+++ b/target/linux/generic/backport-6.6/791-v6.2-01-net-phy-Add-driver-for-Motorcomm-yt8521-gigabit-ethernet.patch
@@ -0,0 +1,1724 @@
+From 70479a40954cf353e87a486997a3477108c75aa9 Mon Sep 17 00:00:00 2001
+From: Frank <Frank.Sae@motor-comm.com>
+Date: Fri, 28 Oct 2022 17:26:21 +0800
+Subject: [PATCH] net: phy: Add driver for Motorcomm yt8521 gigabit ethernet
+ phy
+
+Add a driver for the motorcomm yt8521 gigabit ethernet phy. We have verified
+ the driver on StarFive VisionFive development board, which is developed by
+ Shanghai StarFive Technology Co., Ltd.. On the board, yt8521 gigabit ethernet
+ phy works in utp mode, RGMII interface, supports 1000M/100M/10M speeds, and
+ wol(magic package).
+
+Signed-off-by: Frank <Frank.Sae@motor-comm.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ MAINTAINERS | 1 +
+ drivers/net/phy/Kconfig | 2 +-
+ drivers/net/phy/motorcomm.c | 1635 ++++++++++++++++++++++++++++++++++-
+ 3 files changed, 1635 insertions(+), 3 deletions(-)
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -13964,6 +13964,7 @@ F: include/uapi/linux/meye.h
+
+ MOTORCOMM PHY DRIVER
+ M: Peter Geis <pgwipeout@gmail.com>
++M: Frank <Frank.Sae@motor-comm.com>
+ L: netdev@vger.kernel.org
+ S: Maintained
+ F: drivers/net/phy/motorcomm.c
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -257,7 +257,7 @@ config MOTORCOMM_PHY
+ tristate "Motorcomm PHYs"
+ help
+ Enables support for Motorcomm network PHYs.
+- Currently supports the YT8511 gigabit PHY.
++ Currently supports the YT8511, YT8521 Gigabit Ethernet PHYs.
+
+ config NATIONAL_PHY
+ tristate "National Semiconductor PHYs"
+--- a/drivers/net/phy/motorcomm.c
++++ b/drivers/net/phy/motorcomm.c
+@@ -1,15 +1,106 @@
+ // SPDX-License-Identifier: GPL-2.0+
+ /*
+- * Driver for Motorcomm PHYs
++ * Motorcomm 8511/8521 PHY driver.
+ *
+ * Author: Peter Geis <pgwipeout@gmail.com>
++ * Author: Frank <Frank.Sae@motor-comm.com>
+ */
+
++#include <linux/etherdevice.h>
+ #include <linux/kernel.h>
+ #include <linux/module.h>
+ #include <linux/phy.h>
+
+ #define PHY_ID_YT8511 0x0000010a
++#define PHY_ID_YT8521 0x0000011A
++
++/* YT8521 Register Overview
++ * UTP Register space | FIBER Register space
++ * ------------------------------------------------------------
++ * | UTP MII | FIBER MII |
++ * | UTP MMD | |
++ * | UTP Extended | FIBER Extended |
++ * ------------------------------------------------------------
++ * | Common Extended |
++ * ------------------------------------------------------------
++ */
++
++/* 0x10 ~ 0x15 , 0x1E and 0x1F are common MII registers of yt phy */
++
++/* Specific Function Control Register */
++#define YTPHY_SPECIFIC_FUNCTION_CONTROL_REG 0x10
++
++/* 2b00 Manual MDI configuration
++ * 2b01 Manual MDIX configuration
++ * 2b10 Reserved
++ * 2b11 Enable automatic crossover for all modes *default*
++ */
++#define YTPHY_SFCR_MDI_CROSSOVER_MODE_MASK (BIT(6) | BIT(5))
++#define YTPHY_SFCR_CROSSOVER_EN BIT(3)
++#define YTPHY_SFCR_SQE_TEST_EN BIT(2)
++#define YTPHY_SFCR_POLARITY_REVERSAL_EN BIT(1)
++#define YTPHY_SFCR_JABBER_DIS BIT(0)
++
++/* Specific Status Register */
++#define YTPHY_SPECIFIC_STATUS_REG 0x11
++#define YTPHY_SSR_SPEED_MODE_OFFSET 14
++
++#define YTPHY_SSR_SPEED_MODE_MASK (BIT(15) | BIT(14))
++#define YTPHY_SSR_SPEED_10M 0x0
++#define YTPHY_SSR_SPEED_100M 0x1
++#define YTPHY_SSR_SPEED_1000M 0x2
++#define YTPHY_SSR_DUPLEX_OFFSET 13
++#define YTPHY_SSR_DUPLEX BIT(13)
++#define YTPHY_SSR_PAGE_RECEIVED BIT(12)
++#define YTPHY_SSR_SPEED_DUPLEX_RESOLVED BIT(11)
++#define YTPHY_SSR_LINK BIT(10)
++#define YTPHY_SSR_MDIX_CROSSOVER BIT(6)
++#define YTPHY_SSR_DOWNGRADE BIT(5)
++#define YTPHY_SSR_TRANSMIT_PAUSE BIT(3)
++#define YTPHY_SSR_RECEIVE_PAUSE BIT(2)
++#define YTPHY_SSR_POLARITY BIT(1)
++#define YTPHY_SSR_JABBER BIT(0)
++
++/* Interrupt enable Register */
++#define YTPHY_INTERRUPT_ENABLE_REG 0x12
++#define YTPHY_IER_WOL BIT(6)
++
++/* Interrupt Status Register */
++#define YTPHY_INTERRUPT_STATUS_REG 0x13
++#define YTPHY_ISR_AUTONEG_ERR BIT(15)
++#define YTPHY_ISR_SPEED_CHANGED BIT(14)
++#define YTPHY_ISR_DUPLEX_CHANGED BIT(13)
++#define YTPHY_ISR_PAGE_RECEIVED BIT(12)
++#define YTPHY_ISR_LINK_FAILED BIT(11)
++#define YTPHY_ISR_LINK_SUCCESSED BIT(10)
++#define YTPHY_ISR_WOL BIT(6)
++#define YTPHY_ISR_WIRESPEED_DOWNGRADE BIT(5)
++#define YTPHY_ISR_SERDES_LINK_FAILED BIT(3)
++#define YTPHY_ISR_SERDES_LINK_SUCCESSED BIT(2)
++#define YTPHY_ISR_POLARITY_CHANGED BIT(1)
++#define YTPHY_ISR_JABBER_HAPPENED BIT(0)
++
++/* Speed Auto Downgrade Control Register */
++#define YTPHY_SPEED_AUTO_DOWNGRADE_CONTROL_REG 0x14
++#define YTPHY_SADCR_SPEED_DOWNGRADE_EN BIT(5)
++
++/* If these bits are set to 3, the PHY attempts five times ( 3(set value) +
++ * additional 2) before downgrading, default 0x3
++ */
++#define YTPHY_SADCR_SPEED_RETRY_LIMIT (0x3 << 2)
++
++/* Rx Error Counter Register */
++#define YTPHY_RX_ERROR_COUNTER_REG 0x15
++
++/* Extended Register's Address Offset Register */
++#define YTPHY_PAGE_SELECT 0x1E
++
++/* Extended Register's Data Register */
++#define YTPHY_PAGE_DATA 0x1F
++
++/* FIBER Auto-Negotiation link partner ability */
++#define YTPHY_FLPA_PAUSE (0x3 << 7)
++#define YTPHY_FLPA_ASYM_PAUSE (0x2 << 7)
+
+ #define YT8511_PAGE_SELECT 0x1e
+ #define YT8511_PAGE 0x1f
+@@ -38,6 +129,352 @@
+ #define YT8511_DELAY_FE_TX_EN (0xf << 12)
+ #define YT8511_DELAY_FE_TX_DIS (0x2 << 12)
+
++/* Extended register is different from MMD Register and MII Register.
++ * We can use ytphy_read_ext/ytphy_write_ext/ytphy_modify_ext function to
++ * operate extended register.
++ * Extended Register start
++ */
++
++/* Phy gmii clock gating Register */
++#define YT8521_CLOCK_GATING_REG 0xC
++#define YT8521_CGR_RX_CLK_EN BIT(12)
++
++#define YT8521_EXTREG_SLEEP_CONTROL1_REG 0x27
++#define YT8521_ESC1R_SLEEP_SW BIT(15)
++#define YT8521_ESC1R_PLLON_SLP BIT(14)
++
++/* Phy fiber Link timer cfg2 Register */
++#define YT8521_LINK_TIMER_CFG2_REG 0xA5
++#define YT8521_LTCR_EN_AUTOSEN BIT(15)
++
++/* 0xA000, 0xA001, 0xA003 ,and 0xA006 ~ 0xA00A are common ext registers
++ * of yt8521 phy. There is no need to switch reg space when operating these
++ * registers.
++ */
++
++#define YT8521_REG_SPACE_SELECT_REG 0xA000
++#define YT8521_RSSR_SPACE_MASK BIT(1)
++#define YT8521_RSSR_FIBER_SPACE (0x1 << 1)
++#define YT8521_RSSR_UTP_SPACE (0x0 << 1)
++#define YT8521_RSSR_TO_BE_ARBITRATED (0xFF)
++
++#define YT8521_CHIP_CONFIG_REG 0xA001
++#define YT8521_CCR_SW_RST BIT(15)
++
++#define YT8521_CCR_MODE_SEL_MASK (BIT(2) | BIT(1) | BIT(0))
++#define YT8521_CCR_MODE_UTP_TO_RGMII 0
++#define YT8521_CCR_MODE_FIBER_TO_RGMII 1
++#define YT8521_CCR_MODE_UTP_FIBER_TO_RGMII 2
++#define YT8521_CCR_MODE_UTP_TO_SGMII 3
++#define YT8521_CCR_MODE_SGPHY_TO_RGMAC 4
++#define YT8521_CCR_MODE_SGMAC_TO_RGPHY 5
++#define YT8521_CCR_MODE_UTP_TO_FIBER_AUTO 6
++#define YT8521_CCR_MODE_UTP_TO_FIBER_FORCE 7
++
++/* 3 phy polling modes,poll mode combines utp and fiber mode*/
++#define YT8521_MODE_FIBER 0x1
++#define YT8521_MODE_UTP 0x2
++#define YT8521_MODE_POLL 0x3
++
++#define YT8521_RGMII_CONFIG1_REG 0xA003
++
++/* TX Gig-E Delay is bits 3:0, default 0x1
++ * TX Fast-E Delay is bits 7:4, default 0xf
++ * RX Delay is bits 13:10, default 0x0
++ * Delay = 150ps * N
++ * On = 2250ps, off = 0ps
++ */
++#define YT8521_RC1R_RX_DELAY_MASK (0xF << 10)
++#define YT8521_RC1R_RX_DELAY_EN (0xF << 10)
++#define YT8521_RC1R_RX_DELAY_DIS (0x0 << 10)
++#define YT8521_RC1R_FE_TX_DELAY_MASK (0xF << 4)
++#define YT8521_RC1R_FE_TX_DELAY_EN (0xF << 4)
++#define YT8521_RC1R_FE_TX_DELAY_DIS (0x0 << 4)
++#define YT8521_RC1R_GE_TX_DELAY_MASK (0xF << 0)
++#define YT8521_RC1R_GE_TX_DELAY_EN (0xF << 0)
++#define YT8521_RC1R_GE_TX_DELAY_DIS (0x0 << 0)
++
++#define YTPHY_MISC_CONFIG_REG 0xA006
++#define YTPHY_MCR_FIBER_SPEED_MASK BIT(0)
++#define YTPHY_MCR_FIBER_1000BX (0x1 << 0)
++#define YTPHY_MCR_FIBER_100FX (0x0 << 0)
++
++/* WOL MAC ADDR: MACADDR2(highest), MACADDR1(middle), MACADDR0(lowest) */
++#define YTPHY_WOL_MACADDR2_REG 0xA007
++#define YTPHY_WOL_MACADDR1_REG 0xA008
++#define YTPHY_WOL_MACADDR0_REG 0xA009
++
++#define YTPHY_WOL_CONFIG_REG 0xA00A
++#define YTPHY_WCR_INTR_SEL BIT(6)
++#define YTPHY_WCR_ENABLE BIT(3)
++
++/* 2b00 84ms
++ * 2b01 168ms *default*
++ * 2b10 336ms
++ * 2b11 672ms
++ */
++#define YTPHY_WCR_PULSE_WIDTH_MASK (BIT(2) | BIT(1))
++#define YTPHY_WCR_PULSE_WIDTH_672MS (BIT(2) | BIT(1))
++
++/* 1b0 Interrupt and WOL events is level triggered and active LOW *default*
++ * 1b1 Interrupt and WOL events is pulse triggered and active LOW
++ */
++#define YTPHY_WCR_TYPE_PULSE BIT(0)
++
++/* Extended Register end */
++
++struct yt8521_priv {
++ /* combo_advertising is used for case of YT8521 in combo mode,
++ * this means that yt8521 may work in utp or fiber mode which depends
++ * on which media is connected (YT8521_RSSR_TO_BE_ARBITRATED).
++ */
++ __ETHTOOL_DECLARE_LINK_MODE_MASK(combo_advertising);
++
++ /* YT8521_MODE_FIBER / YT8521_MODE_UTP / YT8521_MODE_POLL*/
++ u8 polling_mode;
++ u8 strap_mode; /* 8 working modes */
++ /* current reg page of yt8521 phy:
++ * YT8521_RSSR_UTP_SPACE
++ * YT8521_RSSR_FIBER_SPACE
++ * YT8521_RSSR_TO_BE_ARBITRATED
++ */
++ u8 reg_page;
++};
++
++/**
++ * ytphy_read_ext() - read a PHY's extended register
++ * @phydev: a pointer to a &struct phy_device
++ * @regnum: register number to read
++ *
++ * NOTE:The caller must have taken the MDIO bus lock.
++ *
++ * returns the value of regnum reg or negative error code
++ */
++static int ytphy_read_ext(struct phy_device *phydev, u16 regnum)
++{
++ int ret;
++
++ ret = __phy_write(phydev, YTPHY_PAGE_SELECT, regnum);
++ if (ret < 0)
++ return ret;
++
++ return __phy_read(phydev, YTPHY_PAGE_DATA);
++}
++
++/**
++ * ytphy_read_ext_with_lock() - read a PHY's extended register
++ * @phydev: a pointer to a &struct phy_device
++ * @regnum: register number to read
++ *
++ * returns the value of regnum reg or negative error code
++ */
++static int ytphy_read_ext_with_lock(struct phy_device *phydev, u16 regnum)
++{
++ int ret;
++
++ phy_lock_mdio_bus(phydev);
++ ret = ytphy_read_ext(phydev, regnum);
++ phy_unlock_mdio_bus(phydev);
++
++ return ret;
++}
++
++/**
++ * ytphy_write_ext() - write a PHY's extended register
++ * @phydev: a pointer to a &struct phy_device
++ * @regnum: register number to write
++ * @val: value to write to @regnum
++ *
++ * NOTE:The caller must have taken the MDIO bus lock.
++ *
++ * returns 0 or negative error code
++ */
++static int ytphy_write_ext(struct phy_device *phydev, u16 regnum, u16 val)
++{
++ int ret;
++
++ ret = __phy_write(phydev, YTPHY_PAGE_SELECT, regnum);
++ if (ret < 0)
++ return ret;
++
++ return __phy_write(phydev, YTPHY_PAGE_DATA, val);
++}
++
++/**
++ * ytphy_write_ext_with_lock() - write a PHY's extended register
++ * @phydev: a pointer to a &struct phy_device
++ * @regnum: register number to write
++ * @val: value to write to @regnum
++ *
++ * returns 0 or negative error code
++ */
++static int ytphy_write_ext_with_lock(struct phy_device *phydev, u16 regnum,
++ u16 val)
++{
++ int ret;
++
++ phy_lock_mdio_bus(phydev);
++ ret = ytphy_write_ext(phydev, regnum, val);
++ phy_unlock_mdio_bus(phydev);
++
++ return ret;
++}
++
++/**
++ * ytphy_modify_ext() - bits modify a PHY's extended register
++ * @phydev: a pointer to a &struct phy_device
++ * @regnum: register number to write
++ * @mask: bit mask of bits to clear
++ * @set: bit mask of bits to set
++ *
++ * NOTE: Convenience function which allows a PHY's extended register to be
++ * modified as new register value = (old register value & ~mask) | set.
++ * The caller must have taken the MDIO bus lock.
++ *
++ * returns 0 or negative error code
++ */
++static int ytphy_modify_ext(struct phy_device *phydev, u16 regnum, u16 mask,
++ u16 set)
++{
++ int ret;
++
++ ret = __phy_write(phydev, YTPHY_PAGE_SELECT, regnum);
++ if (ret < 0)
++ return ret;
++
++ return __phy_modify(phydev, YTPHY_PAGE_DATA, mask, set);
++}
++
++/**
++ * ytphy_modify_ext_with_lock() - bits modify a PHY's extended register
++ * @phydev: a pointer to a &struct phy_device
++ * @regnum: register number to write
++ * @mask: bit mask of bits to clear
++ * @set: bit mask of bits to set
++ *
++ * NOTE: Convenience function which allows a PHY's extended register to be
++ * modified as new register value = (old register value & ~mask) | set.
++ *
++ * returns 0 or negative error code
++ */
++static int ytphy_modify_ext_with_lock(struct phy_device *phydev, u16 regnum,
++ u16 mask, u16 set)
++{
++ int ret;
++
++ phy_lock_mdio_bus(phydev);
++ ret = ytphy_modify_ext(phydev, regnum, mask, set);
++ phy_unlock_mdio_bus(phydev);
++
++ return ret;
++}
++
++/**
++ * ytphy_get_wol() - report whether wake-on-lan is enabled
++ * @phydev: a pointer to a &struct phy_device
++ * @wol: a pointer to a &struct ethtool_wolinfo
++ *
++ * NOTE: YTPHY_WOL_CONFIG_REG is common ext reg.
++ */
++static void ytphy_get_wol(struct phy_device *phydev,
++ struct ethtool_wolinfo *wol)
++{
++ int wol_config;
++
++ wol->supported = WAKE_MAGIC;
++ wol->wolopts = 0;
++
++ wol_config = ytphy_read_ext_with_lock(phydev, YTPHY_WOL_CONFIG_REG);
++ if (wol_config < 0)
++ return;
++
++ if (wol_config & YTPHY_WCR_ENABLE)
++ wol->wolopts |= WAKE_MAGIC;
++}
++
++/**
++ * ytphy_set_wol() - turn wake-on-lan on or off
++ * @phydev: a pointer to a &struct phy_device
++ * @wol: a pointer to a &struct ethtool_wolinfo
++ *
++ * NOTE: YTPHY_WOL_CONFIG_REG, YTPHY_WOL_MACADDR2_REG, YTPHY_WOL_MACADDR1_REG
++ * and YTPHY_WOL_MACADDR0_REG are common ext reg. The
++ * YTPHY_INTERRUPT_ENABLE_REG of UTP is special, fiber also use this register.
++ *
++ * returns 0 or negative errno code
++ */
++static int ytphy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
++{
++ struct net_device *p_attached_dev;
++ const u16 mac_addr_reg[] = {
++ YTPHY_WOL_MACADDR2_REG,
++ YTPHY_WOL_MACADDR1_REG,
++ YTPHY_WOL_MACADDR0_REG,
++ };
++ const u8 *mac_addr;
++ int old_page;
++ int ret = 0;
++ u16 mask;
++ u16 val;
++ u8 i;
++
++ if (wol->wolopts & WAKE_MAGIC) {
++ p_attached_dev = phydev->attached_dev;
++ if (!p_attached_dev)
++ return -ENODEV;
++
++ mac_addr = (const u8 *)p_attached_dev->dev_addr;
++ if (!is_valid_ether_addr(mac_addr))
++ return -EINVAL;
++
++ /* lock mdio bus then switch to utp reg space */
++ old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE);
++ if (old_page < 0)
++ goto err_restore_page;
++
++ /* Store the device address for the magic packet */
++ for (i = 0; i < 3; i++) {
++ ret = ytphy_write_ext(phydev, mac_addr_reg[i],
++ ((mac_addr[i * 2] << 8)) |
++ (mac_addr[i * 2 + 1]));
++ if (ret < 0)
++ goto err_restore_page;
++ }
++
++ /* Enable WOL feature */
++ mask = YTPHY_WCR_PULSE_WIDTH_MASK | YTPHY_WCR_INTR_SEL;
++ val = YTPHY_WCR_ENABLE | YTPHY_WCR_INTR_SEL;
++ val |= YTPHY_WCR_TYPE_PULSE | YTPHY_WCR_PULSE_WIDTH_672MS;
++ ret = ytphy_modify_ext(phydev, YTPHY_WOL_CONFIG_REG, mask, val);
++ if (ret < 0)
++ goto err_restore_page;
++
++ /* Enable WOL interrupt */
++ ret = __phy_modify(phydev, YTPHY_INTERRUPT_ENABLE_REG, 0,
++ YTPHY_IER_WOL);
++ if (ret < 0)
++ goto err_restore_page;
++
++ } else {
++ old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE);
++ if (old_page < 0)
++ goto err_restore_page;
++
++ /* Disable WOL feature */
++ mask = YTPHY_WCR_ENABLE | YTPHY_WCR_INTR_SEL;
++ ret = ytphy_modify_ext(phydev, YTPHY_WOL_CONFIG_REG, mask, 0);
++
++ /* Disable WOL interrupt */
++ ret = __phy_modify(phydev, YTPHY_INTERRUPT_ENABLE_REG,
++ YTPHY_IER_WOL, 0);
++ if (ret < 0)
++ goto err_restore_page;
++ }
++
++err_restore_page:
++ return phy_restore_page(phydev, old_page, ret);
++}
++
+ static int yt8511_read_page(struct phy_device *phydev)
+ {
+ return __phy_read(phydev, YT8511_PAGE_SELECT);
+@@ -111,6 +548,1181 @@ err_restore_page:
+ return phy_restore_page(phydev, oldpage, ret);
+ }
+
++/**
++ * yt8521_read_page() - read reg page
++ * @phydev: a pointer to a &struct phy_device
++ *
++ * returns current reg space of yt8521 (YT8521_RSSR_FIBER_SPACE/
++ * YT8521_RSSR_UTP_SPACE) or negative errno code
++ */
++static int yt8521_read_page(struct phy_device *phydev)
++{
++ int old_page;
++
++ old_page = ytphy_read_ext(phydev, YT8521_REG_SPACE_SELECT_REG);
++ if (old_page < 0)
++ return old_page;
++
++ if ((old_page & YT8521_RSSR_SPACE_MASK) == YT8521_RSSR_FIBER_SPACE)
++ return YT8521_RSSR_FIBER_SPACE;
++
++ return YT8521_RSSR_UTP_SPACE;
++};
++
++/**
++ * yt8521_write_page() - write reg page
++ * @phydev: a pointer to a &struct phy_device
++ * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to write.
++ *
++ * returns 0 or negative errno code
++ */
++static int yt8521_write_page(struct phy_device *phydev, int page)
++{
++ int mask = YT8521_RSSR_SPACE_MASK;
++ int set;
++
++ if ((page & YT8521_RSSR_SPACE_MASK) == YT8521_RSSR_FIBER_SPACE)
++ set = YT8521_RSSR_FIBER_SPACE;
++ else
++ set = YT8521_RSSR_UTP_SPACE;
++
++ return ytphy_modify_ext(phydev, YT8521_REG_SPACE_SELECT_REG, mask, set);
++};
++
++/**
++ * yt8521_probe() - read chip config then set suitable polling_mode
++ * @phydev: a pointer to a &struct phy_device
++ *
++ * returns 0 or negative errno code
++ */
++static int yt8521_probe(struct phy_device *phydev)
++{
++ struct device *dev = &phydev->mdio.dev;
++ struct yt8521_priv *priv;
++ int chip_config;
++ int ret;
++
++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
++
++ phydev->priv = priv;
++
++ chip_config = ytphy_read_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG);
++ if (chip_config < 0)
++ return chip_config;
++
++ priv->strap_mode = chip_config & YT8521_CCR_MODE_SEL_MASK;
++ switch (priv->strap_mode) {
++ case YT8521_CCR_MODE_FIBER_TO_RGMII:
++ case YT8521_CCR_MODE_SGPHY_TO_RGMAC:
++ case YT8521_CCR_MODE_SGMAC_TO_RGPHY:
++ priv->polling_mode = YT8521_MODE_FIBER;
++ priv->reg_page = YT8521_RSSR_FIBER_SPACE;
++ phydev->port = PORT_FIBRE;
++ break;
++ case YT8521_CCR_MODE_UTP_FIBER_TO_RGMII:
++ case YT8521_CCR_MODE_UTP_TO_FIBER_AUTO:
++ case YT8521_CCR_MODE_UTP_TO_FIBER_FORCE:
++ priv->polling_mode = YT8521_MODE_POLL;
++ priv->reg_page = YT8521_RSSR_TO_BE_ARBITRATED;
++ phydev->port = PORT_NONE;
++ break;
++ case YT8521_CCR_MODE_UTP_TO_SGMII:
++ case YT8521_CCR_MODE_UTP_TO_RGMII:
++ priv->polling_mode = YT8521_MODE_UTP;
++ priv->reg_page = YT8521_RSSR_UTP_SPACE;
++ phydev->port = PORT_TP;
++ break;
++ }
++ /* set default reg space */
++ if (priv->reg_page != YT8521_RSSR_TO_BE_ARBITRATED) {
++ ret = ytphy_write_ext_with_lock(phydev,
++ YT8521_REG_SPACE_SELECT_REG,
++ priv->reg_page);
++ if (ret < 0)
++ return ret;
++ }
++
++ return 0;
++}
++
++/**
++ * ytphy_utp_read_lpa() - read LPA then setup lp_advertising for utp
++ * @phydev: a pointer to a &struct phy_device
++ *
++ * NOTE:The caller must have taken the MDIO bus lock.
++ *
++ * returns 0 or negative errno code
++ */
++static int ytphy_utp_read_lpa(struct phy_device *phydev)
++{
++ int lpa, lpagb;
++
++ if (phydev->autoneg == AUTONEG_ENABLE) {
++ if (!phydev->autoneg_complete) {
++ mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising,
++ 0);
++ mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
++ return 0;
++ }
++
++ if (phydev->is_gigabit_capable) {
++ lpagb = __phy_read(phydev, MII_STAT1000);
++ if (lpagb < 0)
++ return lpagb;
++
++ if (lpagb & LPA_1000MSFAIL) {
++ int adv = __phy_read(phydev, MII_CTRL1000);
++
++ if (adv < 0)
++ return adv;
++
++ if (adv & CTL1000_ENABLE_MASTER)
++ phydev_err(phydev, "Master/Slave resolution failed, maybe conflicting manual settings?\n");
++ else
++ phydev_err(phydev, "Master/Slave resolution failed\n");
++ return -ENOLINK;
++ }
++
++ mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising,
++ lpagb);
++ }
++
++ lpa = __phy_read(phydev, MII_LPA);
++ if (lpa < 0)
++ return lpa;
++
++ mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, lpa);
++ } else {
++ linkmode_zero(phydev->lp_advertising);
++ }
++
++ return 0;
++}
++
++/**
++ * yt8521_adjust_status() - update speed and duplex to phydev. when in fiber
++ * mode, adjust speed and duplex.
++ * @phydev: a pointer to a &struct phy_device
++ * @status: yt8521 status read from YTPHY_SPECIFIC_STATUS_REG
++ * @is_utp: false(yt8521 work in fiber mode) or true(yt8521 work in utp mode)
++ *
++ * NOTE:The caller must have taken the MDIO bus lock.
++ *
++ * returns 0
++ */
++static int yt8521_adjust_status(struct phy_device *phydev, int status,
++ bool is_utp)
++{
++ int speed_mode, duplex;
++ int speed;
++ int err;
++ int lpa;
++
++ if (is_utp)
++ duplex = (status & YTPHY_SSR_DUPLEX) >> YTPHY_SSR_DUPLEX_OFFSET;
++ else
++ duplex = DUPLEX_FULL; /* for fiber, it always DUPLEX_FULL */
++
++ speed_mode = (status & YTPHY_SSR_SPEED_MODE_MASK) >>
++ YTPHY_SSR_SPEED_MODE_OFFSET;
++
++ switch (speed_mode) {
++ case YTPHY_SSR_SPEED_10M:
++ if (is_utp)
++ speed = SPEED_10;
++ else
++ /* for fiber, it will never run here, default to
++ * SPEED_UNKNOWN
++ */
++ speed = SPEED_UNKNOWN;
++ break;
++ case YTPHY_SSR_SPEED_100M:
++ speed = SPEED_100;
++ break;
++ case YTPHY_SSR_SPEED_1000M:
++ speed = SPEED_1000;
++ break;
++ default:
++ speed = SPEED_UNKNOWN;
++ break;
++ }
++
++ phydev->speed = speed;
++ phydev->duplex = duplex;
++
++ if (is_utp) {
++ err = ytphy_utp_read_lpa(phydev);
++ if (err < 0)
++ return err;
++
++ phy_resolve_aneg_pause(phydev);
++ } else {
++ lpa = __phy_read(phydev, MII_LPA);
++ if (lpa < 0)
++ return lpa;
++
++ /* only support 1000baseX Full */
++ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
++ phydev->lp_advertising, lpa & LPA_1000XFULL);
++
++ if (!(lpa & YTPHY_FLPA_PAUSE)) {
++ phydev->pause = 0;
++ phydev->asym_pause = 0;
++ } else if ((lpa & YTPHY_FLPA_ASYM_PAUSE)) {
++ phydev->pause = 1;
++ phydev->asym_pause = 1;
++ } else {
++ phydev->pause = 1;
++ phydev->asym_pause = 0;
++ }
++ }
++
++ return 0;
++}
++
++/**
++ * yt8521_read_status_paged() - determines the speed and duplex of one page
++ * @phydev: a pointer to a &struct phy_device
++ * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to
++ * operate.
++ *
++ * returns 1 (utp or fiber link),0 (no link) or negative errno code
++ */
++static int yt8521_read_status_paged(struct phy_device *phydev, int page)
++{
++ int fiber_latch_val;
++ int fiber_curr_val;
++ int old_page;
++ int ret = 0;
++ int status;
++ int link;
++
++ linkmode_zero(phydev->lp_advertising);
++ phydev->duplex = DUPLEX_UNKNOWN;
++ phydev->speed = SPEED_UNKNOWN;
++ phydev->asym_pause = 0;
++ phydev->pause = 0;
++
++ /* YT8521 has two reg space (utp/fiber) for linkup with utp/fiber
++ * respectively. but for utp/fiber combo mode, reg space should be
++ * arbitrated based on media priority. by default, utp takes
++ * priority. reg space should be properly set before read
++ * YTPHY_SPECIFIC_STATUS_REG.
++ */
++
++ page &= YT8521_RSSR_SPACE_MASK;
++ old_page = phy_select_page(phydev, page);
++ if (old_page < 0)
++ goto err_restore_page;
++
++ /* Read YTPHY_SPECIFIC_STATUS_REG, which indicates the speed and duplex
++ * of the PHY is actually using.
++ */
++ ret = __phy_read(phydev, YTPHY_SPECIFIC_STATUS_REG);
++ if (ret < 0)
++ goto err_restore_page;
++
++ status = ret;
++ link = !!(status & YTPHY_SSR_LINK);
++
++ /* When PHY is in fiber mode, speed transferred from 1000Mbps to
++ * 100Mbps,there is not link down from YTPHY_SPECIFIC_STATUS_REG, so
++ * we need check MII_BMSR to identify such case.
++ */
++ if (page == YT8521_RSSR_FIBER_SPACE) {
++ ret = __phy_read(phydev, MII_BMSR);
++ if (ret < 0)
++ goto err_restore_page;
++
++ fiber_latch_val = ret;
++ ret = __phy_read(phydev, MII_BMSR);
++ if (ret < 0)
++ goto err_restore_page;
++
++ fiber_curr_val = ret;
++ if (link && fiber_latch_val != fiber_curr_val) {
++ link = 0;
++ phydev_info(phydev,
++ "%s, fiber link down detect, latch = %04x, curr = %04x\n",
++ __func__, fiber_latch_val, fiber_curr_val);
++ }
++ } else {
++ /* Read autonegotiation status */
++ ret = __phy_read(phydev, MII_BMSR);
++ if (ret < 0)
++ goto err_restore_page;
++
++ phydev->autoneg_complete = ret & BMSR_ANEGCOMPLETE ? 1 : 0;
++ }
++
++ if (link) {
++ if (page == YT8521_RSSR_UTP_SPACE)
++ yt8521_adjust_status(phydev, status, true);
++ else
++ yt8521_adjust_status(phydev, status, false);
++ }
++ return phy_restore_page(phydev, old_page, link);
++
++err_restore_page:
++ return phy_restore_page(phydev, old_page, ret);
++}
++
++/**
++ * yt8521_read_status() - determines the negotiated speed and duplex
++ * @phydev: a pointer to a &struct phy_device
++ *
++ * returns 0 or negative errno code
++ */
++static int yt8521_read_status(struct phy_device *phydev)
++{
++ struct yt8521_priv *priv = phydev->priv;
++ int link_fiber = 0;
++ int link_utp;
++ int link;
++ int ret;
++
++ if (priv->reg_page != YT8521_RSSR_TO_BE_ARBITRATED) {
++ link = yt8521_read_status_paged(phydev, priv->reg_page);
++ if (link < 0)
++ return link;
++ } else {
++ /* when page is YT8521_RSSR_TO_BE_ARBITRATED, arbitration is
++ * needed. by default, utp is higher priority.
++ */
++
++ link_utp = yt8521_read_status_paged(phydev,
++ YT8521_RSSR_UTP_SPACE);
++ if (link_utp < 0)
++ return link_utp;
++
++ if (!link_utp) {
++ link_fiber = yt8521_read_status_paged(phydev,
++ YT8521_RSSR_FIBER_SPACE);
++ if (link_fiber < 0)
++ return link_fiber;
++ }
++
++ link = link_utp || link_fiber;
++ }
++
++ if (link) {
++ if (phydev->link == 0) {
++ /* arbitrate reg space based on linkup media type. */
++ if (priv->polling_mode == YT8521_MODE_POLL &&
++ priv->reg_page == YT8521_RSSR_TO_BE_ARBITRATED) {
++ if (link_fiber)
++ priv->reg_page =
++ YT8521_RSSR_FIBER_SPACE;
++ else
++ priv->reg_page = YT8521_RSSR_UTP_SPACE;
++
++ ret = ytphy_write_ext_with_lock(phydev,
++ YT8521_REG_SPACE_SELECT_REG,
++ priv->reg_page);
++ if (ret < 0)
++ return ret;
++
++ phydev->port = link_fiber ? PORT_FIBRE : PORT_TP;
++
++ phydev_info(phydev, "%s, link up, media: %s\n",
++ __func__,
++ (phydev->port == PORT_TP) ?
++ "UTP" : "Fiber");
++ }
++ }
++ phydev->link = 1;
++ } else {
++ if (phydev->link == 1) {
++ phydev_info(phydev, "%s, link down, media: %s\n",
++ __func__, (phydev->port == PORT_TP) ?
++ "UTP" : "Fiber");
++
++ /* When in YT8521_MODE_POLL mode, need prepare for next
++ * arbitration.
++ */
++ if (priv->polling_mode == YT8521_MODE_POLL) {
++ priv->reg_page = YT8521_RSSR_TO_BE_ARBITRATED;
++ phydev->port = PORT_NONE;
++ }
++ }
++
++ phydev->link = 0;
++ }
++
++ return 0;
++}
++
++/**
++ * yt8521_modify_bmcr_paged - bits modify a PHY's BMCR register of one page
++ * @phydev: the phy_device struct
++ * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to operate
++ * @mask: bit mask of bits to clear
++ * @set: bit mask of bits to set
++ *
++ * NOTE: Convenience function which allows a PHY's BMCR register to be
++ * modified as new register value = (old register value & ~mask) | set.
++ * YT8521 has two space (utp/fiber) and three mode (utp/fiber/poll), each space
++ * has MII_BMCR. poll mode combines utp and faber,so need do both.
++ * If it is reset, it will wait for completion.
++ *
++ * returns 0 or negative errno code
++ */
++static int yt8521_modify_bmcr_paged(struct phy_device *phydev, int page,
++ u16 mask, u16 set)
++{
++ int max_cnt = 500; /* the max wait time of reset ~ 500 ms */
++ int old_page;
++ int ret = 0;
++
++ old_page = phy_select_page(phydev, page & YT8521_RSSR_SPACE_MASK);
++ if (old_page < 0)
++ goto err_restore_page;
++
++ ret = __phy_modify(phydev, MII_BMCR, mask, set);
++ if (ret < 0)
++ goto err_restore_page;
++
++ /* If it is reset, need to wait for the reset to complete */
++ if (set == BMCR_RESET) {
++ while (max_cnt--) {
++ usleep_range(1000, 1100);
++ ret = __phy_read(phydev, MII_BMCR);
++ if (ret < 0)
++ goto err_restore_page;
++
++ if (!(ret & BMCR_RESET))
++ return phy_restore_page(phydev, old_page, 0);
++ }
++ }
++
++err_restore_page:
++ return phy_restore_page(phydev, old_page, ret);
++}
++
++/**
++ * yt8521_modify_utp_fiber_bmcr - bits modify a PHY's BMCR register
++ * @phydev: the phy_device struct
++ * @mask: bit mask of bits to clear
++ * @set: bit mask of bits to set
++ *
++ * NOTE: Convenience function which allows a PHY's BMCR register to be
++ * modified as new register value = (old register value & ~mask) | set.
++ * YT8521 has two space (utp/fiber) and three mode (utp/fiber/poll), each space
++ * has MII_BMCR. poll mode combines utp and faber,so need do both.
++ *
++ * returns 0 or negative errno code
++ */
++static int yt8521_modify_utp_fiber_bmcr(struct phy_device *phydev, u16 mask,
++ u16 set)
++{
++ struct yt8521_priv *priv = phydev->priv;
++ int ret;
++
++ if (priv->reg_page != YT8521_RSSR_TO_BE_ARBITRATED) {
++ ret = yt8521_modify_bmcr_paged(phydev, priv->reg_page, mask,
++ set);
++ if (ret < 0)
++ return ret;
++ } else {
++ ret = yt8521_modify_bmcr_paged(phydev, YT8521_RSSR_UTP_SPACE,
++ mask, set);
++ if (ret < 0)
++ return ret;
++
++ ret = yt8521_modify_bmcr_paged(phydev, YT8521_RSSR_FIBER_SPACE,
++ mask, set);
++ if (ret < 0)
++ return ret;
++ }
++ return 0;
++}
++
++/**
++ * yt8521_soft_reset() - called to issue a PHY software reset
++ * @phydev: a pointer to a &struct phy_device
++ *
++ * returns 0 or negative errno code
++ */
++static int yt8521_soft_reset(struct phy_device *phydev)
++{
++ return yt8521_modify_utp_fiber_bmcr(phydev, 0, BMCR_RESET);
++}
++
++/**
++ * yt8521_suspend() - suspend the hardware
++ * @phydev: a pointer to a &struct phy_device
++ *
++ * returns 0 or negative errno code
++ */
++static int yt8521_suspend(struct phy_device *phydev)
++{
++ int wol_config;
++
++ /* YTPHY_WOL_CONFIG_REG is common ext reg */
++ wol_config = ytphy_read_ext_with_lock(phydev, YTPHY_WOL_CONFIG_REG);
++ if (wol_config < 0)
++ return wol_config;
++
++ /* if wol enable, do nothing */
++ if (wol_config & YTPHY_WCR_ENABLE)
++ return 0;
++
++ return yt8521_modify_utp_fiber_bmcr(phydev, 0, BMCR_PDOWN);
++}
++
++/**
++ * yt8521_resume() - resume the hardware
++ * @phydev: a pointer to a &struct phy_device
++ *
++ * returns 0 or negative errno code
++ */
++static int yt8521_resume(struct phy_device *phydev)
++{
++ int ret;
++ int wol_config;
++
++ /* disable auto sleep */
++ ret = ytphy_modify_ext_with_lock(phydev,
++ YT8521_EXTREG_SLEEP_CONTROL1_REG,
++ YT8521_ESC1R_SLEEP_SW, 0);
++ if (ret < 0)
++ return ret;
++
++ wol_config = ytphy_read_ext_with_lock(phydev, YTPHY_WOL_CONFIG_REG);
++ if (wol_config < 0)
++ return wol_config;
++
++ /* if wol enable, do nothing */
++ if (wol_config & YTPHY_WCR_ENABLE)
++ return 0;
++
++ return yt8521_modify_utp_fiber_bmcr(phydev, BMCR_PDOWN, 0);
++}
++
++/**
++ * yt8521_config_init() - called to initialize the PHY
++ * @phydev: a pointer to a &struct phy_device
++ *
++ * returns 0 or negative errno code
++ */
++static int yt8521_config_init(struct phy_device *phydev)
++{
++ int old_page;
++ int ret = 0;
++ u16 val;
++
++ old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE);
++ if (old_page < 0)
++ goto err_restore_page;
++
++ switch (phydev->interface) {
++ case PHY_INTERFACE_MODE_RGMII:
++ val = YT8521_RC1R_GE_TX_DELAY_DIS | YT8521_RC1R_GE_TX_DELAY_DIS;
++ val |= YT8521_RC1R_RX_DELAY_DIS;
++ break;
++ case PHY_INTERFACE_MODE_RGMII_RXID:
++ val = YT8521_RC1R_GE_TX_DELAY_DIS | YT8521_RC1R_GE_TX_DELAY_DIS;
++ val |= YT8521_RC1R_RX_DELAY_EN;
++ break;
++ case PHY_INTERFACE_MODE_RGMII_TXID:
++ val = YT8521_RC1R_GE_TX_DELAY_EN | YT8521_RC1R_GE_TX_DELAY_EN;
++ val |= YT8521_RC1R_RX_DELAY_DIS;
++ break;
++ case PHY_INTERFACE_MODE_RGMII_ID:
++ val = YT8521_RC1R_GE_TX_DELAY_EN | YT8521_RC1R_GE_TX_DELAY_EN;
++ val |= YT8521_RC1R_RX_DELAY_EN;
++ break;
++ case PHY_INTERFACE_MODE_SGMII:
++ break;
++ default: /* do not support other modes */
++ ret = -EOPNOTSUPP;
++ goto err_restore_page;
++ }
++
++ /* set rgmii delay mode */
++ if (phydev->interface != PHY_INTERFACE_MODE_SGMII) {
++ ret = ytphy_modify_ext(phydev, YT8521_RGMII_CONFIG1_REG,
++ (YT8521_RC1R_RX_DELAY_MASK |
++ YT8521_RC1R_FE_TX_DELAY_MASK |
++ YT8521_RC1R_GE_TX_DELAY_MASK),
++ val);
++ if (ret < 0)
++ goto err_restore_page;
++ }
++
++ /* disable auto sleep */
++ ret = ytphy_modify_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1_REG,
++ YT8521_ESC1R_SLEEP_SW, 0);
++ if (ret < 0)
++ goto err_restore_page;
++
++ /* enable RXC clock when no wire plug */
++ ret = ytphy_modify_ext(phydev, YT8521_CLOCK_GATING_REG,
++ YT8521_CGR_RX_CLK_EN, 0);
++ if (ret < 0)
++ goto err_restore_page;
++
++err_restore_page:
++ return phy_restore_page(phydev, old_page, ret);
++}
++
++/**
++ * yt8521_prepare_fiber_features() - A small helper function that setup
++ * fiber's features.
++ * @phydev: a pointer to a &struct phy_device
++ * @dst: a pointer to store fiber's features
++ */
++static void yt8521_prepare_fiber_features(struct phy_device *phydev,
++ unsigned long *dst)
++{
++ linkmode_set_bit(ETHTOOL_LINK_MODE_100baseFX_Full_BIT, dst);
++ linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, dst);
++ linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, dst);
++ linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, dst);
++}
++
++/**
++ * yt8521_fiber_setup_forced - configures/forces speed from @phydev
++ * @phydev: target phy_device struct
++ *
++ * NOTE:The caller must have taken the MDIO bus lock.
++ *
++ * returns 0 or negative errno code
++ */
++static int yt8521_fiber_setup_forced(struct phy_device *phydev)
++{
++ u16 val;
++ int ret;
++
++ if (phydev->speed == SPEED_1000)
++ val = YTPHY_MCR_FIBER_1000BX;
++ else if (phydev->speed == SPEED_100)
++ val = YTPHY_MCR_FIBER_100FX;
++ else
++ return -EINVAL;
++
++ ret = __phy_modify(phydev, MII_BMCR, BMCR_ANENABLE, 0);
++ if (ret < 0)
++ return ret;
++
++ /* disable Fiber auto sensing */
++ ret = ytphy_modify_ext(phydev, YT8521_LINK_TIMER_CFG2_REG,
++ YT8521_LTCR_EN_AUTOSEN, 0);
++ if (ret < 0)
++ return ret;
++
++ ret = ytphy_modify_ext(phydev, YTPHY_MISC_CONFIG_REG,
++ YTPHY_MCR_FIBER_SPEED_MASK, val);
++ if (ret < 0)
++ return ret;
++
++ return ytphy_modify_ext(phydev, YT8521_CHIP_CONFIG_REG,
++ YT8521_CCR_SW_RST, 0);
++}
++
++/**
++ * ytphy_check_and_restart_aneg - Enable and restart auto-negotiation
++ * @phydev: target phy_device struct
++ * @restart: whether aneg restart is requested
++ *
++ * NOTE:The caller must have taken the MDIO bus lock.
++ *
++ * returns 0 or negative errno code
++ */
++static int ytphy_check_and_restart_aneg(struct phy_device *phydev, bool restart)
++{
++ int ret;
++
++ if (!restart) {
++ /* Advertisement hasn't changed, but maybe aneg was never on to
++ * begin with? Or maybe phy was isolated?
++ */
++ ret = __phy_read(phydev, MII_BMCR);
++ if (ret < 0)
++ return ret;
++
++ if (!(ret & BMCR_ANENABLE) || (ret & BMCR_ISOLATE))
++ restart = true;
++ }
++ /* Enable and Restart Autonegotiation
++ * Don't isolate the PHY if we're negotiating
++ */
++ if (restart)
++ return __phy_modify(phydev, MII_BMCR, BMCR_ISOLATE,
++ BMCR_ANENABLE | BMCR_ANRESTART);
++
++ return 0;
++}
++
++/**
++ * yt8521_fiber_config_aneg - restart auto-negotiation or write
++ * YTPHY_MISC_CONFIG_REG.
++ * @phydev: target phy_device struct
++ *
++ * NOTE:The caller must have taken the MDIO bus lock.
++ *
++ * returns 0 or negative errno code
++ */
++static int yt8521_fiber_config_aneg(struct phy_device *phydev)
++{
++ int err, changed = 0;
++ int bmcr;
++ u16 adv;
++
++ if (phydev->autoneg != AUTONEG_ENABLE)
++ return yt8521_fiber_setup_forced(phydev);
++
++ /* enable Fiber auto sensing */
++ err = ytphy_modify_ext(phydev, YT8521_LINK_TIMER_CFG2_REG,
++ 0, YT8521_LTCR_EN_AUTOSEN);
++ if (err < 0)
++ return err;
++
++ err = ytphy_modify_ext(phydev, YT8521_CHIP_CONFIG_REG,
++ YT8521_CCR_SW_RST, 0);
++ if (err < 0)
++ return err;
++
++ bmcr = __phy_read(phydev, MII_BMCR);
++ if (bmcr < 0)
++ return bmcr;
++
++ /* When it is coming from fiber forced mode, add bmcr power down
++ * and power up to let aneg work fine.
++ */
++ if (!(bmcr & BMCR_ANENABLE)) {
++ __phy_modify(phydev, MII_BMCR, 0, BMCR_PDOWN);
++ usleep_range(1000, 1100);
++ __phy_modify(phydev, MII_BMCR, BMCR_PDOWN, 0);
++ }
++
++ adv = linkmode_adv_to_mii_adv_x(phydev->advertising,
++ ETHTOOL_LINK_MODE_1000baseX_Full_BIT);
++
++ /* Setup fiber advertisement */
++ err = __phy_modify_changed(phydev, MII_ADVERTISE,
++ ADVERTISE_1000XHALF | ADVERTISE_1000XFULL |
++ ADVERTISE_1000XPAUSE |
++ ADVERTISE_1000XPSE_ASYM,
++ adv);
++ if (err < 0)
++ return err;
++
++ if (err > 0)
++ changed = 1;
++
++ return ytphy_check_and_restart_aneg(phydev, changed);
++}
++
++/**
++ * ytphy_setup_master_slave
++ * @phydev: target phy_device struct
++ *
++ * NOTE: The caller must have taken the MDIO bus lock.
++ *
++ * returns 0 or negative errno code
++ */
++static int ytphy_setup_master_slave(struct phy_device *phydev)
++{
++ u16 ctl = 0;
++
++ if (!phydev->is_gigabit_capable)
++ return 0;
++
++ switch (phydev->master_slave_set) {
++ case MASTER_SLAVE_CFG_MASTER_PREFERRED:
++ ctl |= CTL1000_PREFER_MASTER;
++ break;
++ case MASTER_SLAVE_CFG_SLAVE_PREFERRED:
++ break;
++ case MASTER_SLAVE_CFG_MASTER_FORCE:
++ ctl |= CTL1000_AS_MASTER;
++ fallthrough;
++ case MASTER_SLAVE_CFG_SLAVE_FORCE:
++ ctl |= CTL1000_ENABLE_MASTER;
++ break;
++ case MASTER_SLAVE_CFG_UNKNOWN:
++ case MASTER_SLAVE_CFG_UNSUPPORTED:
++ return 0;
++ default:
++ phydev_warn(phydev, "Unsupported Master/Slave mode\n");
++ return -EOPNOTSUPP;
++ }
++
++ return __phy_modify_changed(phydev, MII_CTRL1000,
++ (CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER |
++ CTL1000_PREFER_MASTER), ctl);
++}
++
++/**
++ * ytphy_utp_config_advert - sanitize and advertise auto-negotiation parameters
++ * @phydev: target phy_device struct
++ *
++ * NOTE: Writes MII_ADVERTISE with the appropriate values,
++ * after sanitizing the values to make sure we only advertise
++ * what is supported. Returns < 0 on error, 0 if the PHY's advertisement
++ * hasn't changed, and > 0 if it has changed.
++ * The caller must have taken the MDIO bus lock.
++ *
++ * returns 0 or negative errno code
++ */
++static int ytphy_utp_config_advert(struct phy_device *phydev)
++{
++ int err, bmsr, changed = 0;
++ u32 adv;
++
++ /* Only allow advertising what this PHY supports */
++ linkmode_and(phydev->advertising, phydev->advertising,
++ phydev->supported);
++
++ adv = linkmode_adv_to_mii_adv_t(phydev->advertising);
++
++ /* Setup standard advertisement */
++ err = __phy_modify_changed(phydev, MII_ADVERTISE,
++ ADVERTISE_ALL | ADVERTISE_100BASE4 |
++ ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM,
++ adv);
++ if (err < 0)
++ return err;
++ if (err > 0)
++ changed = 1;
++
++ bmsr = __phy_read(phydev, MII_BMSR);
++ if (bmsr < 0)
++ return bmsr;
++
++ /* Per 802.3-2008, Section 22.2.4.2.16 Extended status all
++ * 1000Mbits/sec capable PHYs shall have the BMSR_ESTATEN bit set to a
++ * logical 1.
++ */
++ if (!(bmsr & BMSR_ESTATEN))
++ return changed;
++
++ adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising);
++
++ err = __phy_modify_changed(phydev, MII_CTRL1000,
++ ADVERTISE_1000FULL | ADVERTISE_1000HALF,
++ adv);
++ if (err < 0)
++ return err;
++ if (err > 0)
++ changed = 1;
++
++ return changed;
++}
++
++/**
++ * ytphy_utp_config_aneg - restart auto-negotiation or write BMCR
++ * @phydev: target phy_device struct
++ * @changed: whether autoneg is requested
++ *
++ * NOTE: If auto-negotiation is enabled, we configure the
++ * advertising, and then restart auto-negotiation. If it is not
++ * enabled, then we write the BMCR.
++ * The caller must have taken the MDIO bus lock.
++ *
++ * returns 0 or negative errno code
++ */
++static int ytphy_utp_config_aneg(struct phy_device *phydev, bool changed)
++{
++ int err;
++ u16 ctl;
++
++ err = ytphy_setup_master_slave(phydev);
++ if (err < 0)
++ return err;
++ else if (err)
++ changed = true;
++
++ if (phydev->autoneg != AUTONEG_ENABLE) {
++ /* configures/forces speed/duplex from @phydev */
++
++ ctl = mii_bmcr_encode_fixed(phydev->speed, phydev->duplex);
++
++ return __phy_modify(phydev, MII_BMCR, ~(BMCR_LOOPBACK |
++ BMCR_ISOLATE | BMCR_PDOWN), ctl);
++ }
++
++ err = ytphy_utp_config_advert(phydev);
++ if (err < 0) /* error */
++ return err;
++ else if (err)
++ changed = true;
++
++ return ytphy_check_and_restart_aneg(phydev, changed);
++}
++
++/**
++ * yt8521_config_aneg_paged() - switch reg space then call genphy_config_aneg
++ * of one page
++ * @phydev: a pointer to a &struct phy_device
++ * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to
++ * operate.
++ *
++ * returns 0 or negative errno code
++ */
++static int yt8521_config_aneg_paged(struct phy_device *phydev, int page)
++{
++ __ETHTOOL_DECLARE_LINK_MODE_MASK(fiber_supported);
++ struct yt8521_priv *priv = phydev->priv;
++ int old_page;
++ int ret = 0;
++
++ page &= YT8521_RSSR_SPACE_MASK;
++
++ old_page = phy_select_page(phydev, page);
++ if (old_page < 0)
++ goto err_restore_page;
++
++ /* If reg_page is YT8521_RSSR_TO_BE_ARBITRATED,
++ * phydev->advertising should be updated.
++ */
++ if (priv->reg_page == YT8521_RSSR_TO_BE_ARBITRATED) {
++ linkmode_zero(fiber_supported);
++ yt8521_prepare_fiber_features(phydev, fiber_supported);
++
++ /* prepare fiber_supported, then setup advertising. */
++ if (page == YT8521_RSSR_FIBER_SPACE) {
++ linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT,
++ fiber_supported);
++ linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
++ fiber_supported);
++ linkmode_and(phydev->advertising,
++ priv->combo_advertising, fiber_supported);
++ } else {
++ /* ETHTOOL_LINK_MODE_Autoneg_BIT is also used in utp */
++ linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++ fiber_supported);
++ linkmode_andnot(phydev->advertising,
++ priv->combo_advertising,
++ fiber_supported);
++ }
++ }
++
++ if (page == YT8521_RSSR_FIBER_SPACE)
++ ret = yt8521_fiber_config_aneg(phydev);
++ else
++ ret = ytphy_utp_config_aneg(phydev, false);
++
++err_restore_page:
++ return phy_restore_page(phydev, old_page, ret);
++}
++
++/**
++ * yt8521_config_aneg() - change reg space then call yt8521_config_aneg_paged
++ * @phydev: a pointer to a &struct phy_device
++ *
++ * returns 0 or negative errno code
++ */
++static int yt8521_config_aneg(struct phy_device *phydev)
++{
++ struct yt8521_priv *priv = phydev->priv;
++ int ret;
++
++ if (priv->reg_page != YT8521_RSSR_TO_BE_ARBITRATED) {
++ ret = yt8521_config_aneg_paged(phydev, priv->reg_page);
++ if (ret < 0)
++ return ret;
++ } else {
++ /* If reg_page is YT8521_RSSR_TO_BE_ARBITRATED,
++ * phydev->advertising need to be saved at first run.
++ * Because it contains the advertising which supported by both
++ * mac and yt8521(utp and fiber).
++ */
++ if (linkmode_empty(priv->combo_advertising)) {
++ linkmode_copy(priv->combo_advertising,
++ phydev->advertising);
++ }
++
++ ret = yt8521_config_aneg_paged(phydev, YT8521_RSSR_UTP_SPACE);
++ if (ret < 0)
++ return ret;
++
++ ret = yt8521_config_aneg_paged(phydev, YT8521_RSSR_FIBER_SPACE);
++ if (ret < 0)
++ return ret;
++
++ /* we don't known which will be link, so restore
++ * phydev->advertising as default value.
++ */
++ linkmode_copy(phydev->advertising, priv->combo_advertising);
++ }
++ return 0;
++}
++
++/**
++ * yt8521_aneg_done_paged() - determines the auto negotiation result of one
++ * page.
++ * @phydev: a pointer to a &struct phy_device
++ * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to
++ * operate.
++ *
++ * returns 0(no link)or 1(fiber or utp link) or negative errno code
++ */
++static int yt8521_aneg_done_paged(struct phy_device *phydev, int page)
++{
++ int old_page;
++ int ret = 0;
++ int link;
++
++ old_page = phy_select_page(phydev, page & YT8521_RSSR_SPACE_MASK);
++ if (old_page < 0)
++ goto err_restore_page;
++
++ ret = __phy_read(phydev, YTPHY_SPECIFIC_STATUS_REG);
++ if (ret < 0)
++ goto err_restore_page;
++
++ link = !!(ret & YTPHY_SSR_LINK);
++ ret = link;
++
++err_restore_page:
++ return phy_restore_page(phydev, old_page, ret);
++}
++
++/**
++ * yt8521_aneg_done() - determines the auto negotiation result
++ * @phydev: a pointer to a &struct phy_device
++ *
++ * returns 0(no link)or 1(fiber or utp link) or negative errno code
++ */
++static int yt8521_aneg_done(struct phy_device *phydev)
++{
++ struct yt8521_priv *priv = phydev->priv;
++ int link_fiber = 0;
++ int link_utp;
++ int link;
++
++ if (priv->reg_page != YT8521_RSSR_TO_BE_ARBITRATED) {
++ link = yt8521_aneg_done_paged(phydev, priv->reg_page);
++ } else {
++ link_utp = yt8521_aneg_done_paged(phydev,
++ YT8521_RSSR_UTP_SPACE);
++ if (link_utp < 0)
++ return link_utp;
++
++ if (!link_utp) {
++ link_fiber = yt8521_aneg_done_paged(phydev,
++ YT8521_RSSR_FIBER_SPACE);
++ if (link_fiber < 0)
++ return link_fiber;
++ }
++ link = link_fiber || link_utp;
++ phydev_info(phydev, "%s, link_fiber: %d, link_utp: %d\n",
++ __func__, link_fiber, link_utp);
++ }
++
++ return link;
++}
++
++/**
++ * ytphy_utp_read_abilities - read PHY abilities from Clause 22 registers
++ * @phydev: target phy_device struct
++ *
++ * NOTE: Reads the PHY's abilities and populates
++ * phydev->supported accordingly.
++ * The caller must have taken the MDIO bus lock.
++ *
++ * returns 0 or negative errno code
++ */
++static int ytphy_utp_read_abilities(struct phy_device *phydev)
++{
++ int val;
++
++ linkmode_set_bit_array(phy_basic_ports_array,
++ ARRAY_SIZE(phy_basic_ports_array),
++ phydev->supported);
++
++ val = __phy_read(phydev, MII_BMSR);
++ if (val < 0)
++ return val;
++
++ linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported,
++ val & BMSR_ANEGCAPABLE);
++
++ linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, phydev->supported,
++ val & BMSR_100FULL);
++ linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, phydev->supported,
++ val & BMSR_100HALF);
++ linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, phydev->supported,
++ val & BMSR_10FULL);
++ linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, phydev->supported,
++ val & BMSR_10HALF);
++
++ if (val & BMSR_ESTATEN) {
++ val = __phy_read(phydev, MII_ESTATUS);
++ if (val < 0)
++ return val;
++
++ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
++ phydev->supported, val & ESTATUS_1000_TFULL);
++ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
++ phydev->supported, val & ESTATUS_1000_THALF);
++ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
++ phydev->supported, val & ESTATUS_1000_XFULL);
++ }
++
++ return 0;
++}
++
++/**
++ * yt8521_get_features_paged() - read supported link modes for one page
++ * @phydev: a pointer to a &struct phy_device
++ * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to
++ * operate.
++ *
++ * returns 0 or negative errno code
++ */
++static int yt8521_get_features_paged(struct phy_device *phydev, int page)
++{
++ int old_page;
++ int ret = 0;
++
++ page &= YT8521_RSSR_SPACE_MASK;
++ old_page = phy_select_page(phydev, page);
++ if (old_page < 0)
++ goto err_restore_page;
++
++ if (page == YT8521_RSSR_FIBER_SPACE) {
++ linkmode_zero(phydev->supported);
++ yt8521_prepare_fiber_features(phydev, phydev->supported);
++ } else {
++ ret = ytphy_utp_read_abilities(phydev);
++ if (ret < 0)
++ goto err_restore_page;
++ }
++
++err_restore_page:
++ return phy_restore_page(phydev, old_page, ret);
++}
++
++/**
++ * yt8521_get_features - switch reg space then call yt8521_get_features_paged
++ * @phydev: target phy_device struct
++ *
++ * returns 0 or negative errno code
++ */
++static int yt8521_get_features(struct phy_device *phydev)
++{
++ struct yt8521_priv *priv = phydev->priv;
++ int ret;
++
++ if (priv->reg_page != YT8521_RSSR_TO_BE_ARBITRATED) {
++ ret = yt8521_get_features_paged(phydev, priv->reg_page);
++ } else {
++ ret = yt8521_get_features_paged(phydev,
++ YT8521_RSSR_UTP_SPACE);
++ if (ret < 0)
++ return ret;
++
++ /* add fiber's features to phydev->supported */
++ yt8521_prepare_fiber_features(phydev, phydev->supported);
++ }
++ return ret;
++}
++
+ static struct phy_driver motorcomm_phy_drvs[] = {
+ {
+ PHY_ID_MATCH_EXACT(PHY_ID_YT8511),
+@@ -121,16 +1733,35 @@ static struct phy_driver motorcomm_phy_d
+ .read_page = yt8511_read_page,
+ .write_page = yt8511_write_page,
+ },
++ {
++ PHY_ID_MATCH_EXACT(PHY_ID_YT8521),
++ .name = "YT8521 Gigabit Ethernet",
++ .get_features = yt8521_get_features,
++ .probe = yt8521_probe,
++ .read_page = yt8521_read_page,
++ .write_page = yt8521_write_page,
++ .get_wol = ytphy_get_wol,
++ .set_wol = ytphy_set_wol,
++ .config_aneg = yt8521_config_aneg,
++ .aneg_done = yt8521_aneg_done,
++ .config_init = yt8521_config_init,
++ .read_status = yt8521_read_status,
++ .soft_reset = yt8521_soft_reset,
++ .suspend = yt8521_suspend,
++ .resume = yt8521_resume,
++ },
+ };
+
+ module_phy_driver(motorcomm_phy_drvs);
+
+-MODULE_DESCRIPTION("Motorcomm PHY driver");
++MODULE_DESCRIPTION("Motorcomm 8511/8521 PHY driver");
+ MODULE_AUTHOR("Peter Geis");
++MODULE_AUTHOR("Frank");
+ MODULE_LICENSE("GPL");
+
+ static const struct mdio_device_id __maybe_unused motorcomm_tbl[] = {
+ { PHY_ID_MATCH_EXACT(PHY_ID_YT8511) },
++ { PHY_ID_MATCH_EXACT(PHY_ID_YT8521) },
+ { /* sentinal */ }
+ };
+
diff --git a/target/linux/generic/backport-6.6/791-v6.2-02-net-phy-fix-yt8521-duplicated-argument-to-or.patch b/target/linux/generic/backport-6.6/791-v6.2-02-net-phy-fix-yt8521-duplicated-argument-to-or.patch
new file mode 100644
index 0000000000..cce71c8d84
--- /dev/null
+++ b/target/linux/generic/backport-6.6/791-v6.2-02-net-phy-fix-yt8521-duplicated-argument-to-or.patch
@@ -0,0 +1,49 @@
+From 4e0243e7128c9b25ea2739136076a95d6adaba5e Mon Sep 17 00:00:00 2001
+From: Frank <Frank.Sae@motor-comm.com>
+Date: Fri, 4 Nov 2022 16:44:41 +0800
+Subject: [PATCH] net: phy: fix yt8521 duplicated argument to & or |
+
+cocci warnings: (new ones prefixed by >>)
+>> drivers/net/phy/motorcomm.c:1122:8-35: duplicated argument to & or |
+ drivers/net/phy/motorcomm.c:1126:8-35: duplicated argument to & or |
+ drivers/net/phy/motorcomm.c:1130:8-34: duplicated argument to & or |
+ drivers/net/phy/motorcomm.c:1134:8-34: duplicated argument to & or |
+
+ The second YT8521_RC1R_GE_TX_DELAY_xx should be YT8521_RC1R_FE_TX_DELAY_xx.
+
+Fixes: 70479a40954c ("net: phy: Add driver for Motorcomm yt8521 gigabit ethernet phy")
+Reported-by: kernel test robot <lkp@intel.com>
+Reported-by: Julia Lawall <julia.lawall@lip6.fr>
+Signed-off-by: Frank <Frank.Sae@motor-comm.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/motorcomm.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/phy/motorcomm.c
++++ b/drivers/net/phy/motorcomm.c
+@@ -1119,19 +1119,19 @@ static int yt8521_config_init(struct phy
+
+ switch (phydev->interface) {
+ case PHY_INTERFACE_MODE_RGMII:
+- val = YT8521_RC1R_GE_TX_DELAY_DIS | YT8521_RC1R_GE_TX_DELAY_DIS;
++ val = YT8521_RC1R_GE_TX_DELAY_DIS | YT8521_RC1R_FE_TX_DELAY_DIS;
+ val |= YT8521_RC1R_RX_DELAY_DIS;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+- val = YT8521_RC1R_GE_TX_DELAY_DIS | YT8521_RC1R_GE_TX_DELAY_DIS;
++ val = YT8521_RC1R_GE_TX_DELAY_DIS | YT8521_RC1R_FE_TX_DELAY_DIS;
+ val |= YT8521_RC1R_RX_DELAY_EN;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+- val = YT8521_RC1R_GE_TX_DELAY_EN | YT8521_RC1R_GE_TX_DELAY_EN;
++ val = YT8521_RC1R_GE_TX_DELAY_EN | YT8521_RC1R_FE_TX_DELAY_EN;
+ val |= YT8521_RC1R_RX_DELAY_DIS;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_ID:
+- val = YT8521_RC1R_GE_TX_DELAY_EN | YT8521_RC1R_GE_TX_DELAY_EN;
++ val = YT8521_RC1R_GE_TX_DELAY_EN | YT8521_RC1R_FE_TX_DELAY_EN;
+ val |= YT8521_RC1R_RX_DELAY_EN;
+ break;
+ case PHY_INTERFACE_MODE_SGMII:
diff --git a/target/linux/generic/backport-6.6/791-v6.2-03-net-phy-add-Motorcomm-YT8531S-phy-id.patch b/target/linux/generic/backport-6.6/791-v6.2-03-net-phy-add-Motorcomm-YT8531S-phy-id.patch
new file mode 100644
index 0000000000..d22cc69425
--- /dev/null
+++ b/target/linux/generic/backport-6.6/791-v6.2-03-net-phy-add-Motorcomm-YT8531S-phy-id.patch
@@ -0,0 +1,140 @@
+From 813abcd98fb1b2cccf850cdfa092a4bfc50b2363 Mon Sep 17 00:00:00 2001
+From: Frank <Frank.Sae@motor-comm.com>
+Date: Tue, 22 Nov 2022 16:42:32 +0800
+Subject: [PATCH] net: phy: add Motorcomm YT8531S phy id.
+
+We added patch for motorcomm.c to support YT8531S. This patch has
+been tested on AM335x platform which has one YT8531S interface
+card and passed all test cases.
+The tested cases indluding: YT8531S UTP function with support of
+10M/100M/1000M; YT8531S Fiber function with support of 100M/1000M;
+and YT8531S Combo function that supports auto detection of media type.
+
+Since most functions of YT8531S are similar to YT8521 and we reuse some
+codes for YT8521 in the patch file.
+
+Signed-off-by: Frank <Frank.Sae@motor-comm.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/Kconfig | 2 +-
+ drivers/net/phy/motorcomm.c | 52 +++++++++++++++++++++++++++++++++----
+ 2 files changed, 48 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -257,7 +257,7 @@ config MOTORCOMM_PHY
+ tristate "Motorcomm PHYs"
+ help
+ Enables support for Motorcomm network PHYs.
+- Currently supports the YT8511, YT8521 Gigabit Ethernet PHYs.
++ Currently supports the YT8511, YT8521, YT8531S Gigabit Ethernet PHYs.
+
+ config NATIONAL_PHY
+ tristate "National Semiconductor PHYs"
+--- a/drivers/net/phy/motorcomm.c
++++ b/drivers/net/phy/motorcomm.c
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0+
+ /*
+- * Motorcomm 8511/8521 PHY driver.
++ * Motorcomm 8511/8521/8531S PHY driver.
+ *
+ * Author: Peter Geis <pgwipeout@gmail.com>
+ * Author: Frank <Frank.Sae@motor-comm.com>
+@@ -12,9 +12,10 @@
+ #include <linux/phy.h>
+
+ #define PHY_ID_YT8511 0x0000010a
+-#define PHY_ID_YT8521 0x0000011A
++#define PHY_ID_YT8521 0x0000011A
++#define PHY_ID_YT8531S 0x4F51E91A
+
+-/* YT8521 Register Overview
++/* YT8521/YT8531S Register Overview
+ * UTP Register space | FIBER Register space
+ * ------------------------------------------------------------
+ * | UTP MII | FIBER MII |
+@@ -147,7 +148,7 @@
+ #define YT8521_LINK_TIMER_CFG2_REG 0xA5
+ #define YT8521_LTCR_EN_AUTOSEN BIT(15)
+
+-/* 0xA000, 0xA001, 0xA003 ,and 0xA006 ~ 0xA00A are common ext registers
++/* 0xA000, 0xA001, 0xA003, 0xA006 ~ 0xA00A and 0xA012 are common ext registers
+ * of yt8521 phy. There is no need to switch reg space when operating these
+ * registers.
+ */
+@@ -221,6 +222,9 @@
+ */
+ #define YTPHY_WCR_TYPE_PULSE BIT(0)
+
++#define YT8531S_SYNCE_CFG_REG 0xA012
++#define YT8531S_SCR_SYNCE_ENABLE BIT(6)
++
+ /* Extended Register end */
+
+ struct yt8521_priv {
+@@ -648,6 +652,26 @@ static int yt8521_probe(struct phy_devic
+ }
+
+ /**
++ * yt8531s_probe() - read chip config then set suitable polling_mode
++ * @phydev: a pointer to a &struct phy_device
++ *
++ * returns 0 or negative errno code
++ */
++static int yt8531s_probe(struct phy_device *phydev)
++{
++ int ret;
++
++ /* Disable SyncE clock output by default */
++ ret = ytphy_modify_ext_with_lock(phydev, YT8531S_SYNCE_CFG_REG,
++ YT8531S_SCR_SYNCE_ENABLE, 0);
++ if (ret < 0)
++ return ret;
++
++ /* same as yt8521_probe */
++ return yt8521_probe(phydev);
++}
++
++/**
+ * ytphy_utp_read_lpa() - read LPA then setup lp_advertising for utp
+ * @phydev: a pointer to a &struct phy_device
+ *
+@@ -1750,11 +1774,28 @@ static struct phy_driver motorcomm_phy_d
+ .suspend = yt8521_suspend,
+ .resume = yt8521_resume,
+ },
++ {
++ PHY_ID_MATCH_EXACT(PHY_ID_YT8531S),
++ .name = "YT8531S Gigabit Ethernet",
++ .get_features = yt8521_get_features,
++ .probe = yt8531s_probe,
++ .read_page = yt8521_read_page,
++ .write_page = yt8521_write_page,
++ .get_wol = ytphy_get_wol,
++ .set_wol = ytphy_set_wol,
++ .config_aneg = yt8521_config_aneg,
++ .aneg_done = yt8521_aneg_done,
++ .config_init = yt8521_config_init,
++ .read_status = yt8521_read_status,
++ .soft_reset = yt8521_soft_reset,
++ .suspend = yt8521_suspend,
++ .resume = yt8521_resume,
++ },
+ };
+
+ module_phy_driver(motorcomm_phy_drvs);
+
+-MODULE_DESCRIPTION("Motorcomm 8511/8521 PHY driver");
++MODULE_DESCRIPTION("Motorcomm 8511/8521/8531S PHY driver");
+ MODULE_AUTHOR("Peter Geis");
+ MODULE_AUTHOR("Frank");
+ MODULE_LICENSE("GPL");
+@@ -1762,6 +1803,7 @@ MODULE_LICENSE("GPL");
+ static const struct mdio_device_id __maybe_unused motorcomm_tbl[] = {
+ { PHY_ID_MATCH_EXACT(PHY_ID_YT8511) },
+ { PHY_ID_MATCH_EXACT(PHY_ID_YT8521) },
++ { PHY_ID_MATCH_EXACT(PHY_ID_YT8531S) },
+ { /* sentinal */ }
+ };
+
diff --git a/target/linux/generic/backport-6.6/791-v6.3-04-net-phy-fix-the-spelling-problem-of-Sentinel.patch b/target/linux/generic/backport-6.6/791-v6.3-04-net-phy-fix-the-spelling-problem-of-Sentinel.patch
new file mode 100644
index 0000000000..94fc32aadb
--- /dev/null
+++ b/target/linux/generic/backport-6.6/791-v6.3-04-net-phy-fix-the-spelling-problem-of-Sentinel.patch
@@ -0,0 +1,26 @@
+From 4104a713204d62aca482eebb0c6226d82a0721eb Mon Sep 17 00:00:00 2001
+From: Frank Sae <Frank.Sae@motor-comm.com>
+Date: Sat, 28 Jan 2023 14:35:57 +0800
+Subject: [PATCH] net: phy: fix the spelling problem of Sentinel
+
+CHECK: 'sentinal' may be misspelled - perhaps 'sentinel'?
+
+Signed-off-by: Frank Sae <Frank.Sae@motor-comm.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/20230128063558.5850-1-Frank.Sae@motor-comm.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/motorcomm.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/phy/motorcomm.c
++++ b/drivers/net/phy/motorcomm.c
+@@ -1804,7 +1804,7 @@ static const struct mdio_device_id __may
+ { PHY_ID_MATCH_EXACT(PHY_ID_YT8511) },
+ { PHY_ID_MATCH_EXACT(PHY_ID_YT8521) },
+ { PHY_ID_MATCH_EXACT(PHY_ID_YT8531S) },
+- { /* sentinal */ }
++ { /* sentinel */ }
+ };
+
+ MODULE_DEVICE_TABLE(mdio, motorcomm_tbl);
diff --git a/target/linux/generic/backport-6.6/791-v6.3-05-net-phy-motorcomm-change-the-phy-id-of-yt8521-and-yt8531s.patch b/target/linux/generic/backport-6.6/791-v6.3-05-net-phy-motorcomm-change-the-phy-id-of-yt8521-and-yt8531s.patch
new file mode 100644
index 0000000000..076fa82d26
--- /dev/null
+++ b/target/linux/generic/backport-6.6/791-v6.3-05-net-phy-motorcomm-change-the-phy-id-of-yt8521-and-yt8531s.patch
@@ -0,0 +1,29 @@
+From 3c1dc22162d673d595855d24f95200ed2643f88f Mon Sep 17 00:00:00 2001
+From: Frank Sae <Frank.Sae@motor-comm.com>
+Date: Sat, 28 Jan 2023 14:35:58 +0800
+Subject: [PATCH] net: phy: motorcomm: change the phy id of yt8521 and yt8531s
+ to lowercase
+
+The phy id is usually defined in lower case.
+
+Signed-off-by: Frank Sae <Frank.Sae@motor-comm.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/20230128063558.5850-2-Frank.Sae@motor-comm.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/motorcomm.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/motorcomm.c
++++ b/drivers/net/phy/motorcomm.c
+@@ -12,8 +12,8 @@
+ #include <linux/phy.h>
+
+ #define PHY_ID_YT8511 0x0000010a
+-#define PHY_ID_YT8521 0x0000011A
+-#define PHY_ID_YT8531S 0x4F51E91A
++#define PHY_ID_YT8521 0x0000011a
++#define PHY_ID_YT8531S 0x4f51e91a
+
+ /* YT8521/YT8531S Register Overview
+ * UTP Register space | FIBER Register space
diff --git a/target/linux/generic/backport-6.6/791-v6.3-06-net-phy-Add-BIT-macro-for-Motorcomm-yt8521-yt8531-gigabit.patch b/target/linux/generic/backport-6.6/791-v6.3-06-net-phy-Add-BIT-macro-for-Motorcomm-yt8521-yt8531-gigabit.patch
new file mode 100644
index 0000000000..ba9a6ab4cc
--- /dev/null
+++ b/target/linux/generic/backport-6.6/791-v6.3-06-net-phy-Add-BIT-macro-for-Motorcomm-yt8521-yt8531-gigabit.patch
@@ -0,0 +1,107 @@
+From 4869a146cd60fc8115230f0a45e15e534c531922 Mon Sep 17 00:00:00 2001
+From: Frank Sae <Frank.Sae@motor-comm.com>
+Date: Thu, 2 Feb 2023 11:00:34 +0800
+Subject: [PATCH] net: phy: Add BIT macro for Motorcomm yt8521/yt8531 gigabit
+ ethernet phy
+
+Add BIT macro for Motorcomm yt8521/yt8531 gigabit ethernet phy.
+ This is a preparatory patch. Add BIT macro for 0xA012 reg, and
+ supplement for 0xA001 and 0xA003 reg. These will be used to support dts.
+
+Signed-off-by: Frank Sae <Frank.Sae@motor-comm.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/motorcomm.c | 55 ++++++++++++++++++++++++++++++++++---
+ 1 file changed, 51 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/phy/motorcomm.c
++++ b/drivers/net/phy/motorcomm.c
+@@ -161,6 +161,11 @@
+
+ #define YT8521_CHIP_CONFIG_REG 0xA001
+ #define YT8521_CCR_SW_RST BIT(15)
++/* 1b0 disable 1.9ns rxc clock delay *default*
++ * 1b1 enable 1.9ns rxc clock delay
++ */
++#define YT8521_CCR_RXC_DLY_EN BIT(8)
++#define YT8521_CCR_RXC_DLY_1_900_NS 1900
+
+ #define YT8521_CCR_MODE_SEL_MASK (BIT(2) | BIT(1) | BIT(0))
+ #define YT8521_CCR_MODE_UTP_TO_RGMII 0
+@@ -178,22 +183,41 @@
+ #define YT8521_MODE_POLL 0x3
+
+ #define YT8521_RGMII_CONFIG1_REG 0xA003
+-
++/* 1b0 use original tx_clk_rgmii *default*
++ * 1b1 use inverted tx_clk_rgmii.
++ */
++#define YT8521_RC1R_TX_CLK_SEL_INVERTED BIT(14)
+ /* TX Gig-E Delay is bits 3:0, default 0x1
+ * TX Fast-E Delay is bits 7:4, default 0xf
+ * RX Delay is bits 13:10, default 0x0
+ * Delay = 150ps * N
+ * On = 2250ps, off = 0ps
+ */
+-#define YT8521_RC1R_RX_DELAY_MASK (0xF << 10)
++#define YT8521_RC1R_RX_DELAY_MASK GENMASK(13, 10)
+ #define YT8521_RC1R_RX_DELAY_EN (0xF << 10)
+ #define YT8521_RC1R_RX_DELAY_DIS (0x0 << 10)
+-#define YT8521_RC1R_FE_TX_DELAY_MASK (0xF << 4)
++#define YT8521_RC1R_FE_TX_DELAY_MASK GENMASK(7, 4)
+ #define YT8521_RC1R_FE_TX_DELAY_EN (0xF << 4)
+ #define YT8521_RC1R_FE_TX_DELAY_DIS (0x0 << 4)
+-#define YT8521_RC1R_GE_TX_DELAY_MASK (0xF << 0)
++#define YT8521_RC1R_GE_TX_DELAY_MASK GENMASK(3, 0)
+ #define YT8521_RC1R_GE_TX_DELAY_EN (0xF << 0)
+ #define YT8521_RC1R_GE_TX_DELAY_DIS (0x0 << 0)
++#define YT8521_RC1R_RGMII_0_000_NS 0
++#define YT8521_RC1R_RGMII_0_150_NS 1
++#define YT8521_RC1R_RGMII_0_300_NS 2
++#define YT8521_RC1R_RGMII_0_450_NS 3
++#define YT8521_RC1R_RGMII_0_600_NS 4
++#define YT8521_RC1R_RGMII_0_750_NS 5
++#define YT8521_RC1R_RGMII_0_900_NS 6
++#define YT8521_RC1R_RGMII_1_050_NS 7
++#define YT8521_RC1R_RGMII_1_200_NS 8
++#define YT8521_RC1R_RGMII_1_350_NS 9
++#define YT8521_RC1R_RGMII_1_500_NS 10
++#define YT8521_RC1R_RGMII_1_650_NS 11
++#define YT8521_RC1R_RGMII_1_800_NS 12
++#define YT8521_RC1R_RGMII_1_950_NS 13
++#define YT8521_RC1R_RGMII_2_100_NS 14
++#define YT8521_RC1R_RGMII_2_250_NS 15
+
+ #define YTPHY_MISC_CONFIG_REG 0xA006
+ #define YTPHY_MCR_FIBER_SPEED_MASK BIT(0)
+@@ -222,6 +246,29 @@
+ */
+ #define YTPHY_WCR_TYPE_PULSE BIT(0)
+
++#define YTPHY_SYNCE_CFG_REG 0xA012
++#define YT8521_SCR_SYNCE_ENABLE BIT(5)
++/* 1b0 output 25m clock
++ * 1b1 output 125m clock *default*
++ */
++#define YT8521_SCR_CLK_FRE_SEL_125M BIT(3)
++#define YT8521_SCR_CLK_SRC_MASK GENMASK(2, 1)
++#define YT8521_SCR_CLK_SRC_PLL_125M 0
++#define YT8521_SCR_CLK_SRC_UTP_RX 1
++#define YT8521_SCR_CLK_SRC_SDS_RX 2
++#define YT8521_SCR_CLK_SRC_REF_25M 3
++#define YT8531_SCR_SYNCE_ENABLE BIT(6)
++/* 1b0 output 25m clock *default*
++ * 1b1 output 125m clock
++ */
++#define YT8531_SCR_CLK_FRE_SEL_125M BIT(4)
++#define YT8531_SCR_CLK_SRC_MASK GENMASK(3, 1)
++#define YT8531_SCR_CLK_SRC_PLL_125M 0
++#define YT8531_SCR_CLK_SRC_UTP_RX 1
++#define YT8531_SCR_CLK_SRC_SDS_RX 2
++#define YT8531_SCR_CLK_SRC_CLOCK_FROM_DIGITAL 3
++#define YT8531_SCR_CLK_SRC_REF_25M 4
++#define YT8531_SCR_CLK_SRC_SSC_25M 5
+ #define YT8531S_SYNCE_CFG_REG 0xA012
+ #define YT8531S_SCR_SYNCE_ENABLE BIT(6)
+
diff --git a/target/linux/generic/backport-6.6/791-v6.3-07-net-phy-Add-dts-support-for-Motorcomm-yt8521-gigabit.patch b/target/linux/generic/backport-6.6/791-v6.3-07-net-phy-Add-dts-support-for-Motorcomm-yt8521-gigabit.patch
new file mode 100644
index 0000000000..6d89fae84c
--- /dev/null
+++ b/target/linux/generic/backport-6.6/791-v6.3-07-net-phy-Add-dts-support-for-Motorcomm-yt8521-gigabit.patch
@@ -0,0 +1,343 @@
+From a6e68f0f8769f79c67cdcfb6302feecd36197dec Mon Sep 17 00:00:00 2001
+From: Frank Sae <Frank.Sae@motor-comm.com>
+Date: Thu, 2 Feb 2023 11:00:35 +0800
+Subject: [PATCH] net: phy: Add dts support for Motorcomm yt8521 gigabit
+ ethernet phy
+
+Add dts support for Motorcomm yt8521 gigabit ethernet phy.
+ Add ytphy_rgmii_clk_delay_config function to support dst config for
+ the delay of rgmii clk. This funciont is common for yt8521, yt8531s
+ and yt8531.
+ This patch has been verified on AM335x platform.
+
+Signed-off-by: Frank Sae <Frank.Sae@motor-comm.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/motorcomm.c | 253 ++++++++++++++++++++++++++++--------
+ 1 file changed, 199 insertions(+), 54 deletions(-)
+
+--- a/drivers/net/phy/motorcomm.c
++++ b/drivers/net/phy/motorcomm.c
+@@ -10,6 +10,7 @@
+ #include <linux/kernel.h>
+ #include <linux/module.h>
+ #include <linux/phy.h>
++#include <linux/of.h>
+
+ #define PHY_ID_YT8511 0x0000010a
+ #define PHY_ID_YT8521 0x0000011a
+@@ -187,21 +188,9 @@
+ * 1b1 use inverted tx_clk_rgmii.
+ */
+ #define YT8521_RC1R_TX_CLK_SEL_INVERTED BIT(14)
+-/* TX Gig-E Delay is bits 3:0, default 0x1
+- * TX Fast-E Delay is bits 7:4, default 0xf
+- * RX Delay is bits 13:10, default 0x0
+- * Delay = 150ps * N
+- * On = 2250ps, off = 0ps
+- */
+ #define YT8521_RC1R_RX_DELAY_MASK GENMASK(13, 10)
+-#define YT8521_RC1R_RX_DELAY_EN (0xF << 10)
+-#define YT8521_RC1R_RX_DELAY_DIS (0x0 << 10)
+ #define YT8521_RC1R_FE_TX_DELAY_MASK GENMASK(7, 4)
+-#define YT8521_RC1R_FE_TX_DELAY_EN (0xF << 4)
+-#define YT8521_RC1R_FE_TX_DELAY_DIS (0x0 << 4)
+ #define YT8521_RC1R_GE_TX_DELAY_MASK GENMASK(3, 0)
+-#define YT8521_RC1R_GE_TX_DELAY_EN (0xF << 0)
+-#define YT8521_RC1R_GE_TX_DELAY_DIS (0x0 << 0)
+ #define YT8521_RC1R_RGMII_0_000_NS 0
+ #define YT8521_RC1R_RGMII_0_150_NS 1
+ #define YT8521_RC1R_RGMII_0_300_NS 2
+@@ -274,6 +263,10 @@
+
+ /* Extended Register end */
+
++#define YTPHY_DTS_OUTPUT_CLK_DIS 0
++#define YTPHY_DTS_OUTPUT_CLK_25M 25000000
++#define YTPHY_DTS_OUTPUT_CLK_125M 125000000
++
+ struct yt8521_priv {
+ /* combo_advertising is used for case of YT8521 in combo mode,
+ * this means that yt8521 may work in utp or fiber mode which depends
+@@ -641,6 +634,142 @@ static int yt8521_write_page(struct phy_
+ };
+
+ /**
++ * struct ytphy_cfg_reg_map - map a config value to a register value
++ * @cfg: value in device configuration
++ * @reg: value in the register
++ */
++struct ytphy_cfg_reg_map {
++ u32 cfg;
++ u32 reg;
++};
++
++static const struct ytphy_cfg_reg_map ytphy_rgmii_delays[] = {
++ /* for tx delay / rx delay with YT8521_CCR_RXC_DLY_EN is not set. */
++ { 0, YT8521_RC1R_RGMII_0_000_NS },
++ { 150, YT8521_RC1R_RGMII_0_150_NS },
++ { 300, YT8521_RC1R_RGMII_0_300_NS },
++ { 450, YT8521_RC1R_RGMII_0_450_NS },
++ { 600, YT8521_RC1R_RGMII_0_600_NS },
++ { 750, YT8521_RC1R_RGMII_0_750_NS },
++ { 900, YT8521_RC1R_RGMII_0_900_NS },
++ { 1050, YT8521_RC1R_RGMII_1_050_NS },
++ { 1200, YT8521_RC1R_RGMII_1_200_NS },
++ { 1350, YT8521_RC1R_RGMII_1_350_NS },
++ { 1500, YT8521_RC1R_RGMII_1_500_NS },
++ { 1650, YT8521_RC1R_RGMII_1_650_NS },
++ { 1800, YT8521_RC1R_RGMII_1_800_NS },
++ { 1950, YT8521_RC1R_RGMII_1_950_NS }, /* default tx/rx delay */
++ { 2100, YT8521_RC1R_RGMII_2_100_NS },
++ { 2250, YT8521_RC1R_RGMII_2_250_NS },
++
++ /* only for rx delay with YT8521_CCR_RXC_DLY_EN is set. */
++ { 0 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_0_000_NS },
++ { 150 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_0_150_NS },
++ { 300 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_0_300_NS },
++ { 450 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_0_450_NS },
++ { 600 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_0_600_NS },
++ { 750 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_0_750_NS },
++ { 900 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_0_900_NS },
++ { 1050 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_1_050_NS },
++ { 1200 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_1_200_NS },
++ { 1350 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_1_350_NS },
++ { 1500 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_1_500_NS },
++ { 1650 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_1_650_NS },
++ { 1800 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_1_800_NS },
++ { 1950 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_1_950_NS },
++ { 2100 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_2_100_NS },
++ { 2250 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_2_250_NS }
++};
++
++static u32 ytphy_get_delay_reg_value(struct phy_device *phydev,
++ const char *prop_name,
++ const struct ytphy_cfg_reg_map *tbl,
++ int tb_size,
++ u16 *rxc_dly_en,
++ u32 dflt)
++{
++ struct device_node *node = phydev->mdio.dev.of_node;
++ int tb_size_half = tb_size / 2;
++ u32 val;
++ int i;
++
++ if (of_property_read_u32(node, prop_name, &val))
++ goto err_dts_val;
++
++ /* when rxc_dly_en is NULL, it is get the delay for tx, only half of
++ * tb_size is valid.
++ */
++ if (!rxc_dly_en)
++ tb_size = tb_size_half;
++
++ for (i = 0; i < tb_size; i++) {
++ if (tbl[i].cfg == val) {
++ if (rxc_dly_en && i < tb_size_half)
++ *rxc_dly_en = 0;
++ return tbl[i].reg;
++ }
++ }
++
++ phydev_warn(phydev, "Unsupported value %d for %s using default (%u)\n",
++ val, prop_name, dflt);
++
++err_dts_val:
++ /* when rxc_dly_en is not NULL, it is get the delay for rx.
++ * The rx default in dts and ytphy_rgmii_clk_delay_config is 1950 ps,
++ * so YT8521_CCR_RXC_DLY_EN should not be set.
++ */
++ if (rxc_dly_en)
++ *rxc_dly_en = 0;
++
++ return dflt;
++}
++
++static int ytphy_rgmii_clk_delay_config(struct phy_device *phydev)
++{
++ int tb_size = ARRAY_SIZE(ytphy_rgmii_delays);
++ u16 rxc_dly_en = YT8521_CCR_RXC_DLY_EN;
++ u32 rx_reg, tx_reg;
++ u16 mask, val = 0;
++ int ret;
++
++ rx_reg = ytphy_get_delay_reg_value(phydev, "rx-internal-delay-ps",
++ ytphy_rgmii_delays, tb_size,
++ &rxc_dly_en,
++ YT8521_RC1R_RGMII_1_950_NS);
++ tx_reg = ytphy_get_delay_reg_value(phydev, "tx-internal-delay-ps",
++ ytphy_rgmii_delays, tb_size, NULL,
++ YT8521_RC1R_RGMII_1_950_NS);
++
++ switch (phydev->interface) {
++ case PHY_INTERFACE_MODE_RGMII:
++ rxc_dly_en = 0;
++ break;
++ case PHY_INTERFACE_MODE_RGMII_RXID:
++ val |= FIELD_PREP(YT8521_RC1R_RX_DELAY_MASK, rx_reg);
++ break;
++ case PHY_INTERFACE_MODE_RGMII_TXID:
++ rxc_dly_en = 0;
++ val |= FIELD_PREP(YT8521_RC1R_GE_TX_DELAY_MASK, tx_reg);
++ break;
++ case PHY_INTERFACE_MODE_RGMII_ID:
++ val |= FIELD_PREP(YT8521_RC1R_RX_DELAY_MASK, rx_reg) |
++ FIELD_PREP(YT8521_RC1R_GE_TX_DELAY_MASK, tx_reg);
++ break;
++ default: /* do not support other modes */
++ return -EOPNOTSUPP;
++ }
++
++ ret = ytphy_modify_ext(phydev, YT8521_CHIP_CONFIG_REG,
++ YT8521_CCR_RXC_DLY_EN, rxc_dly_en);
++ if (ret < 0)
++ return ret;
++
++ /* Generally, it is not necessary to adjust YT8521_RC1R_FE_TX_DELAY */
++ mask = YT8521_RC1R_RX_DELAY_MASK | YT8521_RC1R_GE_TX_DELAY_MASK;
++ return ytphy_modify_ext(phydev, YT8521_RGMII_CONFIG1_REG, mask, val);
++}
++
++/**
+ * yt8521_probe() - read chip config then set suitable polling_mode
+ * @phydev: a pointer to a &struct phy_device
+ *
+@@ -648,9 +777,12 @@ static int yt8521_write_page(struct phy_
+ */
+ static int yt8521_probe(struct phy_device *phydev)
+ {
++ struct device_node *node = phydev->mdio.dev.of_node;
+ struct device *dev = &phydev->mdio.dev;
+ struct yt8521_priv *priv;
+ int chip_config;
++ u16 mask, val;
++ u32 freq;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+@@ -695,7 +827,45 @@ static int yt8521_probe(struct phy_devic
+ return ret;
+ }
+
+- return 0;
++ if (of_property_read_u32(node, "motorcomm,clk-out-frequency-hz", &freq))
++ freq = YTPHY_DTS_OUTPUT_CLK_DIS;
++
++ if (phydev->drv->phy_id == PHY_ID_YT8521) {
++ switch (freq) {
++ case YTPHY_DTS_OUTPUT_CLK_DIS:
++ mask = YT8521_SCR_SYNCE_ENABLE;
++ val = 0;
++ break;
++ case YTPHY_DTS_OUTPUT_CLK_25M:
++ mask = YT8521_SCR_SYNCE_ENABLE |
++ YT8521_SCR_CLK_SRC_MASK |
++ YT8521_SCR_CLK_FRE_SEL_125M;
++ val = YT8521_SCR_SYNCE_ENABLE |
++ FIELD_PREP(YT8521_SCR_CLK_SRC_MASK,
++ YT8521_SCR_CLK_SRC_REF_25M);
++ break;
++ case YTPHY_DTS_OUTPUT_CLK_125M:
++ mask = YT8521_SCR_SYNCE_ENABLE |
++ YT8521_SCR_CLK_SRC_MASK |
++ YT8521_SCR_CLK_FRE_SEL_125M;
++ val = YT8521_SCR_SYNCE_ENABLE |
++ YT8521_SCR_CLK_FRE_SEL_125M |
++ FIELD_PREP(YT8521_SCR_CLK_SRC_MASK,
++ YT8521_SCR_CLK_SRC_PLL_125M);
++ break;
++ default:
++ phydev_warn(phydev, "Freq err:%u\n", freq);
++ return -EINVAL;
++ }
++ } else if (phydev->drv->phy_id == PHY_ID_YT8531S) {
++ return 0;
++ } else {
++ phydev_warn(phydev, "PHY id err\n");
++ return -EINVAL;
++ }
++
++ return ytphy_modify_ext_with_lock(phydev, YTPHY_SYNCE_CFG_REG, mask,
++ val);
+ }
+
+ /**
+@@ -1180,61 +1350,36 @@ static int yt8521_resume(struct phy_devi
+ */
+ static int yt8521_config_init(struct phy_device *phydev)
+ {
++ struct device_node *node = phydev->mdio.dev.of_node;
+ int old_page;
+ int ret = 0;
+- u16 val;
+
+ old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE);
+ if (old_page < 0)
+ goto err_restore_page;
+
+- switch (phydev->interface) {
+- case PHY_INTERFACE_MODE_RGMII:
+- val = YT8521_RC1R_GE_TX_DELAY_DIS | YT8521_RC1R_FE_TX_DELAY_DIS;
+- val |= YT8521_RC1R_RX_DELAY_DIS;
+- break;
+- case PHY_INTERFACE_MODE_RGMII_RXID:
+- val = YT8521_RC1R_GE_TX_DELAY_DIS | YT8521_RC1R_FE_TX_DELAY_DIS;
+- val |= YT8521_RC1R_RX_DELAY_EN;
+- break;
+- case PHY_INTERFACE_MODE_RGMII_TXID:
+- val = YT8521_RC1R_GE_TX_DELAY_EN | YT8521_RC1R_FE_TX_DELAY_EN;
+- val |= YT8521_RC1R_RX_DELAY_DIS;
+- break;
+- case PHY_INTERFACE_MODE_RGMII_ID:
+- val = YT8521_RC1R_GE_TX_DELAY_EN | YT8521_RC1R_FE_TX_DELAY_EN;
+- val |= YT8521_RC1R_RX_DELAY_EN;
+- break;
+- case PHY_INTERFACE_MODE_SGMII:
+- break;
+- default: /* do not support other modes */
+- ret = -EOPNOTSUPP;
+- goto err_restore_page;
+- }
+-
+ /* set rgmii delay mode */
+ if (phydev->interface != PHY_INTERFACE_MODE_SGMII) {
+- ret = ytphy_modify_ext(phydev, YT8521_RGMII_CONFIG1_REG,
+- (YT8521_RC1R_RX_DELAY_MASK |
+- YT8521_RC1R_FE_TX_DELAY_MASK |
+- YT8521_RC1R_GE_TX_DELAY_MASK),
+- val);
++ ret = ytphy_rgmii_clk_delay_config(phydev);
+ if (ret < 0)
+ goto err_restore_page;
+ }
+
+- /* disable auto sleep */
+- ret = ytphy_modify_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1_REG,
+- YT8521_ESC1R_SLEEP_SW, 0);
+- if (ret < 0)
+- goto err_restore_page;
+-
+- /* enable RXC clock when no wire plug */
+- ret = ytphy_modify_ext(phydev, YT8521_CLOCK_GATING_REG,
+- YT8521_CGR_RX_CLK_EN, 0);
+- if (ret < 0)
+- goto err_restore_page;
++ if (of_property_read_bool(node, "motorcomm,auto-sleep-disabled")) {
++ /* disable auto sleep */
++ ret = ytphy_modify_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1_REG,
++ YT8521_ESC1R_SLEEP_SW, 0);
++ if (ret < 0)
++ goto err_restore_page;
++ }
+
++ if (of_property_read_bool(node, "motorcomm,keep-pll-enabled")) {
++ /* enable RXC clock when no wire plug */
++ ret = ytphy_modify_ext(phydev, YT8521_CLOCK_GATING_REG,
++ YT8521_CGR_RX_CLK_EN, 0);
++ if (ret < 0)
++ goto err_restore_page;
++ }
+ err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+ }
diff --git a/target/linux/generic/backport-6.6/791-v6.3-08-net-phy-Add-dts-support-for-Motorcomm-yt8531s-gigabit.patch b/target/linux/generic/backport-6.6/791-v6.3-08-net-phy-Add-dts-support-for-Motorcomm-yt8531s-gigabit.patch
new file mode 100644
index 0000000000..86fc04695c
--- /dev/null
+++ b/target/linux/generic/backport-6.6/791-v6.3-08-net-phy-Add-dts-support-for-Motorcomm-yt8531s-gigabit.patch
@@ -0,0 +1,100 @@
+From 36152f87dda4af221b16258751451d9cd3d0fb0b Mon Sep 17 00:00:00 2001
+From: Frank Sae <Frank.Sae@motor-comm.com>
+Date: Thu, 2 Feb 2023 11:00:36 +0800
+Subject: [PATCH] net: phy: Add dts support for Motorcomm yt8531s gigabit
+ ethernet phy
+
+Add dts support for Motorcomm yt8531s gigabit ethernet phy.
+ Change yt8521_probe to support clk config of yt8531s. Becase
+ yt8521_probe does the things which yt8531s is needed, so
+ removed yt8531s function.
+ This patch has been verified on AM335x platform with yt8531s board.
+
+Signed-off-by: Frank Sae <Frank.Sae@motor-comm.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/motorcomm.c | 51 ++++++++++++++++++++-----------------
+ 1 file changed, 27 insertions(+), 24 deletions(-)
+
+--- a/drivers/net/phy/motorcomm.c
++++ b/drivers/net/phy/motorcomm.c
+@@ -258,8 +258,6 @@
+ #define YT8531_SCR_CLK_SRC_CLOCK_FROM_DIGITAL 3
+ #define YT8531_SCR_CLK_SRC_REF_25M 4
+ #define YT8531_SCR_CLK_SRC_SSC_25M 5
+-#define YT8531S_SYNCE_CFG_REG 0xA012
+-#define YT8531S_SCR_SYNCE_ENABLE BIT(6)
+
+ /* Extended Register end */
+
+@@ -858,7 +856,32 @@ static int yt8521_probe(struct phy_devic
+ return -EINVAL;
+ }
+ } else if (phydev->drv->phy_id == PHY_ID_YT8531S) {
+- return 0;
++ switch (freq) {
++ case YTPHY_DTS_OUTPUT_CLK_DIS:
++ mask = YT8531_SCR_SYNCE_ENABLE;
++ val = 0;
++ break;
++ case YTPHY_DTS_OUTPUT_CLK_25M:
++ mask = YT8531_SCR_SYNCE_ENABLE |
++ YT8531_SCR_CLK_SRC_MASK |
++ YT8531_SCR_CLK_FRE_SEL_125M;
++ val = YT8531_SCR_SYNCE_ENABLE |
++ FIELD_PREP(YT8531_SCR_CLK_SRC_MASK,
++ YT8531_SCR_CLK_SRC_REF_25M);
++ break;
++ case YTPHY_DTS_OUTPUT_CLK_125M:
++ mask = YT8531_SCR_SYNCE_ENABLE |
++ YT8531_SCR_CLK_SRC_MASK |
++ YT8531_SCR_CLK_FRE_SEL_125M;
++ val = YT8531_SCR_SYNCE_ENABLE |
++ YT8531_SCR_CLK_FRE_SEL_125M |
++ FIELD_PREP(YT8531_SCR_CLK_SRC_MASK,
++ YT8531_SCR_CLK_SRC_PLL_125M);
++ break;
++ default:
++ phydev_warn(phydev, "Freq err:%u\n", freq);
++ return -EINVAL;
++ }
+ } else {
+ phydev_warn(phydev, "PHY id err\n");
+ return -EINVAL;
+@@ -869,26 +892,6 @@ static int yt8521_probe(struct phy_devic
+ }
+
+ /**
+- * yt8531s_probe() - read chip config then set suitable polling_mode
+- * @phydev: a pointer to a &struct phy_device
+- *
+- * returns 0 or negative errno code
+- */
+-static int yt8531s_probe(struct phy_device *phydev)
+-{
+- int ret;
+-
+- /* Disable SyncE clock output by default */
+- ret = ytphy_modify_ext_with_lock(phydev, YT8531S_SYNCE_CFG_REG,
+- YT8531S_SCR_SYNCE_ENABLE, 0);
+- if (ret < 0)
+- return ret;
+-
+- /* same as yt8521_probe */
+- return yt8521_probe(phydev);
+-}
+-
+-/**
+ * ytphy_utp_read_lpa() - read LPA then setup lp_advertising for utp
+ * @phydev: a pointer to a &struct phy_device
+ *
+@@ -1970,7 +1973,7 @@ static struct phy_driver motorcomm_phy_d
+ PHY_ID_MATCH_EXACT(PHY_ID_YT8531S),
+ .name = "YT8531S Gigabit Ethernet",
+ .get_features = yt8521_get_features,
+- .probe = yt8531s_probe,
++ .probe = yt8521_probe,
+ .read_page = yt8521_read_page,
+ .write_page = yt8521_write_page,
+ .get_wol = ytphy_get_wol,
diff --git a/target/linux/generic/backport-6.6/791-v6.3-09-net-phy-Add-driver-for-Motorcomm-yt8531-gigabit-ethernet.patch b/target/linux/generic/backport-6.6/791-v6.3-09-net-phy-Add-driver-for-Motorcomm-yt8531-gigabit-ethernet.patch
new file mode 100644
index 0000000000..60eea4fa47
--- /dev/null
+++ b/target/linux/generic/backport-6.6/791-v6.3-09-net-phy-Add-driver-for-Motorcomm-yt8531-gigabit-ethernet.patch
@@ -0,0 +1,302 @@
+From 4ac94f728a588e7096dd5010cd7141a309ea7805 Mon Sep 17 00:00:00 2001
+From: Frank Sae <Frank.Sae@motor-comm.com>
+Date: Thu, 2 Feb 2023 11:00:37 +0800
+Subject: [PATCH] net: phy: Add driver for Motorcomm yt8531 gigabit ethernet
+ phy
+
+Add a driver for the motorcomm yt8531 gigabit ethernet phy. We have
+ verified the driver on AM335x platform with yt8531 board. On the
+ board, yt8531 gigabit ethernet phy works in utp mode, RGMII
+ interface, supports 1000M/100M/10M speeds, and wol(magic package).
+
+Signed-off-by: Frank Sae <Frank.Sae@motor-comm.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/Kconfig | 2 +-
+ drivers/net/phy/motorcomm.c | 208 +++++++++++++++++++++++++++++++++++-
+ 2 files changed, 207 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -257,7 +257,7 @@ config MOTORCOMM_PHY
+ tristate "Motorcomm PHYs"
+ help
+ Enables support for Motorcomm network PHYs.
+- Currently supports the YT8511, YT8521, YT8531S Gigabit Ethernet PHYs.
++ Currently supports YT85xx Gigabit Ethernet PHYs.
+
+ config NATIONAL_PHY
+ tristate "National Semiconductor PHYs"
+--- a/drivers/net/phy/motorcomm.c
++++ b/drivers/net/phy/motorcomm.c
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0+
+ /*
+- * Motorcomm 8511/8521/8531S PHY driver.
++ * Motorcomm 8511/8521/8531/8531S PHY driver.
+ *
+ * Author: Peter Geis <pgwipeout@gmail.com>
+ * Author: Frank <Frank.Sae@motor-comm.com>
+@@ -14,6 +14,7 @@
+
+ #define PHY_ID_YT8511 0x0000010a
+ #define PHY_ID_YT8521 0x0000011a
++#define PHY_ID_YT8531 0x4f51e91b
+ #define PHY_ID_YT8531S 0x4f51e91a
+
+ /* YT8521/YT8531S Register Overview
+@@ -517,6 +518,61 @@ err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+ }
+
++static int yt8531_set_wol(struct phy_device *phydev,
++ struct ethtool_wolinfo *wol)
++{
++ const u16 mac_addr_reg[] = {
++ YTPHY_WOL_MACADDR2_REG,
++ YTPHY_WOL_MACADDR1_REG,
++ YTPHY_WOL_MACADDR0_REG,
++ };
++ const u8 *mac_addr;
++ u16 mask, val;
++ int ret;
++ u8 i;
++
++ if (wol->wolopts & WAKE_MAGIC) {
++ mac_addr = phydev->attached_dev->dev_addr;
++
++ /* Store the device address for the magic packet */
++ for (i = 0; i < 3; i++) {
++ ret = ytphy_write_ext_with_lock(phydev, mac_addr_reg[i],
++ ((mac_addr[i * 2] << 8)) |
++ (mac_addr[i * 2 + 1]));
++ if (ret < 0)
++ return ret;
++ }
++
++ /* Enable WOL feature */
++ mask = YTPHY_WCR_PULSE_WIDTH_MASK | YTPHY_WCR_INTR_SEL;
++ val = YTPHY_WCR_ENABLE | YTPHY_WCR_INTR_SEL;
++ val |= YTPHY_WCR_TYPE_PULSE | YTPHY_WCR_PULSE_WIDTH_672MS;
++ ret = ytphy_modify_ext_with_lock(phydev, YTPHY_WOL_CONFIG_REG,
++ mask, val);
++ if (ret < 0)
++ return ret;
++
++ /* Enable WOL interrupt */
++ ret = phy_modify(phydev, YTPHY_INTERRUPT_ENABLE_REG, 0,
++ YTPHY_IER_WOL);
++ if (ret < 0)
++ return ret;
++ } else {
++ /* Disable WOL feature */
++ mask = YTPHY_WCR_ENABLE | YTPHY_WCR_INTR_SEL;
++ ret = ytphy_modify_ext_with_lock(phydev, YTPHY_WOL_CONFIG_REG,
++ mask, 0);
++
++ /* Disable WOL interrupt */
++ ret = phy_modify(phydev, YTPHY_INTERRUPT_ENABLE_REG,
++ YTPHY_IER_WOL, 0);
++ if (ret < 0)
++ return ret;
++ }
++
++ return 0;
++}
++
+ static int yt8511_read_page(struct phy_device *phydev)
+ {
+ return __phy_read(phydev, YT8511_PAGE_SELECT);
+@@ -767,6 +823,17 @@ static int ytphy_rgmii_clk_delay_config(
+ return ytphy_modify_ext(phydev, YT8521_RGMII_CONFIG1_REG, mask, val);
+ }
+
++static int ytphy_rgmii_clk_delay_config_with_lock(struct phy_device *phydev)
++{
++ int ret;
++
++ phy_lock_mdio_bus(phydev);
++ ret = ytphy_rgmii_clk_delay_config(phydev);
++ phy_unlock_mdio_bus(phydev);
++
++ return ret;
++}
++
+ /**
+ * yt8521_probe() - read chip config then set suitable polling_mode
+ * @phydev: a pointer to a &struct phy_device
+@@ -891,6 +958,43 @@ static int yt8521_probe(struct phy_devic
+ val);
+ }
+
++static int yt8531_probe(struct phy_device *phydev)
++{
++ struct device_node *node = phydev->mdio.dev.of_node;
++ u16 mask, val;
++ u32 freq;
++
++ if (of_property_read_u32(node, "motorcomm,clk-out-frequency-hz", &freq))
++ freq = YTPHY_DTS_OUTPUT_CLK_DIS;
++
++ switch (freq) {
++ case YTPHY_DTS_OUTPUT_CLK_DIS:
++ mask = YT8531_SCR_SYNCE_ENABLE;
++ val = 0;
++ break;
++ case YTPHY_DTS_OUTPUT_CLK_25M:
++ mask = YT8531_SCR_SYNCE_ENABLE | YT8531_SCR_CLK_SRC_MASK |
++ YT8531_SCR_CLK_FRE_SEL_125M;
++ val = YT8531_SCR_SYNCE_ENABLE |
++ FIELD_PREP(YT8531_SCR_CLK_SRC_MASK,
++ YT8531_SCR_CLK_SRC_REF_25M);
++ break;
++ case YTPHY_DTS_OUTPUT_CLK_125M:
++ mask = YT8531_SCR_SYNCE_ENABLE | YT8531_SCR_CLK_SRC_MASK |
++ YT8531_SCR_CLK_FRE_SEL_125M;
++ val = YT8531_SCR_SYNCE_ENABLE | YT8531_SCR_CLK_FRE_SEL_125M |
++ FIELD_PREP(YT8531_SCR_CLK_SRC_MASK,
++ YT8531_SCR_CLK_SRC_PLL_125M);
++ break;
++ default:
++ phydev_warn(phydev, "Freq err:%u\n", freq);
++ return -EINVAL;
++ }
++
++ return ytphy_modify_ext_with_lock(phydev, YTPHY_SYNCE_CFG_REG, mask,
++ val);
++}
++
+ /**
+ * ytphy_utp_read_lpa() - read LPA then setup lp_advertising for utp
+ * @phydev: a pointer to a &struct phy_device
+@@ -1387,6 +1491,94 @@ err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+ }
+
++static int yt8531_config_init(struct phy_device *phydev)
++{
++ struct device_node *node = phydev->mdio.dev.of_node;
++ int ret;
++
++ ret = ytphy_rgmii_clk_delay_config_with_lock(phydev);
++ if (ret < 0)
++ return ret;
++
++ if (of_property_read_bool(node, "motorcomm,auto-sleep-disabled")) {
++ /* disable auto sleep */
++ ret = ytphy_modify_ext_with_lock(phydev,
++ YT8521_EXTREG_SLEEP_CONTROL1_REG,
++ YT8521_ESC1R_SLEEP_SW, 0);
++ if (ret < 0)
++ return ret;
++ }
++
++ if (of_property_read_bool(node, "motorcomm,keep-pll-enabled")) {
++ /* enable RXC clock when no wire plug */
++ ret = ytphy_modify_ext_with_lock(phydev,
++ YT8521_CLOCK_GATING_REG,
++ YT8521_CGR_RX_CLK_EN, 0);
++ if (ret < 0)
++ return ret;
++ }
++
++ return 0;
++}
++
++/**
++ * yt8531_link_change_notify() - Adjust the tx clock direction according to
++ * the current speed and dts config.
++ * @phydev: a pointer to a &struct phy_device
++ *
++ * NOTE: This function is only used to adapt to VF2 with JH7110 SoC. Please
++ * keep "motorcomm,tx-clk-adj-enabled" not exist in dts when the soc is not
++ * JH7110.
++ */
++static void yt8531_link_change_notify(struct phy_device *phydev)
++{
++ struct device_node *node = phydev->mdio.dev.of_node;
++ bool tx_clk_adj_enabled = false;
++ bool tx_clk_1000_inverted;
++ bool tx_clk_100_inverted;
++ bool tx_clk_10_inverted;
++ u16 val = 0;
++ int ret;
++
++ if (of_property_read_bool(node, "motorcomm,tx-clk-adj-enabled"))
++ tx_clk_adj_enabled = true;
++
++ if (!tx_clk_adj_enabled)
++ return;
++
++ if (of_property_read_bool(node, "motorcomm,tx-clk-10-inverted"))
++ tx_clk_10_inverted = true;
++ if (of_property_read_bool(node, "motorcomm,tx-clk-100-inverted"))
++ tx_clk_100_inverted = true;
++ if (of_property_read_bool(node, "motorcomm,tx-clk-1000-inverted"))
++ tx_clk_1000_inverted = true;
++
++ if (phydev->speed < 0)
++ return;
++
++ switch (phydev->speed) {
++ case SPEED_1000:
++ if (tx_clk_1000_inverted)
++ val = YT8521_RC1R_TX_CLK_SEL_INVERTED;
++ break;
++ case SPEED_100:
++ if (tx_clk_100_inverted)
++ val = YT8521_RC1R_TX_CLK_SEL_INVERTED;
++ break;
++ case SPEED_10:
++ if (tx_clk_10_inverted)
++ val = YT8521_RC1R_TX_CLK_SEL_INVERTED;
++ break;
++ default:
++ return;
++ }
++
++ ret = ytphy_modify_ext_with_lock(phydev, YT8521_RGMII_CONFIG1_REG,
++ YT8521_RC1R_TX_CLK_SEL_INVERTED, val);
++ if (ret < 0)
++ phydev_warn(phydev, "Modify TX_CLK_SEL err:%d\n", ret);
++}
++
+ /**
+ * yt8521_prepare_fiber_features() - A small helper function that setup
+ * fiber's features.
+@@ -1970,6 +2162,17 @@ static struct phy_driver motorcomm_phy_d
+ .resume = yt8521_resume,
+ },
+ {
++ PHY_ID_MATCH_EXACT(PHY_ID_YT8531),
++ .name = "YT8531 Gigabit Ethernet",
++ .probe = yt8531_probe,
++ .config_init = yt8531_config_init,
++ .suspend = genphy_suspend,
++ .resume = genphy_resume,
++ .get_wol = ytphy_get_wol,
++ .set_wol = yt8531_set_wol,
++ .link_change_notify = yt8531_link_change_notify,
++ },
++ {
+ PHY_ID_MATCH_EXACT(PHY_ID_YT8531S),
+ .name = "YT8531S Gigabit Ethernet",
+ .get_features = yt8521_get_features,
+@@ -1990,7 +2193,7 @@ static struct phy_driver motorcomm_phy_d
+
+ module_phy_driver(motorcomm_phy_drvs);
+
+-MODULE_DESCRIPTION("Motorcomm 8511/8521/8531S PHY driver");
++MODULE_DESCRIPTION("Motorcomm 8511/8521/8531/8531S PHY driver");
+ MODULE_AUTHOR("Peter Geis");
+ MODULE_AUTHOR("Frank");
+ MODULE_LICENSE("GPL");
+@@ -1998,6 +2201,7 @@ MODULE_LICENSE("GPL");
+ static const struct mdio_device_id __maybe_unused motorcomm_tbl[] = {
+ { PHY_ID_MATCH_EXACT(PHY_ID_YT8511) },
+ { PHY_ID_MATCH_EXACT(PHY_ID_YT8521) },
++ { PHY_ID_MATCH_EXACT(PHY_ID_YT8531) },
+ { PHY_ID_MATCH_EXACT(PHY_ID_YT8531S) },
+ { /* sentinel */ }
+ };
diff --git a/target/linux/generic/backport-6.6/791-v6.3-10-net-phy-motorcomm-uninitialized-variables-in.patch b/target/linux/generic/backport-6.6/791-v6.3-10-net-phy-motorcomm-uninitialized-variables-in.patch
new file mode 100644
index 0000000000..29ae86dbbc
--- /dev/null
+++ b/target/linux/generic/backport-6.6/791-v6.3-10-net-phy-motorcomm-uninitialized-variables-in.patch
@@ -0,0 +1,34 @@
+From 9753613f7399601f9bae6ee81e9d4274246c98ab Mon Sep 17 00:00:00 2001
+From: Dan Carpenter <error27@gmail.com>
+Date: Wed, 15 Feb 2023 07:21:47 +0300
+Subject: [PATCH] net: phy: motorcomm: uninitialized variables in
+ yt8531_link_change_notify()
+
+These booleans are never set to false, but are just used without being
+initialized.
+
+Fixes: 4ac94f728a58 ("net: phy: Add driver for Motorcomm yt8531 gigabit ethernet phy")
+Signed-off-by: Dan Carpenter <error27@gmail.com>
+Reviewed-by: Frank Sae <Frank.Sae@motor-comm.com>
+Link: https://lore.kernel.org/r/Y+xd2yJet2ImHLoQ@kili
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/motorcomm.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/motorcomm.c
++++ b/drivers/net/phy/motorcomm.c
+@@ -1533,10 +1533,10 @@ static int yt8531_config_init(struct phy
+ static void yt8531_link_change_notify(struct phy_device *phydev)
+ {
+ struct device_node *node = phydev->mdio.dev.of_node;
++ bool tx_clk_1000_inverted = false;
++ bool tx_clk_100_inverted = false;
++ bool tx_clk_10_inverted = false;
+ bool tx_clk_adj_enabled = false;
+- bool tx_clk_1000_inverted;
+- bool tx_clk_100_inverted;
+- bool tx_clk_10_inverted;
+ u16 val = 0;
+ int ret;
+
diff --git a/target/linux/generic/backport-6.6/791-v6.6-11-net-phy-motorcomm-Add-pad-drive-strength-cfg-support.patch b/target/linux/generic/backport-6.6/791-v6.6-11-net-phy-motorcomm-Add-pad-drive-strength-cfg-support.patch
new file mode 100644
index 0000000000..010ca9b68a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/791-v6.6-11-net-phy-motorcomm-Add-pad-drive-strength-cfg-support.patch
@@ -0,0 +1,170 @@
+From 7a561e9351ae7e3fb1f08584d40b49c1e55dde60 Mon Sep 17 00:00:00 2001
+From: Samin Guo <samin.guo@starfivetech.com>
+Date: Thu, 20 Jul 2023 19:15:09 +0800
+Subject: [PATCH] net: phy: motorcomm: Add pad drive strength cfg support
+
+The motorcomm phy (YT8531) supports the ability to adjust the drive
+strength of the rx_clk/rx_data, and the default strength may not be
+suitable for all boards. So add configurable options to better match
+the boards.(e.g. StarFive VisionFive 2)
+
+When we configure the drive strength, we need to read the current
+LDO voltage value to ensure that it is a legal value at that LDO
+voltage.
+
+Reviewed-by: Hal Feng <hal.feng@starfivetech.com>
+Signed-off-by: Samin Guo <samin.guo@starfivetech.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/motorcomm.c | 118 ++++++++++++++++++++++++++++++++++++
+ 1 file changed, 118 insertions(+)
+
+--- a/drivers/net/phy/motorcomm.c
++++ b/drivers/net/phy/motorcomm.c
+@@ -163,6 +163,10 @@
+
+ #define YT8521_CHIP_CONFIG_REG 0xA001
+ #define YT8521_CCR_SW_RST BIT(15)
++#define YT8531_RGMII_LDO_VOL_MASK GENMASK(5, 4)
++#define YT8531_LDO_VOL_3V3 0x0
++#define YT8531_LDO_VOL_1V8 0x2
++
+ /* 1b0 disable 1.9ns rxc clock delay *default*
+ * 1b1 enable 1.9ns rxc clock delay
+ */
+@@ -236,6 +240,12 @@
+ */
+ #define YTPHY_WCR_TYPE_PULSE BIT(0)
+
++#define YTPHY_PAD_DRIVE_STRENGTH_REG 0xA010
++#define YT8531_RGMII_RXC_DS_MASK GENMASK(15, 13)
++#define YT8531_RGMII_RXD_DS_HI_MASK BIT(12) /* Bit 2 of rxd_ds */
++#define YT8531_RGMII_RXD_DS_LOW_MASK GENMASK(5, 4) /* Bit 1/0 of rxd_ds */
++#define YT8531_RGMII_RX_DS_DEFAULT 0x3
++
+ #define YTPHY_SYNCE_CFG_REG 0xA012
+ #define YT8521_SCR_SYNCE_ENABLE BIT(5)
+ /* 1b0 output 25m clock
+@@ -835,6 +845,110 @@ static int ytphy_rgmii_clk_delay_config_
+ }
+
+ /**
++ * struct ytphy_ldo_vol_map - map a current value to a register value
++ * @vol: ldo voltage
++ * @ds: value in the register
++ * @cur: value in device configuration
++ */
++struct ytphy_ldo_vol_map {
++ u32 vol;
++ u32 ds;
++ u32 cur;
++};
++
++static const struct ytphy_ldo_vol_map yt8531_ldo_vol[] = {
++ {.vol = YT8531_LDO_VOL_1V8, .ds = 0, .cur = 1200},
++ {.vol = YT8531_LDO_VOL_1V8, .ds = 1, .cur = 2100},
++ {.vol = YT8531_LDO_VOL_1V8, .ds = 2, .cur = 2700},
++ {.vol = YT8531_LDO_VOL_1V8, .ds = 3, .cur = 2910},
++ {.vol = YT8531_LDO_VOL_1V8, .ds = 4, .cur = 3110},
++ {.vol = YT8531_LDO_VOL_1V8, .ds = 5, .cur = 3600},
++ {.vol = YT8531_LDO_VOL_1V8, .ds = 6, .cur = 3970},
++ {.vol = YT8531_LDO_VOL_1V8, .ds = 7, .cur = 4350},
++ {.vol = YT8531_LDO_VOL_3V3, .ds = 0, .cur = 3070},
++ {.vol = YT8531_LDO_VOL_3V3, .ds = 1, .cur = 4080},
++ {.vol = YT8531_LDO_VOL_3V3, .ds = 2, .cur = 4370},
++ {.vol = YT8531_LDO_VOL_3V3, .ds = 3, .cur = 4680},
++ {.vol = YT8531_LDO_VOL_3V3, .ds = 4, .cur = 5020},
++ {.vol = YT8531_LDO_VOL_3V3, .ds = 5, .cur = 5450},
++ {.vol = YT8531_LDO_VOL_3V3, .ds = 6, .cur = 5740},
++ {.vol = YT8531_LDO_VOL_3V3, .ds = 7, .cur = 6140},
++};
++
++static u32 yt8531_get_ldo_vol(struct phy_device *phydev)
++{
++ u32 val;
++
++ val = ytphy_read_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG);
++ val = FIELD_GET(YT8531_RGMII_LDO_VOL_MASK, val);
++
++ return val <= YT8531_LDO_VOL_1V8 ? val : YT8531_LDO_VOL_1V8;
++}
++
++static int yt8531_get_ds_map(struct phy_device *phydev, u32 cur)
++{
++ u32 vol;
++ int i;
++
++ vol = yt8531_get_ldo_vol(phydev);
++ for (i = 0; i < ARRAY_SIZE(yt8531_ldo_vol); i++) {
++ if (yt8531_ldo_vol[i].vol == vol && yt8531_ldo_vol[i].cur == cur)
++ return yt8531_ldo_vol[i].ds;
++ }
++
++ return -EINVAL;
++}
++
++static int yt8531_set_ds(struct phy_device *phydev)
++{
++ struct device_node *node = phydev->mdio.dev.of_node;
++ u32 ds_field_low, ds_field_hi, val;
++ int ret, ds;
++
++ /* set rgmii rx clk driver strength */
++ if (!of_property_read_u32(node, "motorcomm,rx-clk-drv-microamp", &val)) {
++ ds = yt8531_get_ds_map(phydev, val);
++ if (ds < 0)
++ return dev_err_probe(&phydev->mdio.dev, ds,
++ "No matching current value was found.\n");
++ } else {
++ ds = YT8531_RGMII_RX_DS_DEFAULT;
++ }
++
++ ret = ytphy_modify_ext_with_lock(phydev,
++ YTPHY_PAD_DRIVE_STRENGTH_REG,
++ YT8531_RGMII_RXC_DS_MASK,
++ FIELD_PREP(YT8531_RGMII_RXC_DS_MASK, ds));
++ if (ret < 0)
++ return ret;
++
++ /* set rgmii rx data driver strength */
++ if (!of_property_read_u32(node, "motorcomm,rx-data-drv-microamp", &val)) {
++ ds = yt8531_get_ds_map(phydev, val);
++ if (ds < 0)
++ return dev_err_probe(&phydev->mdio.dev, ds,
++ "No matching current value was found.\n");
++ } else {
++ ds = YT8531_RGMII_RX_DS_DEFAULT;
++ }
++
++ ds_field_hi = FIELD_GET(BIT(2), ds);
++ ds_field_hi = FIELD_PREP(YT8531_RGMII_RXD_DS_HI_MASK, ds_field_hi);
++
++ ds_field_low = FIELD_GET(GENMASK(1, 0), ds);
++ ds_field_low = FIELD_PREP(YT8531_RGMII_RXD_DS_LOW_MASK, ds_field_low);
++
++ ret = ytphy_modify_ext_with_lock(phydev,
++ YTPHY_PAD_DRIVE_STRENGTH_REG,
++ YT8531_RGMII_RXD_DS_LOW_MASK | YT8531_RGMII_RXD_DS_HI_MASK,
++ ds_field_low | ds_field_hi);
++ if (ret < 0)
++ return ret;
++
++ return 0;
++}
++
++/**
+ * yt8521_probe() - read chip config then set suitable polling_mode
+ * @phydev: a pointer to a &struct phy_device
+ *
+@@ -1518,6 +1632,10 @@ static int yt8531_config_init(struct phy
+ return ret;
+ }
+
++ ret = yt8531_set_ds(phydev);
++ if (ret < 0)
++ return ret;
++
+ return 0;
+ }
+
diff --git a/target/linux/generic/backport-6.6/792-v6.6-net-phylink-add-pcs_enable-pcs_disable-methods.patch b/target/linux/generic/backport-6.6/792-v6.6-net-phylink-add-pcs_enable-pcs_disable-methods.patch
new file mode 100644
index 0000000000..f82c8fc622
--- /dev/null
+++ b/target/linux/generic/backport-6.6/792-v6.6-net-phylink-add-pcs_enable-pcs_disable-methods.patch
@@ -0,0 +1,173 @@
+From 90ef0a7b0622c62758b2638604927867775479ea Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 13 Jul 2023 09:42:07 +0100
+Subject: [PATCH] net: phylink: add pcs_enable()/pcs_disable() methods
+
+Add phylink PCS enable/disable callbacks that will allow us to place
+IEEE 802.3 register compliant PCS in power-down mode while not being
+used.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/phylink.c | 48 +++++++++++++++++++++++++++++++--------
+ include/linux/phylink.h | 16 +++++++++++++
+ 2 files changed, 55 insertions(+), 9 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -34,6 +34,10 @@ enum {
+ PHYLINK_DISABLE_STOPPED,
+ PHYLINK_DISABLE_LINK,
+ PHYLINK_DISABLE_MAC_WOL,
++
++ PCS_STATE_DOWN = 0,
++ PCS_STATE_STARTING,
++ PCS_STATE_STARTED,
+ };
+
+ /**
+@@ -72,6 +76,7 @@ struct phylink {
+ struct phylink_link_state phy_state;
+ struct work_struct resolve;
+ unsigned int pcs_neg_mode;
++ unsigned int pcs_state;
+
+ bool mac_link_dropped;
+ bool using_mac_select_pcs;
+@@ -992,6 +997,22 @@ static void phylink_resolve_an_pause(str
+ }
+ }
+
++static void phylink_pcs_disable(struct phylink_pcs *pcs)
++{
++ if (pcs && pcs->ops->pcs_disable)
++ pcs->ops->pcs_disable(pcs);
++}
++
++static int phylink_pcs_enable(struct phylink_pcs *pcs)
++{
++ int err = 0;
++
++ if (pcs && pcs->ops->pcs_enable)
++ err = pcs->ops->pcs_enable(pcs);
++
++ return err;
++}
++
+ static int phylink_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ const struct phylink_link_state *state,
+ bool permit_pause_to_mac)
+@@ -1094,11 +1115,17 @@ static void phylink_major_config(struct
+ /* If we have a new PCS, switch to the new PCS after preparing the MAC
+ * for the change.
+ */
+- if (pcs_changed)
++ if (pcs_changed) {
++ phylink_pcs_disable(pl->pcs);
++
+ pl->pcs = pcs;
++ }
+
+ phylink_mac_config(pl, state);
+
++ if (pl->pcs_state == PCS_STATE_STARTING || pcs_changed)
++ phylink_pcs_enable(pl->pcs);
++
+ neg_mode = pl->cur_link_an_mode;
+ if (pl->pcs && pl->pcs->neg_mode)
+ neg_mode = pl->pcs_neg_mode;
+@@ -1586,6 +1613,7 @@ struct phylink *phylink_create(struct ph
+ pl->link_config.pause = MLO_PAUSE_AN;
+ pl->link_config.speed = SPEED_UNKNOWN;
+ pl->link_config.duplex = DUPLEX_UNKNOWN;
++ pl->pcs_state = PCS_STATE_DOWN;
+ pl->mac_ops = mac_ops;
+ __set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
+ timer_setup(&pl->link_poll, phylink_fixed_poll, 0);
+@@ -1987,6 +2015,8 @@ void phylink_start(struct phylink *pl)
+ if (pl->netdev)
+ netif_carrier_off(pl->netdev);
+
++ pl->pcs_state = PCS_STATE_STARTING;
++
+ /* Apply the link configuration to the MAC when starting. This allows
+ * a fixed-link to start with the correct parameters, and also
+ * ensures that we set the appropriate advertisement for Serdes links.
+@@ -1997,6 +2027,8 @@ void phylink_start(struct phylink *pl)
+ */
+ phylink_mac_initial_config(pl, true);
+
++ pl->pcs_state = PCS_STATE_STARTED;
++
+ phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_STOPPED);
+
+ if (pl->cfg_link_an_mode == MLO_AN_FIXED && pl->link_gpio) {
+@@ -2015,15 +2047,9 @@ void phylink_start(struct phylink *pl)
+ poll = true;
+ }
+
+- switch (pl->cfg_link_an_mode) {
+- case MLO_AN_FIXED:
++ if (pl->cfg_link_an_mode == MLO_AN_FIXED)
+ poll |= pl->config->poll_fixed_state;
+- break;
+- case MLO_AN_INBAND:
+- if (pl->pcs)
+- poll |= pl->pcs->poll;
+- break;
+- }
++
+ if (poll)
+ mod_timer(&pl->link_poll, jiffies + HZ);
+ if (pl->phydev)
+@@ -2060,6 +2086,10 @@ void phylink_stop(struct phylink *pl)
+ }
+
+ phylink_run_resolve_and_disable(pl, PHYLINK_DISABLE_STOPPED);
++
++ pl->pcs_state = PCS_STATE_DOWN;
++
++ phylink_pcs_disable(pl->pcs);
+ }
+ EXPORT_SYMBOL_GPL(phylink_stop);
+
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -533,6 +533,8 @@ struct phylink_pcs {
+ /**
+ * struct phylink_pcs_ops - MAC PCS operations structure.
+ * @pcs_validate: validate the link configuration.
++ * @pcs_enable: enable the PCS.
++ * @pcs_disable: disable the PCS.
+ * @pcs_get_state: read the current MAC PCS link state from the hardware.
+ * @pcs_config: configure the MAC PCS for the selected mode and state.
+ * @pcs_an_restart: restart 802.3z BaseX autonegotiation.
+@@ -542,6 +544,8 @@ struct phylink_pcs {
+ struct phylink_pcs_ops {
+ int (*pcs_validate)(struct phylink_pcs *pcs, unsigned long *supported,
+ const struct phylink_link_state *state);
++ int (*pcs_enable)(struct phylink_pcs *pcs);
++ void (*pcs_disable)(struct phylink_pcs *pcs);
+ void (*pcs_get_state)(struct phylink_pcs *pcs,
+ struct phylink_link_state *state);
+ int (*pcs_config)(struct phylink_pcs *pcs, unsigned int neg_mode,
+@@ -572,6 +576,18 @@ int pcs_validate(struct phylink_pcs *pcs
+ const struct phylink_link_state *state);
+
+ /**
++ * pcs_enable() - enable the PCS.
++ * @pcs: a pointer to a &struct phylink_pcs.
++ */
++int pcs_enable(struct phylink_pcs *pcs);
++
++/**
++ * pcs_disable() - disable the PCS.
++ * @pcs: a pointer to a &struct phylink_pcs.
++ */
++void pcs_disable(struct phylink_pcs *pcs);
++
++/**
+ * pcs_get_state() - Read the current inband link state from the hardware
+ * @pcs: a pointer to a &struct phylink_pcs.
+ * @state: a pointer to a &struct phylink_link_state.
diff --git a/target/linux/generic/backport-6.6/793-v6.6-net-pcs-lynxi-implement-pcs_disable-op.patch b/target/linux/generic/backport-6.6/793-v6.6-net-pcs-lynxi-implement-pcs_disable-op.patch
new file mode 100644
index 0000000000..6b6369761a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/793-v6.6-net-pcs-lynxi-implement-pcs_disable-op.patch
@@ -0,0 +1,44 @@
+From e4ccdfb78a47132f2d215658aab8902fc457c4b4 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Fri, 18 Aug 2023 04:07:46 +0100
+Subject: [PATCH 082/125] net: pcs: lynxi: implement pcs_disable op
+
+When switching from 10GBase-R/5GBase-R/USXGMII to one of the interface
+modes provided by mtk-pcs-lynxi we need to make sure to always perform
+a full configuration of the PHYA.
+
+Implement pcs_disable op which resets the stored interface mode to
+PHY_INTERFACE_MODE_NA to trigger a full reconfiguration once the LynxI
+PCS driver had previously been deselected in favor of another PCS
+driver such as the to-be-added driver for the USXGMII PCS found in
+MT7988.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/f23d1a60d2c9d2fb72e32dcb0eaa5f7e867a3d68.1692327891.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/pcs/pcs-mtk-lynxi.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+--- a/drivers/net/pcs/pcs-mtk-lynxi.c
++++ b/drivers/net/pcs/pcs-mtk-lynxi.c
+@@ -234,11 +234,19 @@ static void mtk_pcs_lynxi_link_up(struct
+ }
+ }
+
++static void mtk_pcs_lynxi_disable(struct phylink_pcs *pcs)
++{
++ struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
++
++ mpcs->interface = PHY_INTERFACE_MODE_NA;
++}
++
+ static const struct phylink_pcs_ops mtk_pcs_lynxi_ops = {
+ .pcs_get_state = mtk_pcs_lynxi_get_state,
+ .pcs_config = mtk_pcs_lynxi_config,
+ .pcs_an_restart = mtk_pcs_lynxi_restart_an,
+ .pcs_link_up = mtk_pcs_lynxi_link_up,
++ .pcs_disable = mtk_pcs_lynxi_disable,
+ };
+
+ struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
diff --git a/target/linux/generic/backport-6.6/794-v6.2-net-core-Allow-live-renaming-when-an-interface-is-up.patch b/target/linux/generic/backport-6.6/794-v6.2-net-core-Allow-live-renaming-when-an-interface-is-up.patch
new file mode 100644
index 0000000000..c4141eee93
--- /dev/null
+++ b/target/linux/generic/backport-6.6/794-v6.2-net-core-Allow-live-renaming-when-an-interface-is-up.patch
@@ -0,0 +1,136 @@
+From: Andy Ren <andy.ren@getcruise.com>
+Date: Mon, 7 Nov 2022 09:42:42 -0800
+Subject: [PATCH] net/core: Allow live renaming when an interface is up
+
+Allow a network interface to be renamed when the interface
+is up.
+
+As described in the netconsole documentation [1], when netconsole is
+used as a built-in, it will bring up the specified interface as soon as
+possible. As a result, user space will not be able to rename the
+interface since the kernel disallows renaming of interfaces that are
+administratively up unless the 'IFF_LIVE_RENAME_OK' private flag was set
+by the kernel.
+
+The original solution [2] to this problem was to add a new parameter to
+the netconsole configuration parameters that allows renaming of
+the interface used by netconsole while it is administratively up.
+However, during the discussion that followed, it became apparent that we
+have no reason to keep the current restriction and instead we should
+allow user space to rename interfaces regardless of their administrative
+state:
+
+1. The restriction was put in place over 20 years ago when renaming was
+only possible via IOCTL and before rtnetlink started notifying user
+space about such changes like it does today.
+
+2. The 'IFF_LIVE_RENAME_OK' flag was added over 3 years ago in version
+5.2 and no regressions were reported.
+
+3. In-kernel listeners to 'NETDEV_CHANGENAME' do not seem to care about
+the administrative state of interface.
+
+Therefore, allow user space to rename running interfaces by removing the
+restriction and the associated 'IFF_LIVE_RENAME_OK' flag. Help in
+possible triage by emitting a message to the kernel log that an
+interface was renamed while UP.
+
+[1] https://www.kernel.org/doc/Documentation/networking/netconsole.rst
+[2] https://lore.kernel.org/netdev/20221102002420.2613004-1-andy.ren@getcruise.com/
+
+Signed-off-by: Andy Ren <andy.ren@getcruise.com>
+Reviewed-by: Ido Schimmel <idosch@nvidia.com>
+Reviewed-by: David Ahern <dsahern@kernel.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/include/linux/netdevice.h
++++ b/include/linux/netdevice.h
+@@ -1691,7 +1691,6 @@ struct net_device_ops {
+ * @IFF_FAILOVER: device is a failover master device
+ * @IFF_FAILOVER_SLAVE: device is lower dev of a failover master device
+ * @IFF_L3MDEV_RX_HANDLER: only invoke the rx handler of L3 master device
+- * @IFF_LIVE_RENAME_OK: rename is allowed while device is up and running
+ * @IFF_TX_SKB_NO_LINEAR: device/driver is capable of xmitting frames with
+ * skb_headlen(skb) == 0 (data starts from frag0)
+ * @IFF_CHANGE_PROTO_DOWN: device supports setting carrier via IFLA_PROTO_DOWN
+@@ -1727,7 +1726,7 @@ enum netdev_priv_flags {
+ IFF_FAILOVER = 1<<27,
+ IFF_FAILOVER_SLAVE = 1<<28,
+ IFF_L3MDEV_RX_HANDLER = 1<<29,
+- IFF_LIVE_RENAME_OK = 1<<30,
++ /* was IFF_LIVE_RENAME_OK */
+ IFF_TX_SKB_NO_LINEAR = BIT_ULL(31),
+ IFF_CHANGE_PROTO_DOWN = BIT_ULL(32),
+ };
+@@ -1762,7 +1761,6 @@ enum netdev_priv_flags {
+ #define IFF_FAILOVER IFF_FAILOVER
+ #define IFF_FAILOVER_SLAVE IFF_FAILOVER_SLAVE
+ #define IFF_L3MDEV_RX_HANDLER IFF_L3MDEV_RX_HANDLER
+-#define IFF_LIVE_RENAME_OK IFF_LIVE_RENAME_OK
+ #define IFF_TX_SKB_NO_LINEAR IFF_TX_SKB_NO_LINEAR
+
+ /* Specifies the type of the struct net_device::ml_priv pointer */
+--- a/net/core/dev.c
++++ b/net/core/dev.c
+@@ -1188,22 +1188,6 @@ int dev_change_name(struct net_device *d
+
+ net = dev_net(dev);
+
+- /* Some auto-enslaved devices e.g. failover slaves are
+- * special, as userspace might rename the device after
+- * the interface had been brought up and running since
+- * the point kernel initiated auto-enslavement. Allow
+- * live name change even when these slave devices are
+- * up and running.
+- *
+- * Typically, users of these auto-enslaving devices
+- * don't actually care about slave name change, as
+- * they are supposed to operate on master interface
+- * directly.
+- */
+- if (dev->flags & IFF_UP &&
+- likely(!(dev->priv_flags & IFF_LIVE_RENAME_OK)))
+- return -EBUSY;
+-
+ down_write(&devnet_rename_sem);
+
+ if (strncmp(newname, dev->name, IFNAMSIZ) == 0) {
+@@ -1220,7 +1204,8 @@ int dev_change_name(struct net_device *d
+ }
+
+ if (oldname[0] && !strchr(oldname, '%'))
+- netdev_info(dev, "renamed from %s\n", oldname);
++ netdev_info(dev, "renamed from %s%s\n", oldname,
++ dev->flags & IFF_UP ? " (while UP)" : "");
+
+ old_assign_type = dev->name_assign_type;
+ dev->name_assign_type = NET_NAME_RENAMED;
+--- a/net/core/failover.c
++++ b/net/core/failover.c
+@@ -80,14 +80,14 @@ static int failover_slave_register(struc
+ goto err_upper_link;
+ }
+
+- slave_dev->priv_flags |= (IFF_FAILOVER_SLAVE | IFF_LIVE_RENAME_OK);
++ slave_dev->priv_flags |= IFF_FAILOVER_SLAVE;
+
+ if (fops && fops->slave_register &&
+ !fops->slave_register(slave_dev, failover_dev))
+ return NOTIFY_OK;
+
+ netdev_upper_dev_unlink(slave_dev, failover_dev);
+- slave_dev->priv_flags &= ~(IFF_FAILOVER_SLAVE | IFF_LIVE_RENAME_OK);
++ slave_dev->priv_flags &= ~IFF_FAILOVER_SLAVE;
+ err_upper_link:
+ netdev_rx_handler_unregister(slave_dev);
+ done:
+@@ -121,7 +121,7 @@ int failover_slave_unregister(struct net
+
+ netdev_rx_handler_unregister(slave_dev);
+ netdev_upper_dev_unlink(slave_dev, failover_dev);
+- slave_dev->priv_flags &= ~(IFF_FAILOVER_SLAVE | IFF_LIVE_RENAME_OK);
++ slave_dev->priv_flags &= ~IFF_FAILOVER_SLAVE;
+
+ if (fops && fops->slave_unregister &&
+ !fops->slave_unregister(slave_dev, failover_dev))
diff --git a/target/linux/generic/backport-6.6/795-v6.3-02-cdc_ether-no-need-to-blacklist-any-r8152-devices.patch b/target/linux/generic/backport-6.6/795-v6.3-02-cdc_ether-no-need-to-blacklist-any-r8152-devices.patch
new file mode 100644
index 0000000000..17131c16d3
--- /dev/null
+++ b/target/linux/generic/backport-6.6/795-v6.3-02-cdc_ether-no-need-to-blacklist-any-r8152-devices.patch
@@ -0,0 +1,158 @@
+From 69649ef8405320f81497f4757faac8234f61b167 Mon Sep 17 00:00:00 2001
+From: Bjørn Mork <bjorn@mork.no>
+Date: Fri, 6 Jan 2023 17:07:39 +0100
+Subject: [PATCH] cdc_ether: no need to blacklist any r8152 devices
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The r8152 driver does not need this anymore.
+
+Dropping blacklist entries adds optional support for these
+devices in ECM mode.
+
+The 8153 devices are handled by the r8153_ecm driver when
+in ECM mode, and must still be blacklisted here.
+
+Signed-off-by: Bjørn Mork <bjorn@mork.no>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/usb/cdc_ether.c | 114 ------------------------------------
+ 1 file changed, 114 deletions(-)
+
+--- a/drivers/net/usb/cdc_ether.c
++++ b/drivers/net/usb/cdc_ether.c
+@@ -768,13 +768,6 @@ static const struct usb_device_id produc
+ .driver_info = 0,
+ },
+
+-/* Realtek RTL8152 Based USB 2.0 Ethernet Adapters */
+-{
+- USB_DEVICE_AND_INTERFACE_INFO(REALTEK_VENDOR_ID, 0x8152, USB_CLASS_COMM,
+- USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+- .driver_info = 0,
+-},
+-
+ /* Realtek RTL8153 Based USB 3.0 Ethernet Adapters */
+ {
+ USB_DEVICE_AND_INTERFACE_INFO(REALTEK_VENDOR_ID, 0x8153, USB_CLASS_COMM,
+@@ -782,119 +775,12 @@ static const struct usb_device_id produc
+ .driver_info = 0,
+ },
+
+-/* Samsung USB Ethernet Adapters */
+-{
+- USB_DEVICE_AND_INTERFACE_INFO(SAMSUNG_VENDOR_ID, 0xa101, USB_CLASS_COMM,
+- USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+- .driver_info = 0,
+-},
+-
+-#if IS_ENABLED(CONFIG_USB_RTL8152)
+-/* Linksys USB3GIGV1 Ethernet Adapter */
+-{
+- USB_DEVICE_AND_INTERFACE_INFO(LINKSYS_VENDOR_ID, 0x0041, USB_CLASS_COMM,
+- USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+- .driver_info = 0,
+-},
+-#endif
+-
+-/* Lenovo ThinkPad OneLink+ Dock (based on Realtek RTL8153) */
+-{
+- USB_DEVICE_AND_INTERFACE_INFO(LENOVO_VENDOR_ID, 0x3054, USB_CLASS_COMM,
+- USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+- .driver_info = 0,
+-},
+-
+-/* ThinkPad USB-C Dock (based on Realtek RTL8153) */
+-{
+- USB_DEVICE_AND_INTERFACE_INFO(LENOVO_VENDOR_ID, 0x3062, USB_CLASS_COMM,
+- USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+- .driver_info = 0,
+-},
+-
+-/* ThinkPad Thunderbolt 3 Dock (based on Realtek RTL8153) */
+-{
+- USB_DEVICE_AND_INTERFACE_INFO(LENOVO_VENDOR_ID, 0x3069, USB_CLASS_COMM,
+- USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+- .driver_info = 0,
+-},
+-
+-/* ThinkPad Thunderbolt 3 Dock Gen 2 (based on Realtek RTL8153) */
+-{
+- USB_DEVICE_AND_INTERFACE_INFO(LENOVO_VENDOR_ID, 0x3082, USB_CLASS_COMM,
+- USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+- .driver_info = 0,
+-},
+-
+-/* Lenovo Thinkpad USB 3.0 Ethernet Adapters (based on Realtek RTL8153) */
+-{
+- USB_DEVICE_AND_INTERFACE_INFO(LENOVO_VENDOR_ID, 0x7205, USB_CLASS_COMM,
+- USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+- .driver_info = 0,
+-},
+-
+-/* Lenovo USB C to Ethernet Adapter (based on Realtek RTL8153) */
+-{
+- USB_DEVICE_AND_INTERFACE_INFO(LENOVO_VENDOR_ID, 0x720c, USB_CLASS_COMM,
+- USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+- .driver_info = 0,
+-},
+-
+-/* Lenovo USB-C Travel Hub (based on Realtek RTL8153) */
+-{
+- USB_DEVICE_AND_INTERFACE_INFO(LENOVO_VENDOR_ID, 0x7214, USB_CLASS_COMM,
+- USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+- .driver_info = 0,
+-},
+-
+ /* Lenovo Powered USB-C Travel Hub (4X90S92381, based on Realtek RTL8153) */
+ {
+ USB_DEVICE_AND_INTERFACE_INFO(LENOVO_VENDOR_ID, 0x721e, USB_CLASS_COMM,
+ USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+ .driver_info = 0,
+ },
+-
+-/* ThinkPad USB-C Dock Gen 2 (based on Realtek RTL8153) */
+-{
+- USB_DEVICE_AND_INTERFACE_INFO(LENOVO_VENDOR_ID, 0xa387, USB_CLASS_COMM,
+- USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+- .driver_info = 0,
+-},
+-
+-/* NVIDIA Tegra USB 3.0 Ethernet Adapters (based on Realtek RTL8153) */
+-{
+- USB_DEVICE_AND_INTERFACE_INFO(NVIDIA_VENDOR_ID, 0x09ff, USB_CLASS_COMM,
+- USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+- .driver_info = 0,
+-},
+-
+-/* Microsoft Surface 2 dock (based on Realtek RTL8152) */
+-{
+- USB_DEVICE_AND_INTERFACE_INFO(MICROSOFT_VENDOR_ID, 0x07ab, USB_CLASS_COMM,
+- USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+- .driver_info = 0,
+-},
+-
+-/* Microsoft Surface Ethernet Adapter (based on Realtek RTL8153) */
+-{
+- USB_DEVICE_AND_INTERFACE_INFO(MICROSOFT_VENDOR_ID, 0x07c6, USB_CLASS_COMM,
+- USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+- .driver_info = 0,
+-},
+-
+-/* Microsoft Surface Ethernet Adapter (based on Realtek RTL8153B) */
+-{
+- USB_DEVICE_AND_INTERFACE_INFO(MICROSOFT_VENDOR_ID, 0x0927, USB_CLASS_COMM,
+- USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+- .driver_info = 0,
+-},
+-
+-/* TP-LINK UE300 USB 3.0 Ethernet Adapters (based on Realtek RTL8153) */
+-{
+- USB_DEVICE_AND_INTERFACE_INFO(TPLINK_VENDOR_ID, 0x0601, USB_CLASS_COMM,
+- USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+- .driver_info = 0,
+-},
+
+ /* Aquantia AQtion USB to 5GbE Controller (based on AQC111U) */
+ {
diff --git a/target/linux/generic/backport-6.6/795-v6.3-05-r8152-reduce-the-control-transfer-of-rtl8152_get_ver.patch b/target/linux/generic/backport-6.6/795-v6.3-05-r8152-reduce-the-control-transfer-of-rtl8152_get_ver.patch
new file mode 100644
index 0000000000..565fbb3074
--- /dev/null
+++ b/target/linux/generic/backport-6.6/795-v6.3-05-r8152-reduce-the-control-transfer-of-rtl8152_get_ver.patch
@@ -0,0 +1,46 @@
+From 02767440e1dda9861a11ca1dbe0f19a760b1d5c2 Mon Sep 17 00:00:00 2001
+From: Hayes Wang <hayeswang@realtek.com>
+Date: Thu, 19 Jan 2023 15:40:43 +0800
+Subject: [PATCH] r8152: reduce the control transfer of rtl8152_get_version()
+
+Reduce the control transfer by moving calling rtl8152_get_version() in
+rtl8152_probe(). This could prevent from calling rtl8152_get_version()
+for unnecessary situations. For example, after setting config #2 for the
+device, there are two interfaces and rtl8152_probe() may be called
+twice. However, we don't need to call rtl8152_get_version() for this
+situation.
+
+Signed-off-by: Hayes Wang <hayeswang@realtek.com>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/usb/r8152.c | 9 +++++----
+ 1 file changed, 5 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/usb/r8152.c
++++ b/drivers/net/usb/r8152.c
+@@ -9638,20 +9638,21 @@ static int rtl8152_probe(struct usb_inte
+ const struct usb_device_id *id)
+ {
+ struct usb_device *udev = interface_to_usbdev(intf);
+- u8 version = rtl8152_get_version(intf);
+ struct r8152 *tp;
+ struct net_device *netdev;
++ u8 version;
+ int ret;
+
+- if (version == RTL_VER_UNKNOWN)
+- return -ENODEV;
+-
+ if (intf->cur_altsetting->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC)
+ return -ENODEV;
+
+ if (!rtl_check_vendor_ok(intf))
+ return -ENODEV;
+
++ version = rtl8152_get_version(intf);
++ if (version == RTL_VER_UNKNOWN)
++ return -ENODEV;
++
+ usb_reset_device(udev);
+ netdev = alloc_etherdev(sizeof(struct r8152));
+ if (!netdev) {
diff --git a/target/linux/generic/backport-6.6/795-v6.3-06-r8152-Add-__GFP_NOWARN-to-big-allocations.patch b/target/linux/generic/backport-6.6/795-v6.3-06-r8152-Add-__GFP_NOWARN-to-big-allocations.patch
new file mode 100644
index 0000000000..cdabca36d2
--- /dev/null
+++ b/target/linux/generic/backport-6.6/795-v6.3-06-r8152-Add-__GFP_NOWARN-to-big-allocations.patch
@@ -0,0 +1,55 @@
+From 5cc33f139e11b893ff6dc60d8a0ae865a65521ac Mon Sep 17 00:00:00 2001
+From: Douglas Anderson <dianders@chromium.org>
+Date: Thu, 6 Apr 2023 17:14:26 -0700
+Subject: [PATCH] r8152: Add __GFP_NOWARN to big allocations
+
+When memory is a little tight on my system, it's pretty easy to see
+warnings that look like this.
+
+ ksoftirqd/0: page allocation failure: order:3, mode:0x40a20(GFP_ATOMIC|__GFP_COMP), nodemask=(null),cpuset=/,mems_allowed=0
+ ...
+ Call trace:
+ dump_backtrace+0x0/0x1e8
+ show_stack+0x20/0x2c
+ dump_stack_lvl+0x60/0x78
+ dump_stack+0x18/0x38
+ warn_alloc+0x104/0x174
+ __alloc_pages+0x588/0x67c
+ alloc_rx_agg+0xa0/0x190 [r8152 ...]
+ r8152_poll+0x270/0x760 [r8152 ...]
+ __napi_poll+0x44/0x1ec
+ net_rx_action+0x100/0x300
+ __do_softirq+0xec/0x38c
+ run_ksoftirqd+0x38/0xec
+ smpboot_thread_fn+0xb8/0x248
+ kthread+0x134/0x154
+ ret_from_fork+0x10/0x20
+
+On a fragmented system it's normal that order 3 allocations will
+sometimes fail, especially atomic ones. The driver handles these
+failures fine and the WARN just creates spam in the logs for this
+case. The __GFP_NOWARN flag is exactly for this situation, so add it
+to the allocation.
+
+NOTE: my testing is on a 5.15 system, but there should be no reason
+that this would be fundamentally different on a mainline kernel.
+
+Signed-off-by: Douglas Anderson <dianders@chromium.org>
+Acked-by: Hayes Wang <hayeswang@realtek.com>
+Link: https://lore.kernel.org/r/20230406171411.1.I84dbef45786af440fd269b71e9436a96a8e7a152@changeid
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/usb/r8152.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/usb/r8152.c
++++ b/drivers/net/usb/r8152.c
+@@ -1947,7 +1947,7 @@ static struct rx_agg *alloc_rx_agg(struc
+ if (!rx_agg)
+ return NULL;
+
+- rx_agg->page = alloc_pages(mflags | __GFP_COMP, order);
++ rx_agg->page = alloc_pages(mflags | __GFP_COMP | __GFP_NOWARN, order);
+ if (!rx_agg->page)
+ goto free_rx;
+
diff --git a/target/linux/generic/backport-6.6/795-v6.6-08-r8152-adjust-generic_ocp_write-function.patch b/target/linux/generic/backport-6.6/795-v6.6-08-r8152-adjust-generic_ocp_write-function.patch
new file mode 100644
index 0000000000..3ba79d6cc6
--- /dev/null
+++ b/target/linux/generic/backport-6.6/795-v6.6-08-r8152-adjust-generic_ocp_write-function.patch
@@ -0,0 +1,70 @@
+From 57df0fb9d511f91202114813e90128d65c0589f0 Mon Sep 17 00:00:00 2001
+From: Hayes Wang <hayeswang@realtek.com>
+Date: Wed, 26 Jul 2023 11:08:07 +0800
+Subject: [PATCH] r8152: adjust generic_ocp_write function
+
+Reduce the control transfer if all bytes of first or the last DWORD are
+written.
+
+The original method is to split the control transfer into three parts
+(the first DWORD, middle continuous data, and the last DWORD). However,
+they could be combined if whole bytes of the first DWORD or last DWORD
+are written. That is, the first DWORD or the last DWORD could be combined
+with the middle continuous data, if the byte_en is 0xff.
+
+Signed-off-by: Hayes Wang <hayeswang@realtek.com>
+Link: https://lore.kernel.org/r/20230726030808.9093-418-nic_swsd@realtek.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/usb/r8152.c | 29 ++++++++++++++++++-----------
+ 1 file changed, 18 insertions(+), 11 deletions(-)
+
+--- a/drivers/net/usb/r8152.c
++++ b/drivers/net/usb/r8152.c
+@@ -1313,16 +1313,24 @@ static int generic_ocp_write(struct r815
+ byteen_end = byteen & BYTE_EN_END_MASK;
+
+ byen = byteen_start | (byteen_start << 4);
+- ret = set_registers(tp, index, type | byen, 4, data);
+- if (ret < 0)
+- goto error1;
+-
+- index += 4;
+- data += 4;
+- size -= 4;
+
+- if (size) {
++ /* Split the first DWORD if the byte_en is not 0xff */
++ if (byen != BYTE_EN_DWORD) {
++ ret = set_registers(tp, index, type | byen, 4, data);
++ if (ret < 0)
++ goto error1;
++
++ index += 4;
++ data += 4;
+ size -= 4;
++ }
++
++ if (size) {
++ byen = byteen_end | (byteen_end >> 4);
++
++ /* Split the last DWORD if the byte_en is not 0xff */
++ if (byen != BYTE_EN_DWORD)
++ size -= 4;
+
+ while (size) {
+ if (size > limit) {
+@@ -1349,10 +1357,9 @@ static int generic_ocp_write(struct r815
+ }
+ }
+
+- byen = byteen_end | (byteen_end >> 4);
+- ret = set_registers(tp, index, type | byen, 4, data);
+- if (ret < 0)
+- goto error1;
++ /* Set the last DWORD */
++ if (byen != BYTE_EN_DWORD)
++ ret = set_registers(tp, index, type | byen, 4, data);
+ }
+
+ error1:
diff --git a/target/linux/generic/backport-6.6/795-v6.6-09-r8152-set-bp-in-bulk.patch b/target/linux/generic/backport-6.6/795-v6.6-09-r8152-set-bp-in-bulk.patch
new file mode 100644
index 0000000000..bc70c5af02
--- /dev/null
+++ b/target/linux/generic/backport-6.6/795-v6.6-09-r8152-set-bp-in-bulk.patch
@@ -0,0 +1,129 @@
+From e5c266a61186b462c388c53a3564c375e72f2244 Mon Sep 17 00:00:00 2001
+From: Hayes Wang <hayeswang@realtek.com>
+Date: Wed, 26 Jul 2023 11:08:08 +0800
+Subject: [PATCH] r8152: set bp in bulk
+
+PLA_BP_0 ~ PLA_BP_15 (0xfc28 ~ 0xfc46) are continuous registers, so we
+could combine the control transfers into one control transfer.
+
+Signed-off-by: Hayes Wang <hayeswang@realtek.com>
+Link: https://lore.kernel.org/r/20230726030808.9093-419-nic_swsd@realtek.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/usb/r8152.c | 75 ++++++++++++++---------------------------
+ 1 file changed, 25 insertions(+), 50 deletions(-)
+
+--- a/drivers/net/usb/r8152.c
++++ b/drivers/net/usb/r8152.c
+@@ -3990,29 +3990,10 @@ static void rtl_reset_bmu(struct r8152 *
+ /* Clear the bp to stop the firmware before loading a new one */
+ static void rtl_clear_bp(struct r8152 *tp, u16 type)
+ {
+- switch (tp->version) {
+- case RTL_VER_01:
+- case RTL_VER_02:
+- case RTL_VER_07:
+- break;
+- case RTL_VER_03:
+- case RTL_VER_04:
+- case RTL_VER_05:
+- case RTL_VER_06:
+- ocp_write_byte(tp, type, PLA_BP_EN, 0);
+- break;
+- case RTL_VER_14:
+- ocp_write_word(tp, type, USB_BP2_EN, 0);
++ u16 bp[16] = {0};
++ u16 bp_num;
+
+- ocp_write_word(tp, type, USB_BP_8, 0);
+- ocp_write_word(tp, type, USB_BP_9, 0);
+- ocp_write_word(tp, type, USB_BP_10, 0);
+- ocp_write_word(tp, type, USB_BP_11, 0);
+- ocp_write_word(tp, type, USB_BP_12, 0);
+- ocp_write_word(tp, type, USB_BP_13, 0);
+- ocp_write_word(tp, type, USB_BP_14, 0);
+- ocp_write_word(tp, type, USB_BP_15, 0);
+- break;
++ switch (tp->version) {
+ case RTL_VER_08:
+ case RTL_VER_09:
+ case RTL_VER_10:
+@@ -4020,32 +4001,31 @@ static void rtl_clear_bp(struct r8152 *t
+ case RTL_VER_12:
+ case RTL_VER_13:
+ case RTL_VER_15:
+- default:
+ if (type == MCU_TYPE_USB) {
+ ocp_write_word(tp, MCU_TYPE_USB, USB_BP2_EN, 0);
+-
+- ocp_write_word(tp, MCU_TYPE_USB, USB_BP_8, 0);
+- ocp_write_word(tp, MCU_TYPE_USB, USB_BP_9, 0);
+- ocp_write_word(tp, MCU_TYPE_USB, USB_BP_10, 0);
+- ocp_write_word(tp, MCU_TYPE_USB, USB_BP_11, 0);
+- ocp_write_word(tp, MCU_TYPE_USB, USB_BP_12, 0);
+- ocp_write_word(tp, MCU_TYPE_USB, USB_BP_13, 0);
+- ocp_write_word(tp, MCU_TYPE_USB, USB_BP_14, 0);
+- ocp_write_word(tp, MCU_TYPE_USB, USB_BP_15, 0);
+- } else {
+- ocp_write_byte(tp, MCU_TYPE_PLA, PLA_BP_EN, 0);
++ bp_num = 16;
++ break;
+ }
++ fallthrough;
++ case RTL_VER_03:
++ case RTL_VER_04:
++ case RTL_VER_05:
++ case RTL_VER_06:
++ ocp_write_byte(tp, type, PLA_BP_EN, 0);
++ fallthrough;
++ case RTL_VER_01:
++ case RTL_VER_02:
++ case RTL_VER_07:
++ bp_num = 8;
++ break;
++ case RTL_VER_14:
++ default:
++ ocp_write_word(tp, type, USB_BP2_EN, 0);
++ bp_num = 16;
+ break;
+ }
+
+- ocp_write_word(tp, type, PLA_BP_0, 0);
+- ocp_write_word(tp, type, PLA_BP_1, 0);
+- ocp_write_word(tp, type, PLA_BP_2, 0);
+- ocp_write_word(tp, type, PLA_BP_3, 0);
+- ocp_write_word(tp, type, PLA_BP_4, 0);
+- ocp_write_word(tp, type, PLA_BP_5, 0);
+- ocp_write_word(tp, type, PLA_BP_6, 0);
+- ocp_write_word(tp, type, PLA_BP_7, 0);
++ generic_ocp_write(tp, PLA_BP_0, BYTE_EN_DWORD, bp_num << 1, bp, type);
+
+ /* wait 3 ms to make sure the firmware is stopped */
+ usleep_range(3000, 6000);
+@@ -5022,10 +5002,9 @@ static void rtl8152_fw_phy_nc_apply(stru
+
+ static void rtl8152_fw_mac_apply(struct r8152 *tp, struct fw_mac *mac)
+ {
+- u16 bp_en_addr, bp_index, type, bp_num, fw_ver_reg;
++ u16 bp_en_addr, type, fw_ver_reg;
+ u32 length;
+ u8 *data;
+- int i;
+
+ switch (__le32_to_cpu(mac->blk_hdr.type)) {
+ case RTL_FW_PLA:
+@@ -5067,12 +5046,8 @@ static void rtl8152_fw_mac_apply(struct
+ ocp_write_word(tp, type, __le16_to_cpu(mac->bp_ba_addr),
+ __le16_to_cpu(mac->bp_ba_value));
+
+- bp_index = __le16_to_cpu(mac->bp_start);
+- bp_num = __le16_to_cpu(mac->bp_num);
+- for (i = 0; i < bp_num; i++) {
+- ocp_write_word(tp, type, bp_index, __le16_to_cpu(mac->bp[i]));
+- bp_index += 2;
+- }
++ generic_ocp_write(tp, __le16_to_cpu(mac->bp_start), BYTE_EN_DWORD,
++ __le16_to_cpu(mac->bp_num) << 1, mac->bp, type);
+
+ bp_en_addr = __le16_to_cpu(mac->bp_en_addr);
+ if (bp_en_addr)
diff --git a/target/linux/generic/backport-6.6/795-v6.6-10-eth-r8152-try-to-use-a-normal-budget.patch b/target/linux/generic/backport-6.6/795-v6.6-10-eth-r8152-try-to-use-a-normal-budget.patch
new file mode 100644
index 0000000000..d7fdcdb2c6
--- /dev/null
+++ b/target/linux/generic/backport-6.6/795-v6.6-10-eth-r8152-try-to-use-a-normal-budget.patch
@@ -0,0 +1,39 @@
+From cf74eb5a5bc867258e7d0b0d1c3c4a60e1e3de2f Mon Sep 17 00:00:00 2001
+From: Jakub Kicinski <kuba@kernel.org>
+Date: Mon, 14 Aug 2023 08:35:21 -0700
+Subject: [PATCH] eth: r8152: try to use a normal budget
+
+Mario reports that loading r8152 on his system leads to a:
+
+ netif_napi_add_weight() called with weight 256
+
+warning getting printed. We don't have any solid data
+on why such high budget was chosen, and it may cause
+stalls in processing other softirqs and rt threads.
+So try to switch back to the default (64) weight.
+
+If this slows down someone's system we should investigate
+which part of stopping starting the NAPI poll in this
+driver are expensive.
+
+Reported-by: Mario Limonciello <mario.limonciello@amd.com>
+Link: https://lore.kernel.org/all/0bfd445a-81f7-f702-08b0-bd5a72095e49@amd.com/
+Acked-by: Hayes Wang <hayeswang@realtek.com>
+Link: https://lore.kernel.org/r/20230814153521.2697982-1-kuba@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/usb/r8152.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/drivers/net/usb/r8152.c
++++ b/drivers/net/usb/r8152.c
+@@ -9784,8 +9784,7 @@ static int rtl8152_probe(struct usb_inte
+
+ usb_set_intfdata(intf, tp);
+
+- netif_napi_add_weight(netdev, &tp->napi, r8152_poll,
+- tp->support_2500full ? 256 : 64);
++ netif_napi_add(netdev, &tp->napi, r8152_poll);
+
+ ret = register_netdev(netdev);
+ if (ret != 0) {
diff --git a/target/linux/generic/backport-6.6/795-v6.6-13-r8152-Block-future-register-access-if-register-acces.patch b/target/linux/generic/backport-6.6/795-v6.6-13-r8152-Block-future-register-access-if-register-acces.patch
new file mode 100644
index 0000000000..8901767be5
--- /dev/null
+++ b/target/linux/generic/backport-6.6/795-v6.6-13-r8152-Block-future-register-access-if-register-acces.patch
@@ -0,0 +1,398 @@
+From d9962b0d42029bcb40fe3c38bce06d1870fa4df4 Mon Sep 17 00:00:00 2001
+From: Douglas Anderson <dianders@chromium.org>
+Date: Fri, 20 Oct 2023 14:06:59 -0700
+Subject: [PATCH] r8152: Block future register access if register access fails
+
+Even though the functions to read/write registers can fail, most of
+the places in the r8152 driver that read/write register values don't
+check error codes. The lack of error code checking is problematic in
+at least two ways.
+
+The first problem is that the r8152 driver often uses code patterns
+similar to this:
+ x = read_register()
+ x = x | SOME_BIT;
+ write_register(x);
+
+...with the above pattern, if the read_register() fails and returns
+garbage then we'll end up trying to write modified garbage back to the
+Realtek adapter. If the write_register() succeeds that's bad. Note
+that as of commit f53a7ad18959 ("r8152: Set memory to all 0xFFs on
+failed reg reads") the "garbage" returned by read_register() will at
+least be consistent garbage, but it is still garbage.
+
+It turns out that this problem is very serious. Writing garbage to
+some of the hardware registers on the Ethernet adapter can put the
+adapter in such a bad state that it needs to be power cycled (fully
+unplugged and plugged in again) before it can enumerate again.
+
+The second problem is that the r8152 driver generally has functions
+that are long sequences of register writes. Assuming everything will
+be OK if a random register write fails in the middle isn't a great
+assumption.
+
+One might wonder if the above two problems are real. You could ask if
+we would really have a successful write after a failed read. It turns
+out that the answer appears to be "yes, this can happen". In fact,
+we've seen at least two distinct failure modes where this happens.
+
+On a sc7180-trogdor Chromebook if you drop into kdb for a while and
+then resume, you can see:
+1. We get a "Tx timeout"
+2. The "Tx timeout" queues up a USB reset.
+3. In rtl8152_pre_reset() we try to reinit the hardware.
+4. The first several (2-9) register accesses fail with a timeout, then
+ things recover.
+
+The above test case was actually fixed by the patch ("r8152: Increase
+USB control msg timeout to 5000ms as per spec") but at least shows
+that we really can see successful calls after failed ones.
+
+On a different (AMD) based Chromebook with a particular adapter, we
+found that during reboot tests we'd also sometimes get a transitory
+failure. In this case we saw -EPIPE being returned sometimes. Retrying
+worked, but retrying is not always safe for all register accesses
+since reading/writing some registers might have side effects (like
+registers that clear on read).
+
+Let's fully lock out all register access if a register access fails.
+When we do this, we'll try to queue up a USB reset and try to unlock
+register access after the reset. This is slightly tricker than it
+sounds since the r8152 driver has an optimized reset sequence that
+only works reliably after probe happens. In order to handle this, we
+avoid the optimized reset if probe didn't finish. Instead, we simply
+retry the probe routine in this case.
+
+When locking out access, we'll use the existing infrastructure that
+the driver was using when it detected we were unplugged. This keeps us
+from getting stuck in delay loops in some parts of the driver.
+
+Signed-off-by: Douglas Anderson <dianders@chromium.org>
+Reviewed-by: Grant Grundler <grundler@chromium.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/usb/r8152.c | 207 ++++++++++++++++++++++++++++++++++------
+ 1 file changed, 176 insertions(+), 31 deletions(-)
+
+--- a/drivers/net/usb/r8152.c
++++ b/drivers/net/usb/r8152.c
+@@ -772,6 +772,9 @@ enum rtl8152_flags {
+ SCHEDULE_TASKLET,
+ GREEN_ETHERNET,
+ RX_EPROTO,
++ IN_PRE_RESET,
++ PROBED_WITH_NO_ERRORS,
++ PROBE_SHOULD_RETRY,
+ };
+
+ #define DEVICE_ID_LENOVO_USB_C_TRAVEL_HUB 0x721e
+@@ -952,6 +955,8 @@ struct r8152 {
+ u8 version;
+ u8 duplex;
+ u8 autoneg;
++
++ unsigned int reg_access_reset_count;
+ };
+
+ /**
+@@ -1199,6 +1204,96 @@ static unsigned int agg_buf_sz = 16384;
+
+ #define RTL_LIMITED_TSO_SIZE (size_to_mtu(agg_buf_sz) - sizeof(struct tx_desc))
+
++/* If register access fails then we block access and issue a reset. If this
++ * happens too many times in a row without a successful access then we stop
++ * trying to reset and just leave access blocked.
++ */
++#define REGISTER_ACCESS_MAX_RESETS 3
++
++static void rtl_set_inaccessible(struct r8152 *tp)
++{
++ set_bit(RTL8152_INACCESSIBLE, &tp->flags);
++ smp_mb__after_atomic();
++}
++
++static void rtl_set_accessible(struct r8152 *tp)
++{
++ clear_bit(RTL8152_INACCESSIBLE, &tp->flags);
++ smp_mb__after_atomic();
++}
++
++static
++int r8152_control_msg(struct r8152 *tp, unsigned int pipe, __u8 request,
++ __u8 requesttype, __u16 value, __u16 index, void *data,
++ __u16 size, const char *msg_tag)
++{
++ struct usb_device *udev = tp->udev;
++ int ret;
++
++ if (test_bit(RTL8152_INACCESSIBLE, &tp->flags))
++ return -ENODEV;
++
++ ret = usb_control_msg(udev, pipe, request, requesttype,
++ value, index, data, size,
++ USB_CTRL_GET_TIMEOUT);
++
++ /* No need to issue a reset to report an error if the USB device got
++ * unplugged; just return immediately.
++ */
++ if (ret == -ENODEV)
++ return ret;
++
++ /* If the write was successful then we're done */
++ if (ret >= 0) {
++ tp->reg_access_reset_count = 0;
++ return ret;
++ }
++
++ dev_err(&udev->dev,
++ "Failed to %s %d bytes at %#06x/%#06x (%d)\n",
++ msg_tag, size, value, index, ret);
++
++ /* Block all future register access until we reset. Much of the code
++ * in the driver doesn't check for errors. Notably, many parts of the
++ * driver do a read/modify/write of a register value without
++ * confirming that the read succeeded. Writing back modified garbage
++ * like this can fully wedge the adapter, requiring a power cycle.
++ */
++ rtl_set_inaccessible(tp);
++
++ /* If probe hasn't yet finished, then we'll request a retry of the
++ * whole probe routine if we get any control transfer errors. We
++ * never have to clear this bit since we free/reallocate the whole "tp"
++ * structure if we retry probe.
++ */
++ if (!test_bit(PROBED_WITH_NO_ERRORS, &tp->flags)) {
++ set_bit(PROBE_SHOULD_RETRY, &tp->flags);
++ return ret;
++ }
++
++ /* Failing to access registers in pre-reset is not surprising since we
++ * wouldn't be resetting if things were behaving normally. The register
++ * access we do in pre-reset isn't truly mandatory--we're just reusing
++ * the disable() function and trying to be nice by powering the
++ * adapter down before resetting it. Thus, if we're in pre-reset,
++ * we'll return right away and not try to queue up yet another reset.
++ * We know the post-reset is already coming.
++ */
++ if (test_bit(IN_PRE_RESET, &tp->flags))
++ return ret;
++
++ if (tp->reg_access_reset_count < REGISTER_ACCESS_MAX_RESETS) {
++ usb_queue_reset_device(tp->intf);
++ tp->reg_access_reset_count++;
++ } else if (tp->reg_access_reset_count == REGISTER_ACCESS_MAX_RESETS) {
++ dev_err(&udev->dev,
++ "Tried to reset %d times; giving up.\n",
++ REGISTER_ACCESS_MAX_RESETS);
++ }
++
++ return ret;
++}
++
+ static
+ int get_registers(struct r8152 *tp, u16 value, u16 index, u16 size, void *data)
+ {
+@@ -1209,9 +1304,10 @@ int get_registers(struct r8152 *tp, u16
+ if (!tmp)
+ return -ENOMEM;
+
+- ret = usb_control_msg(tp->udev, tp->pipe_ctrl_in,
+- RTL8152_REQ_GET_REGS, RTL8152_REQT_READ,
+- value, index, tmp, size, USB_CTRL_GET_TIMEOUT);
++ ret = r8152_control_msg(tp, tp->pipe_ctrl_in,
++ RTL8152_REQ_GET_REGS, RTL8152_REQT_READ,
++ value, index, tmp, size, "read");
++
+ if (ret < 0)
+ memset(data, 0xff, size);
+ else
+@@ -1232,9 +1328,9 @@ int set_registers(struct r8152 *tp, u16
+ if (!tmp)
+ return -ENOMEM;
+
+- ret = usb_control_msg(tp->udev, tp->pipe_ctrl_out,
+- RTL8152_REQ_SET_REGS, RTL8152_REQT_WRITE,
+- value, index, tmp, size, USB_CTRL_SET_TIMEOUT);
++ ret = r8152_control_msg(tp, tp->pipe_ctrl_out,
++ RTL8152_REQ_SET_REGS, RTL8152_REQT_WRITE,
++ value, index, tmp, size, "write");
+
+ kfree(tmp);
+
+@@ -1243,10 +1339,8 @@ int set_registers(struct r8152 *tp, u16
+
+ static void rtl_set_unplug(struct r8152 *tp)
+ {
+- if (tp->udev->state == USB_STATE_NOTATTACHED) {
+- set_bit(RTL8152_INACCESSIBLE, &tp->flags);
+- smp_mb__after_atomic();
+- }
++ if (tp->udev->state == USB_STATE_NOTATTACHED)
++ rtl_set_inaccessible(tp);
+ }
+
+ static int generic_ocp_read(struct r8152 *tp, u16 index, u16 size,
+@@ -8275,7 +8369,7 @@ static int rtl8152_pre_reset(struct usb_
+ struct r8152 *tp = usb_get_intfdata(intf);
+ struct net_device *netdev;
+
+- if (!tp)
++ if (!tp || !test_bit(PROBED_WITH_NO_ERRORS, &tp->flags))
+ return 0;
+
+ netdev = tp->netdev;
+@@ -8290,7 +8384,9 @@ static int rtl8152_pre_reset(struct usb_
+ napi_disable(&tp->napi);
+ if (netif_carrier_ok(netdev)) {
+ mutex_lock(&tp->control);
++ set_bit(IN_PRE_RESET, &tp->flags);
+ tp->rtl_ops.disable(tp);
++ clear_bit(IN_PRE_RESET, &tp->flags);
+ mutex_unlock(&tp->control);
+ }
+
+@@ -8303,9 +8399,11 @@ static int rtl8152_post_reset(struct usb
+ struct net_device *netdev;
+ struct sockaddr sa;
+
+- if (!tp)
++ if (!tp || !test_bit(PROBED_WITH_NO_ERRORS, &tp->flags))
+ return 0;
+
++ rtl_set_accessible(tp);
++
+ /* reset the MAC address in case of policy change */
+ if (determine_ethernet_addr(tp, &sa) >= 0) {
+ rtnl_lock();
+@@ -9507,17 +9605,29 @@ static u8 __rtl_get_hw_ver(struct usb_de
+ __le32 *tmp;
+ u8 version;
+ int ret;
++ int i;
+
+ tmp = kmalloc(sizeof(*tmp), GFP_KERNEL);
+ if (!tmp)
+ return 0;
+
+- ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+- RTL8152_REQ_GET_REGS, RTL8152_REQT_READ,
+- PLA_TCR0, MCU_TYPE_PLA, tmp, sizeof(*tmp),
+- USB_CTRL_GET_TIMEOUT);
+- if (ret > 0)
+- ocp_data = (__le32_to_cpu(*tmp) >> 16) & VERSION_MASK;
++ /* Retry up to 3 times in case there is a transitory error. We do this
++ * since retrying a read of the version is always safe and this
++ * function doesn't take advantage of r8152_control_msg().
++ */
++ for (i = 0; i < 3; i++) {
++ ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
++ RTL8152_REQ_GET_REGS, RTL8152_REQT_READ,
++ PLA_TCR0, MCU_TYPE_PLA, tmp, sizeof(*tmp),
++ USB_CTRL_GET_TIMEOUT);
++ if (ret > 0) {
++ ocp_data = (__le32_to_cpu(*tmp) >> 16) & VERSION_MASK;
++ break;
++ }
++ }
++
++ if (i != 0 && ret > 0)
++ dev_warn(&udev->dev, "Needed %d retries to read version\n", i);
+
+ kfree(tmp);
+
+@@ -9616,25 +9726,14 @@ static bool rtl8152_supports_lenovo_macp
+ return 0;
+ }
+
+-static int rtl8152_probe(struct usb_interface *intf,
+- const struct usb_device_id *id)
++static int rtl8152_probe_once(struct usb_interface *intf,
++ const struct usb_device_id *id, u8 version)
+ {
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct r8152 *tp;
+ struct net_device *netdev;
+- u8 version;
+ int ret;
+
+- if (intf->cur_altsetting->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC)
+- return -ENODEV;
+-
+- if (!rtl_check_vendor_ok(intf))
+- return -ENODEV;
+-
+- version = rtl8152_get_version(intf);
+- if (version == RTL_VER_UNKNOWN)
+- return -ENODEV;
+-
+ usb_reset_device(udev);
+ netdev = alloc_etherdev(sizeof(struct r8152));
+ if (!netdev) {
+@@ -9797,10 +9896,20 @@ static int rtl8152_probe(struct usb_inte
+ else
+ device_set_wakeup_enable(&udev->dev, false);
+
++ /* If we saw a control transfer error while probing then we may
++ * want to try probe() again. Consider this an error.
++ */
++ if (test_bit(PROBE_SHOULD_RETRY, &tp->flags))
++ goto out2;
++
++ set_bit(PROBED_WITH_NO_ERRORS, &tp->flags);
+ netif_info(tp, probe, netdev, "%s\n", DRIVER_VERSION);
+
+ return 0;
+
++out2:
++ unregister_netdev(netdev);
++
+ out1:
+ tasklet_kill(&tp->tx_tl);
+ cancel_delayed_work_sync(&tp->hw_phy_work);
+@@ -9809,10 +9918,46 @@ out1:
+ rtl8152_release_firmware(tp);
+ usb_set_intfdata(intf, NULL);
+ out:
++ if (test_bit(PROBE_SHOULD_RETRY, &tp->flags))
++ ret = -EAGAIN;
++
+ free_netdev(netdev);
+ return ret;
+ }
+
++#define RTL8152_PROBE_TRIES 3
++
++static int rtl8152_probe(struct usb_interface *intf,
++ const struct usb_device_id *id)
++{
++ u8 version;
++ int ret;
++ int i;
++
++ if (intf->cur_altsetting->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC)
++ return -ENODEV;
++
++ if (!rtl_check_vendor_ok(intf))
++ return -ENODEV;
++
++ version = rtl8152_get_version(intf);
++ if (version == RTL_VER_UNKNOWN)
++ return -ENODEV;
++
++ for (i = 0; i < RTL8152_PROBE_TRIES; i++) {
++ ret = rtl8152_probe_once(intf, id, version);
++ if (ret != -EAGAIN)
++ break;
++ }
++ if (ret == -EAGAIN) {
++ dev_err(&intf->dev,
++ "r8152 failed probe after %d tries; giving up\n", i);
++ return -ENODEV;
++ }
++
++ return ret;
++}
++
+ static void rtl8152_disconnect(struct usb_interface *intf)
+ {
+ struct r8152 *tp = usb_get_intfdata(intf);
diff --git a/target/linux/generic/backport-6.6/795-v6.6-14-r8152-break-the-loop-when-the-budget-is-exhausted.patch b/target/linux/generic/backport-6.6/795-v6.6-14-r8152-break-the-loop-when-the-budget-is-exhausted.patch
new file mode 100644
index 0000000000..7bbd1be820
--- /dev/null
+++ b/target/linux/generic/backport-6.6/795-v6.6-14-r8152-break-the-loop-when-the-budget-is-exhausted.patch
@@ -0,0 +1,83 @@
+From 66eee612a1ba39f9a76a9ace4a34d012044767fb Mon Sep 17 00:00:00 2001
+From: Hayes Wang <hayeswang@realtek.com>
+Date: Tue, 26 Sep 2023 19:17:13 +0800
+Subject: [PATCH] r8152: break the loop when the budget is exhausted
+
+[ Upstream commit 2cf51f931797d9a47e75d999d0993a68cbd2a560 ]
+
+A bulk transfer of the USB may contain many packets. And, the total
+number of the packets in the bulk transfer may be more than budget.
+
+Originally, only budget packets would be handled by napi_gro_receive(),
+and the other packets would be queued in the driver for next schedule.
+
+This patch would break the loop about getting next bulk transfer, when
+the budget is exhausted. That is, only the current bulk transfer would
+be handled, and the other bulk transfers would be queued for next
+schedule. Besides, the packets which are more than the budget in the
+current bulk trasnfer would be still queued in the driver, as the
+original method.
+
+In addition, a bulk transfer wouldn't contain more than 400 packets, so
+the check of queue length is unnecessary. Therefore, I replace it with
+WARN_ON_ONCE().
+
+Fixes: cf74eb5a5bc8 ("eth: r8152: try to use a normal budget")
+Signed-off-by: Hayes Wang <hayeswang@realtek.com>
+Link: https://lore.kernel.org/r/20230926111714.9448-433-nic_swsd@realtek.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/net/usb/r8152.c | 18 +++++++++++++-----
+ 1 file changed, 13 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/usb/r8152.c
++++ b/drivers/net/usb/r8152.c
+@@ -2542,7 +2542,7 @@ static int rx_bottom(struct r8152 *tp, i
+ }
+ }
+
+- if (list_empty(&tp->rx_done))
++ if (list_empty(&tp->rx_done) || work_done >= budget)
+ goto out1;
+
+ clear_bit(RX_EPROTO, &tp->flags);
+@@ -2558,6 +2558,15 @@ static int rx_bottom(struct r8152 *tp, i
+ struct urb *urb;
+ u8 *rx_data;
+
++ /* A bulk transfer of USB may contain may packets, so the
++ * total packets may more than the budget. Deal with all
++ * packets in current bulk transfer, and stop to handle the
++ * next bulk transfer until next schedule, if budget is
++ * exhausted.
++ */
++ if (work_done >= budget)
++ break;
++
+ list_del_init(cursor);
+
+ agg = list_entry(cursor, struct rx_agg, list);
+@@ -2577,9 +2586,7 @@ static int rx_bottom(struct r8152 *tp, i
+ unsigned int pkt_len, rx_frag_head_sz;
+ struct sk_buff *skb;
+
+- /* limit the skb numbers for rx_queue */
+- if (unlikely(skb_queue_len(&tp->rx_queue) >= 1000))
+- break;
++ WARN_ON_ONCE(skb_queue_len(&tp->rx_queue) >= 1000);
+
+ pkt_len = le32_to_cpu(rx_desc->opts1) & RX_LEN_MASK;
+ if (pkt_len < ETH_ZLEN)
+@@ -2657,9 +2664,10 @@ submit:
+ }
+ }
+
++ /* Splice the remained list back to rx_done for next schedule */
+ if (!list_empty(&rx_queue)) {
+ spin_lock_irqsave(&tp->rx_lock, flags);
+- list_splice_tail(&rx_queue, &tp->rx_done);
++ list_splice(&rx_queue, &tp->rx_done);
+ spin_unlock_irqrestore(&tp->rx_lock, flags);
+ }
+
diff --git a/target/linux/generic/backport-6.6/795-v6.6-15-net-usb-cdc_ether-add-u-blox-0x1313-composition.patch b/target/linux/generic/backport-6.6/795-v6.6-15-net-usb-cdc_ether-add-u-blox-0x1313-composition.patch
new file mode 100644
index 0000000000..c858152067
--- /dev/null
+++ b/target/linux/generic/backport-6.6/795-v6.6-15-net-usb-cdc_ether-add-u-blox-0x1313-composition.patch
@@ -0,0 +1,47 @@
+From 1b0fce8c8e69485e49a7d34aac3d4c2a2aa15d62 Mon Sep 17 00:00:00 2001
+From: Davide Tronchin <davide.tronchin.94@gmail.com>
+Date: Thu, 29 Jun 2023 12:37:36 +0200
+Subject: [PATCH] net: usb: cdc_ether: add u-blox 0x1313 composition.
+
+Add CDC-ECM support for LARA-R6 01B.
+
+The new LARA-R6 product variant identified by the "01B" string can be
+configured (by AT interface) in three different USB modes:
+* Default mode (Vendor ID: 0x1546 Product ID: 0x1311) with 4 serial
+interfaces
+* RmNet mode (Vendor ID: 0x1546 Product ID: 0x1312) with 4 serial
+interfaces and 1 RmNet virtual network interface
+* CDC-ECM mode (Vendor ID: 0x1546 Product ID: 0x1313) with 4 serial
+interface and 1 CDC-ECM virtual network interface
+The first 4 interfaces of all the 3 configurations (default, RmNet, ECM)
+are the same.
+
+In CDC-ECM mode LARA-R6 01B exposes the following interfaces:
+If 0: Diagnostic
+If 1: AT parser
+If 2: AT parser
+If 3: AT parset/alternative functions
+If 4: CDC-ECM interface
+
+Signed-off-by: Davide Tronchin <davide.tronchin.94@gmail.com>
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/usb/cdc_ether.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/drivers/net/usb/cdc_ether.c
++++ b/drivers/net/usb/cdc_ether.c
+@@ -879,6 +879,12 @@ static const struct usb_device_id produc
+ USB_CDC_PROTO_NONE),
+ .driver_info = (unsigned long)&wwan_info,
+ }, {
++ /* U-blox LARA-R6 01B */
++ USB_DEVICE_AND_INTERFACE_INFO(UBLOX_VENDOR_ID, 0x1313, USB_CLASS_COMM,
++ USB_CDC_SUBCLASS_ETHERNET,
++ USB_CDC_PROTO_NONE),
++ .driver_info = (unsigned long)&wwan_info,
++}, {
+ /* ZTE modules */
+ USB_VENDOR_AND_INTERFACE_INFO(ZTE_VENDOR_ID, USB_CLASS_COMM,
+ USB_CDC_SUBCLASS_ETHERNET,
diff --git a/target/linux/generic/backport-6.6/795-v6.7-16-r8152-use-napi_gro_frags.patch b/target/linux/generic/backport-6.6/795-v6.7-16-r8152-use-napi_gro_frags.patch
new file mode 100644
index 0000000000..3c9680a279
--- /dev/null
+++ b/target/linux/generic/backport-6.6/795-v6.7-16-r8152-use-napi_gro_frags.patch
@@ -0,0 +1,122 @@
+From 788d30daa8f97f06166b6a63f0e51f2a4c2f036a Mon Sep 17 00:00:00 2001
+From: Hayes Wang <hayeswang@realtek.com>
+Date: Tue, 26 Sep 2023 19:17:14 +0800
+Subject: [PATCH] r8152: use napi_gro_frags
+
+Use napi_gro_frags() for the skb of fragments when the work_done is less
+than budget.
+
+Signed-off-by: Hayes Wang <hayeswang@realtek.com>
+Link: https://lore.kernel.org/r/20230926111714.9448-434-nic_swsd@realtek.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/usb/r8152.c | 67 ++++++++++++++++++++++++++++++-----------
+ 1 file changed, 50 insertions(+), 17 deletions(-)
+
+--- a/drivers/net/usb/r8152.c
++++ b/drivers/net/usb/r8152.c
+@@ -2583,8 +2583,9 @@ static int rx_bottom(struct r8152 *tp, i
+ while (urb->actual_length > len_used) {
+ struct net_device *netdev = tp->netdev;
+ struct net_device_stats *stats = &netdev->stats;
+- unsigned int pkt_len, rx_frag_head_sz;
++ unsigned int pkt_len, rx_frag_head_sz, len;
+ struct sk_buff *skb;
++ bool use_frags;
+
+ WARN_ON_ONCE(skb_queue_len(&tp->rx_queue) >= 1000);
+
+@@ -2597,45 +2598,77 @@ static int rx_bottom(struct r8152 *tp, i
+ break;
+
+ pkt_len -= ETH_FCS_LEN;
++ len = pkt_len;
+ rx_data += sizeof(struct rx_desc);
+
+- if (!agg_free || tp->rx_copybreak > pkt_len)
+- rx_frag_head_sz = pkt_len;
++ if (!agg_free || tp->rx_copybreak > len)
++ use_frags = false;
+ else
+- rx_frag_head_sz = tp->rx_copybreak;
++ use_frags = true;
++
++ if (use_frags) {
++ /* If the budget is exhausted, the packet
++ * would be queued in the driver. That is,
++ * napi_gro_frags() wouldn't be called, so
++ * we couldn't use napi_get_frags().
++ */
++ if (work_done >= budget) {
++ rx_frag_head_sz = tp->rx_copybreak;
++ skb = napi_alloc_skb(napi,
++ rx_frag_head_sz);
++ } else {
++ rx_frag_head_sz = 0;
++ skb = napi_get_frags(napi);
++ }
++ } else {
++ rx_frag_head_sz = 0;
++ skb = napi_alloc_skb(napi, len);
++ }
+
+- skb = napi_alloc_skb(napi, rx_frag_head_sz);
+ if (!skb) {
+ stats->rx_dropped++;
+ goto find_next_rx;
+ }
+
+ skb->ip_summed = r8152_rx_csum(tp, rx_desc);
+- memcpy(skb->data, rx_data, rx_frag_head_sz);
+- skb_put(skb, rx_frag_head_sz);
+- pkt_len -= rx_frag_head_sz;
+- rx_data += rx_frag_head_sz;
+- if (pkt_len) {
++ rtl_rx_vlan_tag(rx_desc, skb);
++
++ if (use_frags) {
++ if (rx_frag_head_sz) {
++ memcpy(skb->data, rx_data,
++ rx_frag_head_sz);
++ skb_put(skb, rx_frag_head_sz);
++ len -= rx_frag_head_sz;
++ rx_data += rx_frag_head_sz;
++ skb->protocol = eth_type_trans(skb,
++ netdev);
++ }
++
+ skb_add_rx_frag(skb, 0, agg->page,
+ agg_offset(agg, rx_data),
+- pkt_len,
+- SKB_DATA_ALIGN(pkt_len));
++ len, SKB_DATA_ALIGN(len));
+ get_page(agg->page);
++ } else {
++ memcpy(skb->data, rx_data, len);
++ skb_put(skb, len);
++ skb->protocol = eth_type_trans(skb, netdev);
+ }
+
+- skb->protocol = eth_type_trans(skb, netdev);
+- rtl_rx_vlan_tag(rx_desc, skb);
+ if (work_done < budget) {
++ if (use_frags)
++ napi_gro_frags(napi);
++ else
++ napi_gro_receive(napi, skb);
++
+ work_done++;
+ stats->rx_packets++;
+- stats->rx_bytes += skb->len;
+- napi_gro_receive(napi, skb);
++ stats->rx_bytes += pkt_len;
+ } else {
+ __skb_queue_tail(&tp->rx_queue, skb);
+ }
+
+ find_next_rx:
+- rx_data = rx_agg_align(rx_data + pkt_len + ETH_FCS_LEN);
++ rx_data = rx_agg_align(rx_data + len + ETH_FCS_LEN);
+ rx_desc = (struct rx_desc *)rx_data;
+ len_used = agg_offset(agg, rx_data);
+ len_used += sizeof(struct rx_desc);
diff --git a/target/linux/generic/backport-6.6/800-v6.3-leds-Move-led_init_default_state_get-to-the-global-h.patch b/target/linux/generic/backport-6.6/800-v6.3-leds-Move-led_init_default_state_get-to-the-global-h.patch
new file mode 100644
index 0000000000..592111fb95
--- /dev/null
+++ b/target/linux/generic/backport-6.6/800-v6.3-leds-Move-led_init_default_state_get-to-the-global-h.patch
@@ -0,0 +1,39 @@
+From 156a5bb89ca6f3edd2be0bfd0de15e575442927e Mon Sep 17 00:00:00 2001
+From: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+Date: Tue, 3 Jan 2023 15:12:47 +0200
+Subject: [PATCH] leds: Move led_init_default_state_get() to the global header
+
+There are users inside and outside LED framework that have implemented
+a local copy of led_init_default_state_get(). In order to deduplicate
+that, as the first step move the declaration from LED header to the
+global one.
+
+Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+Signed-off-by: Lee Jones <lee@kernel.org>
+Link: https://lore.kernel.org/r/20230103131256.33894-3-andriy.shevchenko@linux.intel.com
+---
+ drivers/leds/leds.h | 1 -
+ include/linux/leds.h | 2 ++
+ 2 files changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/leds/leds.h
++++ b/drivers/leds/leds.h
+@@ -27,7 +27,6 @@ ssize_t led_trigger_read(struct file *fi
+ ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr, char *buf,
+ loff_t pos, size_t count);
+-enum led_default_state led_init_default_state_get(struct fwnode_handle *fwnode);
+
+ extern struct rw_semaphore leds_list_lock;
+ extern struct list_head leds_list;
+--- a/include/linux/leds.h
++++ b/include/linux/leds.h
+@@ -63,6 +63,8 @@ struct led_init_data {
+ bool devname_mandatory;
+ };
+
++enum led_default_state led_init_default_state_get(struct fwnode_handle *fwnode);
++
+ struct led_hw_trigger_type {
+ int dummy;
+ };
diff --git a/target/linux/generic/backport-6.6/801-v6.4-01-net-dsa-qca8k-move-qca8k_port_to_phy-to-header.patch b/target/linux/generic/backport-6.6/801-v6.4-01-net-dsa-qca8k-move-qca8k_port_to_phy-to-header.patch
new file mode 100644
index 0000000000..f6a025fa12
--- /dev/null
+++ b/target/linux/generic/backport-6.6/801-v6.4-01-net-dsa-qca8k-move-qca8k_port_to_phy-to-header.patch
@@ -0,0 +1,67 @@
+From 3e8b4d6277fd19d98c817576954dd6a4ff3caa2b Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 17 Apr 2023 17:17:23 +0200
+Subject: [PATCH 1/9] net: dsa: qca8k: move qca8k_port_to_phy() to header
+
+Move qca8k_port_to_phy() to qca8k header as it's useful for future
+reference in Switch LEDs module since the same logic is applied to get
+the right index of the switch port.
+Make it inline as it's simple function that just decrease the port.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Michal Kubiak <michal.kubiak@intel.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/qca8k-8xxx.c | 15 ---------------
+ drivers/net/dsa/qca/qca8k.h | 14 ++++++++++++++
+ 2 files changed, 14 insertions(+), 15 deletions(-)
+
+--- a/drivers/net/dsa/qca/qca8k-8xxx.c
++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
+@@ -789,21 +789,6 @@ err_clear_skb:
+ return ret;
+ }
+
+-static u32
+-qca8k_port_to_phy(int port)
+-{
+- /* From Andrew Lunn:
+- * Port 0 has no internal phy.
+- * Port 1 has an internal PHY at MDIO address 0.
+- * Port 2 has an internal PHY at MDIO address 1.
+- * ...
+- * Port 5 has an internal PHY at MDIO address 4.
+- * Port 6 has no internal PHY.
+- */
+-
+- return port - 1;
+-}
+-
+ static int
+ qca8k_mdio_busy_wait(struct mii_bus *bus, u32 reg, u32 mask)
+ {
+--- a/drivers/net/dsa/qca/qca8k.h
++++ b/drivers/net/dsa/qca/qca8k.h
+@@ -421,6 +421,20 @@ struct qca8k_fdb {
+ u8 mac[6];
+ };
+
++static inline u32 qca8k_port_to_phy(int port)
++{
++ /* From Andrew Lunn:
++ * Port 0 has no internal phy.
++ * Port 1 has an internal PHY at MDIO address 0.
++ * Port 2 has an internal PHY at MDIO address 1.
++ * ...
++ * Port 5 has an internal PHY at MDIO address 4.
++ * Port 6 has no internal PHY.
++ */
++
++ return port - 1;
++}
++
+ /* Common setup function */
+ extern const struct qca8k_mib_desc ar8327_mib[];
+ extern const struct regmap_access_table qca8k_readable_table;
diff --git a/target/linux/generic/backport-6.6/801-v6.4-02-net-dsa-qca8k-add-LEDs-basic-support.patch b/target/linux/generic/backport-6.6/801-v6.4-02-net-dsa-qca8k-add-LEDs-basic-support.patch
new file mode 100644
index 0000000000..409fe9c7a1
--- /dev/null
+++ b/target/linux/generic/backport-6.6/801-v6.4-02-net-dsa-qca8k-add-LEDs-basic-support.patch
@@ -0,0 +1,435 @@
+From 1e264f9d2918b5737023c44a23ae04def1095210 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 17 Apr 2023 17:17:24 +0200
+Subject: [PATCH 2/9] net: dsa: qca8k: add LEDs basic support
+
+Add LEDs basic support for qca8k Switch Family by adding basic
+brightness_set() support.
+
+Since these LEDs refelect port status, the default label is set to
+":port". DT binding should describe the color and function of the
+LEDs using standard LEDs api.
+Each LED always have the device name as prefix. The device name is
+composed from the mii bus id and the PHY addr resulting in example
+names like:
+- qca8k-0.0:00:amber:lan
+- qca8k-0.0:00:white:lan
+- qca8k-0.0:01:amber:lan
+- qca8k-0.0:01:white:lan
+
+These LEDs supports only blocking variant of the brightness_set()
+function since they can sleep during access of the switch leds to set
+the brightness.
+
+While at it add to the qca8k header file each mode defined by the Switch
+Documentation for future use.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/Kconfig | 8 ++
+ drivers/net/dsa/qca/Makefile | 3 +
+ drivers/net/dsa/qca/qca8k-8xxx.c | 5 +
+ drivers/net/dsa/qca/qca8k-leds.c | 239 +++++++++++++++++++++++++++++++
+ drivers/net/dsa/qca/qca8k.h | 60 ++++++++
+ drivers/net/dsa/qca/qca8k_leds.h | 16 +++
+ 6 files changed, 331 insertions(+)
+ create mode 100644 drivers/net/dsa/qca/qca8k-leds.c
+ create mode 100644 drivers/net/dsa/qca/qca8k_leds.h
+
+--- a/drivers/net/dsa/qca/Kconfig
++++ b/drivers/net/dsa/qca/Kconfig
+@@ -15,3 +15,11 @@ config NET_DSA_QCA8K
+ help
+ This enables support for the Qualcomm Atheros QCA8K Ethernet
+ switch chips.
++
++config NET_DSA_QCA8K_LEDS_SUPPORT
++ bool "Qualcomm Atheros QCA8K Ethernet switch family LEDs support"
++ depends on NET_DSA_QCA8K
++ depends on LEDS_CLASS
++ help
++ This enabled support for LEDs present on the Qualcomm Atheros
++ QCA8K Ethernet switch chips.
+--- a/drivers/net/dsa/qca/Makefile
++++ b/drivers/net/dsa/qca/Makefile
+@@ -2,3 +2,6 @@
+ obj-$(CONFIG_NET_DSA_AR9331) += ar9331.o
+ obj-$(CONFIG_NET_DSA_QCA8K) += qca8k.o
+ qca8k-y += qca8k-common.o qca8k-8xxx.o
++ifdef CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT
++qca8k-y += qca8k-leds.o
++endif
+--- a/drivers/net/dsa/qca/qca8k-8xxx.c
++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
+@@ -22,6 +22,7 @@
+ #include <linux/dsa/tag_qca.h>
+
+ #include "qca8k.h"
++#include "qca8k_leds.h"
+
+ static void
+ qca8k_split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
+@@ -1851,6 +1852,10 @@ qca8k_setup(struct dsa_switch *ds)
+ if (ret)
+ return ret;
+
++ ret = qca8k_setup_led_ctrl(priv);
++ if (ret)
++ return ret;
++
+ qca8k_setup_pcs(priv, &priv->pcs_port_0, 0);
+ qca8k_setup_pcs(priv, &priv->pcs_port_6, 6);
+
+--- /dev/null
++++ b/drivers/net/dsa/qca/qca8k-leds.c
+@@ -0,0 +1,239 @@
++// SPDX-License-Identifier: GPL-2.0
++#include <linux/regmap.h>
++#include <net/dsa.h>
++
++#include "qca8k.h"
++#include "qca8k_leds.h"
++
++static int
++qca8k_get_enable_led_reg(int port_num, int led_num, struct qca8k_led_pattern_en *reg_info)
++{
++ switch (port_num) {
++ case 0:
++ reg_info->reg = QCA8K_LED_CTRL_REG(led_num);
++ reg_info->shift = QCA8K_LED_PHY0123_CONTROL_RULE_SHIFT;
++ break;
++ case 1:
++ case 2:
++ case 3:
++ /* Port 123 are controlled on a different reg */
++ reg_info->reg = QCA8K_LED_CTRL3_REG;
++ reg_info->shift = QCA8K_LED_PHY123_PATTERN_EN_SHIFT(port_num, led_num);
++ break;
++ case 4:
++ reg_info->reg = QCA8K_LED_CTRL_REG(led_num);
++ reg_info->shift = QCA8K_LED_PHY4_CONTROL_RULE_SHIFT;
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
++static int
++qca8k_led_brightness_set(struct qca8k_led *led,
++ enum led_brightness brightness)
++{
++ struct qca8k_led_pattern_en reg_info;
++ struct qca8k_priv *priv = led->priv;
++ u32 mask, val;
++
++ qca8k_get_enable_led_reg(led->port_num, led->led_num, &reg_info);
++
++ val = QCA8K_LED_ALWAYS_OFF;
++ if (brightness)
++ val = QCA8K_LED_ALWAYS_ON;
++
++ /* HW regs to control brightness is special and port 1-2-3
++ * are placed in a different reg.
++ *
++ * To control port 0 brightness:
++ * - the 2 bit (15, 14) of:
++ * - QCA8K_LED_CTRL0_REG for led1
++ * - QCA8K_LED_CTRL1_REG for led2
++ * - QCA8K_LED_CTRL2_REG for led3
++ *
++ * To control port 4:
++ * - the 2 bit (31, 30) of:
++ * - QCA8K_LED_CTRL0_REG for led1
++ * - QCA8K_LED_CTRL1_REG for led2
++ * - QCA8K_LED_CTRL2_REG for led3
++ *
++ * To control port 1:
++ * - the 2 bit at (9, 8) of QCA8K_LED_CTRL3_REG are used for led1
++ * - the 2 bit at (11, 10) of QCA8K_LED_CTRL3_REG are used for led2
++ * - the 2 bit at (13, 12) of QCA8K_LED_CTRL3_REG are used for led3
++ *
++ * To control port 2:
++ * - the 2 bit at (15, 14) of QCA8K_LED_CTRL3_REG are used for led1
++ * - the 2 bit at (17, 16) of QCA8K_LED_CTRL3_REG are used for led2
++ * - the 2 bit at (19, 18) of QCA8K_LED_CTRL3_REG are used for led3
++ *
++ * To control port 3:
++ * - the 2 bit at (21, 20) of QCA8K_LED_CTRL3_REG are used for led1
++ * - the 2 bit at (23, 22) of QCA8K_LED_CTRL3_REG are used for led2
++ * - the 2 bit at (25, 24) of QCA8K_LED_CTRL3_REG are used for led3
++ *
++ * To abstract this and have less code, we use the port and led numm
++ * to calculate the shift and the correct reg due to this problem of
++ * not having a 1:1 map of LED with the regs.
++ */
++ if (led->port_num == 0 || led->port_num == 4) {
++ mask = QCA8K_LED_PATTERN_EN_MASK;
++ val <<= QCA8K_LED_PATTERN_EN_SHIFT;
++ } else {
++ mask = QCA8K_LED_PHY123_PATTERN_EN_MASK;
++ }
++
++ return regmap_update_bits(priv->regmap, reg_info.reg,
++ mask << reg_info.shift,
++ val << reg_info.shift);
++}
++
++static int
++qca8k_cled_brightness_set_blocking(struct led_classdev *ldev,
++ enum led_brightness brightness)
++{
++ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
++
++ return qca8k_led_brightness_set(led, brightness);
++}
++
++static enum led_brightness
++qca8k_led_brightness_get(struct qca8k_led *led)
++{
++ struct qca8k_led_pattern_en reg_info;
++ struct qca8k_priv *priv = led->priv;
++ u32 val;
++ int ret;
++
++ qca8k_get_enable_led_reg(led->port_num, led->led_num, &reg_info);
++
++ ret = regmap_read(priv->regmap, reg_info.reg, &val);
++ if (ret)
++ return 0;
++
++ val >>= reg_info.shift;
++
++ if (led->port_num == 0 || led->port_num == 4) {
++ val &= QCA8K_LED_PATTERN_EN_MASK;
++ val >>= QCA8K_LED_PATTERN_EN_SHIFT;
++ } else {
++ val &= QCA8K_LED_PHY123_PATTERN_EN_MASK;
++ }
++
++ /* Assume brightness ON only when the LED is set to always ON */
++ return val == QCA8K_LED_ALWAYS_ON;
++}
++
++static int
++qca8k_parse_port_leds(struct qca8k_priv *priv, struct fwnode_handle *port, int port_num)
++{
++ struct fwnode_handle *led = NULL, *leds = NULL;
++ struct led_init_data init_data = { };
++ struct dsa_switch *ds = priv->ds;
++ enum led_default_state state;
++ struct qca8k_led *port_led;
++ int led_num, led_index;
++ int ret;
++
++ leds = fwnode_get_named_child_node(port, "leds");
++ if (!leds) {
++ dev_dbg(priv->dev, "No Leds node specified in device tree for port %d!\n",
++ port_num);
++ return 0;
++ }
++
++ fwnode_for_each_child_node(leds, led) {
++ /* Reg represent the led number of the port.
++ * Each port can have at most 3 leds attached
++ * Commonly:
++ * 1. is gigabit led
++ * 2. is mbit led
++ * 3. additional status led
++ */
++ if (fwnode_property_read_u32(led, "reg", &led_num))
++ continue;
++
++ if (led_num >= QCA8K_LED_PORT_COUNT) {
++ dev_warn(priv->dev, "Invalid LED reg %d defined for port %d",
++ led_num, port_num);
++ continue;
++ }
++
++ led_index = QCA8K_LED_PORT_INDEX(port_num, led_num);
++
++ port_led = &priv->ports_led[led_index];
++ port_led->port_num = port_num;
++ port_led->led_num = led_num;
++ port_led->priv = priv;
++
++ state = led_init_default_state_get(led);
++ switch (state) {
++ case LEDS_DEFSTATE_ON:
++ port_led->cdev.brightness = 1;
++ qca8k_led_brightness_set(port_led, 1);
++ break;
++ case LEDS_DEFSTATE_KEEP:
++ port_led->cdev.brightness =
++ qca8k_led_brightness_get(port_led);
++ break;
++ default:
++ port_led->cdev.brightness = 0;
++ qca8k_led_brightness_set(port_led, 0);
++ }
++
++ port_led->cdev.max_brightness = 1;
++ port_led->cdev.brightness_set_blocking = qca8k_cled_brightness_set_blocking;
++ init_data.default_label = ":port";
++ init_data.fwnode = led;
++ init_data.devname_mandatory = true;
++ init_data.devicename = kasprintf(GFP_KERNEL, "%s:0%d", ds->slave_mii_bus->id,
++ port_num);
++ if (!init_data.devicename)
++ return -ENOMEM;
++
++ ret = devm_led_classdev_register_ext(priv->dev, &port_led->cdev, &init_data);
++ if (ret)
++ dev_warn(priv->dev, "Failed to init LED %d for port %d", led_num, port_num);
++
++ kfree(init_data.devicename);
++ }
++
++ return 0;
++}
++
++int
++qca8k_setup_led_ctrl(struct qca8k_priv *priv)
++{
++ struct fwnode_handle *ports, *port;
++ int port_num;
++ int ret;
++
++ ports = device_get_named_child_node(priv->dev, "ports");
++ if (!ports) {
++ dev_info(priv->dev, "No ports node specified in device tree!");
++ return 0;
++ }
++
++ fwnode_for_each_child_node(ports, port) {
++ if (fwnode_property_read_u32(port, "reg", &port_num))
++ continue;
++
++ /* Skip checking for CPU port 0 and CPU port 6 as not supported */
++ if (port_num == 0 || port_num == 6)
++ continue;
++
++ /* Each port can have at most 3 different leds attached.
++ * Switch port starts from 0 to 6, but port 0 and 6 are CPU
++ * port. The port index needs to be decreased by one to identify
++ * the correct port for LED setup.
++ */
++ ret = qca8k_parse_port_leds(priv, port, qca8k_port_to_phy(port_num));
++ if (ret)
++ return ret;
++ }
++
++ return 0;
++}
+--- a/drivers/net/dsa/qca/qca8k.h
++++ b/drivers/net/dsa/qca/qca8k.h
+@@ -11,6 +11,7 @@
+ #include <linux/delay.h>
+ #include <linux/regmap.h>
+ #include <linux/gpio.h>
++#include <linux/leds.h>
+ #include <linux/dsa/tag_qca.h>
+
+ #define QCA8K_ETHERNET_MDIO_PRIORITY 7
+@@ -85,6 +86,51 @@
+ #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
++
++/* LED control register */
++#define QCA8K_LED_PORT_COUNT 3
++#define QCA8K_LED_COUNT ((QCA8K_NUM_PORTS - QCA8K_NUM_CPU_PORTS) * QCA8K_LED_PORT_COUNT)
++#define QCA8K_LED_RULE_COUNT 6
++#define QCA8K_LED_RULE_MAX 11
++#define QCA8K_LED_PORT_INDEX(_phy, _led) (((_phy) * QCA8K_LED_PORT_COUNT) + (_led))
++
++#define QCA8K_LED_PHY123_PATTERN_EN_SHIFT(_phy, _led) ((((_phy) - 1) * 6) + 8 + (2 * (_led)))
++#define QCA8K_LED_PHY123_PATTERN_EN_MASK GENMASK(1, 0)
++
++#define QCA8K_LED_PHY0123_CONTROL_RULE_SHIFT 0
++#define QCA8K_LED_PHY4_CONTROL_RULE_SHIFT 16
++
++#define QCA8K_LED_CTRL_REG(_i) (0x050 + (_i) * 4)
++#define QCA8K_LED_CTRL0_REG 0x50
++#define QCA8K_LED_CTRL1_REG 0x54
++#define QCA8K_LED_CTRL2_REG 0x58
++#define QCA8K_LED_CTRL3_REG 0x5C
++#define QCA8K_LED_CTRL_SHIFT(_i) (((_i) % 2) * 16)
++#define QCA8K_LED_CTRL_MASK GENMASK(15, 0)
++#define QCA8K_LED_RULE_MASK GENMASK(13, 0)
++#define QCA8K_LED_BLINK_FREQ_MASK GENMASK(1, 0)
++#define QCA8K_LED_BLINK_FREQ_SHITF 0
++#define QCA8K_LED_BLINK_2HZ 0
++#define QCA8K_LED_BLINK_4HZ 1
++#define QCA8K_LED_BLINK_8HZ 2
++#define QCA8K_LED_BLINK_AUTO 3
++#define QCA8K_LED_LINKUP_OVER_MASK BIT(2)
++#define QCA8K_LED_TX_BLINK_MASK BIT(4)
++#define QCA8K_LED_RX_BLINK_MASK BIT(5)
++#define QCA8K_LED_COL_BLINK_MASK BIT(7)
++#define QCA8K_LED_LINK_10M_EN_MASK BIT(8)
++#define QCA8K_LED_LINK_100M_EN_MASK BIT(9)
++#define QCA8K_LED_LINK_1000M_EN_MASK BIT(10)
++#define QCA8K_LED_POWER_ON_LIGHT_MASK BIT(11)
++#define QCA8K_LED_HALF_DUPLEX_MASK BIT(12)
++#define QCA8K_LED_FULL_DUPLEX_MASK BIT(13)
++#define QCA8K_LED_PATTERN_EN_MASK GENMASK(15, 14)
++#define QCA8K_LED_PATTERN_EN_SHIFT 14
++#define QCA8K_LED_ALWAYS_OFF 0
++#define QCA8K_LED_ALWAYS_BLINK_4HZ 1
++#define QCA8K_LED_ALWAYS_ON 2
++#define QCA8K_LED_RULE_CONTROLLED 3
++
+ #define QCA8K_GOL_MAC_ADDR0 0x60
+ #define QCA8K_GOL_MAC_ADDR1 0x64
+ #define QCA8K_MAX_FRAME_SIZE 0x78
+@@ -382,6 +428,19 @@ struct qca8k_pcs {
+ int port;
+ };
+
++struct qca8k_led_pattern_en {
++ u32 reg;
++ u8 shift;
++};
++
++struct qca8k_led {
++ u8 port_num;
++ u8 led_num;
++ u16 old_rule;
++ struct qca8k_priv *priv;
++ struct led_classdev cdev;
++};
++
+ struct qca8k_priv {
+ u8 switch_id;
+ u8 switch_revision;
+@@ -406,6 +465,7 @@ struct qca8k_priv {
+ struct qca8k_pcs pcs_port_0;
+ struct qca8k_pcs pcs_port_6;
+ const struct qca8k_match_data *info;
++ struct qca8k_led ports_led[QCA8K_LED_COUNT];
+ };
+
+ struct qca8k_mib_desc {
+--- /dev/null
++++ b/drivers/net/dsa/qca/qca8k_leds.h
+@@ -0,0 +1,16 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++
++#ifndef __QCA8K_LEDS_H
++#define __QCA8K_LEDS_H
++
++/* Leds Support function */
++#ifdef CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT
++int qca8k_setup_led_ctrl(struct qca8k_priv *priv);
++#else
++static inline int qca8k_setup_led_ctrl(struct qca8k_priv *priv)
++{
++ return 0;
++}
++#endif
++
++#endif /* __QCA8K_LEDS_H */
diff --git a/target/linux/generic/backport-6.6/801-v6.4-03-net-dsa-qca8k-add-LEDs-blink_set-support.patch b/target/linux/generic/backport-6.6/801-v6.4-03-net-dsa-qca8k-add-LEDs-blink_set-support.patch
new file mode 100644
index 0000000000..231c4156df
--- /dev/null
+++ b/target/linux/generic/backport-6.6/801-v6.4-03-net-dsa-qca8k-add-LEDs-blink_set-support.patch
@@ -0,0 +1,74 @@
+From 91acadcc6e599dfc62717abcdad58a459cfb1684 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 17 Apr 2023 17:17:25 +0200
+Subject: [PATCH 3/9] net: dsa: qca8k: add LEDs blink_set() support
+
+Add LEDs blink_set() support to qca8k Switch Family.
+These LEDs support hw accellerated blinking at a fixed rate
+of 4Hz.
+
+Reject any other value since not supported by the LEDs switch.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Acked-by: Pavel Machek <pavel@ucw.cz>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/qca8k-leds.c | 38 ++++++++++++++++++++++++++++++++
+ 1 file changed, 38 insertions(+)
+
+--- a/drivers/net/dsa/qca/qca8k-leds.c
++++ b/drivers/net/dsa/qca/qca8k-leds.c
+@@ -128,6 +128,43 @@ qca8k_led_brightness_get(struct qca8k_le
+ }
+
+ static int
++qca8k_cled_blink_set(struct led_classdev *ldev,
++ unsigned long *delay_on,
++ unsigned long *delay_off)
++{
++ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
++ u32 mask, val = QCA8K_LED_ALWAYS_BLINK_4HZ;
++ struct qca8k_led_pattern_en reg_info;
++ struct qca8k_priv *priv = led->priv;
++
++ if (*delay_on == 0 && *delay_off == 0) {
++ *delay_on = 125;
++ *delay_off = 125;
++ }
++
++ if (*delay_on != 125 || *delay_off != 125) {
++ /* The hardware only supports blinking at 4Hz. Fall back
++ * to software implementation in other cases.
++ */
++ return -EINVAL;
++ }
++
++ qca8k_get_enable_led_reg(led->port_num, led->led_num, &reg_info);
++
++ if (led->port_num == 0 || led->port_num == 4) {
++ mask = QCA8K_LED_PATTERN_EN_MASK;
++ val <<= QCA8K_LED_PATTERN_EN_SHIFT;
++ } else {
++ mask = QCA8K_LED_PHY123_PATTERN_EN_MASK;
++ }
++
++ regmap_update_bits(priv->regmap, reg_info.reg, mask << reg_info.shift,
++ val << reg_info.shift);
++
++ return 0;
++}
++
++static int
+ qca8k_parse_port_leds(struct qca8k_priv *priv, struct fwnode_handle *port, int port_num)
+ {
+ struct fwnode_handle *led = NULL, *leds = NULL;
+@@ -186,6 +223,7 @@ qca8k_parse_port_leds(struct qca8k_priv
+
+ port_led->cdev.max_brightness = 1;
+ port_led->cdev.brightness_set_blocking = qca8k_cled_brightness_set_blocking;
++ port_led->cdev.blink_set = qca8k_cled_blink_set;
+ init_data.default_label = ":port";
+ init_data.fwnode = led;
+ init_data.devname_mandatory = true;
diff --git a/target/linux/generic/backport-6.6/801-v6.4-04-leds-Provide-stubs-for-when-CLASS_LED-NEW_LEDS-are-d.patch b/target/linux/generic/backport-6.6/801-v6.4-04-leds-Provide-stubs-for-when-CLASS_LED-NEW_LEDS-are-d.patch
new file mode 100644
index 0000000000..bc905b4468
--- /dev/null
+++ b/target/linux/generic/backport-6.6/801-v6.4-04-leds-Provide-stubs-for-when-CLASS_LED-NEW_LEDS-are-d.patch
@@ -0,0 +1,59 @@
+From e5029edd53937a29801ef507cee12e657ff31ea9 Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 17 Apr 2023 17:17:26 +0200
+Subject: [PATCH 4/9] leds: Provide stubs for when CLASS_LED & NEW_LEDS are
+ disabled
+
+Provide stubs for devm_led_classdev_register_ext() and
+led_init_default_state_get() so that LED drivers embedded within other
+drivers such as PHYs and Ethernet switches still build when LEDS_CLASS
+or NEW_LEDS are disabled. This also helps with Kconfig dependencies,
+which are somewhat hairy for phylib and mdio and only get worse when
+adding a dependency on LED_CLASS.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/linux/leds.h | 18 ++++++++++++++++++
+ 1 file changed, 18 insertions(+)
+
+--- a/include/linux/leds.h
++++ b/include/linux/leds.h
+@@ -63,7 +63,15 @@ struct led_init_data {
+ bool devname_mandatory;
+ };
+
++#if IS_ENABLED(CONFIG_NEW_LEDS)
+ enum led_default_state led_init_default_state_get(struct fwnode_handle *fwnode);
++#else
++static inline enum led_default_state
++led_init_default_state_get(struct fwnode_handle *fwnode)
++{
++ return LEDS_DEFSTATE_OFF;
++}
++#endif
+
+ struct led_hw_trigger_type {
+ int dummy;
+@@ -198,9 +206,19 @@ static inline int led_classdev_register(
+ return led_classdev_register_ext(parent, led_cdev, NULL);
+ }
+
++#if IS_ENABLED(CONFIG_LEDS_CLASS)
+ int devm_led_classdev_register_ext(struct device *parent,
+ struct led_classdev *led_cdev,
+ struct led_init_data *init_data);
++#else
++static inline int
++devm_led_classdev_register_ext(struct device *parent,
++ struct led_classdev *led_cdev,
++ struct led_init_data *init_data)
++{
++ return 0;
++}
++#endif
+
+ static inline int devm_led_classdev_register(struct device *parent,
+ struct led_classdev *led_cdev)
diff --git a/target/linux/generic/backport-6.6/801-v6.4-05-net-phy-Add-a-binding-for-PHY-LEDs.patch b/target/linux/generic/backport-6.6/801-v6.4-05-net-phy-Add-a-binding-for-PHY-LEDs.patch
new file mode 100644
index 0000000000..0d2c0bcd83
--- /dev/null
+++ b/target/linux/generic/backport-6.6/801-v6.4-05-net-phy-Add-a-binding-for-PHY-LEDs.patch
@@ -0,0 +1,191 @@
+From 01e5b728e9e43ae444e0369695a5f72209906464 Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 17 Apr 2023 17:17:27 +0200
+Subject: [PATCH 5/9] net: phy: Add a binding for PHY LEDs
+
+Define common binding parsing for all PHY drivers with LEDs using
+phylib. Parse the DT as part of the phy_probe and add LEDs to the
+linux LED class infrastructure. For the moment, provide a dummy
+brightness function, which will later be replaced with a call into the
+PHY driver. This allows testing since the LED core might otherwise
+reject an LED whose brightness cannot be set.
+
+Add a dependency on LED_CLASS. It either needs to be built in, or not
+enabled, since a modular build can result in linker errors.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/Kconfig | 1 +
+ drivers/net/phy/phy_device.c | 76 ++++++++++++++++++++++++++++++++++++
+ include/linux/phy.h | 16 ++++++++
+ 3 files changed, 93 insertions(+)
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -18,6 +18,7 @@ menuconfig PHYLIB
+ depends on NETDEVICES
+ select MDIO_DEVICE
+ select MDIO_DEVRES
++ depends on LEDS_CLASS || LEDS_CLASS=n
+ help
+ Ethernet controllers are usually attached to PHY
+ devices. This option provides infrastructure for
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -19,10 +19,12 @@
+ #include <linux/interrupt.h>
+ #include <linux/io.h>
+ #include <linux/kernel.h>
++#include <linux/list.h>
+ #include <linux/mdio.h>
+ #include <linux/mii.h>
+ #include <linux/mm.h>
+ #include <linux/module.h>
++#include <linux/of.h>
+ #include <linux/netdevice.h>
+ #include <linux/phy.h>
+ #include <linux/phy_led_triggers.h>
+@@ -642,6 +644,7 @@ struct phy_device *phy_device_create(str
+ device_initialize(&mdiodev->dev);
+
+ dev->state = PHY_DOWN;
++ INIT_LIST_HEAD(&dev->leds);
+
+ mutex_init(&dev->lock);
+ INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);
+@@ -3035,6 +3038,74 @@ static bool phy_drv_supports_irq(struct
+ return phydrv->config_intr && phydrv->handle_interrupt;
+ }
+
++/* Dummy implementation until calls into PHY driver are added */
++static int phy_led_set_brightness(struct led_classdev *led_cdev,
++ enum led_brightness value)
++{
++ return 0;
++}
++
++static int of_phy_led(struct phy_device *phydev,
++ struct device_node *led)
++{
++ struct device *dev = &phydev->mdio.dev;
++ struct led_init_data init_data = {};
++ struct led_classdev *cdev;
++ struct phy_led *phyled;
++ int err;
++
++ phyled = devm_kzalloc(dev, sizeof(*phyled), GFP_KERNEL);
++ if (!phyled)
++ return -ENOMEM;
++
++ cdev = &phyled->led_cdev;
++
++ err = of_property_read_u8(led, "reg", &phyled->index);
++ if (err)
++ return err;
++
++ cdev->brightness_set_blocking = phy_led_set_brightness;
++ cdev->max_brightness = 1;
++ init_data.devicename = dev_name(&phydev->mdio.dev);
++ init_data.fwnode = of_fwnode_handle(led);
++ init_data.devname_mandatory = true;
++
++ err = devm_led_classdev_register_ext(dev, cdev, &init_data);
++ if (err)
++ return err;
++
++ list_add(&phyled->list, &phydev->leds);
++
++ return 0;
++}
++
++static int of_phy_leds(struct phy_device *phydev)
++{
++ struct device_node *node = phydev->mdio.dev.of_node;
++ struct device_node *leds, *led;
++ int err;
++
++ if (!IS_ENABLED(CONFIG_OF_MDIO))
++ return 0;
++
++ if (!node)
++ return 0;
++
++ leds = of_get_child_by_name(node, "leds");
++ if (!leds)
++ return 0;
++
++ for_each_available_child_of_node(leds, led) {
++ err = of_phy_led(phydev, led);
++ if (err) {
++ of_node_put(led);
++ return err;
++ }
++ }
++
++ return 0;
++}
++
+ /**
+ * fwnode_mdio_find_device - Given a fwnode, find the mdio_device
+ * @fwnode: pointer to the mdio_device's fwnode
+@@ -3213,6 +3284,11 @@ static int phy_probe(struct device *dev)
+ /* Set the state to READY by default */
+ phydev->state = PHY_READY;
+
++ /* Get the LEDs from the device tree, and instantiate standard
++ * LEDs for them.
++ */
++ err = of_phy_leds(phydev);
++
+ out:
+ /* Re-assert the reset signal on error */
+ if (err)
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -14,6 +14,7 @@
+ #include <linux/compiler.h>
+ #include <linux/spinlock.h>
+ #include <linux/ethtool.h>
++#include <linux/leds.h>
+ #include <linux/linkmode.h>
+ #include <linux/netlink.h>
+ #include <linux/mdio.h>
+@@ -606,6 +607,7 @@ struct macsec_ops;
+ * @phy_num_led_triggers: Number of triggers in @phy_led_triggers
+ * @led_link_trigger: LED trigger for link up/down
+ * @last_triggered: last LED trigger for link speed
++ * @leds: list of PHY LED structures
+ * @master_slave_set: User requested master/slave configuration
+ * @master_slave_get: Current master/slave advertisement
+ * @master_slave_state: Current master/slave configuration
+@@ -698,6 +700,7 @@ struct phy_device {
+
+ struct phy_led_trigger *led_link_trigger;
+ #endif
++ struct list_head leds;
+
+ /*
+ * Interrupt number for this PHY
+@@ -772,6 +775,19 @@ struct phy_tdr_config {
+ #define PHY_PAIR_ALL -1
+
+ /**
++ * struct phy_led: An LED driven by the PHY
++ *
++ * @list: List of LEDs
++ * @led_cdev: Standard LED class structure
++ * @index: Number of the LED
++ */
++struct phy_led {
++ struct list_head list;
++ struct led_classdev led_cdev;
++ u8 index;
++};
++
++/**
+ * struct phy_driver - Driver structure for a particular PHY type
+ *
+ * @mdiodrv: Data common to all MDIO devices
diff --git a/target/linux/generic/backport-6.6/801-v6.4-06-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch b/target/linux/generic/backport-6.6/801-v6.4-06-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch
new file mode 100644
index 0000000000..4873c40a77
--- /dev/null
+++ b/target/linux/generic/backport-6.6/801-v6.4-06-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch
@@ -0,0 +1,97 @@
+From 684818189b04b095b34964ed4a3ea5249a840eab Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 17 Apr 2023 17:17:28 +0200
+Subject: [PATCH 6/9] net: phy: phy_device: Call into the PHY driver to set LED
+ brightness
+
+Linux LEDs can be software controlled via the brightness file in /sys.
+LED drivers need to implement a brightness_set function which the core
+will call. Implement an intermediary in phy_device, which will call
+into the phy driver if it implements the necessary function.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/phy_device.c | 15 ++++++++++++---
+ include/linux/phy.h | 13 +++++++++++++
+ 2 files changed, 25 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -3038,11 +3038,18 @@ static bool phy_drv_supports_irq(struct
+ return phydrv->config_intr && phydrv->handle_interrupt;
+ }
+
+-/* Dummy implementation until calls into PHY driver are added */
+ static int phy_led_set_brightness(struct led_classdev *led_cdev,
+ enum led_brightness value)
+ {
+- return 0;
++ struct phy_led *phyled = to_phy_led(led_cdev);
++ struct phy_device *phydev = phyled->phydev;
++ int err;
++
++ mutex_lock(&phydev->lock);
++ err = phydev->drv->led_brightness_set(phydev, phyled->index, value);
++ mutex_unlock(&phydev->lock);
++
++ return err;
+ }
+
+ static int of_phy_led(struct phy_device *phydev,
+@@ -3059,12 +3066,14 @@ static int of_phy_led(struct phy_device
+ return -ENOMEM;
+
+ cdev = &phyled->led_cdev;
++ phyled->phydev = phydev;
+
+ err = of_property_read_u8(led, "reg", &phyled->index);
+ if (err)
+ return err;
+
+- cdev->brightness_set_blocking = phy_led_set_brightness;
++ if (phydev->drv->led_brightness_set)
++ cdev->brightness_set_blocking = phy_led_set_brightness;
+ cdev->max_brightness = 1;
+ init_data.devicename = dev_name(&phydev->mdio.dev);
+ init_data.fwnode = of_fwnode_handle(led);
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -778,15 +778,19 @@ struct phy_tdr_config {
+ * struct phy_led: An LED driven by the PHY
+ *
+ * @list: List of LEDs
++ * @phydev: PHY this LED is attached to
+ * @led_cdev: Standard LED class structure
+ * @index: Number of the LED
+ */
+ struct phy_led {
+ struct list_head list;
++ struct phy_device *phydev;
+ struct led_classdev led_cdev;
+ u8 index;
+ };
+
++#define to_phy_led(d) container_of(d, struct phy_led, led_cdev)
++
+ /**
+ * struct phy_driver - Driver structure for a particular PHY type
+ *
+@@ -1001,6 +1005,15 @@ struct phy_driver {
+ int (*get_sqi)(struct phy_device *dev);
+ /** @get_sqi_max: Get the maximum signal quality indication */
+ int (*get_sqi_max)(struct phy_device *dev);
++
++ /**
++ * @led_brightness_set: Set a PHY LED brightness. Index
++ * indicates which of the PHYs led should be set. Value
++ * follows the standard LED class meaning, e.g. LED_OFF,
++ * LED_HALF, LED_FULL.
++ */
++ int (*led_brightness_set)(struct phy_device *dev,
++ u8 index, enum led_brightness value);
+ };
+ #define to_phy_driver(d) container_of(to_mdio_common_driver(d), \
+ struct phy_driver, mdiodrv)
diff --git a/target/linux/generic/backport-6.6/801-v6.4-07-net-phy-marvell-Add-software-control-of-the-LEDs.patch b/target/linux/generic/backport-6.6/801-v6.4-07-net-phy-marvell-Add-software-control-of-the-LEDs.patch
new file mode 100644
index 0000000000..5114c0e6da
--- /dev/null
+++ b/target/linux/generic/backport-6.6/801-v6.4-07-net-phy-marvell-Add-software-control-of-the-LEDs.patch
@@ -0,0 +1,112 @@
+From 2d3960e58ef7c83fe1dbf952f056b9e906cb6df8 Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 17 Apr 2023 17:17:29 +0200
+Subject: [PATCH 7/9] net: phy: marvell: Add software control of the LEDs
+
+Add a brightness function, so the LEDs can be controlled from
+software using the standard Linux LED infrastructure.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/marvell.c | 45 ++++++++++++++++++++++++++++++++++-----
+ 1 file changed, 40 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/marvell.c
++++ b/drivers/net/phy/marvell.c
+@@ -144,11 +144,13 @@
+ /* WOL Event Interrupt Enable */
+ #define MII_88E1318S_PHY_CSIER_WOL_EIE BIT(7)
+
+-/* LED Timer Control Register */
+-#define MII_88E1318S_PHY_LED_TCR 0x12
+-#define MII_88E1318S_PHY_LED_TCR_FORCE_INT BIT(15)
+-#define MII_88E1318S_PHY_LED_TCR_INTn_ENABLE BIT(7)
+-#define MII_88E1318S_PHY_LED_TCR_INT_ACTIVE_LOW BIT(11)
++#define MII_88E1318S_PHY_LED_FUNC 0x10
++#define MII_88E1318S_PHY_LED_FUNC_OFF (0x8)
++#define MII_88E1318S_PHY_LED_FUNC_ON (0x9)
++#define MII_88E1318S_PHY_LED_TCR 0x12
++#define MII_88E1318S_PHY_LED_TCR_FORCE_INT BIT(15)
++#define MII_88E1318S_PHY_LED_TCR_INTn_ENABLE BIT(7)
++#define MII_88E1318S_PHY_LED_TCR_INT_ACTIVE_LOW BIT(11)
+
+ /* Magic Packet MAC address registers */
+ #define MII_88E1318S_PHY_MAGIC_PACKET_WORD2 0x17
+@@ -2832,6 +2834,34 @@ static int marvell_hwmon_probe(struct ph
+ }
+ #endif
+
++static int m88e1318_led_brightness_set(struct phy_device *phydev,
++ u8 index, enum led_brightness value)
++{
++ int reg;
++
++ reg = phy_read_paged(phydev, MII_MARVELL_LED_PAGE,
++ MII_88E1318S_PHY_LED_FUNC);
++ if (reg < 0)
++ return reg;
++
++ switch (index) {
++ case 0:
++ case 1:
++ case 2:
++ reg &= ~(0xf << (4 * index));
++ if (value == LED_OFF)
++ reg |= MII_88E1318S_PHY_LED_FUNC_OFF << (4 * index);
++ else
++ reg |= MII_88E1318S_PHY_LED_FUNC_ON << (4 * index);
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ return phy_write_paged(phydev, MII_MARVELL_LED_PAGE,
++ MII_88E1318S_PHY_LED_FUNC, reg);
++}
++
+ static int marvell_probe(struct phy_device *phydev)
+ {
+ struct marvell_priv *priv;
+@@ -3081,6 +3111,7 @@ static struct phy_driver marvell_drivers
+ .get_sset_count = marvell_get_sset_count,
+ .get_strings = marvell_get_strings,
+ .get_stats = marvell_get_stats,
++ .led_brightness_set = m88e1318_led_brightness_set,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1145,
+@@ -3187,6 +3218,7 @@ static struct phy_driver marvell_drivers
+ .cable_test_start = marvell_vct7_cable_test_start,
+ .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
++ .led_brightness_set = m88e1318_led_brightness_set,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1540,
+@@ -3213,6 +3245,7 @@ static struct phy_driver marvell_drivers
+ .cable_test_start = marvell_vct7_cable_test_start,
+ .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
++ .led_brightness_set = m88e1318_led_brightness_set,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1545,
+@@ -3239,6 +3272,7 @@ static struct phy_driver marvell_drivers
+ .cable_test_start = marvell_vct7_cable_test_start,
+ .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
++ .led_brightness_set = m88e1318_led_brightness_set,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E3016,
+@@ -3380,6 +3414,7 @@ static struct phy_driver marvell_drivers
+ .get_stats = marvell_get_stats,
+ .get_tunable = m88e1540_get_tunable,
+ .set_tunable = m88e1540_set_tunable,
++ .led_brightness_set = m88e1318_led_brightness_set,
+ },
+ };
+
diff --git a/target/linux/generic/backport-6.6/801-v6.4-08-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch b/target/linux/generic/backport-6.6/801-v6.4-08-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch
new file mode 100644
index 0000000000..00bdcc5468
--- /dev/null
+++ b/target/linux/generic/backport-6.6/801-v6.4-08-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch
@@ -0,0 +1,73 @@
+From 4e901018432e38eab35d2a352661ce4727795be1 Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 17 Apr 2023 17:17:30 +0200
+Subject: [PATCH 8/9] net: phy: phy_device: Call into the PHY driver to set LED
+ blinking
+
+Linux LEDs can be requested to perform hardware accelerated
+blinking. Pass this to the PHY driver, if it implements the op.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/phy_device.c | 18 ++++++++++++++++++
+ include/linux/phy.h | 12 ++++++++++++
+ 2 files changed, 30 insertions(+)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -3052,6 +3052,22 @@ static int phy_led_set_brightness(struct
+ return err;
+ }
+
++static int phy_led_blink_set(struct led_classdev *led_cdev,
++ unsigned long *delay_on,
++ unsigned long *delay_off)
++{
++ struct phy_led *phyled = to_phy_led(led_cdev);
++ struct phy_device *phydev = phyled->phydev;
++ int err;
++
++ mutex_lock(&phydev->lock);
++ err = phydev->drv->led_blink_set(phydev, phyled->index,
++ delay_on, delay_off);
++ mutex_unlock(&phydev->lock);
++
++ return err;
++}
++
+ static int of_phy_led(struct phy_device *phydev,
+ struct device_node *led)
+ {
+@@ -3074,6 +3090,8 @@ static int of_phy_led(struct phy_device
+
+ if (phydev->drv->led_brightness_set)
+ cdev->brightness_set_blocking = phy_led_set_brightness;
++ if (phydev->drv->led_blink_set)
++ cdev->blink_set = phy_led_blink_set;
+ cdev->max_brightness = 1;
+ init_data.devicename = dev_name(&phydev->mdio.dev);
+ init_data.fwnode = of_fwnode_handle(led);
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -1014,6 +1014,18 @@ struct phy_driver {
+ */
+ int (*led_brightness_set)(struct phy_device *dev,
+ u8 index, enum led_brightness value);
++
++ /**
++ * @led_blink_set: Set a PHY LED brightness. Index indicates
++ * which of the PHYs led should be configured to blink. Delays
++ * are in milliseconds and if both are zero then a sensible
++ * default should be chosen. The call should adjust the
++ * timings in that case and if it can't match the values
++ * specified exactly.
++ */
++ int (*led_blink_set)(struct phy_device *dev, u8 index,
++ unsigned long *delay_on,
++ unsigned long *delay_off);
+ };
+ #define to_phy_driver(d) container_of(to_mdio_common_driver(d), \
+ struct phy_driver, mdiodrv)
diff --git a/target/linux/generic/backport-6.6/801-v6.4-09-net-phy-marvell-Implement-led_blink_set.patch b/target/linux/generic/backport-6.6/801-v6.4-09-net-phy-marvell-Implement-led_blink_set.patch
new file mode 100644
index 0000000000..8d5081a43c
--- /dev/null
+++ b/target/linux/generic/backport-6.6/801-v6.4-09-net-phy-marvell-Implement-led_blink_set.patch
@@ -0,0 +1,104 @@
+From ea9e86485decb2ac1750005bd96c166c9b780406 Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 17 Apr 2023 17:17:31 +0200
+Subject: [PATCH 9/9] net: phy: marvell: Implement led_blink_set()
+
+The Marvell PHY can blink the LEDs, simple on/off. All LEDs blink at
+the same rate, and the reset default is 84ms per blink, which is
+around 12Hz.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/marvell.c | 36 ++++++++++++++++++++++++++++++++++++
+ 1 file changed, 36 insertions(+)
+
+--- a/drivers/net/phy/marvell.c
++++ b/drivers/net/phy/marvell.c
+@@ -147,6 +147,8 @@
+ #define MII_88E1318S_PHY_LED_FUNC 0x10
+ #define MII_88E1318S_PHY_LED_FUNC_OFF (0x8)
+ #define MII_88E1318S_PHY_LED_FUNC_ON (0x9)
++#define MII_88E1318S_PHY_LED_FUNC_HI_Z (0xa)
++#define MII_88E1318S_PHY_LED_FUNC_BLINK (0xb)
+ #define MII_88E1318S_PHY_LED_TCR 0x12
+ #define MII_88E1318S_PHY_LED_TCR_FORCE_INT BIT(15)
+ #define MII_88E1318S_PHY_LED_TCR_INTn_ENABLE BIT(7)
+@@ -2862,6 +2864,35 @@ static int m88e1318_led_brightness_set(s
+ MII_88E1318S_PHY_LED_FUNC, reg);
+ }
+
++static int m88e1318_led_blink_set(struct phy_device *phydev, u8 index,
++ unsigned long *delay_on,
++ unsigned long *delay_off)
++{
++ int reg;
++
++ reg = phy_read_paged(phydev, MII_MARVELL_LED_PAGE,
++ MII_88E1318S_PHY_LED_FUNC);
++ if (reg < 0)
++ return reg;
++
++ switch (index) {
++ case 0:
++ case 1:
++ case 2:
++ reg &= ~(0xf << (4 * index));
++ reg |= MII_88E1318S_PHY_LED_FUNC_BLINK << (4 * index);
++ /* Reset default is 84ms */
++ *delay_on = 84 / 2;
++ *delay_off = 84 / 2;
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ return phy_write_paged(phydev, MII_MARVELL_LED_PAGE,
++ MII_88E1318S_PHY_LED_FUNC, reg);
++}
++
+ static int marvell_probe(struct phy_device *phydev)
+ {
+ struct marvell_priv *priv;
+@@ -3112,6 +3143,7 @@ static struct phy_driver marvell_drivers
+ .get_strings = marvell_get_strings,
+ .get_stats = marvell_get_stats,
+ .led_brightness_set = m88e1318_led_brightness_set,
++ .led_blink_set = m88e1318_led_blink_set,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1145,
+@@ -3219,6 +3251,7 @@ static struct phy_driver marvell_drivers
+ .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
+ .led_brightness_set = m88e1318_led_brightness_set,
++ .led_blink_set = m88e1318_led_blink_set,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1540,
+@@ -3246,6 +3279,7 @@ static struct phy_driver marvell_drivers
+ .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
+ .led_brightness_set = m88e1318_led_brightness_set,
++ .led_blink_set = m88e1318_led_blink_set,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1545,
+@@ -3273,6 +3307,7 @@ static struct phy_driver marvell_drivers
+ .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
+ .led_brightness_set = m88e1318_led_brightness_set,
++ .led_blink_set = m88e1318_led_blink_set,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E3016,
+@@ -3415,6 +3450,7 @@ static struct phy_driver marvell_drivers
+ .get_tunable = m88e1540_get_tunable,
+ .set_tunable = m88e1540_set_tunable,
+ .led_brightness_set = m88e1318_led_brightness_set,
++ .led_blink_set = m88e1318_led_blink_set,
+ },
+ };
+
diff --git a/target/linux/generic/backport-6.6/802-v6.4-net-phy-marvell-Fix-inconsistent-indenting-in-led_bl.patch b/target/linux/generic/backport-6.6/802-v6.4-net-phy-marvell-Fix-inconsistent-indenting-in-led_bl.patch
new file mode 100644
index 0000000000..56df3f095e
--- /dev/null
+++ b/target/linux/generic/backport-6.6/802-v6.4-net-phy-marvell-Fix-inconsistent-indenting-in-led_bl.patch
@@ -0,0 +1,38 @@
+From 4774ad841bef97cc51df90195338c5b2573dd4cb Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Sun, 23 Apr 2023 19:28:00 +0200
+Subject: [PATCH] net: phy: marvell: Fix inconsistent indenting in
+ led_blink_set
+
+Fix inconsistent indeinting in m88e1318_led_blink_set reported by kernel
+test robot, probably done by the presence of an if condition dropped in
+later revision of the same code.
+
+Reported-by: kernel test robot <lkp@intel.com>
+Link: https://lore.kernel.org/oe-kbuild-all/202304240007.0VEX8QYG-lkp@intel.com/
+Fixes: ea9e86485dec ("net: phy: marvell: Implement led_blink_set()")
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/20230423172800.3470-1-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/marvell.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/phy/marvell.c
++++ b/drivers/net/phy/marvell.c
+@@ -2880,10 +2880,10 @@ static int m88e1318_led_blink_set(struct
+ case 1:
+ case 2:
+ reg &= ~(0xf << (4 * index));
+- reg |= MII_88E1318S_PHY_LED_FUNC_BLINK << (4 * index);
+- /* Reset default is 84ms */
+- *delay_on = 84 / 2;
+- *delay_off = 84 / 2;
++ reg |= MII_88E1318S_PHY_LED_FUNC_BLINK << (4 * index);
++ /* Reset default is 84ms */
++ *delay_on = 84 / 2;
++ *delay_off = 84 / 2;
+ break;
+ default:
+ return -EINVAL;
diff --git a/target/linux/generic/backport-6.6/803-v6.5-02-leds-trigger-netdev-Drop-NETDEV_LED_MODE_LINKUP-from.patch b/target/linux/generic/backport-6.6/803-v6.5-02-leds-trigger-netdev-Drop-NETDEV_LED_MODE_LINKUP-from.patch
new file mode 100644
index 0000000000..3170c26058
--- /dev/null
+++ b/target/linux/generic/backport-6.6/803-v6.5-02-leds-trigger-netdev-Drop-NETDEV_LED_MODE_LINKUP-from.patch
@@ -0,0 +1,87 @@
+From e2f24cb1b5daf9a4f6f3ba574c1fa74aab9807a4 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 19 Apr 2023 23:07:40 +0200
+Subject: [PATCH 2/5] leds: trigger: netdev: Drop NETDEV_LED_MODE_LINKUP from
+ mode
+
+Putting NETDEV_LED_MODE_LINKUP in the same list of the netdev trigger
+modes is wrong as it's used to set the link state of the device and not
+to set a blink mode as it's done by NETDEV_LED_LINK, NETDEV_LED_TX and
+NETDEV_LED_RX. It's also wrong to put this state in the same bitmap of the
+netdev trigger mode and should be external to it.
+
+Drop NETDEV_LED_MODE_LINKUP from mode list and convert to a simple bool
+that will be true or false based on the carrier link. No functional
+change intended.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Lee Jones <lee@kernel.org>
+Link: https://lore.kernel.org/r/20230419210743.3594-3-ansuelsmth@gmail.com
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 19 ++++++++-----------
+ 1 file changed, 8 insertions(+), 11 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -50,10 +50,10 @@ struct led_netdev_data {
+ unsigned int last_activity;
+
+ unsigned long mode;
++ bool carrier_link_up;
+ #define NETDEV_LED_LINK 0
+ #define NETDEV_LED_TX 1
+ #define NETDEV_LED_RX 2
+-#define NETDEV_LED_MODE_LINKUP 3
+ };
+
+ enum netdev_led_attr {
+@@ -73,9 +73,9 @@ static void set_baseline_state(struct le
+ if (!led_cdev->blink_brightness)
+ led_cdev->blink_brightness = led_cdev->max_brightness;
+
+- if (!test_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode))
++ if (!trigger_data->carrier_link_up) {
+ led_set_brightness(led_cdev, LED_OFF);
+- else {
++ } else {
+ if (test_bit(NETDEV_LED_LINK, &trigger_data->mode))
+ led_set_brightness(led_cdev,
+ led_cdev->blink_brightness);
+@@ -131,10 +131,9 @@ static ssize_t device_name_store(struct
+ trigger_data->net_dev =
+ dev_get_by_name(&init_net, trigger_data->device_name);
+
+- clear_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode);
++ trigger_data->carrier_link_up = false;
+ if (trigger_data->net_dev != NULL)
+- if (netif_carrier_ok(trigger_data->net_dev))
+- set_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode);
++ trigger_data->carrier_link_up = netif_carrier_ok(trigger_data->net_dev);
+
+ trigger_data->last_activity = 0;
+
+@@ -315,11 +314,10 @@ static int netdev_trig_notify(struct not
+
+ spin_lock_bh(&trigger_data->lock);
+
+- clear_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode);
++ trigger_data->carrier_link_up = false;
+ switch (evt) {
+ case NETDEV_CHANGENAME:
+- if (netif_carrier_ok(dev))
+- set_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode);
++ trigger_data->carrier_link_up = netif_carrier_ok(dev);
+ fallthrough;
+ case NETDEV_REGISTER:
+ if (trigger_data->net_dev)
+@@ -333,8 +331,7 @@ static int netdev_trig_notify(struct not
+ break;
+ case NETDEV_UP:
+ case NETDEV_CHANGE:
+- if (netif_carrier_ok(dev))
+- set_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode);
++ trigger_data->carrier_link_up = netif_carrier_ok(dev);
+ break;
+ }
+
diff --git a/target/linux/generic/backport-6.6/803-v6.5-03-leds-trigger-netdev-Rename-add-namespace-to-netdev-t.patch b/target/linux/generic/backport-6.6/803-v6.5-03-leds-trigger-netdev-Rename-add-namespace-to-netdev-t.patch
new file mode 100644
index 0000000000..19cc1d7c9e
--- /dev/null
+++ b/target/linux/generic/backport-6.6/803-v6.5-03-leds-trigger-netdev-Rename-add-namespace-to-netdev-t.patch
@@ -0,0 +1,149 @@
+From bdec9cb83936e0ac4cb87fed5b49fad0175f7dec Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 19 Apr 2023 23:07:41 +0200
+Subject: [PATCH 3/5] leds: trigger: netdev: Rename add namespace to netdev
+ trigger enum modes
+
+Rename NETDEV trigger enum modes to a more symbolic name and add a
+namespace to them.
+
+Also add __TRIGGER_NETDEV_MAX to identify the max modes of the netdev
+trigger.
+
+This is a cleanup to drop the define and no behaviour change are
+intended.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Lee Jones <lee@kernel.org>
+Link: https://lore.kernel.org/r/20230419210743.3594-4-ansuelsmth@gmail.com
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 58 ++++++++++++---------------
+ 1 file changed, 25 insertions(+), 33 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -51,15 +51,15 @@ struct led_netdev_data {
+
+ unsigned long mode;
+ bool carrier_link_up;
+-#define NETDEV_LED_LINK 0
+-#define NETDEV_LED_TX 1
+-#define NETDEV_LED_RX 2
+ };
+
+-enum netdev_led_attr {
+- NETDEV_ATTR_LINK,
+- NETDEV_ATTR_TX,
+- NETDEV_ATTR_RX
++enum led_trigger_netdev_modes {
++ TRIGGER_NETDEV_LINK = 0,
++ TRIGGER_NETDEV_TX,
++ TRIGGER_NETDEV_RX,
++
++ /* Keep last */
++ __TRIGGER_NETDEV_MAX,
+ };
+
+ static void set_baseline_state(struct led_netdev_data *trigger_data)
+@@ -76,7 +76,7 @@ static void set_baseline_state(struct le
+ if (!trigger_data->carrier_link_up) {
+ led_set_brightness(led_cdev, LED_OFF);
+ } else {
+- if (test_bit(NETDEV_LED_LINK, &trigger_data->mode))
++ if (test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode))
+ led_set_brightness(led_cdev,
+ led_cdev->blink_brightness);
+ else
+@@ -85,8 +85,8 @@ static void set_baseline_state(struct le
+ /* If we are looking for RX/TX start periodically
+ * checking stats
+ */
+- if (test_bit(NETDEV_LED_TX, &trigger_data->mode) ||
+- test_bit(NETDEV_LED_RX, &trigger_data->mode))
++ if (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) ||
++ test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode))
+ schedule_delayed_work(&trigger_data->work, 0);
+ }
+ }
+@@ -146,20 +146,16 @@ static ssize_t device_name_store(struct
+ static DEVICE_ATTR_RW(device_name);
+
+ static ssize_t netdev_led_attr_show(struct device *dev, char *buf,
+- enum netdev_led_attr attr)
++ enum led_trigger_netdev_modes attr)
+ {
+ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+ int bit;
+
+ switch (attr) {
+- case NETDEV_ATTR_LINK:
+- bit = NETDEV_LED_LINK;
+- break;
+- case NETDEV_ATTR_TX:
+- bit = NETDEV_LED_TX;
+- break;
+- case NETDEV_ATTR_RX:
+- bit = NETDEV_LED_RX;
++ case TRIGGER_NETDEV_LINK:
++ case TRIGGER_NETDEV_TX:
++ case TRIGGER_NETDEV_RX:
++ bit = attr;
+ break;
+ default:
+ return -EINVAL;
+@@ -169,7 +165,7 @@ static ssize_t netdev_led_attr_show(stru
+ }
+
+ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
+- size_t size, enum netdev_led_attr attr)
++ size_t size, enum led_trigger_netdev_modes attr)
+ {
+ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+ unsigned long state;
+@@ -181,14 +177,10 @@ static ssize_t netdev_led_attr_store(str
+ return ret;
+
+ switch (attr) {
+- case NETDEV_ATTR_LINK:
+- bit = NETDEV_LED_LINK;
+- break;
+- case NETDEV_ATTR_TX:
+- bit = NETDEV_LED_TX;
+- break;
+- case NETDEV_ATTR_RX:
+- bit = NETDEV_LED_RX;
++ case TRIGGER_NETDEV_LINK:
++ case TRIGGER_NETDEV_TX:
++ case TRIGGER_NETDEV_RX:
++ bit = attr;
+ break;
+ default:
+ return -EINVAL;
+@@ -360,21 +352,21 @@ static void netdev_trig_work(struct work
+ }
+
+ /* If we are not looking for RX/TX then return */
+- if (!test_bit(NETDEV_LED_TX, &trigger_data->mode) &&
+- !test_bit(NETDEV_LED_RX, &trigger_data->mode))
++ if (!test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) &&
++ !test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode))
+ return;
+
+ dev_stats = dev_get_stats(trigger_data->net_dev, &temp);
+ new_activity =
+- (test_bit(NETDEV_LED_TX, &trigger_data->mode) ?
++ (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) ?
+ dev_stats->tx_packets : 0) +
+- (test_bit(NETDEV_LED_RX, &trigger_data->mode) ?
++ (test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) ?
+ dev_stats->rx_packets : 0);
+
+ if (trigger_data->last_activity != new_activity) {
+ led_stop_software_blink(trigger_data->led_cdev);
+
+- invert = test_bit(NETDEV_LED_LINK, &trigger_data->mode);
++ invert = test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode);
+ interval = jiffies_to_msecs(
+ atomic_read(&trigger_data->interval));
+ /* base state is ON (link present) */
diff --git a/target/linux/generic/backport-6.6/803-v6.5-04-leds-trigger-netdev-Convert-device-attr-to-macro.patch b/target/linux/generic/backport-6.6/803-v6.5-04-leds-trigger-netdev-Convert-device-attr-to-macro.patch
new file mode 100644
index 0000000000..3b45951f57
--- /dev/null
+++ b/target/linux/generic/backport-6.6/803-v6.5-04-leds-trigger-netdev-Convert-device-attr-to-macro.patch
@@ -0,0 +1,82 @@
+From 164b67d53476a9d114be85c885bd31f783835be4 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 19 Apr 2023 23:07:42 +0200
+Subject: [PATCH 4/5] leds: trigger: netdev: Convert device attr to macro
+
+Convert link tx and rx device attr to a common macro to reduce common
+code and in preparation for additional attr.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Lee Jones <lee@kernel.org>
+Link: https://lore.kernel.org/r/20230419210743.3594-5-ansuelsmth@gmail.com
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 57 ++++++++-------------------
+ 1 file changed, 16 insertions(+), 41 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -198,47 +198,22 @@ static ssize_t netdev_led_attr_store(str
+ return size;
+ }
+
+-static ssize_t link_show(struct device *dev,
+- struct device_attribute *attr, char *buf)
+-{
+- return netdev_led_attr_show(dev, buf, NETDEV_ATTR_LINK);
+-}
+-
+-static ssize_t link_store(struct device *dev,
+- struct device_attribute *attr, const char *buf, size_t size)
+-{
+- return netdev_led_attr_store(dev, buf, size, NETDEV_ATTR_LINK);
+-}
+-
+-static DEVICE_ATTR_RW(link);
+-
+-static ssize_t tx_show(struct device *dev,
+- struct device_attribute *attr, char *buf)
+-{
+- return netdev_led_attr_show(dev, buf, NETDEV_ATTR_TX);
+-}
+-
+-static ssize_t tx_store(struct device *dev,
+- struct device_attribute *attr, const char *buf, size_t size)
+-{
+- return netdev_led_attr_store(dev, buf, size, NETDEV_ATTR_TX);
+-}
+-
+-static DEVICE_ATTR_RW(tx);
+-
+-static ssize_t rx_show(struct device *dev,
+- struct device_attribute *attr, char *buf)
+-{
+- return netdev_led_attr_show(dev, buf, NETDEV_ATTR_RX);
+-}
+-
+-static ssize_t rx_store(struct device *dev,
+- struct device_attribute *attr, const char *buf, size_t size)
+-{
+- return netdev_led_attr_store(dev, buf, size, NETDEV_ATTR_RX);
+-}
+-
+-static DEVICE_ATTR_RW(rx);
++#define DEFINE_NETDEV_TRIGGER(trigger_name, trigger) \
++ static ssize_t trigger_name##_show(struct device *dev, \
++ struct device_attribute *attr, char *buf) \
++ { \
++ return netdev_led_attr_show(dev, buf, trigger); \
++ } \
++ static ssize_t trigger_name##_store(struct device *dev, \
++ struct device_attribute *attr, const char *buf, size_t size) \
++ { \
++ return netdev_led_attr_store(dev, buf, size, trigger); \
++ } \
++ static DEVICE_ATTR_RW(trigger_name)
++
++DEFINE_NETDEV_TRIGGER(link, TRIGGER_NETDEV_LINK);
++DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX);
++DEFINE_NETDEV_TRIGGER(rx, TRIGGER_NETDEV_RX);
+
+ static ssize_t interval_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
diff --git a/target/linux/generic/backport-6.6/803-v6.5-05-leds-trigger-netdev-Use-mutex-instead-of-spinlocks.patch b/target/linux/generic/backport-6.6/803-v6.5-05-leds-trigger-netdev-Use-mutex-instead-of-spinlocks.patch
new file mode 100644
index 0000000000..180bee9612
--- /dev/null
+++ b/target/linux/generic/backport-6.6/803-v6.5-05-leds-trigger-netdev-Use-mutex-instead-of-spinlocks.patch
@@ -0,0 +1,106 @@
+From d1b9e1391ab2dc80e9db87fe8b2de015c651e4c9 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 19 Apr 2023 23:07:43 +0200
+Subject: [PATCH 5/5] leds: trigger: netdev: Use mutex instead of spinlocks
+
+Some LEDs may require to sleep while doing some operation like setting
+brightness and other cleanup.
+
+For this reason, using a spinlock will cause a sleep under spinlock
+warning.
+
+It should be safe to convert this to a sleepable lock since:
+- sysfs read/write can sleep
+- netdev_trig_work is a work queue and can sleep
+- netdev _trig_notify can sleep
+
+The spinlock was used when brightness didn't support sleeping, but this
+changed and now it supported with brightness_set_blocking().
+
+Convert to mutex lock to permit sleeping using brightness_set_blocking().
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Lee Jones <lee@kernel.org>
+Link: https://lore.kernel.org/r/20230419210743.3594-6-ansuelsmth@gmail.com
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 18 +++++++++---------
+ 1 file changed, 9 insertions(+), 9 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -20,7 +20,7 @@
+ #include <linux/list.h>
+ #include <linux/module.h>
+ #include <linux/netdevice.h>
+-#include <linux/spinlock.h>
++#include <linux/mutex.h>
+ #include <linux/timer.h>
+ #include "../leds.h"
+
+@@ -37,7 +37,7 @@
+ */
+
+ struct led_netdev_data {
+- spinlock_t lock;
++ struct mutex lock;
+
+ struct delayed_work work;
+ struct notifier_block notifier;
+@@ -97,9 +97,9 @@ static ssize_t device_name_show(struct d
+ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+ ssize_t len;
+
+- spin_lock_bh(&trigger_data->lock);
++ mutex_lock(&trigger_data->lock);
+ len = sprintf(buf, "%s\n", trigger_data->device_name);
+- spin_unlock_bh(&trigger_data->lock);
++ mutex_unlock(&trigger_data->lock);
+
+ return len;
+ }
+@@ -115,7 +115,7 @@ static ssize_t device_name_store(struct
+
+ cancel_delayed_work_sync(&trigger_data->work);
+
+- spin_lock_bh(&trigger_data->lock);
++ mutex_lock(&trigger_data->lock);
+
+ if (trigger_data->net_dev) {
+ dev_put(trigger_data->net_dev);
+@@ -138,7 +138,7 @@ static ssize_t device_name_store(struct
+ trigger_data->last_activity = 0;
+
+ set_baseline_state(trigger_data);
+- spin_unlock_bh(&trigger_data->lock);
++ mutex_unlock(&trigger_data->lock);
+
+ return size;
+ }
+@@ -279,7 +279,7 @@ static int netdev_trig_notify(struct not
+
+ cancel_delayed_work_sync(&trigger_data->work);
+
+- spin_lock_bh(&trigger_data->lock);
++ mutex_lock(&trigger_data->lock);
+
+ trigger_data->carrier_link_up = false;
+ switch (evt) {
+@@ -304,7 +304,7 @@ static int netdev_trig_notify(struct not
+
+ set_baseline_state(trigger_data);
+
+- spin_unlock_bh(&trigger_data->lock);
++ mutex_unlock(&trigger_data->lock);
+
+ return NOTIFY_DONE;
+ }
+@@ -365,7 +365,7 @@ static int netdev_trig_activate(struct l
+ if (!trigger_data)
+ return -ENOMEM;
+
+- spin_lock_init(&trigger_data->lock);
++ mutex_init(&trigger_data->lock);
+
+ trigger_data->notifier.notifier_call = netdev_trig_notify;
+ trigger_data->notifier.priority = 10;
diff --git a/target/linux/generic/backport-6.6/804-v6.5-01-leds-add-APIs-for-LEDs-hw-control.patch b/target/linux/generic/backport-6.6/804-v6.5-01-leds-add-APIs-for-LEDs-hw-control.patch
new file mode 100644
index 0000000000..ac18611733
--- /dev/null
+++ b/target/linux/generic/backport-6.6/804-v6.5-01-leds-add-APIs-for-LEDs-hw-control.patch
@@ -0,0 +1,74 @@
+From ed554d3f945179c5b159bddfad7be34b403fe11a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 May 2023 18:32:31 +0200
+Subject: [PATCH 01/13] leds: add APIs for LEDs hw control
+
+Add an option to permit LED driver to declare support for a specific
+trigger to use hw control and setup the LED to blink based on specific
+provided modes.
+
+Add APIs for LEDs hw control. These functions will be used to activate
+hardware control where a LED will use the provided flags, from an
+unique defined supported trigger, to setup the LED to be driven by
+hardware.
+
+Add hw_control_is_supported() to ask the LED driver if the requested
+mode by the trigger are supported and the LED can be setup to follow
+the requested modes.
+
+Deactivate hardware blink control by setting brightness to LED_OFF via
+the brightness_set() callback.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/linux/leds.h | 37 +++++++++++++++++++++++++++++++++++++
+ 1 file changed, 37 insertions(+)
+
+--- a/include/linux/leds.h
++++ b/include/linux/leds.h
+@@ -164,6 +164,43 @@ struct led_classdev {
+
+ /* LEDs that have private triggers have this set */
+ struct led_hw_trigger_type *trigger_type;
++
++ /* Unique trigger name supported by LED set in hw control mode */
++ const char *hw_control_trigger;
++ /*
++ * Check if the LED driver supports the requested mode provided by the
++ * defined supported trigger to setup the LED to hw control mode.
++ *
++ * Return 0 on success. Return -EOPNOTSUPP when the passed flags are not
++ * supported and software fallback needs to be used.
++ * Return a negative error number on any other case for check fail due
++ * to various reason like device not ready or timeouts.
++ */
++ int (*hw_control_is_supported)(struct led_classdev *led_cdev,
++ unsigned long flags);
++ /*
++ * Activate hardware control, LED driver will use the provided flags
++ * from the supported trigger and setup the LED to be driven by hardware
++ * following the requested mode from the trigger flags.
++ * Deactivate hardware blink control by setting brightness to LED_OFF via
++ * the brightness_set() callback.
++ *
++ * Return 0 on success, a negative error number on flags apply fail.
++ */
++ int (*hw_control_set)(struct led_classdev *led_cdev,
++ unsigned long flags);
++ /*
++ * Get from the LED driver the current mode that the LED is set in hw
++ * control mode and put them in flags.
++ * Trigger can use this to get the initial state of a LED already set in
++ * hardware blink control.
++ *
++ * Return 0 on success, a negative error number on failing parsing the
++ * initial mode. Error from this function is NOT FATAL as the device
++ * may be in a not supported initial state by the attached LED trigger.
++ */
++ int (*hw_control_get)(struct led_classdev *led_cdev,
++ unsigned long *flags);
+ #endif
+
+ #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
diff --git a/target/linux/generic/backport-6.6/804-v6.5-02-leds-add-API-to-get-attached-device-for-LED-hw-contr.patch b/target/linux/generic/backport-6.6/804-v6.5-02-leds-add-API-to-get-attached-device-for-LED-hw-contr.patch
new file mode 100644
index 0000000000..1a221727a0
--- /dev/null
+++ b/target/linux/generic/backport-6.6/804-v6.5-02-leds-add-API-to-get-attached-device-for-LED-hw-contr.patch
@@ -0,0 +1,37 @@
+From 052c38eb17e866c5b4cd43924e7a5e20167b55c0 Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 29 May 2023 18:32:32 +0200
+Subject: [PATCH 02/13] leds: add API to get attached device for LED hw control
+
+Some specific LED triggers blink the LED based on events from a device
+or subsystem.
+For example, an LED could be blinked to indicate a network device is
+receiving packets, or a disk is reading blocks. To correctly enable and
+request the hw control of the LED, the trigger has to check if the
+network interface or block device configured via a /sys/class/led file
+match the one the LED driver provide for hw control for.
+
+Provide an API call to get the device which the LED blinks for.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/linux/leds.h | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/include/linux/leds.h
++++ b/include/linux/leds.h
+@@ -201,6 +201,12 @@ struct led_classdev {
+ */
+ int (*hw_control_get)(struct led_classdev *led_cdev,
+ unsigned long *flags);
++ /*
++ * Get the device this LED blinks in response to.
++ * e.g. for a PHY LED, it is the network device. If the LED is
++ * not yet associated to a device, return NULL.
++ */
++ struct device *(*hw_control_get_device)(struct led_classdev *led_cdev);
+ #endif
+
+ #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
diff --git a/target/linux/generic/backport-6.6/804-v6.5-03-Documentation-leds-leds-class-Document-new-Hardware-.patch b/target/linux/generic/backport-6.6/804-v6.5-03-Documentation-leds-leds-class-Document-new-Hardware-.patch
new file mode 100644
index 0000000000..af9fb7fdc3
--- /dev/null
+++ b/target/linux/generic/backport-6.6/804-v6.5-03-Documentation-leds-leds-class-Document-new-Hardware-.patch
@@ -0,0 +1,113 @@
+From 8aa2fd7b66980ecd2e45e95af61cf7eafede1211 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 May 2023 18:32:33 +0200
+Subject: [PATCH 03/13] Documentation: leds: leds-class: Document new Hardware
+ driven LEDs APIs
+
+Document new Hardware driven LEDs APIs.
+
+Some LEDs can be programmed to be driven by hardware. This is not
+limited to blink but also to turn off or on autonomously.
+To support this feature, a LED needs to implement various additional
+ops and needs to declare specific support for the supported triggers.
+
+Add documentation for each required value and API to make hw control
+possible and implementable by both LEDs and triggers.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ Documentation/leds/leds-class.rst | 81 +++++++++++++++++++++++++++++++
+ 1 file changed, 81 insertions(+)
+
+--- a/Documentation/leds/leds-class.rst
++++ b/Documentation/leds/leds-class.rst
+@@ -169,6 +169,87 @@ Setting the brightness to zero with brig
+ should completely turn off the LED and cancel the previously programmed
+ hardware blinking function, if any.
+
++Hardware driven LEDs
++====================
++
++Some LEDs can be programmed to be driven by hardware. This is not
++limited to blink but also to turn off or on autonomously.
++To support this feature, a LED needs to implement various additional
++ops and needs to declare specific support for the supported triggers.
++
++With hw control we refer to the LED driven by hardware.
++
++LED driver must define the following value to support hw control:
++
++ - hw_control_trigger:
++ unique trigger name supported by the LED in hw control
++ mode.
++
++LED driver must implement the following API to support hw control:
++ - hw_control_is_supported:
++ check if the flags passed by the supported trigger can
++ be parsed and activate hw control on the LED.
++
++ Return 0 if the passed flags mask is supported and
++ can be set with hw_control_set().
++
++ If the passed flags mask is not supported -EOPNOTSUPP
++ must be returned, the LED trigger will use software
++ fallback in this case.
++
++ Return a negative error in case of any other error like
++ device not ready or timeouts.
++
++ - hw_control_set:
++ activate hw control. LED driver will use the provided
++ flags passed from the supported trigger, parse them to
++ a set of mode and setup the LED to be driven by hardware
++ following the requested modes.
++
++ Set LED_OFF via the brightness_set to deactivate hw control.
++
++ Return 0 on success, a negative error number on failing to
++ apply flags.
++
++ - hw_control_get:
++ get active modes from a LED already in hw control, parse
++ them and set in flags the current active flags for the
++ supported trigger.
++
++ Return 0 on success, a negative error number on failing
++ parsing the initial mode.
++ Error from this function is NOT FATAL as the device may
++ be in a not supported initial state by the attached LED
++ trigger.
++
++ - hw_control_get_device:
++ return the device associated with the LED driver in
++ hw control. A trigger might use this to match the
++ returned device from this function with a configured
++ device for the trigger as the source for blinking
++ events and correctly enable hw control.
++ (example a netdev trigger configured to blink for a
++ particular dev match the returned dev from get_device
++ to set hw control)
++
++ Returns a pointer to a struct device or NULL if nothing
++ is currently attached.
++
++LED driver can activate additional modes by default to workaround the
++impossibility of supporting each different mode on the supported trigger.
++Examples are hardcoding the blink speed to a set interval, enable special
++feature like bypassing blink if some requirements are not met.
++
++A trigger should first check if the hw control API are supported by the LED
++driver and check if the trigger is supported to verify if hw control is possible,
++use hw_control_is_supported to check if the flags are supported and only at
++the end use hw_control_set to activate hw control.
++
++A trigger can use hw_control_get to check if a LED is already in hw control
++and init their flags.
++
++When the LED is in hw control, no software blink is possible and doing so
++will effectively disable hw control.
+
+ Known Issues
+ ============
diff --git a/target/linux/generic/backport-6.6/804-v6.5-04-leds-trigger-netdev-refactor-code-setting-device-nam.patch b/target/linux/generic/backport-6.6/804-v6.5-04-leds-trigger-netdev-refactor-code-setting-device-nam.patch
new file mode 100644
index 0000000000..3c804c0b41
--- /dev/null
+++ b/target/linux/generic/backport-6.6/804-v6.5-04-leds-trigger-netdev-refactor-code-setting-device-nam.patch
@@ -0,0 +1,69 @@
+From 28a6a2ef18ad840a390d519840c303b03040961c Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 29 May 2023 18:32:34 +0200
+Subject: [PATCH 04/13] leds: trigger: netdev: refactor code setting device
+ name
+
+Move the code into a helper, ready for it to be called at
+other times. No intended behaviour change.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 29 ++++++++++++++++++---------
+ 1 file changed, 20 insertions(+), 9 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -104,15 +104,9 @@ static ssize_t device_name_show(struct d
+ return len;
+ }
+
+-static ssize_t device_name_store(struct device *dev,
+- struct device_attribute *attr, const char *buf,
+- size_t size)
++static int set_device_name(struct led_netdev_data *trigger_data,
++ const char *name, size_t size)
+ {
+- struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+-
+- if (size >= IFNAMSIZ)
+- return -EINVAL;
+-
+ cancel_delayed_work_sync(&trigger_data->work);
+
+ mutex_lock(&trigger_data->lock);
+@@ -122,7 +116,7 @@ static ssize_t device_name_store(struct
+ trigger_data->net_dev = NULL;
+ }
+
+- memcpy(trigger_data->device_name, buf, size);
++ memcpy(trigger_data->device_name, name, size);
+ trigger_data->device_name[size] = 0;
+ if (size > 0 && trigger_data->device_name[size - 1] == '\n')
+ trigger_data->device_name[size - 1] = 0;
+@@ -140,6 +134,23 @@ static ssize_t device_name_store(struct
+ set_baseline_state(trigger_data);
+ mutex_unlock(&trigger_data->lock);
+
++ return 0;
++}
++
++static ssize_t device_name_store(struct device *dev,
++ struct device_attribute *attr, const char *buf,
++ size_t size)
++{
++ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
++ int ret;
++
++ if (size >= IFNAMSIZ)
++ return -EINVAL;
++
++ ret = set_device_name(trigger_data, buf, size);
++
++ if (ret < 0)
++ return ret;
+ return size;
+ }
+
diff --git a/target/linux/generic/backport-6.6/804-v6.5-05-leds-trigger-netdev-introduce-check-for-possible-hw-.patch b/target/linux/generic/backport-6.6/804-v6.5-05-leds-trigger-netdev-introduce-check-for-possible-hw-.patch
new file mode 100644
index 0000000000..284b519482
--- /dev/null
+++ b/target/linux/generic/backport-6.6/804-v6.5-05-leds-trigger-netdev-introduce-check-for-possible-hw-.patch
@@ -0,0 +1,54 @@
+From 4fd1b6d47a7a38e81fdc6f8be2ccd4216b3f93db Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 May 2023 18:32:35 +0200
+Subject: [PATCH 05/13] leds: trigger: netdev: introduce check for possible hw
+ control
+
+Introduce function to check if the requested mode can use hw control in
+preparation for hw control support. Currently everything is handled in
+software so can_hw_control will always return false.
+
+Add knob with the new value hw_control in trigger_data struct to
+set hw control possible. Useful for future implementation to implement
+in set_baseline_state() the required function to set the requested mode
+using LEDs hw control ops and in other function to reject set if hw
+control is currently active.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -51,6 +51,7 @@ struct led_netdev_data {
+
+ unsigned long mode;
+ bool carrier_link_up;
++ bool hw_control;
+ };
+
+ enum led_trigger_netdev_modes {
+@@ -91,6 +92,11 @@ static void set_baseline_state(struct le
+ }
+ }
+
++static bool can_hw_control(struct led_netdev_data *trigger_data)
++{
++ return false;
++}
++
+ static ssize_t device_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+ {
+@@ -204,6 +210,8 @@ static ssize_t netdev_led_attr_store(str
+ else
+ clear_bit(bit, &trigger_data->mode);
+
++ trigger_data->hw_control = can_hw_control(trigger_data);
++
+ set_baseline_state(trigger_data);
+
+ return size;
diff --git a/target/linux/generic/backport-6.6/804-v6.5-06-leds-trigger-netdev-add-basic-check-for-hw-control-s.patch b/target/linux/generic/backport-6.6/804-v6.5-06-leds-trigger-netdev-add-basic-check-for-hw-control-s.patch
new file mode 100644
index 0000000000..09759bc623
--- /dev/null
+++ b/target/linux/generic/backport-6.6/804-v6.5-06-leds-trigger-netdev-add-basic-check-for-hw-control-s.patch
@@ -0,0 +1,42 @@
+From 6352f25f9fadba59d5df2ba7139495759ccc81d5 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 May 2023 18:32:36 +0200
+Subject: [PATCH 06/13] leds: trigger: netdev: add basic check for hw control
+ support
+
+Add basic check for hw control support. Check if the required API are
+defined and check if the defined trigger supported in hw control for the
+LED driver match netdev.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -92,8 +92,22 @@ static void set_baseline_state(struct le
+ }
+ }
+
++static bool supports_hw_control(struct led_classdev *led_cdev)
++{
++ if (!led_cdev->hw_control_get || !led_cdev->hw_control_set ||
++ !led_cdev->hw_control_is_supported)
++ return false;
++
++ return !strcmp(led_cdev->hw_control_trigger, led_cdev->trigger->name);
++}
++
+ static bool can_hw_control(struct led_netdev_data *trigger_data)
+ {
++ struct led_classdev *led_cdev = trigger_data->led_cdev;
++
++ if (!supports_hw_control(led_cdev))
++ return false;
++
+ return false;
+ }
+
diff --git a/target/linux/generic/backport-6.6/804-v6.5-07-leds-trigger-netdev-reject-interval-store-for-hw_con.patch b/target/linux/generic/backport-6.6/804-v6.5-07-leds-trigger-netdev-reject-interval-store-for-hw_con.patch
new file mode 100644
index 0000000000..6634906800
--- /dev/null
+++ b/target/linux/generic/backport-6.6/804-v6.5-07-leds-trigger-netdev-reject-interval-store-for-hw_con.patch
@@ -0,0 +1,28 @@
+From c84c80c7388f887b10dafd70fc55bc6c5fe9fa5a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 May 2023 18:32:37 +0200
+Subject: [PATCH 07/13] leds: trigger: netdev: reject interval store for
+ hw_control
+
+Reject interval store with hw_control enabled. It's are currently not
+supported and MUST be set to the default value with hw control enabled.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -265,6 +265,9 @@ static ssize_t interval_store(struct dev
+ unsigned long value;
+ int ret;
+
++ if (trigger_data->hw_control)
++ return -EINVAL;
++
+ ret = kstrtoul(buf, 0, &value);
+ if (ret)
+ return ret;
diff --git a/target/linux/generic/backport-6.6/804-v6.5-08-leds-trigger-netdev-add-support-for-LED-hw-control.patch b/target/linux/generic/backport-6.6/804-v6.5-08-leds-trigger-netdev-add-support-for-LED-hw-control.patch
new file mode 100644
index 0000000000..52faa4809b
--- /dev/null
+++ b/target/linux/generic/backport-6.6/804-v6.5-08-leds-trigger-netdev-add-support-for-LED-hw-control.patch
@@ -0,0 +1,107 @@
+From 7c145a34ba6e380616af93262fcab9fc7261d851 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 May 2023 18:32:38 +0200
+Subject: [PATCH 08/13] leds: trigger: netdev: add support for LED hw control
+
+Add support for LED hw control for the netdev trigger.
+
+The trigger on calling set_baseline_state to configure a new mode, will
+do various check to verify if hw control can be used for the requested
+mode in can_hw_control() function.
+
+It will first check if the LED driver supports hw control for the netdev
+trigger, then will use hw_control_is_supported() and finally will call
+hw_control_set() to apply the requested mode.
+
+To use such mode, interval MUST be set to the default value and net_dev
+MUST be set. If one of these 2 value are not valid, hw control will
+never be used and normal software fallback is used.
+
+The default interval value is moved to a define to make sure they are
+always synced.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 43 +++++++++++++++++++++++++--
+ 1 file changed, 41 insertions(+), 2 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -24,6 +24,8 @@
+ #include <linux/timer.h>
+ #include "../leds.h"
+
++#define NETDEV_LED_DEFAULT_INTERVAL 50
++
+ /*
+ * Configurable sysfs attributes:
+ *
+@@ -68,6 +70,13 @@ static void set_baseline_state(struct le
+ int current_brightness;
+ struct led_classdev *led_cdev = trigger_data->led_cdev;
+
++ /* Already validated, hw control is possible with the requested mode */
++ if (trigger_data->hw_control) {
++ led_cdev->hw_control_set(led_cdev, trigger_data->mode);
++
++ return;
++ }
++
+ current_brightness = led_cdev->brightness;
+ if (current_brightness)
+ led_cdev->blink_brightness = current_brightness;
+@@ -103,12 +112,42 @@ static bool supports_hw_control(struct l
+
+ static bool can_hw_control(struct led_netdev_data *trigger_data)
+ {
++ unsigned long default_interval = msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL);
++ unsigned int interval = atomic_read(&trigger_data->interval);
+ struct led_classdev *led_cdev = trigger_data->led_cdev;
++ int ret;
+
+ if (!supports_hw_control(led_cdev))
+ return false;
+
+- return false;
++ /*
++ * Interval must be set to the default
++ * value. Any different value is rejected if in hw
++ * control.
++ */
++ if (interval != default_interval)
++ return false;
++
++ /*
++ * net_dev must be set with hw control, otherwise no
++ * blinking can be happening and there is nothing to
++ * offloaded.
++ */
++ if (!trigger_data->net_dev)
++ return false;
++
++ /* Check if the requested mode is supported */
++ ret = led_cdev->hw_control_is_supported(led_cdev, trigger_data->mode);
++ /* Fall back to software blinking if not supported */
++ if (ret == -EOPNOTSUPP)
++ return false;
++ if (ret) {
++ dev_warn(led_cdev->dev,
++ "Current mode check failed with error %d\n", ret);
++ return false;
++ }
++
++ return true;
+ }
+
+ static ssize_t device_name_show(struct device *dev,
+@@ -413,7 +452,7 @@ static int netdev_trig_activate(struct l
+ trigger_data->device_name[0] = 0;
+
+ trigger_data->mode = 0;
+- atomic_set(&trigger_data->interval, msecs_to_jiffies(50));
++ atomic_set(&trigger_data->interval, msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL));
+ trigger_data->last_activity = 0;
+
+ led_set_trigger_data(led_cdev, trigger_data);
diff --git a/target/linux/generic/backport-6.6/804-v6.5-09-leds-trigger-netdev-validate-configured-netdev.patch b/target/linux/generic/backport-6.6/804-v6.5-09-leds-trigger-netdev-validate-configured-netdev.patch
new file mode 100644
index 0000000000..c129ffa4f5
--- /dev/null
+++ b/target/linux/generic/backport-6.6/804-v6.5-09-leds-trigger-netdev-validate-configured-netdev.patch
@@ -0,0 +1,58 @@
+From 33ec0b53befff2c0a7f3aa19ff08556d60585d6b Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 29 May 2023 18:32:39 +0200
+Subject: [PATCH 09/13] leds: trigger: netdev: validate configured netdev
+
+The netdev which the LED should blink for is configurable in
+/sys/class/led/foo/device_name. Ensure when offloading that the
+configured netdev is the same as the netdev the LED is associated
+with. If it is not, only perform software blinking.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 24 ++++++++++++++++++++++--
+ 1 file changed, 22 insertions(+), 2 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -110,6 +110,24 @@ static bool supports_hw_control(struct l
+ return !strcmp(led_cdev->hw_control_trigger, led_cdev->trigger->name);
+ }
+
++/*
++ * Validate the configured netdev is the same as the one associated with
++ * the LED driver in hw control.
++ */
++static bool validate_net_dev(struct led_classdev *led_cdev,
++ struct net_device *net_dev)
++{
++ struct device *dev = led_cdev->hw_control_get_device(led_cdev);
++ struct net_device *ndev;
++
++ if (!dev)
++ return false;
++
++ ndev = to_net_dev(dev);
++
++ return ndev == net_dev;
++}
++
+ static bool can_hw_control(struct led_netdev_data *trigger_data)
+ {
+ unsigned long default_interval = msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL);
+@@ -131,9 +149,11 @@ static bool can_hw_control(struct led_ne
+ /*
+ * net_dev must be set with hw control, otherwise no
+ * blinking can be happening and there is nothing to
+- * offloaded.
++ * offloaded. Additionally, for hw control to be
++ * valid, the configured netdev must be the same as
++ * netdev associated to the LED.
+ */
+- if (!trigger_data->net_dev)
++ if (!validate_net_dev(led_cdev, trigger_data->net_dev))
+ return false;
+
+ /* Check if the requested mode is supported */
diff --git a/target/linux/generic/backport-6.6/804-v6.5-10-leds-trigger-netdev-init-mode-if-hw-control-already-.patch b/target/linux/generic/backport-6.6/804-v6.5-10-leds-trigger-netdev-init-mode-if-hw-control-already-.patch
new file mode 100644
index 0000000000..e20594c99a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/804-v6.5-10-leds-trigger-netdev-init-mode-if-hw-control-already-.patch
@@ -0,0 +1,53 @@
+From 0316cc5629d15880dd3f097d221c55ca648bcd61 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 May 2023 18:32:40 +0200
+Subject: [PATCH 10/13] leds: trigger: netdev: init mode if hw control already
+ active
+
+On netdev trigger activation, hw control may be already active by
+default. If this is the case and a device is actually provided by
+hw_control_get_device(), init the already active mode and set the
+bool to hw_control bool to true to reflect the already set mode in the
+trigger_data.
+
+Co-developed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 17 +++++++++++++++++
+ 1 file changed, 17 insertions(+)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -454,6 +454,8 @@ static void netdev_trig_work(struct work
+ static int netdev_trig_activate(struct led_classdev *led_cdev)
+ {
+ struct led_netdev_data *trigger_data;
++ unsigned long mode;
++ struct device *dev;
+ int rc;
+
+ trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL);
+@@ -475,6 +477,21 @@ static int netdev_trig_activate(struct l
+ atomic_set(&trigger_data->interval, msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL));
+ trigger_data->last_activity = 0;
+
++ /* Check if hw control is active by default on the LED.
++ * Init already enabled mode in hw control.
++ */
++ if (supports_hw_control(led_cdev) &&
++ !led_cdev->hw_control_get(led_cdev, &mode)) {
++ dev = led_cdev->hw_control_get_device(led_cdev);
++ if (dev) {
++ const char *name = dev_name(dev);
++
++ set_device_name(trigger_data, name, strlen(name));
++ trigger_data->hw_control = true;
++ trigger_data->mode = mode;
++ }
++ }
++
+ led_set_trigger_data(led_cdev, trigger_data);
+
+ rc = register_netdevice_notifier(&trigger_data->notifier);
diff --git a/target/linux/generic/backport-6.6/804-v6.5-11-leds-trigger-netdev-expose-netdev-trigger-modes-in-l.patch b/target/linux/generic/backport-6.6/804-v6.5-11-leds-trigger-netdev-expose-netdev-trigger-modes-in-l.patch
new file mode 100644
index 0000000000..70aed850d1
--- /dev/null
+++ b/target/linux/generic/backport-6.6/804-v6.5-11-leds-trigger-netdev-expose-netdev-trigger-modes-in-l.patch
@@ -0,0 +1,54 @@
+From 947acacab5ea151291b861cdfbde16ff5cf1b08c Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 May 2023 18:32:41 +0200
+Subject: [PATCH 11/13] leds: trigger: netdev: expose netdev trigger modes in
+ linux include
+
+Expose netdev trigger modes to make them accessible by LED driver that
+will support netdev trigger for hw control.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 9 ---------
+ include/linux/leds.h | 10 ++++++++++
+ 2 files changed, 10 insertions(+), 9 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -56,15 +56,6 @@ struct led_netdev_data {
+ bool hw_control;
+ };
+
+-enum led_trigger_netdev_modes {
+- TRIGGER_NETDEV_LINK = 0,
+- TRIGGER_NETDEV_TX,
+- TRIGGER_NETDEV_RX,
+-
+- /* Keep last */
+- __TRIGGER_NETDEV_MAX,
+-};
+-
+ static void set_baseline_state(struct led_netdev_data *trigger_data)
+ {
+ int current_brightness;
+--- a/include/linux/leds.h
++++ b/include/linux/leds.h
+@@ -527,6 +527,16 @@ static inline void *led_get_trigger_data
+
+ #endif /* CONFIG_LEDS_TRIGGERS */
+
++/* Trigger specific enum */
++enum led_trigger_netdev_modes {
++ TRIGGER_NETDEV_LINK = 0,
++ TRIGGER_NETDEV_TX,
++ TRIGGER_NETDEV_RX,
++
++ /* Keep last */
++ __TRIGGER_NETDEV_MAX,
++};
++
+ /* Trigger specific functions */
+ #ifdef CONFIG_LEDS_TRIGGER_DISK
+ void ledtrig_disk_activity(bool write);
diff --git a/target/linux/generic/backport-6.6/804-v6.5-12-net-dsa-qca8k-implement-hw_control-ops.patch b/target/linux/generic/backport-6.6/804-v6.5-12-net-dsa-qca8k-implement-hw_control-ops.patch
new file mode 100644
index 0000000000..ad76d89b7b
--- /dev/null
+++ b/target/linux/generic/backport-6.6/804-v6.5-12-net-dsa-qca8k-implement-hw_control-ops.patch
@@ -0,0 +1,200 @@
+From e0256648c831af13cbfe4a1787327fcec01c2807 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 May 2023 18:32:42 +0200
+Subject: [PATCH 12/13] net: dsa: qca8k: implement hw_control ops
+
+Implement hw_control ops to drive Switch LEDs based on hardware events.
+
+Netdev trigger is the declared supported trigger for hw control
+operation and supports the following mode:
+- tx
+- rx
+
+When hw_control_set is called, LEDs are set to follow the requested
+mode.
+Each LEDs will blink at 4Hz by default.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/qca8k-leds.c | 154 +++++++++++++++++++++++++++++++
+ 1 file changed, 154 insertions(+)
+
+--- a/drivers/net/dsa/qca/qca8k-leds.c
++++ b/drivers/net/dsa/qca/qca8k-leds.c
+@@ -32,6 +32,43 @@ qca8k_get_enable_led_reg(int port_num, i
+ }
+
+ static int
++qca8k_get_control_led_reg(int port_num, int led_num, struct qca8k_led_pattern_en *reg_info)
++{
++ reg_info->reg = QCA8K_LED_CTRL_REG(led_num);
++
++ /* 6 total control rule:
++ * 3 control rules for phy0-3 that applies to all their leds
++ * 3 control rules for phy4
++ */
++ if (port_num == 4)
++ reg_info->shift = QCA8K_LED_PHY4_CONTROL_RULE_SHIFT;
++ else
++ reg_info->shift = QCA8K_LED_PHY0123_CONTROL_RULE_SHIFT;
++
++ return 0;
++}
++
++static int
++qca8k_parse_netdev(unsigned long rules, u32 *offload_trigger)
++{
++ /* Parsing specific to netdev trigger */
++ if (test_bit(TRIGGER_NETDEV_TX, &rules))
++ *offload_trigger |= QCA8K_LED_TX_BLINK_MASK;
++ if (test_bit(TRIGGER_NETDEV_RX, &rules))
++ *offload_trigger |= QCA8K_LED_RX_BLINK_MASK;
++
++ if (rules && !*offload_trigger)
++ return -EOPNOTSUPP;
++
++ /* Enable some default rule by default to the requested mode:
++ * - Blink at 4Hz by default
++ */
++ *offload_trigger |= QCA8K_LED_BLINK_4HZ;
++
++ return 0;
++}
++
++static int
+ qca8k_led_brightness_set(struct qca8k_led *led,
+ enum led_brightness brightness)
+ {
+@@ -165,6 +202,119 @@ qca8k_cled_blink_set(struct led_classdev
+ }
+
+ static int
++qca8k_cled_trigger_offload(struct led_classdev *ldev, bool enable)
++{
++ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
++
++ struct qca8k_led_pattern_en reg_info;
++ struct qca8k_priv *priv = led->priv;
++ u32 mask, val = QCA8K_LED_ALWAYS_OFF;
++
++ qca8k_get_enable_led_reg(led->port_num, led->led_num, &reg_info);
++
++ if (enable)
++ val = QCA8K_LED_RULE_CONTROLLED;
++
++ if (led->port_num == 0 || led->port_num == 4) {
++ mask = QCA8K_LED_PATTERN_EN_MASK;
++ val <<= QCA8K_LED_PATTERN_EN_SHIFT;
++ } else {
++ mask = QCA8K_LED_PHY123_PATTERN_EN_MASK;
++ }
++
++ return regmap_update_bits(priv->regmap, reg_info.reg, mask << reg_info.shift,
++ val << reg_info.shift);
++}
++
++static bool
++qca8k_cled_hw_control_status(struct led_classdev *ldev)
++{
++ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
++
++ struct qca8k_led_pattern_en reg_info;
++ struct qca8k_priv *priv = led->priv;
++ u32 val;
++
++ qca8k_get_enable_led_reg(led->port_num, led->led_num, &reg_info);
++
++ regmap_read(priv->regmap, reg_info.reg, &val);
++
++ val >>= reg_info.shift;
++
++ if (led->port_num == 0 || led->port_num == 4) {
++ val &= QCA8K_LED_PATTERN_EN_MASK;
++ val >>= QCA8K_LED_PATTERN_EN_SHIFT;
++ } else {
++ val &= QCA8K_LED_PHY123_PATTERN_EN_MASK;
++ }
++
++ return val == QCA8K_LED_RULE_CONTROLLED;
++}
++
++static int
++qca8k_cled_hw_control_is_supported(struct led_classdev *ldev, unsigned long rules)
++{
++ u32 offload_trigger = 0;
++
++ return qca8k_parse_netdev(rules, &offload_trigger);
++}
++
++static int
++qca8k_cled_hw_control_set(struct led_classdev *ldev, unsigned long rules)
++{
++ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
++ struct qca8k_led_pattern_en reg_info;
++ struct qca8k_priv *priv = led->priv;
++ u32 offload_trigger = 0;
++ int ret;
++
++ ret = qca8k_parse_netdev(rules, &offload_trigger);
++ if (ret)
++ return ret;
++
++ ret = qca8k_cled_trigger_offload(ldev, true);
++ if (ret)
++ return ret;
++
++ qca8k_get_control_led_reg(led->port_num, led->led_num, &reg_info);
++
++ return regmap_update_bits(priv->regmap, reg_info.reg,
++ QCA8K_LED_RULE_MASK << reg_info.shift,
++ offload_trigger << reg_info.shift);
++}
++
++static int
++qca8k_cled_hw_control_get(struct led_classdev *ldev, unsigned long *rules)
++{
++ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
++ struct qca8k_led_pattern_en reg_info;
++ struct qca8k_priv *priv = led->priv;
++ u32 val;
++ int ret;
++
++ /* With hw control not active return err */
++ if (!qca8k_cled_hw_control_status(ldev))
++ return -EINVAL;
++
++ qca8k_get_control_led_reg(led->port_num, led->led_num, &reg_info);
++
++ ret = regmap_read(priv->regmap, reg_info.reg, &val);
++ if (ret)
++ return ret;
++
++ val >>= reg_info.shift;
++ val &= QCA8K_LED_RULE_MASK;
++
++ /* Parsing specific to netdev trigger */
++ if (val & QCA8K_LED_TX_BLINK_MASK)
++ set_bit(TRIGGER_NETDEV_TX, rules);
++ if (val & QCA8K_LED_RX_BLINK_MASK)
++ set_bit(TRIGGER_NETDEV_RX, rules);
++
++ return 0;
++}
++
++static int
+ qca8k_parse_port_leds(struct qca8k_priv *priv, struct fwnode_handle *port, int port_num)
+ {
+ struct fwnode_handle *led = NULL, *leds = NULL;
+@@ -224,6 +374,10 @@ qca8k_parse_port_leds(struct qca8k_priv
+ port_led->cdev.max_brightness = 1;
+ port_led->cdev.brightness_set_blocking = qca8k_cled_brightness_set_blocking;
+ port_led->cdev.blink_set = qca8k_cled_blink_set;
++ port_led->cdev.hw_control_is_supported = qca8k_cled_hw_control_is_supported;
++ port_led->cdev.hw_control_set = qca8k_cled_hw_control_set;
++ port_led->cdev.hw_control_get = qca8k_cled_hw_control_get;
++ port_led->cdev.hw_control_trigger = "netdev";
+ init_data.default_label = ":port";
+ init_data.fwnode = led;
+ init_data.devname_mandatory = true;
diff --git a/target/linux/generic/backport-6.6/804-v6.5-13-net-dsa-qca8k-add-op-to-get-ports-netdev.patch b/target/linux/generic/backport-6.6/804-v6.5-13-net-dsa-qca8k-add-op-to-get-ports-netdev.patch
new file mode 100644
index 0000000000..feb6b9e1e4
--- /dev/null
+++ b/target/linux/generic/backport-6.6/804-v6.5-13-net-dsa-qca8k-add-op-to-get-ports-netdev.patch
@@ -0,0 +1,70 @@
+From 4f53c27f772e27e4cf4e5507d6f4d5980002cb6a Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 29 May 2023 18:32:43 +0200
+Subject: [PATCH 13/13] net: dsa: qca8k: add op to get ports netdev
+
+In order that the LED trigger can blink the switch MAC ports LED, it
+needs to know the netdev associated to the port. Add the callback to
+return the struct device of the netdev.
+
+Add an helper function qca8k_phy_to_port() to convert the phy back to
+dsa_port index, as we reference LED port based on the internal PHY
+index and needs to be converted back.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/qca8k-leds.c | 27 +++++++++++++++++++++++++++
+ 1 file changed, 27 insertions(+)
+
+--- a/drivers/net/dsa/qca/qca8k-leds.c
++++ b/drivers/net/dsa/qca/qca8k-leds.c
+@@ -5,6 +5,18 @@
+ #include "qca8k.h"
+ #include "qca8k_leds.h"
+
++static u32 qca8k_phy_to_port(int phy)
++{
++ /* Internal PHY 0 has port at index 1.
++ * Internal PHY 1 has port at index 2.
++ * Internal PHY 2 has port at index 3.
++ * Internal PHY 3 has port at index 4.
++ * Internal PHY 4 has port at index 5.
++ */
++
++ return phy + 1;
++}
++
+ static int
+ qca8k_get_enable_led_reg(int port_num, int led_num, struct qca8k_led_pattern_en *reg_info)
+ {
+@@ -314,6 +326,20 @@ qca8k_cled_hw_control_get(struct led_cla
+ return 0;
+ }
+
++static struct device *qca8k_cled_hw_control_get_device(struct led_classdev *ldev)
++{
++ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
++ struct qca8k_priv *priv = led->priv;
++ struct dsa_port *dp;
++
++ dp = dsa_to_port(priv->ds, qca8k_phy_to_port(led->port_num));
++ if (!dp)
++ return NULL;
++ if (dp->slave)
++ return &dp->slave->dev;
++ return NULL;
++}
++
+ static int
+ qca8k_parse_port_leds(struct qca8k_priv *priv, struct fwnode_handle *port, int port_num)
+ {
+@@ -377,6 +403,7 @@ qca8k_parse_port_leds(struct qca8k_priv
+ port_led->cdev.hw_control_is_supported = qca8k_cled_hw_control_is_supported;
+ port_led->cdev.hw_control_set = qca8k_cled_hw_control_set;
+ port_led->cdev.hw_control_get = qca8k_cled_hw_control_get;
++ port_led->cdev.hw_control_get_device = qca8k_cled_hw_control_get_device;
+ port_led->cdev.hw_control_trigger = "netdev";
+ init_data.default_label = ":port";
+ init_data.fwnode = led;
diff --git a/target/linux/generic/backport-6.6/805-v6.5-01-leds-trigger-netdev-add-additional-specific-link-spe.patch b/target/linux/generic/backport-6.6/805-v6.5-01-leds-trigger-netdev-add-additional-specific-link-spe.patch
new file mode 100644
index 0000000000..1c564b3897
--- /dev/null
+++ b/target/linux/generic/backport-6.6/805-v6.5-01-leds-trigger-netdev-add-additional-specific-link-spe.patch
@@ -0,0 +1,242 @@
+From d5e01266e7f5fa12400d4c8aa4e86fe89dcc61e9 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 19 Jun 2023 22:46:58 +0200
+Subject: [PATCH 1/3] leds: trigger: netdev: add additional specific link speed
+ mode
+
+Add additional modes for specific link speed. Use ethtool APIs to get the
+current link speed and enable the LED accordingly. Under netdev event
+handler the rtnl lock is already held and is not needed to be set to
+access ethtool APIs.
+
+This is especially useful for PHY and Switch that supports LEDs hw
+control for specific link speed. (example scenario a PHY that have 2 LED
+connected one green and one orange where the green is turned on with
+1000mbps speed and orange is turned on with 10mpbs speed)
+
+On mode set from sysfs we check if we have enabled split link speed mode
+and reject enabling generic link mode to prevent wrong and redundant
+configuration.
+
+Rework logic on the set baseline state to support these new modes to
+select if we need to turn on or off the LED.
+
+Add additional modes:
+- link_10: Turn on LED when link speed is 10mbps
+- link_100: Turn on LED when link speed is 100mbps
+- link_1000: Turn on LED when link speed is 1000mbps
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Acked-by: Lee Jones <lee@kernel.org>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 80 +++++++++++++++++++++++----
+ include/linux/leds.h | 3 +
+ 2 files changed, 73 insertions(+), 10 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -13,6 +13,7 @@
+ #include <linux/atomic.h>
+ #include <linux/ctype.h>
+ #include <linux/device.h>
++#include <linux/ethtool.h>
+ #include <linux/init.h>
+ #include <linux/jiffies.h>
+ #include <linux/kernel.h>
+@@ -21,6 +22,7 @@
+ #include <linux/module.h>
+ #include <linux/netdevice.h>
+ #include <linux/mutex.h>
++#include <linux/rtnetlink.h>
+ #include <linux/timer.h>
+ #include "../leds.h"
+
+@@ -52,6 +54,8 @@ struct led_netdev_data {
+ unsigned int last_activity;
+
+ unsigned long mode;
++ int link_speed;
++
+ bool carrier_link_up;
+ bool hw_control;
+ };
+@@ -77,7 +81,24 @@ static void set_baseline_state(struct le
+ if (!trigger_data->carrier_link_up) {
+ led_set_brightness(led_cdev, LED_OFF);
+ } else {
++ bool blink_on = false;
++
+ if (test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode))
++ blink_on = true;
++
++ if (test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) &&
++ trigger_data->link_speed == SPEED_10)
++ blink_on = true;
++
++ if (test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) &&
++ trigger_data->link_speed == SPEED_100)
++ blink_on = true;
++
++ if (test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) &&
++ trigger_data->link_speed == SPEED_1000)
++ blink_on = true;
++
++ if (blink_on)
+ led_set_brightness(led_cdev,
+ led_cdev->blink_brightness);
+ else
+@@ -161,6 +182,18 @@ static bool can_hw_control(struct led_ne
+ return true;
+ }
+
++static void get_device_state(struct led_netdev_data *trigger_data)
++{
++ struct ethtool_link_ksettings cmd;
++
++ trigger_data->carrier_link_up = netif_carrier_ok(trigger_data->net_dev);
++ if (!trigger_data->carrier_link_up)
++ return;
++
++ if (!__ethtool_get_link_ksettings(trigger_data->net_dev, &cmd))
++ trigger_data->link_speed = cmd.base.speed;
++}
++
+ static ssize_t device_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+ {
+@@ -196,8 +229,12 @@ static int set_device_name(struct led_ne
+ dev_get_by_name(&init_net, trigger_data->device_name);
+
+ trigger_data->carrier_link_up = false;
+- if (trigger_data->net_dev != NULL)
+- trigger_data->carrier_link_up = netif_carrier_ok(trigger_data->net_dev);
++ trigger_data->link_speed = SPEED_UNKNOWN;
++ if (trigger_data->net_dev != NULL) {
++ rtnl_lock();
++ get_device_state(trigger_data);
++ rtnl_unlock();
++ }
+
+ trigger_data->last_activity = 0;
+
+@@ -234,6 +271,9 @@ static ssize_t netdev_led_attr_show(stru
+
+ switch (attr) {
+ case TRIGGER_NETDEV_LINK:
++ case TRIGGER_NETDEV_LINK_10:
++ case TRIGGER_NETDEV_LINK_100:
++ case TRIGGER_NETDEV_LINK_1000:
+ case TRIGGER_NETDEV_TX:
+ case TRIGGER_NETDEV_RX:
+ bit = attr;
+@@ -249,7 +289,7 @@ static ssize_t netdev_led_attr_store(str
+ size_t size, enum led_trigger_netdev_modes attr)
+ {
+ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+- unsigned long state;
++ unsigned long state, mode = trigger_data->mode;
+ int ret;
+ int bit;
+
+@@ -259,6 +299,9 @@ static ssize_t netdev_led_attr_store(str
+
+ switch (attr) {
+ case TRIGGER_NETDEV_LINK:
++ case TRIGGER_NETDEV_LINK_10:
++ case TRIGGER_NETDEV_LINK_100:
++ case TRIGGER_NETDEV_LINK_1000:
+ case TRIGGER_NETDEV_TX:
+ case TRIGGER_NETDEV_RX:
+ bit = attr;
+@@ -267,13 +310,20 @@ static ssize_t netdev_led_attr_store(str
+ return -EINVAL;
+ }
+
+- cancel_delayed_work_sync(&trigger_data->work);
+-
+ if (state)
+- set_bit(bit, &trigger_data->mode);
++ set_bit(bit, &mode);
+ else
+- clear_bit(bit, &trigger_data->mode);
++ clear_bit(bit, &mode);
++
++ if (test_bit(TRIGGER_NETDEV_LINK, &mode) &&
++ (test_bit(TRIGGER_NETDEV_LINK_10, &mode) ||
++ test_bit(TRIGGER_NETDEV_LINK_100, &mode) ||
++ test_bit(TRIGGER_NETDEV_LINK_1000, &mode)))
++ return -EINVAL;
++
++ cancel_delayed_work_sync(&trigger_data->work);
+
++ trigger_data->mode = mode;
+ trigger_data->hw_control = can_hw_control(trigger_data);
+
+ set_baseline_state(trigger_data);
+@@ -295,6 +345,9 @@ static ssize_t netdev_led_attr_store(str
+ static DEVICE_ATTR_RW(trigger_name)
+
+ DEFINE_NETDEV_TRIGGER(link, TRIGGER_NETDEV_LINK);
++DEFINE_NETDEV_TRIGGER(link_10, TRIGGER_NETDEV_LINK_10);
++DEFINE_NETDEV_TRIGGER(link_100, TRIGGER_NETDEV_LINK_100);
++DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000);
+ DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX);
+ DEFINE_NETDEV_TRIGGER(rx, TRIGGER_NETDEV_RX);
+
+@@ -338,6 +391,9 @@ static DEVICE_ATTR_RW(interval);
+ static struct attribute *netdev_trig_attrs[] = {
+ &dev_attr_device_name.attr,
+ &dev_attr_link.attr,
++ &dev_attr_link_10.attr,
++ &dev_attr_link_100.attr,
++ &dev_attr_link_1000.attr,
+ &dev_attr_rx.attr,
+ &dev_attr_tx.attr,
+ &dev_attr_interval.attr,
+@@ -368,9 +424,10 @@ static int netdev_trig_notify(struct not
+ mutex_lock(&trigger_data->lock);
+
+ trigger_data->carrier_link_up = false;
++ trigger_data->link_speed = SPEED_UNKNOWN;
+ switch (evt) {
+ case NETDEV_CHANGENAME:
+- trigger_data->carrier_link_up = netif_carrier_ok(dev);
++ get_device_state(trigger_data);
+ fallthrough;
+ case NETDEV_REGISTER:
+ if (trigger_data->net_dev)
+@@ -384,7 +441,7 @@ static int netdev_trig_notify(struct not
+ break;
+ case NETDEV_UP:
+ case NETDEV_CHANGE:
+- trigger_data->carrier_link_up = netif_carrier_ok(dev);
++ get_device_state(trigger_data);
+ break;
+ }
+
+@@ -427,7 +484,10 @@ static void netdev_trig_work(struct work
+ if (trigger_data->last_activity != new_activity) {
+ led_stop_software_blink(trigger_data->led_cdev);
+
+- invert = test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode);
++ invert = test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode) ||
++ test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) ||
++ test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) ||
++ test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode);
+ interval = jiffies_to_msecs(
+ atomic_read(&trigger_data->interval));
+ /* base state is ON (link present) */
+--- a/include/linux/leds.h
++++ b/include/linux/leds.h
+@@ -530,6 +530,9 @@ static inline void *led_get_trigger_data
+ /* Trigger specific enum */
+ enum led_trigger_netdev_modes {
+ TRIGGER_NETDEV_LINK = 0,
++ TRIGGER_NETDEV_LINK_10,
++ TRIGGER_NETDEV_LINK_100,
++ TRIGGER_NETDEV_LINK_1000,
+ TRIGGER_NETDEV_TX,
+ TRIGGER_NETDEV_RX,
+
diff --git a/target/linux/generic/backport-6.6/805-v6.5-02-leds-trigger-netdev-add-additional-specific-link-dup.patch b/target/linux/generic/backport-6.6/805-v6.5-02-leds-trigger-netdev-add-additional-specific-link-dup.patch
new file mode 100644
index 0000000000..a5ab461828
--- /dev/null
+++ b/target/linux/generic/backport-6.6/805-v6.5-02-leds-trigger-netdev-add-additional-specific-link-dup.patch
@@ -0,0 +1,138 @@
+From f22f95b9ff1551c9bab13104131929f33d51f23f Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 19 Jun 2023 22:46:59 +0200
+Subject: [PATCH 2/3] leds: trigger: netdev: add additional specific link
+ duplex mode
+
+Add additional modes for specific link duplex. Use ethtool APIs to get the
+current link duplex and enable the LED accordingly. Under netdev event
+handler the rtnl lock is already held and is not needed to be set to
+access ethtool APIs.
+
+This is especially useful for PHY and Switch that supports LEDs hw
+control for specific link duplex.
+
+Add additional modes:
+- half_duplex: Turn on LED when link is half duplex
+- full_duplex: Turn on LED when link is full duplex
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Acked-by: Lee Jones <lee@kernel.org>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 27 +++++++++++++++++++++++++--
+ include/linux/leds.h | 2 ++
+ 2 files changed, 27 insertions(+), 2 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -55,6 +55,7 @@ struct led_netdev_data {
+
+ unsigned long mode;
+ int link_speed;
++ u8 duplex;
+
+ bool carrier_link_up;
+ bool hw_control;
+@@ -98,6 +99,14 @@ static void set_baseline_state(struct le
+ trigger_data->link_speed == SPEED_1000)
+ blink_on = true;
+
++ if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) &&
++ trigger_data->duplex == DUPLEX_HALF)
++ blink_on = true;
++
++ if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode) &&
++ trigger_data->duplex == DUPLEX_FULL)
++ blink_on = true;
++
+ if (blink_on)
+ led_set_brightness(led_cdev,
+ led_cdev->blink_brightness);
+@@ -190,8 +199,10 @@ static void get_device_state(struct led_
+ if (!trigger_data->carrier_link_up)
+ return;
+
+- if (!__ethtool_get_link_ksettings(trigger_data->net_dev, &cmd))
++ if (!__ethtool_get_link_ksettings(trigger_data->net_dev, &cmd)) {
+ trigger_data->link_speed = cmd.base.speed;
++ trigger_data->duplex = cmd.base.duplex;
++ }
+ }
+
+ static ssize_t device_name_show(struct device *dev,
+@@ -230,6 +241,7 @@ static int set_device_name(struct led_ne
+
+ trigger_data->carrier_link_up = false;
+ trigger_data->link_speed = SPEED_UNKNOWN;
++ trigger_data->duplex = DUPLEX_UNKNOWN;
+ if (trigger_data->net_dev != NULL) {
+ rtnl_lock();
+ get_device_state(trigger_data);
+@@ -274,6 +286,8 @@ static ssize_t netdev_led_attr_show(stru
+ case TRIGGER_NETDEV_LINK_10:
+ case TRIGGER_NETDEV_LINK_100:
+ case TRIGGER_NETDEV_LINK_1000:
++ case TRIGGER_NETDEV_HALF_DUPLEX:
++ case TRIGGER_NETDEV_FULL_DUPLEX:
+ case TRIGGER_NETDEV_TX:
+ case TRIGGER_NETDEV_RX:
+ bit = attr;
+@@ -302,6 +316,8 @@ static ssize_t netdev_led_attr_store(str
+ case TRIGGER_NETDEV_LINK_10:
+ case TRIGGER_NETDEV_LINK_100:
+ case TRIGGER_NETDEV_LINK_1000:
++ case TRIGGER_NETDEV_HALF_DUPLEX:
++ case TRIGGER_NETDEV_FULL_DUPLEX:
+ case TRIGGER_NETDEV_TX:
+ case TRIGGER_NETDEV_RX:
+ bit = attr;
+@@ -348,6 +364,8 @@ DEFINE_NETDEV_TRIGGER(link, TRIGGER_NETD
+ DEFINE_NETDEV_TRIGGER(link_10, TRIGGER_NETDEV_LINK_10);
+ DEFINE_NETDEV_TRIGGER(link_100, TRIGGER_NETDEV_LINK_100);
+ DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000);
++DEFINE_NETDEV_TRIGGER(half_duplex, TRIGGER_NETDEV_HALF_DUPLEX);
++DEFINE_NETDEV_TRIGGER(full_duplex, TRIGGER_NETDEV_FULL_DUPLEX);
+ DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX);
+ DEFINE_NETDEV_TRIGGER(rx, TRIGGER_NETDEV_RX);
+
+@@ -394,6 +412,8 @@ static struct attribute *netdev_trig_att
+ &dev_attr_link_10.attr,
+ &dev_attr_link_100.attr,
+ &dev_attr_link_1000.attr,
++ &dev_attr_full_duplex.attr,
++ &dev_attr_half_duplex.attr,
+ &dev_attr_rx.attr,
+ &dev_attr_tx.attr,
+ &dev_attr_interval.attr,
+@@ -425,6 +445,7 @@ static int netdev_trig_notify(struct not
+
+ trigger_data->carrier_link_up = false;
+ trigger_data->link_speed = SPEED_UNKNOWN;
++ trigger_data->duplex = DUPLEX_UNKNOWN;
+ switch (evt) {
+ case NETDEV_CHANGENAME:
+ get_device_state(trigger_data);
+@@ -487,7 +508,9 @@ static void netdev_trig_work(struct work
+ invert = test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) ||
+- test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode);
++ test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) ||
++ test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) ||
++ test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode);
+ interval = jiffies_to_msecs(
+ atomic_read(&trigger_data->interval));
+ /* base state is ON (link present) */
+--- a/include/linux/leds.h
++++ b/include/linux/leds.h
+@@ -533,6 +533,8 @@ enum led_trigger_netdev_modes {
+ TRIGGER_NETDEV_LINK_10,
+ TRIGGER_NETDEV_LINK_100,
+ TRIGGER_NETDEV_LINK_1000,
++ TRIGGER_NETDEV_HALF_DUPLEX,
++ TRIGGER_NETDEV_FULL_DUPLEX,
+ TRIGGER_NETDEV_TX,
+ TRIGGER_NETDEV_RX,
+
diff --git a/target/linux/generic/backport-6.6/805-v6.5-03-leds-trigger-netdev-expose-hw_control-status-via-sys.patch b/target/linux/generic/backport-6.6/805-v6.5-03-leds-trigger-netdev-expose-hw_control-status-via-sys.patch
new file mode 100644
index 0000000000..67528cafe0
--- /dev/null
+++ b/target/linux/generic/backport-6.6/805-v6.5-03-leds-trigger-netdev-expose-hw_control-status-via-sys.patch
@@ -0,0 +1,45 @@
+From b655892ffd6d89b0c7407e099c40dbde82ee3f03 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 19 Jun 2023 22:47:00 +0200
+Subject: [PATCH 3/3] leds: trigger: netdev: expose hw_control status via sysfs
+
+Expose hw_control status via sysfs for the netdev trigger to give
+userspace better understanding of the current state of the trigger and
+the LED.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Kalesh AP <kalesh-anakkur.purayil@broadcom.com>
+Acked-by: Lee Jones <lee@kernel.org>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 11 +++++++++++
+ 1 file changed, 11 insertions(+)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -406,6 +406,16 @@ static ssize_t interval_store(struct dev
+
+ static DEVICE_ATTR_RW(interval);
+
++static ssize_t hw_control_show(struct device *dev,
++ struct device_attribute *attr, char *buf)
++{
++ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
++
++ return sprintf(buf, "%d\n", trigger_data->hw_control);
++}
++
++static DEVICE_ATTR_RO(hw_control);
++
+ static struct attribute *netdev_trig_attrs[] = {
+ &dev_attr_device_name.attr,
+ &dev_attr_link.attr,
+@@ -417,6 +427,7 @@ static struct attribute *netdev_trig_att
+ &dev_attr_rx.attr,
+ &dev_attr_tx.attr,
+ &dev_attr_interval.attr,
++ &dev_attr_hw_control.attr,
+ NULL
+ };
+ ATTRIBUTE_GROUPS(netdev_trig);
diff --git a/target/linux/generic/backport-6.6/806-v6.5-net-dsa-qca8k-add-support-for-additional-modes-for-n.patch b/target/linux/generic/backport-6.6/806-v6.5-net-dsa-qca8k-add-support-for-additional-modes-for-n.patch
new file mode 100644
index 0000000000..ac322e1939
--- /dev/null
+++ b/target/linux/generic/backport-6.6/806-v6.5-net-dsa-qca8k-add-support-for-additional-modes-for-n.patch
@@ -0,0 +1,64 @@
+From 2555f35a4f428a9bfdf09aa0459dbfdf59a24a9a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 21 Jun 2023 11:54:09 +0200
+Subject: [PATCH] net: dsa: qca8k: add support for additional modes for netdev
+ trigger
+
+The QCA8K switch supports additional modes that can be handled in
+hardware for the LED netdev trigger.
+
+Add these additional modes to further support the Switch LEDs and
+offload more blink modes.
+
+Add additional modes:
+- link_10
+- link_100
+- link_1000
+- half_duplex
+- full_duplex
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://lore.kernel.org/r/20230621095409.25859-1-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/qca/qca8k-leds.c | 20 ++++++++++++++++++++
+ 1 file changed, 20 insertions(+)
+
+--- a/drivers/net/dsa/qca/qca8k-leds.c
++++ b/drivers/net/dsa/qca/qca8k-leds.c
+@@ -68,6 +68,16 @@ qca8k_parse_netdev(unsigned long rules,
+ *offload_trigger |= QCA8K_LED_TX_BLINK_MASK;
+ if (test_bit(TRIGGER_NETDEV_RX, &rules))
+ *offload_trigger |= QCA8K_LED_RX_BLINK_MASK;
++ if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
++ *offload_trigger |= QCA8K_LED_LINK_10M_EN_MASK;
++ if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
++ *offload_trigger |= QCA8K_LED_LINK_100M_EN_MASK;
++ if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
++ *offload_trigger |= QCA8K_LED_LINK_1000M_EN_MASK;
++ if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
++ *offload_trigger |= QCA8K_LED_HALF_DUPLEX_MASK;
++ if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
++ *offload_trigger |= QCA8K_LED_FULL_DUPLEX_MASK;
+
+ if (rules && !*offload_trigger)
+ return -EOPNOTSUPP;
+@@ -322,6 +332,16 @@ qca8k_cled_hw_control_get(struct led_cla
+ set_bit(TRIGGER_NETDEV_TX, rules);
+ if (val & QCA8K_LED_RX_BLINK_MASK)
+ set_bit(TRIGGER_NETDEV_RX, rules);
++ if (val & QCA8K_LED_LINK_10M_EN_MASK)
++ set_bit(TRIGGER_NETDEV_LINK_10, rules);
++ if (val & QCA8K_LED_LINK_100M_EN_MASK)
++ set_bit(TRIGGER_NETDEV_LINK_100, rules);
++ if (val & QCA8K_LED_LINK_1000M_EN_MASK)
++ set_bit(TRIGGER_NETDEV_LINK_1000, rules);
++ if (val & QCA8K_LED_HALF_DUPLEX_MASK)
++ set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
++ if (val & QCA8K_LED_FULL_DUPLEX_MASK)
++ set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
+
+ return 0;
+ }
diff --git a/target/linux/generic/backport-6.6/807-v6.5-01-net-dsa-mv88e6xxx-pass-directly-chip-structure-to-mv.patch b/target/linux/generic/backport-6.6/807-v6.5-01-net-dsa-mv88e6xxx-pass-directly-chip-structure-to-mv.patch
new file mode 100644
index 0000000000..58777cd280
--- /dev/null
+++ b/target/linux/generic/backport-6.6/807-v6.5-01-net-dsa-mv88e6xxx-pass-directly-chip-structure-to-mv.patch
@@ -0,0 +1,64 @@
+From 4f86eb098e18fd0f032877dfa1a7e8c1503ca409 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Alexis=20Lothor=C3=A9?= <alexis.lothore@bootlin.com>
+Date: Mon, 29 May 2023 10:02:41 +0200
+Subject: [PATCH 1/6] net: dsa: mv88e6xxx: pass directly chip structure to
+ mv88e6xxx_phy_is_internal
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Since this function is a simple helper, we do not need to pass a full
+dsa_switch structure, we can directly pass the mv88e6xxx_chip structure.
+Doing so will allow to share this function with any other function
+not manipulating dsa_switch structure but needing info about number of
+internal phys
+
+Signed-off-by: Alexis Lothoré <alexis.lothore@bootlin.com>
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/mv88e6xxx/chip.c | 10 ++++------
+ 1 file changed, 4 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/dsa/mv88e6xxx/chip.c
++++ b/drivers/net/dsa/mv88e6xxx/chip.c
+@@ -470,10 +470,8 @@ restore_link:
+ return err;
+ }
+
+-static int mv88e6xxx_phy_is_internal(struct dsa_switch *ds, int port)
++static int mv88e6xxx_phy_is_internal(struct mv88e6xxx_chip *chip, int port)
+ {
+- struct mv88e6xxx_chip *chip = ds->priv;
+-
+ return port < chip->info->num_internal_phys;
+ }
+
+@@ -591,7 +589,7 @@ static void mv88e6095_phylink_get_caps(s
+
+ config->mac_capabilities = MAC_SYM_PAUSE | MAC_10 | MAC_100;
+
+- if (mv88e6xxx_phy_is_internal(chip->ds, port)) {
++ if (mv88e6xxx_phy_is_internal(chip, port)) {
+ __set_bit(PHY_INTERFACE_MODE_MII, config->supported_interfaces);
+ } else {
+ if (cmode < ARRAY_SIZE(mv88e6185_phy_interface_modes) &&
+@@ -839,7 +837,7 @@ static void mv88e6xxx_get_caps(struct ds
+ chip->info->ops->phylink_get_caps(chip, port, config);
+ mv88e6xxx_reg_unlock(chip);
+
+- if (mv88e6xxx_phy_is_internal(ds, port)) {
++ if (mv88e6xxx_phy_is_internal(chip, port)) {
+ __set_bit(PHY_INTERFACE_MODE_INTERNAL,
+ config->supported_interfaces);
+ /* Internal ports with no phy-mode need GMII for PHYLIB */
+@@ -860,7 +858,7 @@ static void mv88e6xxx_mac_config(struct
+
+ mv88e6xxx_reg_lock(chip);
+
+- if (mode != MLO_AN_PHY || !mv88e6xxx_phy_is_internal(ds, port)) {
++ if (mode != MLO_AN_PHY || !mv88e6xxx_phy_is_internal(chip, port)) {
+ /* In inband mode, the link may come up at any time while the
+ * link is not forced down. Force the link down while we
+ * reconfigure the interface mode.
diff --git a/target/linux/generic/backport-6.6/807-v6.5-02-net-dsa-mv88e6xxx-use-mv88e6xxx_phy_is_internal-in-m.patch b/target/linux/generic/backport-6.6/807-v6.5-02-net-dsa-mv88e6xxx-use-mv88e6xxx_phy_is_internal-in-m.patch
new file mode 100644
index 0000000000..75a21a931d
--- /dev/null
+++ b/target/linux/generic/backport-6.6/807-v6.5-02-net-dsa-mv88e6xxx-use-mv88e6xxx_phy_is_internal-in-m.patch
@@ -0,0 +1,31 @@
+From 73cbfad9296eed004992806e056db5b48583ca41 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Alexis=20Lothor=C3=A9?= <alexis.lothore@bootlin.com>
+Date: Mon, 29 May 2023 10:02:42 +0200
+Subject: [PATCH 2/6] net: dsa: mv88e6xxx: use mv88e6xxx_phy_is_internal in
+ mv88e6xxx_port_ppu_updates
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Make sure to use existing helper to get internal PHYs count instead of
+redoing it manually
+
+Signed-off-by: Alexis Lothoré <alexis.lothore@bootlin.com>
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/mv88e6xxx/chip.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/dsa/mv88e6xxx/chip.c
++++ b/drivers/net/dsa/mv88e6xxx/chip.c
+@@ -484,7 +484,7 @@ static int mv88e6xxx_port_ppu_updates(st
+ * report whether the port is internal.
+ */
+ if (chip->info->family == MV88E6XXX_FAMILY_6250)
+- return port < chip->info->num_internal_phys;
++ return mv88e6xxx_phy_is_internal(chip, port);
+
+ err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_STS, &reg);
+ if (err) {
diff --git a/target/linux/generic/backport-6.6/807-v6.5-03-net-dsa-mv88e6xxx-add-field-to-specify-internal-phys.patch b/target/linux/generic/backport-6.6/807-v6.5-03-net-dsa-mv88e6xxx-add-field-to-specify-internal-phys.patch
new file mode 100644
index 0000000000..e24dca819b
--- /dev/null
+++ b/target/linux/generic/backport-6.6/807-v6.5-03-net-dsa-mv88e6xxx-add-field-to-specify-internal-phys.patch
@@ -0,0 +1,69 @@
+From 1414d30660d201f515a9d877571ceea9ca190b6a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Alexis=20Lothor=C3=A9?= <alexis.lothore@bootlin.com>
+Date: Mon, 29 May 2023 10:02:43 +0200
+Subject: [PATCH 3/6] net: dsa: mv88e6xxx: add field to specify internal phys
+ layout
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+mv88e6xxx currently assumes that switch equipped with internal phys have
+those phys mapped contiguously starting from port 0 (see
+mv88e6xxx_phy_is_internal). However, some switches have internal PHYs but
+NOT starting from port 0. For example 88e6393X, 88E6193X and 88E6191X have
+integrated PHYs available on ports 1 to 8
+To properly support this offset, add a new field to allow specifying an
+internal PHYs layout. If field is not set, default layout is assumed (start
+at port 0)
+
+Signed-off-by: Alexis Lothoré <alexis.lothore@bootlin.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/mv88e6xxx/chip.c | 4 +++-
+ drivers/net/dsa/mv88e6xxx/chip.h | 5 +++++
+ drivers/net/dsa/mv88e6xxx/global2.c | 5 ++++-
+ 3 files changed, 12 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/dsa/mv88e6xxx/chip.c
++++ b/drivers/net/dsa/mv88e6xxx/chip.c
+@@ -472,7 +472,9 @@ restore_link:
+
+ static int mv88e6xxx_phy_is_internal(struct mv88e6xxx_chip *chip, int port)
+ {
+- return port < chip->info->num_internal_phys;
++ return port >= chip->info->internal_phys_offset &&
++ port < chip->info->num_internal_phys +
++ chip->info->internal_phys_offset;
+ }
+
+ static int mv88e6xxx_port_ppu_updates(struct mv88e6xxx_chip *chip, int port)
+--- a/drivers/net/dsa/mv88e6xxx/chip.h
++++ b/drivers/net/dsa/mv88e6xxx/chip.h
+@@ -167,6 +167,11 @@ struct mv88e6xxx_info {
+
+ /* Supports PTP */
+ bool ptp_support;
++
++ /* Internal PHY start index. 0 means that internal PHYs range starts at
++ * port 0, 1 means internal PHYs range starts at port 1, etc
++ */
++ unsigned int internal_phys_offset;
+ };
+
+ struct mv88e6xxx_atu_entry {
+--- a/drivers/net/dsa/mv88e6xxx/global2.c
++++ b/drivers/net/dsa/mv88e6xxx/global2.c
+@@ -1185,8 +1185,11 @@ int mv88e6xxx_g2_irq_mdio_setup(struct m
+ struct mii_bus *bus)
+ {
+ int phy, irq, err, err_phy;
++ int phy_start = chip->info->internal_phys_offset;
++ int phy_end = chip->info->internal_phys_offset +
++ chip->info->num_internal_phys;
+
+- for (phy = 0; phy < chip->info->num_internal_phys; phy++) {
++ for (phy = phy_start; phy < phy_end; phy++) {
+ irq = irq_find_mapping(chip->g2_irq.domain, phy);
+ if (irq < 0) {
+ err = irq;
diff --git a/target/linux/generic/backport-6.6/807-v6.5-04-net-dsa-mv88e6xxx-fix-88E6393X-family-internal-phys-.patch b/target/linux/generic/backport-6.6/807-v6.5-04-net-dsa-mv88e6xxx-fix-88E6393X-family-internal-phys-.patch
new file mode 100644
index 0000000000..12ea3ebda0
--- /dev/null
+++ b/target/linux/generic/backport-6.6/807-v6.5-04-net-dsa-mv88e6xxx-fix-88E6393X-family-internal-phys-.patch
@@ -0,0 +1,52 @@
+From eb8c75f82a6711387f3b9e03e28923f3e75a761b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Alexis=20Lothor=C3=A9?= <alexis.lothore@bootlin.com>
+Date: Mon, 29 May 2023 10:02:44 +0200
+Subject: [PATCH 4/6] net: dsa: mv88e6xxx: fix 88E6393X family internal phys
+ layout
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+88E6393X/88E6193X/88E6191X switches have in fact 8 internal PHYs, but those
+are not present starting at port 0: supported ports go from 1 to 8
+
+Signed-off-by: Alexis Lothoré <alexis.lothore@bootlin.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/mv88e6xxx/chip.c | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/dsa/mv88e6xxx/chip.c
++++ b/drivers/net/dsa/mv88e6xxx/chip.c
+@@ -5944,7 +5944,8 @@ static const struct mv88e6xxx_info mv88e
+ .name = "Marvell 88E6191X",
+ .num_databases = 4096,
+ .num_ports = 11, /* 10 + Z80 */
+- .num_internal_phys = 9,
++ .num_internal_phys = 8,
++ .internal_phys_offset = 1,
+ .max_vid = 8191,
+ .max_sid = 63,
+ .port_base_addr = 0x0,
+@@ -5967,7 +5968,8 @@ static const struct mv88e6xxx_info mv88e
+ .name = "Marvell 88E6193X",
+ .num_databases = 4096,
+ .num_ports = 11, /* 10 + Z80 */
+- .num_internal_phys = 9,
++ .num_internal_phys = 8,
++ .internal_phys_offset = 1,
+ .max_vid = 8191,
+ .max_sid = 63,
+ .port_base_addr = 0x0,
+@@ -6286,7 +6288,8 @@ static const struct mv88e6xxx_info mv88e
+ .name = "Marvell 88E6393X",
+ .num_databases = 4096,
+ .num_ports = 11, /* 10 + Z80 */
+- .num_internal_phys = 9,
++ .num_internal_phys = 8,
++ .internal_phys_offset = 1,
+ .max_vid = 8191,
+ .max_sid = 63,
+ .port_base_addr = 0x0,
diff --git a/target/linux/generic/backport-6.6/807-v6.5-05-net-dsa-mv88e6xxx-pass-mv88e6xxx_chip-structure-to-p.patch b/target/linux/generic/backport-6.6/807-v6.5-05-net-dsa-mv88e6xxx-pass-mv88e6xxx_chip-structure-to-p.patch
new file mode 100644
index 0000000000..72dfcee82c
--- /dev/null
+++ b/target/linux/generic/backport-6.6/807-v6.5-05-net-dsa-mv88e6xxx-pass-mv88e6xxx_chip-structure-to-p.patch
@@ -0,0 +1,110 @@
+From cef945452c8468efce75ba0dc8420510a5b84af9 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Alexis=20Lothor=C3=A9?= <alexis.lothore@bootlin.com>
+Date: Mon, 29 May 2023 10:02:45 +0200
+Subject: [PATCH 5/6] net: dsa: mv88e6xxx: pass mv88e6xxx_chip structure to
+ port_max_speed_mode
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Some switches families have minor differences on supported link speed for
+ports. Instead of redefining a new port_max_speed_mode for each different
+configuration, allow to pass mv88e6xxx_chip structure to allow
+differentiating those chips by known chip id
+
+Signed-off-by: Alexis Lothoré <alexis.lothore@bootlin.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/mv88e6xxx/chip.c | 2 +-
+ drivers/net/dsa/mv88e6xxx/chip.h | 3 ++-
+ drivers/net/dsa/mv88e6xxx/port.c | 12 ++++++++----
+ drivers/net/dsa/mv88e6xxx/port.h | 12 ++++++++----
+ 4 files changed, 19 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/dsa/mv88e6xxx/chip.c
++++ b/drivers/net/dsa/mv88e6xxx/chip.c
+@@ -3328,7 +3328,7 @@ static int mv88e6xxx_setup_port(struct m
+ caps = pl_config.mac_capabilities;
+
+ if (chip->info->ops->port_max_speed_mode)
+- mode = chip->info->ops->port_max_speed_mode(port);
++ mode = chip->info->ops->port_max_speed_mode(chip, port);
+ else
+ mode = PHY_INTERFACE_MODE_NA;
+
+--- a/drivers/net/dsa/mv88e6xxx/chip.h
++++ b/drivers/net/dsa/mv88e6xxx/chip.h
+@@ -508,7 +508,8 @@ struct mv88e6xxx_ops {
+ int speed, int duplex);
+
+ /* What interface mode should be used for maximum speed? */
+- phy_interface_t (*port_max_speed_mode)(int port);
++ phy_interface_t (*port_max_speed_mode)(struct mv88e6xxx_chip *chip,
++ int port);
+
+ int (*port_tag_remap)(struct mv88e6xxx_chip *chip, int port);
+
+--- a/drivers/net/dsa/mv88e6xxx/port.c
++++ b/drivers/net/dsa/mv88e6xxx/port.c
+@@ -342,7 +342,8 @@ int mv88e6341_port_set_speed_duplex(stru
+ duplex);
+ }
+
+-phy_interface_t mv88e6341_port_max_speed_mode(int port)
++phy_interface_t mv88e6341_port_max_speed_mode(struct mv88e6xxx_chip *chip,
++ int port)
+ {
+ if (port == 5)
+ return PHY_INTERFACE_MODE_2500BASEX;
+@@ -381,7 +382,8 @@ int mv88e6390_port_set_speed_duplex(stru
+ duplex);
+ }
+
+-phy_interface_t mv88e6390_port_max_speed_mode(int port)
++phy_interface_t mv88e6390_port_max_speed_mode(struct mv88e6xxx_chip *chip,
++ int port)
+ {
+ if (port == 9 || port == 10)
+ return PHY_INTERFACE_MODE_2500BASEX;
+@@ -403,7 +405,8 @@ int mv88e6390x_port_set_speed_duplex(str
+ duplex);
+ }
+
+-phy_interface_t mv88e6390x_port_max_speed_mode(int port)
++phy_interface_t mv88e6390x_port_max_speed_mode(struct mv88e6xxx_chip *chip,
++ int port)
+ {
+ if (port == 9 || port == 10)
+ return PHY_INTERFACE_MODE_XAUI;
+@@ -500,7 +503,8 @@ int mv88e6393x_port_set_speed_duplex(str
+ return 0;
+ }
+
+-phy_interface_t mv88e6393x_port_max_speed_mode(int port)
++phy_interface_t mv88e6393x_port_max_speed_mode(struct mv88e6xxx_chip *chip,
++ int port)
+ {
+ if (port == 0 || port == 9 || port == 10)
+ return PHY_INTERFACE_MODE_10GBASER;
+--- a/drivers/net/dsa/mv88e6xxx/port.h
++++ b/drivers/net/dsa/mv88e6xxx/port.h
+@@ -359,10 +359,14 @@ int mv88e6390x_port_set_speed_duplex(str
+ int mv88e6393x_port_set_speed_duplex(struct mv88e6xxx_chip *chip, int port,
+ int speed, int duplex);
+
+-phy_interface_t mv88e6341_port_max_speed_mode(int port);
+-phy_interface_t mv88e6390_port_max_speed_mode(int port);
+-phy_interface_t mv88e6390x_port_max_speed_mode(int port);
+-phy_interface_t mv88e6393x_port_max_speed_mode(int port);
++phy_interface_t mv88e6341_port_max_speed_mode(struct mv88e6xxx_chip *chip,
++ int port);
++phy_interface_t mv88e6390_port_max_speed_mode(struct mv88e6xxx_chip *chip,
++ int port);
++phy_interface_t mv88e6390x_port_max_speed_mode(struct mv88e6xxx_chip *chip,
++ int port);
++phy_interface_t mv88e6393x_port_max_speed_mode(struct mv88e6xxx_chip *chip,
++ int port);
+
+ int mv88e6xxx_port_set_state(struct mv88e6xxx_chip *chip, int port, u8 state);
+
diff --git a/target/linux/generic/backport-6.6/807-v6.5-06-net-dsa-mv88e6xxx-enable-support-for-88E6361-switch.patch b/target/linux/generic/backport-6.6/807-v6.5-06-net-dsa-mv88e6xxx-enable-support-for-88E6361-switch.patch
new file mode 100644
index 0000000000..dc6d5497f2
--- /dev/null
+++ b/target/linux/generic/backport-6.6/807-v6.5-06-net-dsa-mv88e6xxx-enable-support-for-88E6361-switch.patch
@@ -0,0 +1,153 @@
+From 23680321789863bab2d60af507858ce50ff9f56a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Alexis=20Lothor=C3=A9?= <alexis.lothore@bootlin.com>
+Date: Mon, 29 May 2023 10:02:46 +0200
+Subject: [PATCH 6/6] net: dsa: mv88e6xxx: enable support for 88E6361 switch
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Marvell 88E6361 is an 8-port switch derived from the
+88E6393X/88E9193X/88E6191X switches family. It can benefit from the
+existing mv88e6xxx driver by simply adding the proper switch description in
+the driver. Main differences with other switches from this
+family are:
+- 8 ports exposed (instead of 11): ports 1, 2 and 8 not available
+- No 5GBase-x nor SFI/USXGMII support
+
+Signed-off-by: Alexis Lothoré <alexis.lothore@bootlin.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/mv88e6xxx/chip.c | 42 ++++++++++++++++++++++++++++----
+ drivers/net/dsa/mv88e6xxx/chip.h | 3 ++-
+ drivers/net/dsa/mv88e6xxx/port.c | 14 ++++++++---
+ drivers/net/dsa/mv88e6xxx/port.h | 1 +
+ 4 files changed, 51 insertions(+), 9 deletions(-)
+
+--- a/drivers/net/dsa/mv88e6xxx/chip.c
++++ b/drivers/net/dsa/mv88e6xxx/chip.c
+@@ -797,6 +797,8 @@ static void mv88e6393x_phylink_get_caps(
+ unsigned long *supported = config->supported_interfaces;
+ bool is_6191x =
+ chip->info->prod_num == MV88E6XXX_PORT_SWITCH_ID_PROD_6191X;
++ bool is_6361 =
++ chip->info->prod_num == MV88E6XXX_PORT_SWITCH_ID_PROD_6361;
+
+ mv88e6xxx_translate_cmode(chip->ports[port].cmode, supported);
+
+@@ -811,13 +813,17 @@ static void mv88e6393x_phylink_get_caps(
+ /* 6191X supports >1G modes only on port 10 */
+ if (!is_6191x || port == 10) {
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX, supported);
+- __set_bit(PHY_INTERFACE_MODE_5GBASER, supported);
+- __set_bit(PHY_INTERFACE_MODE_10GBASER, supported);
++ config->mac_capabilities |= MAC_2500FD;
++
++ /* 6361 only supports up to 2500BaseX */
++ if (!is_6361) {
++ __set_bit(PHY_INTERFACE_MODE_5GBASER, supported);
++ __set_bit(PHY_INTERFACE_MODE_10GBASER, supported);
++ config->mac_capabilities |= MAC_5000FD |
++ MAC_10000FD;
++ }
+ /* FIXME: USXGMII is not supported yet */
+ /* __set_bit(PHY_INTERFACE_MODE_USXGMII, supported); */
+-
+- config->mac_capabilities |= MAC_2500FD | MAC_5000FD |
+- MAC_10000FD;
+ }
+ }
+
+@@ -6231,6 +6237,32 @@ static const struct mv88e6xxx_info mv88e
+ .ptp_support = true,
+ .ops = &mv88e6352_ops,
+ },
++ [MV88E6361] = {
++ .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6361,
++ .family = MV88E6XXX_FAMILY_6393,
++ .name = "Marvell 88E6361",
++ .num_databases = 4096,
++ .num_macs = 16384,
++ .num_ports = 11,
++ /* Ports 1, 2 and 8 are not routed */
++ .invalid_port_mask = BIT(1) | BIT(2) | BIT(8),
++ .num_internal_phys = 5,
++ .internal_phys_offset = 3,
++ .max_vid = 4095,
++ .max_sid = 63,
++ .port_base_addr = 0x0,
++ .phy_base_addr = 0x0,
++ .global1_addr = 0x1b,
++ .global2_addr = 0x1c,
++ .age_time_coeff = 3750,
++ .g1_irqs = 10,
++ .g2_irqs = 14,
++ .atu_move_port_mask = 0x1f,
++ .pvt = true,
++ .multi_chip = true,
++ .ptp_support = true,
++ .ops = &mv88e6393x_ops,
++ },
+ [MV88E6390] = {
+ .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6390,
+ .family = MV88E6XXX_FAMILY_6390,
+--- a/drivers/net/dsa/mv88e6xxx/chip.h
++++ b/drivers/net/dsa/mv88e6xxx/chip.h
+@@ -82,6 +82,7 @@ enum mv88e6xxx_model {
+ MV88E6350,
+ MV88E6351,
+ MV88E6352,
++ MV88E6361,
+ MV88E6390,
+ MV88E6390X,
+ MV88E6393X,
+@@ -100,7 +101,7 @@ enum mv88e6xxx_family {
+ MV88E6XXX_FAMILY_6351, /* 6171 6175 6350 6351 */
+ MV88E6XXX_FAMILY_6352, /* 6172 6176 6240 6352 */
+ MV88E6XXX_FAMILY_6390, /* 6190 6190X 6191 6290 6390 6390X */
+- MV88E6XXX_FAMILY_6393, /* 6191X 6193X 6393X */
++ MV88E6XXX_FAMILY_6393, /* 6191X 6193X 6361 6393X */
+ };
+
+ /**
+--- a/drivers/net/dsa/mv88e6xxx/port.c
++++ b/drivers/net/dsa/mv88e6xxx/port.c
+@@ -424,6 +424,10 @@ int mv88e6393x_port_set_speed_duplex(str
+ u16 reg, ctrl;
+ int err;
+
++ if (chip->info->prod_num == MV88E6XXX_PORT_SWITCH_ID_PROD_6361 &&
++ speed > 2500)
++ return -EOPNOTSUPP;
++
+ if (speed == 200 && port != 0)
+ return -EOPNOTSUPP;
+
+@@ -506,10 +510,14 @@ int mv88e6393x_port_set_speed_duplex(str
+ phy_interface_t mv88e6393x_port_max_speed_mode(struct mv88e6xxx_chip *chip,
+ int port)
+ {
+- if (port == 0 || port == 9 || port == 10)
+- return PHY_INTERFACE_MODE_10GBASER;
+
+- return PHY_INTERFACE_MODE_NA;
++ if (port != 0 && port != 9 && port != 10)
++ return PHY_INTERFACE_MODE_NA;
++
++ if (chip->info->prod_num == MV88E6XXX_PORT_SWITCH_ID_PROD_6361)
++ return PHY_INTERFACE_MODE_2500BASEX;
++
++ return PHY_INTERFACE_MODE_10GBASER;
+ }
+
+ static int mv88e6xxx_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
+--- a/drivers/net/dsa/mv88e6xxx/port.h
++++ b/drivers/net/dsa/mv88e6xxx/port.h
+@@ -133,6 +133,7 @@
+ #define MV88E6XXX_PORT_SWITCH_ID_PROD_6220 0x2200
+ #define MV88E6XXX_PORT_SWITCH_ID_PROD_6240 0x2400
+ #define MV88E6XXX_PORT_SWITCH_ID_PROD_6250 0x2500
++#define MV88E6XXX_PORT_SWITCH_ID_PROD_6361 0x2610
+ #define MV88E6XXX_PORT_SWITCH_ID_PROD_6290 0x2900
+ #define MV88E6XXX_PORT_SWITCH_ID_PROD_6321 0x3100
+ #define MV88E6XXX_PORT_SWITCH_ID_PROD_6141 0x3400
diff --git a/target/linux/generic/backport-6.6/808-v6.2-0001-nvmem-stm32-move-STM32MP15_BSEC_NUM_LOWER-in-config.patch b/target/linux/generic/backport-6.6/808-v6.2-0001-nvmem-stm32-move-STM32MP15_BSEC_NUM_LOWER-in-config.patch
new file mode 100644
index 0000000000..33759632eb
--- /dev/null
+++ b/target/linux/generic/backport-6.6/808-v6.2-0001-nvmem-stm32-move-STM32MP15_BSEC_NUM_LOWER-in-config.patch
@@ -0,0 +1,82 @@
+From fbfc4ca465a1f8d81bf2d67d95bf7fc67c3cf0c2 Mon Sep 17 00:00:00 2001
+From: Patrick Delaunay <patrick.delaunay@foss.st.com>
+Date: Fri, 18 Nov 2022 06:39:20 +0000
+Subject: [PATCH] nvmem: stm32: move STM32MP15_BSEC_NUM_LOWER in config
+
+Support STM32MP15_BSEC_NUM_LOWER in stm32 romem config to prepare
+the next SoC in STM32MP family.
+
+Signed-off-by: Patrick Delaunay <patrick.delaunay@foss.st.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20221118063932.6418-2-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/stm32-romem.c | 21 ++++++++++++++++-----
+ 1 file changed, 16 insertions(+), 5 deletions(-)
+
+--- a/drivers/nvmem/stm32-romem.c
++++ b/drivers/nvmem/stm32-romem.c
+@@ -22,16 +22,15 @@
+ /* shadow registers offest */
+ #define STM32MP15_BSEC_DATA0 0x200
+
+-/* 32 (x 32-bits) lower shadow registers */
+-#define STM32MP15_BSEC_NUM_LOWER 32
+-
+ struct stm32_romem_cfg {
+ int size;
++ u8 lower;
+ };
+
+ struct stm32_romem_priv {
+ void __iomem *base;
+ struct nvmem_config cfg;
++ u8 lower;
+ };
+
+ static int stm32_romem_read(void *context, unsigned int offset, void *buf,
+@@ -85,7 +84,7 @@ static int stm32_bsec_read(void *context
+ for (i = roffset; (i < roffset + rbytes); i += 4) {
+ u32 otp = i >> 2;
+
+- if (otp < STM32MP15_BSEC_NUM_LOWER) {
++ if (otp < priv->lower) {
+ /* read lower data from shadow registers */
+ val = readl_relaxed(
+ priv->base + STM32MP15_BSEC_DATA0 + i);
+@@ -159,6 +158,8 @@ static int stm32_romem_probe(struct plat
+ priv->cfg.priv = priv;
+ priv->cfg.owner = THIS_MODULE;
+
++ priv->lower = 0;
++
+ cfg = (const struct stm32_romem_cfg *)
+ of_match_device(dev->driver->of_match_table, dev)->data;
+ if (!cfg) {
+@@ -167,6 +168,7 @@ static int stm32_romem_probe(struct plat
+ priv->cfg.reg_read = stm32_romem_read;
+ } else {
+ priv->cfg.size = cfg->size;
++ priv->lower = cfg->lower;
+ priv->cfg.reg_read = stm32_bsec_read;
+ priv->cfg.reg_write = stm32_bsec_write;
+ }
+@@ -174,8 +176,17 @@ static int stm32_romem_probe(struct plat
+ return PTR_ERR_OR_ZERO(devm_nvmem_register(dev, &priv->cfg));
+ }
+
++/*
++ * STM32MP15 BSEC OTP regions: 4096 OTP bits (with 3072 effective bits)
++ * => 96 x 32-bits data words
++ * - Lower: 1K bits, 2:1 redundancy, incremental bit programming
++ * => 32 (x 32-bits) lower shadow registers = words 0 to 31
++ * - Upper: 2K bits, ECC protection, word programming only
++ * => 64 (x 32-bits) = words 32 to 95
++ */
+ static const struct stm32_romem_cfg stm32mp15_bsec_cfg = {
+- .size = 384, /* 96 x 32-bits data words */
++ .size = 384,
++ .lower = 32,
+ };
+
+ static const struct of_device_id stm32_romem_of_match[] = {
diff --git a/target/linux/generic/backport-6.6/808-v6.2-0002-nvmem-stm32-add-warning-when-upper-OTPs-are-updated.patch b/target/linux/generic/backport-6.6/808-v6.2-0002-nvmem-stm32-add-warning-when-upper-OTPs-are-updated.patch
new file mode 100644
index 0000000000..5791df2606
--- /dev/null
+++ b/target/linux/generic/backport-6.6/808-v6.2-0002-nvmem-stm32-add-warning-when-upper-OTPs-are-updated.patch
@@ -0,0 +1,34 @@
+From d61784e6410f3df2028e6eb91b06ffed37a660e0 Mon Sep 17 00:00:00 2001
+From: Patrick Delaunay <patrick.delaunay@foss.st.com>
+Date: Fri, 18 Nov 2022 06:39:21 +0000
+Subject: [PATCH] nvmem: stm32: add warning when upper OTPs are updated
+
+As the upper OTPs are ECC protected, they support only one 32 bits word
+programming.
+For a second modification of this word, these ECC become invalid and
+this OTP will be no more accessible, the shadowed value is invalid.
+
+This patch adds a warning to indicate an upper OTP update, because this
+operation is dangerous as OTP is not locked by the driver after the first
+update to avoid a second update.
+
+Signed-off-by: Patrick Delaunay <patrick.delaunay@foss.st.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20221118063932.6418-3-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/stm32-romem.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/nvmem/stm32-romem.c
++++ b/drivers/nvmem/stm32-romem.c
+@@ -132,6 +132,9 @@ static int stm32_bsec_write(void *contex
+ }
+ }
+
++ if (offset + bytes >= priv->lower * 4)
++ dev_warn(dev, "Update of upper OTPs with ECC protection (word programming, only once)\n");
++
+ return 0;
+ }
+
diff --git a/target/linux/generic/backport-6.6/808-v6.2-0003-nvmem-stm32-add-nvmem-type-attribute.patch b/target/linux/generic/backport-6.6/808-v6.2-0003-nvmem-stm32-add-nvmem-type-attribute.patch
new file mode 100644
index 0000000000..b83ad69c6b
--- /dev/null
+++ b/target/linux/generic/backport-6.6/808-v6.2-0003-nvmem-stm32-add-nvmem-type-attribute.patch
@@ -0,0 +1,26 @@
+From a3816a7d7c097c1da46aad5f5d1e229b607dce04 Mon Sep 17 00:00:00 2001
+From: Patrick Delaunay <patrick.delaunay@foss.st.com>
+Date: Fri, 18 Nov 2022 06:39:22 +0000
+Subject: [PATCH] nvmem: stm32: add nvmem type attribute
+
+Inform NVMEM framework of type attribute for stm32-romem as NVMEM_TYPE_OTP
+so userspace is able to know how the data is stored in BSEC.
+
+Signed-off-by: Patrick Delaunay <patrick.delaunay@foss.st.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20221118063932.6418-4-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/stm32-romem.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/nvmem/stm32-romem.c
++++ b/drivers/nvmem/stm32-romem.c
+@@ -160,6 +160,7 @@ static int stm32_romem_probe(struct plat
+ priv->cfg.dev = dev;
+ priv->cfg.priv = priv;
+ priv->cfg.owner = THIS_MODULE;
++ priv->cfg.type = NVMEM_TYPE_OTP;
+
+ priv->lower = 0;
+
diff --git a/target/linux/generic/backport-6.6/808-v6.2-0004-nvmem-stm32-fix-spelling-typo-in-comment.patch b/target/linux/generic/backport-6.6/808-v6.2-0004-nvmem-stm32-fix-spelling-typo-in-comment.patch
new file mode 100644
index 0000000000..52ba1e65b5
--- /dev/null
+++ b/target/linux/generic/backport-6.6/808-v6.2-0004-nvmem-stm32-fix-spelling-typo-in-comment.patch
@@ -0,0 +1,27 @@
+From 06aac0e11960a7ddccc1888326b5906d017e0f24 Mon Sep 17 00:00:00 2001
+From: Jiangshan Yi <yijiangshan@kylinos.cn>
+Date: Fri, 18 Nov 2022 06:39:24 +0000
+Subject: [PATCH] nvmem: stm32: fix spelling typo in comment
+
+Fix spelling typo in comment.
+
+Reported-by: k2ci <kernel-bot@kylinos.cn>
+Signed-off-by: Jiangshan Yi <yijiangshan@kylinos.cn>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20221118063932.6418-6-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/stm32-romem.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/nvmem/stm32-romem.c
++++ b/drivers/nvmem/stm32-romem.c
+@@ -19,7 +19,7 @@
+ #define STM32_SMC_WRITE_SHADOW 0x03
+ #define STM32_SMC_READ_OTP 0x04
+
+-/* shadow registers offest */
++/* shadow registers offset */
+ #define STM32MP15_BSEC_DATA0 0x200
+
+ struct stm32_romem_cfg {
diff --git a/target/linux/generic/backport-6.6/808-v6.2-0005-nvmem-Kconfig-Fix-spelling-mistake-controlls-control.patch b/target/linux/generic/backport-6.6/808-v6.2-0005-nvmem-Kconfig-Fix-spelling-mistake-controlls-control.patch
new file mode 100644
index 0000000000..8f024f4c1a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/808-v6.2-0005-nvmem-Kconfig-Fix-spelling-mistake-controlls-control.patch
@@ -0,0 +1,27 @@
+From fb817c4ef63e8cfb6e77ae4a2875ae854c80708f Mon Sep 17 00:00:00 2001
+From: Colin Ian King <colin.i.king@gmail.com>
+Date: Fri, 18 Nov 2022 06:39:26 +0000
+Subject: [PATCH] nvmem: Kconfig: Fix spelling mistake "controlls" ->
+ "controls"
+
+There is a spelling mistake in a Kconfig description. Fix it.
+
+Signed-off-by: Colin Ian King <colin.i.king@gmail.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20221118063932.6418-8-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/Kconfig | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/nvmem/Kconfig
++++ b/drivers/nvmem/Kconfig
+@@ -164,7 +164,7 @@ config NVMEM_MICROCHIP_OTPC
+ depends on ARCH_AT91 || COMPILE_TEST
+ help
+ This driver enable the OTP controller available on Microchip SAMA7G5
+- SoCs. It controlls the access to the OTP memory connected to it.
++ SoCs. It controls the access to the OTP memory connected to it.
+
+ config NVMEM_MTK_EFUSE
+ tristate "Mediatek SoCs EFUSE support"
diff --git a/target/linux/generic/backport-6.6/808-v6.2-0006-nvmem-u-boot-env-add-Broadcom-format-support.patch b/target/linux/generic/backport-6.6/808-v6.2-0006-nvmem-u-boot-env-add-Broadcom-format-support.patch
new file mode 100644
index 0000000000..861386ad31
--- /dev/null
+++ b/target/linux/generic/backport-6.6/808-v6.2-0006-nvmem-u-boot-env-add-Broadcom-format-support.patch
@@ -0,0 +1,67 @@
+From ada84d07af6097b2addd18262668ce6cb9e15206 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Fri, 18 Nov 2022 06:39:27 +0000
+Subject: [PATCH] nvmem: u-boot-env: add Broadcom format support
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Broadcom uses U-Boot for a lot of their bcmbca familiy chipsets. They
+decided to store U-Boot environment data inside U-Boot partition and to
+use a custom header (with "uEnv" magic and env data length).
+
+Add support for Broadcom's specific binding and their custom format.
+
+Ref: 6b0584c19d87 ("dt-bindings: nvmem: u-boot,env: add Broadcom's variant binding")
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20221118063932.6418-9-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/u-boot-env.c | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+--- a/drivers/nvmem/u-boot-env.c
++++ b/drivers/nvmem/u-boot-env.c
+@@ -16,6 +16,7 @@
+ enum u_boot_env_format {
+ U_BOOT_FORMAT_SINGLE,
+ U_BOOT_FORMAT_REDUNDANT,
++ U_BOOT_FORMAT_BROADCOM,
+ };
+
+ struct u_boot_env {
+@@ -40,6 +41,13 @@ struct u_boot_env_image_redundant {
+ uint8_t data[];
+ } __packed;
+
++struct u_boot_env_image_broadcom {
++ __le32 magic;
++ __le32 len;
++ __le32 crc32;
++ uint8_t data[0];
++} __packed;
++
+ static int u_boot_env_read(void *context, unsigned int offset, void *val,
+ size_t bytes)
+ {
+@@ -138,6 +146,11 @@ static int u_boot_env_parse(struct u_boo
+ crc32_data_offset = offsetof(struct u_boot_env_image_redundant, data);
+ data_offset = offsetof(struct u_boot_env_image_redundant, data);
+ break;
++ case U_BOOT_FORMAT_BROADCOM:
++ crc32_offset = offsetof(struct u_boot_env_image_broadcom, crc32);
++ crc32_data_offset = offsetof(struct u_boot_env_image_broadcom, data);
++ data_offset = offsetof(struct u_boot_env_image_broadcom, data);
++ break;
+ }
+ crc32 = le32_to_cpu(*(__le32 *)(buf + crc32_offset));
+ crc32_data_len = priv->mtd->size - crc32_data_offset;
+@@ -202,6 +215,7 @@ static const struct of_device_id u_boot_
+ { .compatible = "u-boot,env", .data = (void *)U_BOOT_FORMAT_SINGLE, },
+ { .compatible = "u-boot,env-redundant-bool", .data = (void *)U_BOOT_FORMAT_REDUNDANT, },
+ { .compatible = "u-boot,env-redundant-count", .data = (void *)U_BOOT_FORMAT_REDUNDANT, },
++ { .compatible = "brcm,env", .data = (void *)U_BOOT_FORMAT_BROADCOM, },
+ {},
+ };
+
diff --git a/target/linux/generic/backport-6.6/809-v6.3-0001-nvmem-core-remove-spurious-white-space.patch b/target/linux/generic/backport-6.6/809-v6.3-0001-nvmem-core-remove-spurious-white-space.patch
new file mode 100644
index 0000000000..01400fd490
--- /dev/null
+++ b/target/linux/generic/backport-6.6/809-v6.3-0001-nvmem-core-remove-spurious-white-space.patch
@@ -0,0 +1,26 @@
+From 2e8dc541ae207349b51c65391be625ffe1f86e0c Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Mon, 6 Feb 2023 13:43:41 +0000
+Subject: [PATCH] nvmem: core: remove spurious white space
+
+Remove a spurious white space in for the ida_alloc() call.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230206134356.839737-8-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -764,7 +764,7 @@ struct nvmem_device *nvmem_register(cons
+ if (!nvmem)
+ return ERR_PTR(-ENOMEM);
+
+- rval = ida_alloc(&nvmem_ida, GFP_KERNEL);
++ rval = ida_alloc(&nvmem_ida, GFP_KERNEL);
+ if (rval < 0) {
+ kfree(nvmem);
+ return ERR_PTR(rval);
diff --git a/target/linux/generic/backport-6.6/809-v6.3-0002-nvmem-core-add-an-index-parameter-to-the-cell.patch b/target/linux/generic/backport-6.6/809-v6.3-0002-nvmem-core-add-an-index-parameter-to-the-cell.patch
new file mode 100644
index 0000000000..454d3bf0ed
--- /dev/null
+++ b/target/linux/generic/backport-6.6/809-v6.3-0002-nvmem-core-add-an-index-parameter-to-the-cell.patch
@@ -0,0 +1,180 @@
+From 5d8e6e6c10a3d37486d263b16ddc15991a7e4a88 Mon Sep 17 00:00:00 2001
+From: Michael Walle <michael@walle.cc>
+Date: Mon, 6 Feb 2023 13:43:46 +0000
+Subject: [PATCH] nvmem: core: add an index parameter to the cell
+
+Sometimes a cell can represend multiple values. For example, a base
+ethernet address stored in the NVMEM can be expanded into multiple
+discreet ones by adding an offset.
+
+For this use case, introduce an index parameter which is then used to
+distiguish between values. This parameter will then be passed to the
+post process hook which can then use it to create different values
+during reading.
+
+At the moment, there is only support for the device tree path. You can
+add the index to the phandle, e.g.
+
+ &net {
+ nvmem-cells = <&base_mac_address 2>;
+ nvmem-cell-names = "mac-address";
+ };
+
+ &nvmem_provider {
+ base_mac_address: base-mac-address@0 {
+ #nvmem-cell-cells = <1>;
+ reg = <0 6>;
+ };
+ };
+
+Signed-off-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230206134356.839737-13-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 37 ++++++++++++++++++++++++----------
+ drivers/nvmem/imx-ocotp.c | 4 ++--
+ include/linux/nvmem-provider.h | 4 ++--
+ 3 files changed, 30 insertions(+), 15 deletions(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -60,6 +60,7 @@ struct nvmem_cell_entry {
+ struct nvmem_cell {
+ struct nvmem_cell_entry *entry;
+ const char *id;
++ int index;
+ };
+
+ static DEFINE_MUTEX(nvmem_mutex);
+@@ -1122,7 +1123,8 @@ struct nvmem_device *devm_nvmem_device_g
+ }
+ EXPORT_SYMBOL_GPL(devm_nvmem_device_get);
+
+-static struct nvmem_cell *nvmem_create_cell(struct nvmem_cell_entry *entry, const char *id)
++static struct nvmem_cell *nvmem_create_cell(struct nvmem_cell_entry *entry,
++ const char *id, int index)
+ {
+ struct nvmem_cell *cell;
+ const char *name = NULL;
+@@ -1141,6 +1143,7 @@ static struct nvmem_cell *nvmem_create_c
+
+ cell->id = name;
+ cell->entry = entry;
++ cell->index = index;
+
+ return cell;
+ }
+@@ -1179,7 +1182,7 @@ nvmem_cell_get_from_lookup(struct device
+ __nvmem_device_put(nvmem);
+ cell = ERR_PTR(-ENOENT);
+ } else {
+- cell = nvmem_create_cell(cell_entry, con_id);
++ cell = nvmem_create_cell(cell_entry, con_id, 0);
+ if (IS_ERR(cell))
+ __nvmem_device_put(nvmem);
+ }
+@@ -1227,15 +1230,27 @@ struct nvmem_cell *of_nvmem_cell_get(str
+ struct nvmem_device *nvmem;
+ struct nvmem_cell_entry *cell_entry;
+ struct nvmem_cell *cell;
++ struct of_phandle_args cell_spec;
+ int index = 0;
++ int cell_index = 0;
++ int ret;
+
+ /* if cell name exists, find index to the name */
+ if (id)
+ index = of_property_match_string(np, "nvmem-cell-names", id);
+
+- cell_np = of_parse_phandle(np, "nvmem-cells", index);
+- if (!cell_np)
+- return ERR_PTR(-ENOENT);
++ ret = of_parse_phandle_with_optional_args(np, "nvmem-cells",
++ "#nvmem-cell-cells",
++ index, &cell_spec);
++ if (ret)
++ return ERR_PTR(ret);
++
++ if (cell_spec.args_count > 1)
++ return ERR_PTR(-EINVAL);
++
++ cell_np = cell_spec.np;
++ if (cell_spec.args_count)
++ cell_index = cell_spec.args[0];
+
+ nvmem_np = of_get_parent(cell_np);
+ if (!nvmem_np) {
+@@ -1257,7 +1272,7 @@ struct nvmem_cell *of_nvmem_cell_get(str
+ return ERR_PTR(-ENOENT);
+ }
+
+- cell = nvmem_create_cell(cell_entry, id);
++ cell = nvmem_create_cell(cell_entry, id, cell_index);
+ if (IS_ERR(cell))
+ __nvmem_device_put(nvmem);
+
+@@ -1410,8 +1425,8 @@ static void nvmem_shift_read_buffer_in_p
+ }
+
+ static int __nvmem_cell_read(struct nvmem_device *nvmem,
+- struct nvmem_cell_entry *cell,
+- void *buf, size_t *len, const char *id)
++ struct nvmem_cell_entry *cell,
++ void *buf, size_t *len, const char *id, int index)
+ {
+ int rc;
+
+@@ -1425,7 +1440,7 @@ static int __nvmem_cell_read(struct nvme
+ nvmem_shift_read_buffer_in_place(cell, buf);
+
+ if (nvmem->cell_post_process) {
+- rc = nvmem->cell_post_process(nvmem->priv, id,
++ rc = nvmem->cell_post_process(nvmem->priv, id, index,
+ cell->offset, buf, cell->bytes);
+ if (rc)
+ return rc;
+@@ -1460,7 +1475,7 @@ void *nvmem_cell_read(struct nvmem_cell
+ if (!buf)
+ return ERR_PTR(-ENOMEM);
+
+- rc = __nvmem_cell_read(nvmem, cell->entry, buf, len, cell->id);
++ rc = __nvmem_cell_read(nvmem, cell->entry, buf, len, cell->id, cell->index);
+ if (rc) {
+ kfree(buf);
+ return ERR_PTR(rc);
+@@ -1773,7 +1788,7 @@ ssize_t nvmem_device_cell_read(struct nv
+ if (rc)
+ return rc;
+
+- rc = __nvmem_cell_read(nvmem, &cell, buf, &len, NULL);
++ rc = __nvmem_cell_read(nvmem, &cell, buf, &len, NULL, 0);
+ if (rc)
+ return rc;
+
+--- a/drivers/nvmem/imx-ocotp.c
++++ b/drivers/nvmem/imx-ocotp.c
+@@ -222,8 +222,8 @@ read_end:
+ return ret;
+ }
+
+-static int imx_ocotp_cell_pp(void *context, const char *id, unsigned int offset,
+- void *data, size_t bytes)
++static int imx_ocotp_cell_pp(void *context, const char *id, int index,
++ unsigned int offset, void *data, size_t bytes)
+ {
+ struct ocotp_priv *priv = context;
+
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -20,8 +20,8 @@ typedef int (*nvmem_reg_read_t)(void *pr
+ typedef int (*nvmem_reg_write_t)(void *priv, unsigned int offset,
+ void *val, size_t bytes);
+ /* used for vendor specific post processing of cell data */
+-typedef int (*nvmem_cell_post_process_t)(void *priv, const char *id, unsigned int offset,
+- void *buf, size_t bytes);
++typedef int (*nvmem_cell_post_process_t)(void *priv, const char *id, int index,
++ unsigned int offset, void *buf, size_t bytes);
+
+ enum nvmem_type {
+ NVMEM_TYPE_UNKNOWN = 0,
diff --git a/target/linux/generic/backport-6.6/809-v6.3-0003-nvmem-core-move-struct-nvmem_cell_info-to-nvmem-prov.patch b/target/linux/generic/backport-6.6/809-v6.3-0003-nvmem-core-move-struct-nvmem_cell_info-to-nvmem-prov.patch
new file mode 100644
index 0000000000..f3829b3e17
--- /dev/null
+++ b/target/linux/generic/backport-6.6/809-v6.3-0003-nvmem-core-move-struct-nvmem_cell_info-to-nvmem-prov.patch
@@ -0,0 +1,78 @@
+From fbd03d27776c6121a483921601418e3c8f0ff37e Mon Sep 17 00:00:00 2001
+From: Michael Walle <michael@walle.cc>
+Date: Mon, 6 Feb 2023 13:43:47 +0000
+Subject: [PATCH] nvmem: core: move struct nvmem_cell_info to nvmem-provider.h
+
+struct nvmem_cell_info is used to describe a cell. Thus this should
+really be in the nvmem-provider's header. There are two (unused) nvmem
+access methods which use the nvmem_cell_info to describe the cell to be
+accesses. One can argue, that they will create a cell before accessing,
+thus they are both a provider and a consumer.
+
+struct nvmem_cell_info will get used more and more by nvmem-providers,
+don't force them to also include the consumer header, although they are
+not.
+
+Signed-off-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230206134356.839737-14-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/nvmem-consumer.h | 10 +---------
+ include/linux/nvmem-provider.h | 19 ++++++++++++++++++-
+ 2 files changed, 19 insertions(+), 10 deletions(-)
+
+--- a/include/linux/nvmem-consumer.h
++++ b/include/linux/nvmem-consumer.h
+@@ -18,15 +18,7 @@ struct device_node;
+ /* consumer cookie */
+ struct nvmem_cell;
+ struct nvmem_device;
+-
+-struct nvmem_cell_info {
+- const char *name;
+- unsigned int offset;
+- unsigned int bytes;
+- unsigned int bit_offset;
+- unsigned int nbits;
+- struct device_node *np;
+-};
++struct nvmem_cell_info;
+
+ /**
+ * struct nvmem_cell_lookup - cell lookup entry
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -14,7 +14,6 @@
+ #include <linux/gpio/consumer.h>
+
+ struct nvmem_device;
+-struct nvmem_cell_info;
+ typedef int (*nvmem_reg_read_t)(void *priv, unsigned int offset,
+ void *val, size_t bytes);
+ typedef int (*nvmem_reg_write_t)(void *priv, unsigned int offset,
+@@ -48,6 +47,24 @@ struct nvmem_keepout {
+ };
+
+ /**
++ * struct nvmem_cell_info - NVMEM cell description
++ * @name: Name.
++ * @offset: Offset within the NVMEM device.
++ * @bytes: Length of the cell.
++ * @bit_offset: Bit offset if cell is smaller than a byte.
++ * @nbits: Number of bits.
++ * @np: Optional device_node pointer.
++ */
++struct nvmem_cell_info {
++ const char *name;
++ unsigned int offset;
++ unsigned int bytes;
++ unsigned int bit_offset;
++ unsigned int nbits;
++ struct device_node *np;
++};
++
++/**
+ * struct nvmem_config - NVMEM device configuration
+ *
+ * @dev: Parent device.
diff --git a/target/linux/generic/backport-6.6/809-v6.3-0004-nvmem-core-drop-the-removal-of-the-cells-in-nvmem_ad.patch b/target/linux/generic/backport-6.6/809-v6.3-0004-nvmem-core-drop-the-removal-of-the-cells-in-nvmem_ad.patch
new file mode 100644
index 0000000000..8f996eab34
--- /dev/null
+++ b/target/linux/generic/backport-6.6/809-v6.3-0004-nvmem-core-drop-the-removal-of-the-cells-in-nvmem_ad.patch
@@ -0,0 +1,65 @@
+From cc5bdd323dde6494623f3ffe3a5b887fa21cd375 Mon Sep 17 00:00:00 2001
+From: Michael Walle <michael@walle.cc>
+Date: Mon, 6 Feb 2023 13:43:48 +0000
+Subject: [PATCH] nvmem: core: drop the removal of the cells in
+ nvmem_add_cells()
+
+If nvmem_add_cells() fails, the whole nvmem_register() will fail
+and the cells will then be removed anyway. This is a preparation
+to introduce a nvmem_add_one_cell() which can then be used by
+nvmem_add_cells().
+
+This is then the same to what nvmem_add_cells_from_table() and
+nvmem_add_cells_from_of() do.
+
+Signed-off-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230206134356.839737-15-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 14 ++++----------
+ 1 file changed, 4 insertions(+), 10 deletions(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -515,7 +515,7 @@ static int nvmem_add_cells(struct nvmem_
+ int ncells)
+ {
+ struct nvmem_cell_entry **cells;
+- int i, rval;
++ int i, rval = 0;
+
+ cells = kcalloc(ncells, sizeof(*cells), GFP_KERNEL);
+ if (!cells)
+@@ -525,28 +525,22 @@ static int nvmem_add_cells(struct nvmem_
+ cells[i] = kzalloc(sizeof(**cells), GFP_KERNEL);
+ if (!cells[i]) {
+ rval = -ENOMEM;
+- goto err;
++ goto out;
+ }
+
+ rval = nvmem_cell_info_to_nvmem_cell_entry(nvmem, &info[i], cells[i]);
+ if (rval) {
+ kfree(cells[i]);
+- goto err;
++ goto out;
+ }
+
+ nvmem_cell_entry_add(cells[i]);
+ }
+
++out:
+ /* remove tmp array */
+ kfree(cells);
+
+- return 0;
+-err:
+- while (i--)
+- nvmem_cell_entry_drop(cells[i]);
+-
+- kfree(cells);
+-
+ return rval;
+ }
+
diff --git a/target/linux/generic/backport-6.6/809-v6.3-0005-nvmem-core-add-nvmem_add_one_cell.patch b/target/linux/generic/backport-6.6/809-v6.3-0005-nvmem-core-add-nvmem_add_one_cell.patch
new file mode 100644
index 0000000000..711ce229b2
--- /dev/null
+++ b/target/linux/generic/backport-6.6/809-v6.3-0005-nvmem-core-add-nvmem_add_one_cell.patch
@@ -0,0 +1,122 @@
+From 2ded6830d376d5e7bf43d59f7f7fdf1a59abc676 Mon Sep 17 00:00:00 2001
+From: Michael Walle <michael@walle.cc>
+Date: Mon, 6 Feb 2023 13:43:49 +0000
+Subject: [PATCH] nvmem: core: add nvmem_add_one_cell()
+
+Add a new function to add exactly one cell. This will be used by the
+nvmem layout drivers to add custom cells. In contrast to the
+nvmem_add_cells(), this has the advantage that we don't have to assemble
+a list of cells on runtime.
+
+Signed-off-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230206134356.839737-16-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 59 ++++++++++++++++++++--------------
+ include/linux/nvmem-provider.h | 8 +++++
+ 2 files changed, 43 insertions(+), 24 deletions(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -502,6 +502,36 @@ static int nvmem_cell_info_to_nvmem_cell
+ }
+
+ /**
++ * nvmem_add_one_cell() - Add one cell information to an nvmem device
++ *
++ * @nvmem: nvmem device to add cells to.
++ * @info: nvmem cell info to add to the device
++ *
++ * Return: 0 or negative error code on failure.
++ */
++int nvmem_add_one_cell(struct nvmem_device *nvmem,
++ const struct nvmem_cell_info *info)
++{
++ struct nvmem_cell_entry *cell;
++ int rval;
++
++ cell = kzalloc(sizeof(*cell), GFP_KERNEL);
++ if (!cell)
++ return -ENOMEM;
++
++ rval = nvmem_cell_info_to_nvmem_cell_entry(nvmem, info, cell);
++ if (rval) {
++ kfree(cell);
++ return rval;
++ }
++
++ nvmem_cell_entry_add(cell);
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(nvmem_add_one_cell);
++
++/**
+ * nvmem_add_cells() - Add cell information to an nvmem device
+ *
+ * @nvmem: nvmem device to add cells to.
+@@ -514,34 +544,15 @@ static int nvmem_add_cells(struct nvmem_
+ const struct nvmem_cell_info *info,
+ int ncells)
+ {
+- struct nvmem_cell_entry **cells;
+- int i, rval = 0;
+-
+- cells = kcalloc(ncells, sizeof(*cells), GFP_KERNEL);
+- if (!cells)
+- return -ENOMEM;
++ int i, rval;
+
+ for (i = 0; i < ncells; i++) {
+- cells[i] = kzalloc(sizeof(**cells), GFP_KERNEL);
+- if (!cells[i]) {
+- rval = -ENOMEM;
+- goto out;
+- }
+-
+- rval = nvmem_cell_info_to_nvmem_cell_entry(nvmem, &info[i], cells[i]);
+- if (rval) {
+- kfree(cells[i]);
+- goto out;
+- }
+-
+- nvmem_cell_entry_add(cells[i]);
++ rval = nvmem_add_one_cell(nvmem, &info[i]);
++ if (rval)
++ return rval;
+ }
+
+-out:
+- /* remove tmp array */
+- kfree(cells);
+-
+- return rval;
++ return 0;
+ }
+
+ /**
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -153,6 +153,9 @@ struct nvmem_device *devm_nvmem_register
+ void nvmem_add_cell_table(struct nvmem_cell_table *table);
+ void nvmem_del_cell_table(struct nvmem_cell_table *table);
+
++int nvmem_add_one_cell(struct nvmem_device *nvmem,
++ const struct nvmem_cell_info *info);
++
+ #else
+
+ static inline struct nvmem_device *nvmem_register(const struct nvmem_config *c)
+@@ -170,6 +173,11 @@ devm_nvmem_register(struct device *dev,
+
+ static inline void nvmem_add_cell_table(struct nvmem_cell_table *table) {}
+ static inline void nvmem_del_cell_table(struct nvmem_cell_table *table) {}
++static inline int nvmem_add_one_cell(struct nvmem_device *nvmem,
++ const struct nvmem_cell_info *info)
++{
++ return -EOPNOTSUPP;
++}
+
+ #endif /* CONFIG_NVMEM */
+ #endif /* ifndef _LINUX_NVMEM_PROVIDER_H */
diff --git a/target/linux/generic/backport-6.6/809-v6.3-0006-nvmem-core-use-nvmem_add_one_cell-in-nvmem_add_cells.patch b/target/linux/generic/backport-6.6/809-v6.3-0006-nvmem-core-use-nvmem_add_one_cell-in-nvmem_add_cells.patch
new file mode 100644
index 0000000000..e1791e5c83
--- /dev/null
+++ b/target/linux/generic/backport-6.6/809-v6.3-0006-nvmem-core-use-nvmem_add_one_cell-in-nvmem_add_cells.patch
@@ -0,0 +1,93 @@
+From 50014d659617dc58780a5d31ceb76c82779a9d8b Mon Sep 17 00:00:00 2001
+From: Michael Walle <michael@walle.cc>
+Date: Mon, 6 Feb 2023 13:43:50 +0000
+Subject: [PATCH] nvmem: core: use nvmem_add_one_cell() in
+ nvmem_add_cells_from_of()
+
+Convert nvmem_add_cells_from_of() to use the new nvmem_add_one_cell().
+This will remove duplicate code and it will make it possible to add a
+hook to a nvmem layout in between, which can change fields before the
+cell is finally added.
+
+Signed-off-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230206134356.839737-17-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 45 ++++++++++++++------------------------------
+ 1 file changed, 14 insertions(+), 31 deletions(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -688,15 +688,14 @@ static int nvmem_validate_keepouts(struc
+
+ static int nvmem_add_cells_from_of(struct nvmem_device *nvmem)
+ {
+- struct device_node *parent, *child;
+ struct device *dev = &nvmem->dev;
+- struct nvmem_cell_entry *cell;
++ struct device_node *child;
+ const __be32 *addr;
+- int len;
++ int len, ret;
+
+- parent = dev->of_node;
++ for_each_child_of_node(dev->of_node, child) {
++ struct nvmem_cell_info info = {0};
+
+- for_each_child_of_node(parent, child) {
+ addr = of_get_property(child, "reg", &len);
+ if (!addr)
+ continue;
+@@ -706,40 +705,24 @@ static int nvmem_add_cells_from_of(struc
+ return -EINVAL;
+ }
+
+- cell = kzalloc(sizeof(*cell), GFP_KERNEL);
+- if (!cell) {
+- of_node_put(child);
+- return -ENOMEM;
+- }
+-
+- cell->nvmem = nvmem;
+- cell->offset = be32_to_cpup(addr++);
+- cell->bytes = be32_to_cpup(addr);
+- cell->name = kasprintf(GFP_KERNEL, "%pOFn", child);
++ info.offset = be32_to_cpup(addr++);
++ info.bytes = be32_to_cpup(addr);
++ info.name = kasprintf(GFP_KERNEL, "%pOFn", child);
+
+ addr = of_get_property(child, "bits", &len);
+ if (addr && len == (2 * sizeof(u32))) {
+- cell->bit_offset = be32_to_cpup(addr++);
+- cell->nbits = be32_to_cpup(addr);
++ info.bit_offset = be32_to_cpup(addr++);
++ info.nbits = be32_to_cpup(addr);
+ }
+
+- if (cell->nbits)
+- cell->bytes = DIV_ROUND_UP(
+- cell->nbits + cell->bit_offset,
+- BITS_PER_BYTE);
+-
+- if (!IS_ALIGNED(cell->offset, nvmem->stride)) {
+- dev_err(dev, "cell %s unaligned to nvmem stride %d\n",
+- cell->name, nvmem->stride);
+- /* Cells already added will be freed later. */
+- kfree_const(cell->name);
+- kfree(cell);
++ info.np = of_node_get(child);
++
++ ret = nvmem_add_one_cell(nvmem, &info);
++ kfree(info.name);
++ if (ret) {
+ of_node_put(child);
+- return -EINVAL;
++ return ret;
+ }
+-
+- cell->np = of_node_get(child);
+- nvmem_cell_entry_add(cell);
+ }
+
+ return 0;
diff --git a/target/linux/generic/backport-6.6/809-v6.3-0007-nvmem-stm32-add-OP-TEE-support-for-STM32MP13x.patch b/target/linux/generic/backport-6.6/809-v6.3-0007-nvmem-stm32-add-OP-TEE-support-for-STM32MP13x.patch
new file mode 100644
index 0000000000..172a78b76a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/809-v6.3-0007-nvmem-stm32-add-OP-TEE-support-for-STM32MP13x.patch
@@ -0,0 +1,562 @@
+From 6a0bc3522e746025e2d9a63ab2cb5d7062c2d39c Mon Sep 17 00:00:00 2001
+From: Patrick Delaunay <patrick.delaunay@foss.st.com>
+Date: Mon, 6 Feb 2023 13:43:51 +0000
+Subject: [PATCH] nvmem: stm32: add OP-TEE support for STM32MP13x
+
+For boot with OP-TEE on STM32MP13, the communication with the secure
+world no more use STMicroelectronics SMC but communication with the
+STM32MP BSEC TA, for data access (read/write) or lock operation:
+- all the request are sent to OP-TEE trusted application,
+- for upper OTP with ECC protection and with word programming only
+ each OTP are permanently locked when programmed to avoid ECC error
+ on the second write operation
+
+Signed-off-by: Patrick Delaunay <patrick.delaunay@foss.st.com>
+Reviewed-by: Etienne Carriere <etienne.carriere@linaro.org>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230206134356.839737-18-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/Kconfig | 11 +
+ drivers/nvmem/Makefile | 1 +
+ drivers/nvmem/stm32-bsec-optee-ta.c | 298 ++++++++++++++++++++++++++++
+ drivers/nvmem/stm32-bsec-optee-ta.h | 80 ++++++++
+ drivers/nvmem/stm32-romem.c | 54 ++++-
+ 5 files changed, 441 insertions(+), 3 deletions(-)
+ create mode 100644 drivers/nvmem/stm32-bsec-optee-ta.c
+ create mode 100644 drivers/nvmem/stm32-bsec-optee-ta.h
+
+--- a/drivers/nvmem/Kconfig
++++ b/drivers/nvmem/Kconfig
+@@ -290,9 +290,20 @@ config NVMEM_SPRD_EFUSE
+ This driver can also be built as a module. If so, the module
+ will be called nvmem-sprd-efuse.
+
++config NVMEM_STM32_BSEC_OPTEE_TA
++ bool "STM32MP BSEC OP-TEE TA support for nvmem-stm32-romem driver"
++ depends on OPTEE
++ help
++ Say y here to enable the accesses to STM32MP SoC OTPs by the OP-TEE
++ trusted application STM32MP BSEC.
++
++ This library is a used by stm32-romem driver or included in the module
++ called nvmem-stm32-romem.
++
+ config NVMEM_STM32_ROMEM
+ tristate "STMicroelectronics STM32 factory-programmed memory support"
+ depends on ARCH_STM32 || COMPILE_TEST
++ imply NVMEM_STM32_BSEC_OPTEE_TA
+ help
+ Say y here to enable read-only access for STMicroelectronics STM32
+ factory-programmed memory area.
+--- a/drivers/nvmem/Makefile
++++ b/drivers/nvmem/Makefile
+@@ -61,6 +61,7 @@ obj-$(CONFIG_NVMEM_SPRD_EFUSE) += nvmem
+ nvmem_sprd_efuse-y := sprd-efuse.o
+ obj-$(CONFIG_NVMEM_STM32_ROMEM) += nvmem_stm32_romem.o
+ nvmem_stm32_romem-y := stm32-romem.o
++nvmem_stm32_romem-$(CONFIG_NVMEM_STM32_BSEC_OPTEE_TA) += stm32-bsec-optee-ta.o
+ obj-$(CONFIG_NVMEM_SUNPLUS_OCOTP) += nvmem_sunplus_ocotp.o
+ nvmem_sunplus_ocotp-y := sunplus-ocotp.o
+ obj-$(CONFIG_NVMEM_SUNXI_SID) += nvmem_sunxi_sid.o
+--- /dev/null
++++ b/drivers/nvmem/stm32-bsec-optee-ta.c
+@@ -0,0 +1,298 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * OP-TEE STM32MP BSEC PTA interface, used by STM32 ROMEM driver
++ *
++ * Copyright (C) 2022, STMicroelectronics - All Rights Reserved
++ */
++
++#include <linux/tee_drv.h>
++
++#include "stm32-bsec-optee-ta.h"
++
++/*
++ * Read OTP memory
++ *
++ * [in] value[0].a OTP start offset in byte
++ * [in] value[0].b Access type (0:shadow, 1:fuse, 2:lock)
++ * [out] memref[1].buffer Output buffer to store read values
++ * [out] memref[1].size Size of OTP to be read
++ *
++ * Return codes:
++ * TEE_SUCCESS - Invoke command success
++ * TEE_ERROR_BAD_PARAMETERS - Incorrect input param
++ * TEE_ERROR_ACCESS_DENIED - OTP not accessible by caller
++ */
++#define PTA_BSEC_READ_MEM 0x0
++
++/*
++ * Write OTP memory
++ *
++ * [in] value[0].a OTP start offset in byte
++ * [in] value[0].b Access type (0:shadow, 1:fuse, 2:lock)
++ * [in] memref[1].buffer Input buffer to read values
++ * [in] memref[1].size Size of OTP to be written
++ *
++ * Return codes:
++ * TEE_SUCCESS - Invoke command success
++ * TEE_ERROR_BAD_PARAMETERS - Incorrect input param
++ * TEE_ERROR_ACCESS_DENIED - OTP not accessible by caller
++ */
++#define PTA_BSEC_WRITE_MEM 0x1
++
++/* value of PTA_BSEC access type = value[in] b */
++#define SHADOW_ACCESS 0
++#define FUSE_ACCESS 1
++#define LOCK_ACCESS 2
++
++/* Bitfield definition for LOCK status */
++#define LOCK_PERM BIT(30)
++
++/* OP-TEE STM32MP BSEC TA UUID */
++static const uuid_t stm32mp_bsec_ta_uuid =
++ UUID_INIT(0x94cf71ad, 0x80e6, 0x40b5,
++ 0xa7, 0xc6, 0x3d, 0xc5, 0x01, 0xeb, 0x28, 0x03);
++
++/*
++ * Check whether this driver supports the BSEC TA in the TEE instance
++ * represented by the params (ver/data) to this function.
++ */
++static int stm32_bsec_optee_ta_match(struct tee_ioctl_version_data *ver,
++ const void *data)
++{
++ /* Currently this driver only supports GP compliant, OP-TEE based TA */
++ if ((ver->impl_id == TEE_IMPL_ID_OPTEE) &&
++ (ver->gen_caps & TEE_GEN_CAP_GP))
++ return 1;
++ else
++ return 0;
++}
++
++/* Open a session to OP-TEE for STM32MP BSEC TA */
++static int stm32_bsec_ta_open_session(struct tee_context *ctx, u32 *id)
++{
++ struct tee_ioctl_open_session_arg sess_arg;
++ int rc;
++
++ memset(&sess_arg, 0, sizeof(sess_arg));
++ export_uuid(sess_arg.uuid, &stm32mp_bsec_ta_uuid);
++ sess_arg.clnt_login = TEE_IOCTL_LOGIN_REE_KERNEL;
++ sess_arg.num_params = 0;
++
++ rc = tee_client_open_session(ctx, &sess_arg, NULL);
++ if ((rc < 0) || (sess_arg.ret != 0)) {
++ pr_err("%s: tee_client_open_session failed err:%#x, ret:%#x\n",
++ __func__, sess_arg.ret, rc);
++ if (!rc)
++ rc = -EINVAL;
++ } else {
++ *id = sess_arg.session;
++ }
++
++ return rc;
++}
++
++/* close a session to OP-TEE for STM32MP BSEC TA */
++static void stm32_bsec_ta_close_session(void *ctx, u32 id)
++{
++ tee_client_close_session(ctx, id);
++}
++
++/* stm32_bsec_optee_ta_open() - initialize the STM32MP BSEC TA */
++int stm32_bsec_optee_ta_open(struct tee_context **ctx)
++{
++ struct tee_context *tee_ctx;
++ u32 session_id;
++ int rc;
++
++ /* Open context with TEE driver */
++ tee_ctx = tee_client_open_context(NULL, stm32_bsec_optee_ta_match, NULL, NULL);
++ if (IS_ERR(tee_ctx)) {
++ rc = PTR_ERR(tee_ctx);
++ if (rc == -ENOENT)
++ return -EPROBE_DEFER;
++ pr_err("%s: tee_client_open_context failed (%d)\n", __func__, rc);
++
++ return rc;
++ }
++
++ /* Check STM32MP BSEC TA presence */
++ rc = stm32_bsec_ta_open_session(tee_ctx, &session_id);
++ if (rc) {
++ tee_client_close_context(tee_ctx);
++ return rc;
++ }
++
++ stm32_bsec_ta_close_session(tee_ctx, session_id);
++
++ *ctx = tee_ctx;
++
++ return 0;
++}
++
++/* stm32_bsec_optee_ta_open() - release the PTA STM32MP BSEC TA */
++void stm32_bsec_optee_ta_close(void *ctx)
++{
++ tee_client_close_context(ctx);
++}
++
++/* stm32_bsec_optee_ta_read() - nvmem read access using PTA client driver */
++int stm32_bsec_optee_ta_read(struct tee_context *ctx, unsigned int offset,
++ void *buf, size_t bytes)
++{
++ struct tee_shm *shm;
++ struct tee_ioctl_invoke_arg arg;
++ struct tee_param param[2];
++ u8 *shm_buf;
++ u32 start, num_bytes;
++ int ret;
++ u32 session_id;
++
++ ret = stm32_bsec_ta_open_session(ctx, &session_id);
++ if (ret)
++ return ret;
++
++ memset(&arg, 0, sizeof(arg));
++ memset(&param, 0, sizeof(param));
++
++ arg.func = PTA_BSEC_READ_MEM;
++ arg.session = session_id;
++ arg.num_params = 2;
++
++ /* align access on 32bits */
++ start = ALIGN_DOWN(offset, 4);
++ num_bytes = round_up(offset + bytes - start, 4);
++ param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT;
++ param[0].u.value.a = start;
++ param[0].u.value.b = SHADOW_ACCESS;
++
++ shm = tee_shm_alloc_kernel_buf(ctx, num_bytes);
++ if (IS_ERR(shm)) {
++ ret = PTR_ERR(shm);
++ goto out_tee_session;
++ }
++
++ param[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT;
++ param[1].u.memref.shm = shm;
++ param[1].u.memref.size = num_bytes;
++
++ ret = tee_client_invoke_func(ctx, &arg, param);
++ if (ret < 0 || arg.ret != 0) {
++ pr_err("TA_BSEC invoke failed TEE err:%#x, ret:%#x\n",
++ arg.ret, ret);
++ if (!ret)
++ ret = -EIO;
++ }
++ if (!ret) {
++ shm_buf = tee_shm_get_va(shm, 0);
++ if (IS_ERR(shm_buf)) {
++ ret = PTR_ERR(shm_buf);
++ pr_err("tee_shm_get_va failed for transmit (%d)\n", ret);
++ } else {
++ /* read data from 32 bits aligned buffer */
++ memcpy(buf, &shm_buf[offset % 4], bytes);
++ }
++ }
++
++ tee_shm_free(shm);
++
++out_tee_session:
++ stm32_bsec_ta_close_session(ctx, session_id);
++
++ return ret;
++}
++
++/* stm32_bsec_optee_ta_write() - nvmem write access using PTA client driver */
++int stm32_bsec_optee_ta_write(struct tee_context *ctx, unsigned int lower,
++ unsigned int offset, void *buf, size_t bytes)
++{ struct tee_shm *shm;
++ struct tee_ioctl_invoke_arg arg;
++ struct tee_param param[2];
++ u8 *shm_buf;
++ int ret;
++ u32 session_id;
++
++ ret = stm32_bsec_ta_open_session(ctx, &session_id);
++ if (ret)
++ return ret;
++
++ /* Allow only writing complete 32-bits aligned words */
++ if ((bytes % 4) || (offset % 4))
++ return -EINVAL;
++
++ memset(&arg, 0, sizeof(arg));
++ memset(&param, 0, sizeof(param));
++
++ arg.func = PTA_BSEC_WRITE_MEM;
++ arg.session = session_id;
++ arg.num_params = 2;
++
++ param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT;
++ param[0].u.value.a = offset;
++ param[0].u.value.b = FUSE_ACCESS;
++
++ shm = tee_shm_alloc_kernel_buf(ctx, bytes);
++ if (IS_ERR(shm)) {
++ ret = PTR_ERR(shm);
++ goto out_tee_session;
++ }
++
++ param[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT;
++ param[1].u.memref.shm = shm;
++ param[1].u.memref.size = bytes;
++
++ shm_buf = tee_shm_get_va(shm, 0);
++ if (IS_ERR(shm_buf)) {
++ ret = PTR_ERR(shm_buf);
++ pr_err("tee_shm_get_va failed for transmit (%d)\n", ret);
++ tee_shm_free(shm);
++
++ goto out_tee_session;
++ }
++
++ memcpy(shm_buf, buf, bytes);
++
++ ret = tee_client_invoke_func(ctx, &arg, param);
++ if (ret < 0 || arg.ret != 0) {
++ pr_err("TA_BSEC invoke failed TEE err:%#x, ret:%#x\n", arg.ret, ret);
++ if (!ret)
++ ret = -EIO;
++ }
++ pr_debug("Write OTPs %d to %zu, ret=%d\n", offset / 4, (offset + bytes) / 4, ret);
++
++ /* Lock the upper OTPs with ECC protection, word programming only */
++ if (!ret && ((offset + bytes) >= (lower * 4))) {
++ u32 start, nb_lock;
++ u32 *lock = (u32 *)shm_buf;
++ int i;
++
++ /*
++ * don't lock the lower OTPs, no ECC protection and incremental
++ * bit programming, a second write is allowed
++ */
++ start = max_t(u32, offset, lower * 4);
++ nb_lock = (offset + bytes - start) / 4;
++
++ param[0].u.value.a = start;
++ param[0].u.value.b = LOCK_ACCESS;
++ param[1].u.memref.size = nb_lock * 4;
++
++ for (i = 0; i < nb_lock; i++)
++ lock[i] = LOCK_PERM;
++
++ ret = tee_client_invoke_func(ctx, &arg, param);
++ if (ret < 0 || arg.ret != 0) {
++ pr_err("TA_BSEC invoke failed TEE err:%#x, ret:%#x\n", arg.ret, ret);
++ if (!ret)
++ ret = -EIO;
++ }
++ pr_debug("Lock upper OTPs %d to %d, ret=%d\n",
++ start / 4, start / 4 + nb_lock, ret);
++ }
++
++ tee_shm_free(shm);
++
++out_tee_session:
++ stm32_bsec_ta_close_session(ctx, session_id);
++
++ return ret;
++}
+--- /dev/null
++++ b/drivers/nvmem/stm32-bsec-optee-ta.h
+@@ -0,0 +1,80 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++/*
++ * OP-TEE STM32MP BSEC PTA interface, used by STM32 ROMEM driver
++ *
++ * Copyright (C) 2022, STMicroelectronics - All Rights Reserved
++ */
++
++#if IS_ENABLED(CONFIG_NVMEM_STM32_BSEC_OPTEE_TA)
++/**
++ * stm32_bsec_optee_ta_open() - initialize the STM32 BSEC TA
++ * @ctx: the OP-TEE context on success
++ *
++ * Return:
++ * On success, 0. On failure, -errno.
++ */
++int stm32_bsec_optee_ta_open(struct tee_context **ctx);
++
++/**
++ * stm32_bsec_optee_ta_close() - release the STM32 BSEC TA
++ * @ctx: the OP-TEE context
++ *
++ * This function used to clean the OP-TEE resources initialized in
++ * stm32_bsec_optee_ta_open(); it can be used as callback to
++ * devm_add_action_or_reset()
++ */
++void stm32_bsec_optee_ta_close(void *ctx);
++
++/**
++ * stm32_bsec_optee_ta_read() - nvmem read access using TA client driver
++ * @ctx: the OP-TEE context provided by stm32_bsec_optee_ta_open
++ * @offset: nvmem offset
++ * @buf: buffer to fill with nvem values
++ * @bytes: number of bytes to read
++ *
++ * Return:
++ * On success, 0. On failure, -errno.
++ */
++int stm32_bsec_optee_ta_read(struct tee_context *ctx, unsigned int offset,
++ void *buf, size_t bytes);
++
++/**
++ * stm32_bsec_optee_ta_write() - nvmem write access using TA client driver
++ * @ctx: the OP-TEE context provided by stm32_bsec_optee_ta_open
++ * @lower: number of lower OTP, not protected by ECC
++ * @offset: nvmem offset
++ * @buf: buffer with nvem values
++ * @bytes: number of bytes to write
++ *
++ * Return:
++ * On success, 0. On failure, -errno.
++ */
++int stm32_bsec_optee_ta_write(struct tee_context *ctx, unsigned int lower,
++ unsigned int offset, void *buf, size_t bytes);
++
++#else
++
++static inline int stm32_bsec_optee_ta_open(struct tee_context **ctx)
++{
++ return -EOPNOTSUPP;
++}
++
++static inline void stm32_bsec_optee_ta_close(void *ctx)
++{
++}
++
++static inline int stm32_bsec_optee_ta_read(struct tee_context *ctx,
++ unsigned int offset, void *buf,
++ size_t bytes)
++{
++ return -EOPNOTSUPP;
++}
++
++static inline int stm32_bsec_optee_ta_write(struct tee_context *ctx,
++ unsigned int lower,
++ unsigned int offset, void *buf,
++ size_t bytes)
++{
++ return -EOPNOTSUPP;
++}
++#endif /* CONFIG_NVMEM_STM32_BSEC_OPTEE_TA */
+--- a/drivers/nvmem/stm32-romem.c
++++ b/drivers/nvmem/stm32-romem.c
+@@ -11,6 +11,9 @@
+ #include <linux/module.h>
+ #include <linux/nvmem-provider.h>
+ #include <linux/of_device.h>
++#include <linux/tee_drv.h>
++
++#include "stm32-bsec-optee-ta.h"
+
+ /* BSEC secure service access from non-secure */
+ #define STM32_SMC_BSEC 0x82001003
+@@ -25,12 +28,14 @@
+ struct stm32_romem_cfg {
+ int size;
+ u8 lower;
++ bool ta;
+ };
+
+ struct stm32_romem_priv {
+ void __iomem *base;
+ struct nvmem_config cfg;
+ u8 lower;
++ struct tee_context *ctx;
+ };
+
+ static int stm32_romem_read(void *context, unsigned int offset, void *buf,
+@@ -138,12 +143,29 @@ static int stm32_bsec_write(void *contex
+ return 0;
+ }
+
++static int stm32_bsec_pta_read(void *context, unsigned int offset, void *buf,
++ size_t bytes)
++{
++ struct stm32_romem_priv *priv = context;
++
++ return stm32_bsec_optee_ta_read(priv->ctx, offset, buf, bytes);
++}
++
++static int stm32_bsec_pta_write(void *context, unsigned int offset, void *buf,
++ size_t bytes)
++{
++ struct stm32_romem_priv *priv = context;
++
++ return stm32_bsec_optee_ta_write(priv->ctx, priv->lower, offset, buf, bytes);
++}
++
+ static int stm32_romem_probe(struct platform_device *pdev)
+ {
+ const struct stm32_romem_cfg *cfg;
+ struct device *dev = &pdev->dev;
+ struct stm32_romem_priv *priv;
+ struct resource *res;
++ int rc;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+@@ -173,15 +195,31 @@ static int stm32_romem_probe(struct plat
+ } else {
+ priv->cfg.size = cfg->size;
+ priv->lower = cfg->lower;
+- priv->cfg.reg_read = stm32_bsec_read;
+- priv->cfg.reg_write = stm32_bsec_write;
++ if (cfg->ta) {
++ rc = stm32_bsec_optee_ta_open(&priv->ctx);
++ /* wait for OP-TEE client driver to be up and ready */
++ if (rc)
++ return rc;
++ }
++ if (priv->ctx) {
++ rc = devm_add_action_or_reset(dev, stm32_bsec_optee_ta_close, priv->ctx);
++ if (rc) {
++ dev_err(dev, "devm_add_action_or_reset() failed (%d)\n", rc);
++ return rc;
++ }
++ priv->cfg.reg_read = stm32_bsec_pta_read;
++ priv->cfg.reg_write = stm32_bsec_pta_write;
++ } else {
++ priv->cfg.reg_read = stm32_bsec_read;
++ priv->cfg.reg_write = stm32_bsec_write;
++ }
+ }
+
+ return PTR_ERR_OR_ZERO(devm_nvmem_register(dev, &priv->cfg));
+ }
+
+ /*
+- * STM32MP15 BSEC OTP regions: 4096 OTP bits (with 3072 effective bits)
++ * STM32MP15/13 BSEC OTP regions: 4096 OTP bits (with 3072 effective bits)
+ * => 96 x 32-bits data words
+ * - Lower: 1K bits, 2:1 redundancy, incremental bit programming
+ * => 32 (x 32-bits) lower shadow registers = words 0 to 31
+@@ -191,6 +229,13 @@ static int stm32_romem_probe(struct plat
+ static const struct stm32_romem_cfg stm32mp15_bsec_cfg = {
+ .size = 384,
+ .lower = 32,
++ .ta = false,
++};
++
++static const struct stm32_romem_cfg stm32mp13_bsec_cfg = {
++ .size = 384,
++ .lower = 32,
++ .ta = true,
+ };
+
+ static const struct of_device_id stm32_romem_of_match[] = {
+@@ -198,7 +243,10 @@ static const struct of_device_id stm32_r
+ .compatible = "st,stm32mp15-bsec",
+ .data = (void *)&stm32mp15_bsec_cfg,
+ }, {
++ .compatible = "st,stm32mp13-bsec",
++ .data = (void *)&stm32mp13_bsec_cfg,
+ },
++ { /* sentinel */ },
+ };
+ MODULE_DEVICE_TABLE(of, stm32_romem_of_match);
+
diff --git a/target/linux/generic/backport-6.6/809-v6.3-0008-nvmem-stm32-detect-bsec-pta-presence-for-STM32MP15x.patch b/target/linux/generic/backport-6.6/809-v6.3-0008-nvmem-stm32-detect-bsec-pta-presence-for-STM32MP15x.patch
new file mode 100644
index 0000000000..cea8e93858
--- /dev/null
+++ b/target/linux/generic/backport-6.6/809-v6.3-0008-nvmem-stm32-detect-bsec-pta-presence-for-STM32MP15x.patch
@@ -0,0 +1,85 @@
+From df2f34ef1d924125ffaf29dfdaf7cdbd3183c321 Mon Sep 17 00:00:00 2001
+From: Patrick Delaunay <patrick.delaunay@foss.st.com>
+Date: Mon, 6 Feb 2023 13:43:52 +0000
+Subject: [PATCH] nvmem: stm32: detect bsec pta presence for STM32MP15x
+
+On STM32MP15x SoC, the SMC backend is optional when OP-TEE is used;
+the PTA BSEC should be used as it is done on STM32MP13x platform,
+but the BSEC SMC can be also used: it is a legacy mode in OP-TEE,
+not recommended but used in previous OP-TEE firmware.
+
+The presence of OP-TEE is dynamically detected in STM32MP15x device tree
+and the supported NVMEM backend is dynamically detected:
+- PTA with stm32_bsec_pta_find
+- SMC with stm32_bsec_check
+
+With OP-TEE but without PTA and SMC detection, the probe is deferred for
+STM32MP15x devices.
+
+On STM32MP13x platform, only the PTA is supported with cfg->ta = true
+and this detection is skipped.
+
+Signed-off-by: Patrick Delaunay <patrick.delaunay@foss.st.com>
+Reviewed-by: Etienne Carriere <etienne.carriere@linaro.org>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230206134356.839737-19-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/stm32-romem.c | 38 +++++++++++++++++++++++++++++++++----
+ 1 file changed, 34 insertions(+), 4 deletions(-)
+
+--- a/drivers/nvmem/stm32-romem.c
++++ b/drivers/nvmem/stm32-romem.c
+@@ -159,6 +159,31 @@ static int stm32_bsec_pta_write(void *co
+ return stm32_bsec_optee_ta_write(priv->ctx, priv->lower, offset, buf, bytes);
+ }
+
++static bool stm32_bsec_smc_check(void)
++{
++ u32 val;
++ int ret;
++
++ /* check that the OP-TEE support the BSEC SMC (legacy mode) */
++ ret = stm32_bsec_smc(STM32_SMC_READ_SHADOW, 0, 0, &val);
++
++ return !ret;
++}
++
++static bool optee_presence_check(void)
++{
++ struct device_node *np;
++ bool tee_detected = false;
++
++ /* check that the OP-TEE node is present and available. */
++ np = of_find_compatible_node(NULL, NULL, "linaro,optee-tz");
++ if (np && of_device_is_available(np))
++ tee_detected = true;
++ of_node_put(np);
++
++ return tee_detected;
++}
++
+ static int stm32_romem_probe(struct platform_device *pdev)
+ {
+ const struct stm32_romem_cfg *cfg;
+@@ -195,11 +220,16 @@ static int stm32_romem_probe(struct plat
+ } else {
+ priv->cfg.size = cfg->size;
+ priv->lower = cfg->lower;
+- if (cfg->ta) {
++ if (cfg->ta || optee_presence_check()) {
+ rc = stm32_bsec_optee_ta_open(&priv->ctx);
+- /* wait for OP-TEE client driver to be up and ready */
+- if (rc)
+- return rc;
++ if (rc) {
++ /* wait for OP-TEE client driver to be up and ready */
++ if (rc == -EPROBE_DEFER)
++ return -EPROBE_DEFER;
++ /* BSEC PTA is required or SMC not supported */
++ if (cfg->ta || !stm32_bsec_smc_check())
++ return rc;
++ }
+ }
+ if (priv->ctx) {
+ rc = devm_add_action_or_reset(dev, stm32_bsec_optee_ta_close, priv->ctx);
diff --git a/target/linux/generic/backport-6.6/809-v6.3-0009-nvmem-rave-sp-eeprm-fix-kernel-doc-bad-line-warning.patch b/target/linux/generic/backport-6.6/809-v6.3-0009-nvmem-rave-sp-eeprm-fix-kernel-doc-bad-line-warning.patch
new file mode 100644
index 0000000000..9d6275a737
--- /dev/null
+++ b/target/linux/generic/backport-6.6/809-v6.3-0009-nvmem-rave-sp-eeprm-fix-kernel-doc-bad-line-warning.patch
@@ -0,0 +1,32 @@
+From 3e5ac22aa564026e99defc3a8e02082521a5b231 Mon Sep 17 00:00:00 2001
+From: Randy Dunlap <rdunlap@infradead.org>
+Date: Mon, 6 Feb 2023 13:43:53 +0000
+Subject: [PATCH] nvmem: rave-sp-eeprm: fix kernel-doc bad line warning
+
+Convert an empty line to " *" to avoid a kernel-doc warning:
+
+drivers/nvmem/rave-sp-eeprom.c:48: warning: bad line:
+
+Signed-off-by: Randy Dunlap <rdunlap@infradead.org>
+Cc: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Cc: Andrey Vostrikov <andrey.vostrikov@cogentembedded.com>
+Cc: Nikita Yushchenko <nikita.yoush@cogentembedded.com>
+Cc: Andrey Smirnov <andrew.smirnov@gmail.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230206134356.839737-20-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/rave-sp-eeprom.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/nvmem/rave-sp-eeprom.c
++++ b/drivers/nvmem/rave-sp-eeprom.c
+@@ -45,7 +45,7 @@ enum rave_sp_eeprom_header_size {
+ * @type: Access type (see enum rave_sp_eeprom_access_type)
+ * @success: Success flag (Success = 1, Failure = 0)
+ * @data: Read data
+-
++ *
+ * Note this structure corresponds to RSP_*_EEPROM payload from RAVE
+ * SP ICD
+ */
diff --git a/target/linux/generic/backport-6.6/809-v6.3-0010-nvmem-qcom-spmi-sdam-register-at-device-init-time.patch b/target/linux/generic/backport-6.6/809-v6.3-0010-nvmem-qcom-spmi-sdam-register-at-device-init-time.patch
new file mode 100644
index 0000000000..1ab9e609d3
--- /dev/null
+++ b/target/linux/generic/backport-6.6/809-v6.3-0010-nvmem-qcom-spmi-sdam-register-at-device-init-time.patch
@@ -0,0 +1,43 @@
+From eb7dda20f42a9137e9ee53d5ed3b743d49338cb5 Mon Sep 17 00:00:00 2001
+From: Johan Hovold <johan+linaro@kernel.org>
+Date: Mon, 6 Feb 2023 13:43:54 +0000
+Subject: [PATCH] nvmem: qcom-spmi-sdam: register at device init time
+
+There are currently no in-tree users of the Qualcomm SDAM nvmem driver
+and there is generally no point in registering a driver that can be
+built as a module at subsys init time.
+
+Register the driver at the normal device init time instead and let
+driver core sort out the probe order.
+
+Signed-off-by: Johan Hovold <johan+linaro@kernel.org>
+Reviewed-by: Bjorn Andersson <andersson@kernel.org>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230206134356.839737-21-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/qcom-spmi-sdam.c | 13 +------------
+ 1 file changed, 1 insertion(+), 12 deletions(-)
+
+--- a/drivers/nvmem/qcom-spmi-sdam.c
++++ b/drivers/nvmem/qcom-spmi-sdam.c
+@@ -175,18 +175,7 @@ static struct platform_driver sdam_drive
+ },
+ .probe = sdam_probe,
+ };
+-
+-static int __init sdam_init(void)
+-{
+- return platform_driver_register(&sdam_driver);
+-}
+-subsys_initcall(sdam_init);
+-
+-static void __exit sdam_exit(void)
+-{
+- return platform_driver_unregister(&sdam_driver);
+-}
+-module_exit(sdam_exit);
++module_platform_driver(sdam_driver);
+
+ MODULE_DESCRIPTION("QCOM SPMI SDAM driver");
+ MODULE_LICENSE("GPL v2");
diff --git a/target/linux/generic/backport-6.6/809-v6.3-0011-nvmem-stm32-fix-OPTEE-dependency.patch b/target/linux/generic/backport-6.6/809-v6.3-0011-nvmem-stm32-fix-OPTEE-dependency.patch
new file mode 100644
index 0000000000..dcf704c6ff
--- /dev/null
+++ b/target/linux/generic/backport-6.6/809-v6.3-0011-nvmem-stm32-fix-OPTEE-dependency.patch
@@ -0,0 +1,46 @@
+From 1dc7e37bb0ec1c997fac82031332a38c7610352f Mon Sep 17 00:00:00 2001
+From: Arnd Bergmann <arnd@arndb.de>
+Date: Mon, 6 Feb 2023 13:43:56 +0000
+Subject: [PATCH] nvmem: stm32: fix OPTEE dependency
+
+The stm32 nvmem driver fails to link as built-in when OPTEE
+is a loadable module:
+
+aarch64-linux-ld: drivers/nvmem/stm32-bsec-optee-ta.o: in function `stm32_bsec:
+stm32-bsec-optee-ta.c:(.text+0xc8): undefined reference to `tee_client_open_session'
+aarch64-linux-ld: drivers/nvmem/stm32-bsec-optee-ta.o: in function `stm32_bsec:
+stm32-bsec-optee-ta.c:(.text+0x1fc): undefined reference to `tee_client_open_context'
+
+Change the CONFIG_NVMEM_STM32_ROMEM definition so it can only
+be built-in if OPTEE is either built-in or disabled, and
+make NVMEM_STM32_BSEC_OPTEE_TA a hidden symbol instead.
+
+Signed-off-by: Arnd Bergmann <arnd@arndb.de>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230206134356.839737-23-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/Kconfig | 5 ++---
+ 1 file changed, 2 insertions(+), 3 deletions(-)
+
+--- a/drivers/nvmem/Kconfig
++++ b/drivers/nvmem/Kconfig
+@@ -291,8 +291,7 @@ config NVMEM_SPRD_EFUSE
+ will be called nvmem-sprd-efuse.
+
+ config NVMEM_STM32_BSEC_OPTEE_TA
+- bool "STM32MP BSEC OP-TEE TA support for nvmem-stm32-romem driver"
+- depends on OPTEE
++ def_bool NVMEM_STM32_ROMEM && OPTEE
+ help
+ Say y here to enable the accesses to STM32MP SoC OTPs by the OP-TEE
+ trusted application STM32MP BSEC.
+@@ -303,7 +302,7 @@ config NVMEM_STM32_BSEC_OPTEE_TA
+ config NVMEM_STM32_ROMEM
+ tristate "STMicroelectronics STM32 factory-programmed memory support"
+ depends on ARCH_STM32 || COMPILE_TEST
+- imply NVMEM_STM32_BSEC_OPTEE_TA
++ depends on OPTEE || !OPTEE
+ help
+ Say y here to enable read-only access for STMicroelectronics STM32
+ factory-programmed memory area.
diff --git a/target/linux/generic/backport-6.6/810-v6.3-i915-Move-list_count-to-list.h-as-list_count_nodes-f.patch b/target/linux/generic/backport-6.6/810-v6.3-i915-Move-list_count-to-list.h-as-list_count_nodes-f.patch
new file mode 100644
index 0000000000..60a90136c9
--- /dev/null
+++ b/target/linux/generic/backport-6.6/810-v6.3-i915-Move-list_count-to-list.h-as-list_count_nodes-f.patch
@@ -0,0 +1,75 @@
+From 4d70c74659d9746502b23d055dba03d1d28ec388 Mon Sep 17 00:00:00 2001
+From: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+Date: Wed, 30 Nov 2022 15:48:35 +0200
+Subject: [PATCH] i915: Move list_count() to list.h as list_count_nodes() for
+ broader use
+
+Some of the existing users, and definitely will be new ones, want to
+count existing nodes in the list. Provide a generic API for that by
+moving code from i915 to list.h.
+
+Reviewed-by: Lucas De Marchi <lucas.demarchi@intel.com>
+Acked-by: Jani Nikula <jani.nikula@intel.com>
+Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+Link: https://lore.kernel.org/r/20221130134838.23805-1-andriy.shevchenko@linux.intel.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpu/drm/i915/gt/intel_engine_cs.c | 15 ++-------------
+ include/linux/list.h | 15 +++++++++++++++
+ 2 files changed, 17 insertions(+), 13 deletions(-)
+
+--- a/drivers/gpu/drm/i915/gt/intel_execlists_submission.c
++++ b/drivers/gpu/drm/i915/gt/intel_execlists_submission.c
+@@ -4154,17 +4154,6 @@ void intel_execlists_show_requests(struc
+ spin_unlock_irqrestore(&sched_engine->lock, flags);
+ }
+
+-static unsigned long list_count(struct list_head *list)
+-{
+- struct list_head *pos;
+- unsigned long count = 0;
+-
+- list_for_each(pos, list)
+- count++;
+-
+- return count;
+-}
+-
+ void intel_execlists_dump_active_requests(struct intel_engine_cs *engine,
+ struct i915_request *hung_rq,
+ struct drm_printer *m)
+@@ -4175,8 +4164,8 @@ void intel_execlists_dump_active_request
+
+ intel_engine_dump_active_requests(&engine->sched_engine->requests, hung_rq, m);
+
+- drm_printf(m, "\tOn hold?: %lu\n",
+- list_count(&engine->sched_engine->hold));
++ drm_printf(m, "\tOn hold?: %zu\n",
++ list_count_nodes(&engine->sched_engine->hold));
+
+ spin_unlock_irqrestore(&engine->sched_engine->lock, flags);
+ }
+--- a/include/linux/list.h
++++ b/include/linux/list.h
+@@ -656,6 +656,21 @@ static inline void list_splice_tail_init
+ pos = n, n = pos->prev)
+
+ /**
++ * list_count_nodes - count nodes in the list
++ * @head: the head for your list.
++ */
++static inline size_t list_count_nodes(struct list_head *head)
++{
++ struct list_head *pos;
++ size_t count = 0;
++
++ list_for_each(pos, head)
++ count++;
++
++ return count;
++}
++
++/**
+ * list_entry_is_head - test if the entry points to the head of the list
+ * @pos: the type * to cursor
+ * @head: the head for your list.
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0001-nvmem-xilinx-zynqmp-make-modular.patch b/target/linux/generic/backport-6.6/811-v6.4-0001-nvmem-xilinx-zynqmp-make-modular.patch
new file mode 100644
index 0000000000..8328e87c0a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0001-nvmem-xilinx-zynqmp-make-modular.patch
@@ -0,0 +1,35 @@
+From bcd1fe07def0f070eb5f31594620aaee6f81d31a Mon Sep 17 00:00:00 2001
+From: Nick Alcock <nick.alcock@oracle.com>
+Date: Tue, 4 Apr 2023 18:21:11 +0100
+Subject: [PATCH] nvmem: xilinx: zynqmp: make modular
+
+This driver has a MODULE_LICENSE but is not tristate so cannot be
+built as a module, unlike all its peers: make it modular to match.
+
+Signed-off-by: Nick Alcock <nick.alcock@oracle.com>
+Suggested-by: Michal Simek <michal.simek@amd.com>
+Cc: Luis Chamberlain <mcgrof@kernel.org>
+Cc: linux-modules@vger.kernel.org
+Cc: linux-kernel@vger.kernel.org
+Cc: Hitomi Hasegawa <hasegawa-hitomi@fujitsu.com>
+Cc: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Cc: Michal Simek <michal.simek@xilinx.com>
+Cc: linux-arm-kernel@lists.infradead.org
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-4-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/Kconfig | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/nvmem/Kconfig
++++ b/drivers/nvmem/Kconfig
+@@ -368,7 +368,7 @@ config NVMEM_VF610_OCOTP
+ be called nvmem-vf610-ocotp.
+
+ config NVMEM_ZYNQMP
+- bool "Xilinx ZYNQMP SoC nvmem firmware support"
++ tristate "Xilinx ZYNQMP SoC nvmem firmware support"
+ depends on ARCH_ZYNQMP
+ help
+ This is a driver to access hardware related data like
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0002-nvmem-core-introduce-NVMEM-layouts.patch b/target/linux/generic/backport-6.6/811-v6.4-0002-nvmem-core-introduce-NVMEM-layouts.patch
new file mode 100644
index 0000000000..94cd23c18a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0002-nvmem-core-introduce-NVMEM-layouts.patch
@@ -0,0 +1,387 @@
+From 266570f496b90dea8fda893c2cf7c28d63ae2bd9 Mon Sep 17 00:00:00 2001
+From: Michael Walle <michael@walle.cc>
+Date: Tue, 4 Apr 2023 18:21:21 +0100
+Subject: [PATCH] nvmem: core: introduce NVMEM layouts
+
+NVMEM layouts are used to generate NVMEM cells during runtime. Think of
+an EEPROM with a well-defined conent. For now, the content can be
+described by a device tree or a board file. But this only works if the
+offsets and lengths are static and don't change. One could also argue
+that putting the layout of the EEPROM in the device tree is the wrong
+place. Instead, the device tree should just have a specific compatible
+string.
+
+Right now there are two use cases:
+ (1) The NVMEM cell needs special processing. E.g. if it only specifies
+ a base MAC address offset and you need to add an offset, or it
+ needs to parse a MAC from ASCII format or some proprietary format.
+ (Post processing of cells is added in a later commit).
+ (2) u-boot environment parsing. The cells don't have a particular
+ offset but it needs parsing the content to determine the offsets
+ and length.
+
+Co-developed-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-14-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ Documentation/driver-api/nvmem.rst | 15 ++++
+ drivers/nvmem/Kconfig | 4 +
+ drivers/nvmem/Makefile | 1 +
+ drivers/nvmem/core.c | 120 +++++++++++++++++++++++++++++
+ drivers/nvmem/layouts/Kconfig | 5 ++
+ drivers/nvmem/layouts/Makefile | 4 +
+ include/linux/nvmem-consumer.h | 7 ++
+ include/linux/nvmem-provider.h | 51 ++++++++++++
+ 8 files changed, 207 insertions(+)
+ create mode 100644 drivers/nvmem/layouts/Kconfig
+ create mode 100644 drivers/nvmem/layouts/Makefile
+
+--- a/Documentation/driver-api/nvmem.rst
++++ b/Documentation/driver-api/nvmem.rst
+@@ -185,3 +185,18 @@ ex::
+ =====================
+
+ See Documentation/devicetree/bindings/nvmem/nvmem.txt
++
++8. NVMEM layouts
++================
++
++NVMEM layouts are yet another mechanism to create cells. With the device
++tree binding it is possible to specify simple cells by using an offset
++and a length. Sometimes, the cells doesn't have a static offset, but
++the content is still well defined, e.g. tag-length-values. In this case,
++the NVMEM device content has to be first parsed and the cells need to
++be added accordingly. Layouts let you read the content of the NVMEM device
++and let you add cells dynamically.
++
++Another use case for layouts is the post processing of cells. With layouts,
++it is possible to associate a custom post processing hook to a cell. It
++even possible to add this hook to cells not created by the layout itself.
+--- a/drivers/nvmem/Kconfig
++++ b/drivers/nvmem/Kconfig
+@@ -21,6 +21,10 @@ config NVMEM_SYSFS
+ This interface is mostly used by userspace applications to
+ read/write directly into nvmem.
+
++# Layouts
++
++source "drivers/nvmem/layouts/Kconfig"
++
+ # Devices
+
+ config NVMEM_APPLE_EFUSES
+--- a/drivers/nvmem/Makefile
++++ b/drivers/nvmem/Makefile
+@@ -5,6 +5,7 @@
+
+ obj-$(CONFIG_NVMEM) += nvmem_core.o
+ nvmem_core-y := core.o
++obj-y += layouts/
+
+ # Devices
+ obj-$(CONFIG_NVMEM_APPLE_EFUSES) += nvmem-apple-efuses.o
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -40,6 +40,7 @@ struct nvmem_device {
+ nvmem_reg_write_t reg_write;
+ nvmem_cell_post_process_t cell_post_process;
+ struct gpio_desc *wp_gpio;
++ struct nvmem_layout *layout;
+ void *priv;
+ };
+
+@@ -74,6 +75,9 @@ static LIST_HEAD(nvmem_lookup_list);
+
+ static BLOCKING_NOTIFIER_HEAD(nvmem_notifier);
+
++static DEFINE_SPINLOCK(nvmem_layout_lock);
++static LIST_HEAD(nvmem_layouts);
++
+ static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
+ void *val, size_t bytes)
+ {
+@@ -728,6 +732,101 @@ static int nvmem_add_cells_from_of(struc
+ return 0;
+ }
+
++int __nvmem_layout_register(struct nvmem_layout *layout, struct module *owner)
++{
++ layout->owner = owner;
++
++ spin_lock(&nvmem_layout_lock);
++ list_add(&layout->node, &nvmem_layouts);
++ spin_unlock(&nvmem_layout_lock);
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(__nvmem_layout_register);
++
++void nvmem_layout_unregister(struct nvmem_layout *layout)
++{
++ spin_lock(&nvmem_layout_lock);
++ list_del(&layout->node);
++ spin_unlock(&nvmem_layout_lock);
++}
++EXPORT_SYMBOL_GPL(nvmem_layout_unregister);
++
++static struct nvmem_layout *nvmem_layout_get(struct nvmem_device *nvmem)
++{
++ struct device_node *layout_np, *np = nvmem->dev.of_node;
++ struct nvmem_layout *l, *layout = NULL;
++
++ layout_np = of_get_child_by_name(np, "nvmem-layout");
++ if (!layout_np)
++ return NULL;
++
++ spin_lock(&nvmem_layout_lock);
++
++ list_for_each_entry(l, &nvmem_layouts, node) {
++ if (of_match_node(l->of_match_table, layout_np)) {
++ if (try_module_get(l->owner))
++ layout = l;
++
++ break;
++ }
++ }
++
++ spin_unlock(&nvmem_layout_lock);
++ of_node_put(layout_np);
++
++ return layout;
++}
++
++static void nvmem_layout_put(struct nvmem_layout *layout)
++{
++ if (layout)
++ module_put(layout->owner);
++}
++
++static int nvmem_add_cells_from_layout(struct nvmem_device *nvmem)
++{
++ struct nvmem_layout *layout = nvmem->layout;
++ int ret;
++
++ if (layout && layout->add_cells) {
++ ret = layout->add_cells(&nvmem->dev, nvmem, layout);
++ if (ret)
++ return ret;
++ }
++
++ return 0;
++}
++
++#if IS_ENABLED(CONFIG_OF)
++/**
++ * of_nvmem_layout_get_container() - Get OF node to layout container.
++ *
++ * @nvmem: nvmem device.
++ *
++ * Return: a node pointer with refcount incremented or NULL if no
++ * container exists. Use of_node_put() on it when done.
++ */
++struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem)
++{
++ return of_get_child_by_name(nvmem->dev.of_node, "nvmem-layout");
++}
++EXPORT_SYMBOL_GPL(of_nvmem_layout_get_container);
++#endif
++
++const void *nvmem_layout_get_match_data(struct nvmem_device *nvmem,
++ struct nvmem_layout *layout)
++{
++ struct device_node __maybe_unused *layout_np;
++ const struct of_device_id *match;
++
++ layout_np = of_nvmem_layout_get_container(nvmem);
++ match = of_match_node(layout->of_match_table, layout_np);
++
++ return match ? match->data : NULL;
++}
++EXPORT_SYMBOL_GPL(nvmem_layout_get_match_data);
++
+ /**
+ * nvmem_register() - Register a nvmem device for given nvmem_config.
+ * Also creates a binary entry in /sys/bus/nvmem/devices/dev-name/nvmem
+@@ -834,6 +933,12 @@ struct nvmem_device *nvmem_register(cons
+ goto err_put_device;
+ }
+
++ /*
++ * If the driver supplied a layout by config->layout, the module
++ * pointer will be NULL and nvmem_layout_put() will be a noop.
++ */
++ nvmem->layout = config->layout ?: nvmem_layout_get(nvmem);
++
+ if (config->cells) {
+ rval = nvmem_add_cells(nvmem, config->cells, config->ncells);
+ if (rval)
+@@ -854,12 +959,17 @@ struct nvmem_device *nvmem_register(cons
+ if (rval)
+ goto err_remove_cells;
+
++ rval = nvmem_add_cells_from_layout(nvmem);
++ if (rval)
++ goto err_remove_cells;
++
+ blocking_notifier_call_chain(&nvmem_notifier, NVMEM_ADD, nvmem);
+
+ return nvmem;
+
+ err_remove_cells:
+ nvmem_device_remove_all_cells(nvmem);
++ nvmem_layout_put(nvmem->layout);
+ if (config->compat)
+ nvmem_sysfs_remove_compat(nvmem, config);
+ err_put_device:
+@@ -881,6 +991,7 @@ static void nvmem_device_release(struct
+ device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom);
+
+ nvmem_device_remove_all_cells(nvmem);
++ nvmem_layout_put(nvmem->layout);
+ device_unregister(&nvmem->dev);
+ }
+
+@@ -1246,6 +1357,15 @@ struct nvmem_cell *of_nvmem_cell_get(str
+ return ERR_PTR(-EINVAL);
+ }
+
++ /* nvmem layouts produce cells within the nvmem-layout container */
++ if (of_node_name_eq(nvmem_np, "nvmem-layout")) {
++ nvmem_np = of_get_next_parent(nvmem_np);
++ if (!nvmem_np) {
++ of_node_put(cell_np);
++ return ERR_PTR(-EINVAL);
++ }
++ }
++
+ nvmem = __nvmem_device_get(nvmem_np, device_match_of_node);
+ of_node_put(nvmem_np);
+ if (IS_ERR(nvmem)) {
+--- /dev/null
++++ b/drivers/nvmem/layouts/Kconfig
+@@ -0,0 +1,5 @@
++# SPDX-License-Identifier: GPL-2.0
++
++menu "Layout Types"
++
++endmenu
+--- /dev/null
++++ b/drivers/nvmem/layouts/Makefile
+@@ -0,0 +1,4 @@
++# SPDX-License-Identifier: GPL-2.0
++#
++# Makefile for nvmem layouts.
++#
+--- a/include/linux/nvmem-consumer.h
++++ b/include/linux/nvmem-consumer.h
+@@ -239,6 +239,7 @@ struct nvmem_cell *of_nvmem_cell_get(str
+ const char *id);
+ struct nvmem_device *of_nvmem_device_get(struct device_node *np,
+ const char *name);
++struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem);
+ #else
+ static inline struct nvmem_cell *of_nvmem_cell_get(struct device_node *np,
+ const char *id)
+@@ -251,6 +252,12 @@ static inline struct nvmem_device *of_nv
+ {
+ return ERR_PTR(-EOPNOTSUPP);
+ }
++
++static inline struct device_node *
++of_nvmem_layout_get_container(struct nvmem_device *nvmem)
++{
++ return ERR_PTR(-EOPNOTSUPP);
++}
+ #endif /* CONFIG_NVMEM && CONFIG_OF */
+
+ #endif /* ifndef _LINUX_NVMEM_CONSUMER_H */
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -88,6 +88,7 @@ struct nvmem_cell_info {
+ * @stride: Minimum read/write access stride.
+ * @priv: User context passed to read/write callbacks.
+ * @ignore_wp: Write Protect pin is managed by the provider.
++ * @layout: Fixed layout associated with this nvmem device.
+ *
+ * Note: A default "nvmem<id>" name will be assigned to the device if
+ * no name is specified in its configuration. In such case "<id>" is
+@@ -109,6 +110,7 @@ struct nvmem_config {
+ bool read_only;
+ bool root_only;
+ bool ignore_wp;
++ struct nvmem_layout *layout;
+ struct device_node *of_node;
+ bool no_of_node;
+ nvmem_reg_read_t reg_read;
+@@ -142,6 +144,33 @@ struct nvmem_cell_table {
+ struct list_head node;
+ };
+
++/**
++ * struct nvmem_layout - NVMEM layout definitions
++ *
++ * @name: Layout name.
++ * @of_match_table: Open firmware match table.
++ * @add_cells: Will be called if a nvmem device is found which
++ * has this layout. The function will add layout
++ * specific cells with nvmem_add_one_cell().
++ * @owner: Pointer to struct module.
++ * @node: List node.
++ *
++ * A nvmem device can hold a well defined structure which can just be
++ * evaluated during runtime. For example a TLV list, or a list of "name=val"
++ * pairs. A nvmem layout can parse the nvmem device and add appropriate
++ * cells.
++ */
++struct nvmem_layout {
++ const char *name;
++ const struct of_device_id *of_match_table;
++ int (*add_cells)(struct device *dev, struct nvmem_device *nvmem,
++ struct nvmem_layout *layout);
++
++ /* private */
++ struct module *owner;
++ struct list_head node;
++};
++
+ #if IS_ENABLED(CONFIG_NVMEM)
+
+ struct nvmem_device *nvmem_register(const struct nvmem_config *cfg);
+@@ -156,6 +185,14 @@ void nvmem_del_cell_table(struct nvmem_c
+ int nvmem_add_one_cell(struct nvmem_device *nvmem,
+ const struct nvmem_cell_info *info);
+
++int __nvmem_layout_register(struct nvmem_layout *layout, struct module *owner);
++#define nvmem_layout_register(layout) \
++ __nvmem_layout_register(layout, THIS_MODULE)
++void nvmem_layout_unregister(struct nvmem_layout *layout);
++
++const void *nvmem_layout_get_match_data(struct nvmem_device *nvmem,
++ struct nvmem_layout *layout);
++
+ #else
+
+ static inline struct nvmem_device *nvmem_register(const struct nvmem_config *c)
+@@ -179,5 +216,19 @@ static inline int nvmem_add_one_cell(str
+ return -EOPNOTSUPP;
+ }
+
++static inline int nvmem_layout_register(struct nvmem_layout *layout)
++{
++ return -EOPNOTSUPP;
++}
++
++static inline void nvmem_layout_unregister(struct nvmem_layout *layout) {}
++
++static inline const void *
++nvmem_layout_get_match_data(struct nvmem_device *nvmem,
++ struct nvmem_layout *layout)
++{
++ return NULL;
++}
++
+ #endif /* CONFIG_NVMEM */
+ #endif /* ifndef _LINUX_NVMEM_PROVIDER_H */
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0003-nvmem-core-handle-the-absence-of-expected-layouts.patch b/target/linux/generic/backport-6.6/811-v6.4-0003-nvmem-core-handle-the-absence-of-expected-layouts.patch
new file mode 100644
index 0000000000..6fa7b6382d
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0003-nvmem-core-handle-the-absence-of-expected-layouts.patch
@@ -0,0 +1,61 @@
+From 6468a6f45148fb5e95c86b4efebf63f9abcd2137 Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Tue, 4 Apr 2023 18:21:22 +0100
+Subject: [PATCH] nvmem: core: handle the absence of expected layouts
+
+Make nvmem_layout_get() return -EPROBE_DEFER while the expected layout
+is not available. This condition cannot be triggered today as nvmem
+layout drivers are initialed as part of an early init call, but soon
+these drivers will be converted into modules and be initialized with a
+standard priority, so the unavailability of the drivers might become a
+reality that must be taken care of.
+
+Let's anticipate this by telling the caller the layout might not yet be
+available. A probe deferral is requested in this case.
+
+Please note this does not affect any nvmem device not using layouts,
+because an early check against the "nvmem-layout" container presence
+will return NULL in this case.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Tested-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-15-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 10 +++++++++-
+ 1 file changed, 9 insertions(+), 1 deletion(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -755,7 +755,7 @@ EXPORT_SYMBOL_GPL(nvmem_layout_unregiste
+ static struct nvmem_layout *nvmem_layout_get(struct nvmem_device *nvmem)
+ {
+ struct device_node *layout_np, *np = nvmem->dev.of_node;
+- struct nvmem_layout *l, *layout = NULL;
++ struct nvmem_layout *l, *layout = ERR_PTR(-EPROBE_DEFER);
+
+ layout_np = of_get_child_by_name(np, "nvmem-layout");
+ if (!layout_np)
+@@ -938,6 +938,13 @@ struct nvmem_device *nvmem_register(cons
+ * pointer will be NULL and nvmem_layout_put() will be a noop.
+ */
+ nvmem->layout = config->layout ?: nvmem_layout_get(nvmem);
++ if (IS_ERR(nvmem->layout)) {
++ rval = PTR_ERR(nvmem->layout);
++ nvmem->layout = NULL;
++
++ if (rval == -EPROBE_DEFER)
++ goto err_teardown_compat;
++ }
+
+ if (config->cells) {
+ rval = nvmem_add_cells(nvmem, config->cells, config->ncells);
+@@ -970,6 +977,7 @@ struct nvmem_device *nvmem_register(cons
+ err_remove_cells:
+ nvmem_device_remove_all_cells(nvmem);
+ nvmem_layout_put(nvmem->layout);
++err_teardown_compat:
+ if (config->compat)
+ nvmem_sysfs_remove_compat(nvmem, config);
+ err_put_device:
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0004-nvmem-core-request-layout-modules-loading.patch b/target/linux/generic/backport-6.6/811-v6.4-0004-nvmem-core-request-layout-modules-loading.patch
new file mode 100644
index 0000000000..b9341666f9
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0004-nvmem-core-request-layout-modules-loading.patch
@@ -0,0 +1,52 @@
+From b1c37bec1ccfe5ccab72bc0ddc0dfa45c43e2de2 Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Tue, 4 Apr 2023 18:21:23 +0100
+Subject: [PATCH] nvmem: core: request layout modules loading
+
+When a storage device like an eeprom or an mtd device probes, it
+registers an nvmem device if the nvmem subsystem has been enabled (bool
+symbol). During nvmem registration, if the device is using layouts to
+expose dynamic nvmem cells, the core will first try to get a reference
+over the layout driver callbacks. In practice there is not relationship
+that can be described between the storage driver and the nvmem
+layout. So there is no way we can enforce both drivers will be built-in
+or both will be modules. If the storage device driver is built-in but
+the layout is built as a module, instead of badly failing with an
+endless probe deferral loop, lets just make a modprobe call in case the
+driver was made available in an initramfs with
+of_device_node_request_module(), and offer a fully functional system to
+the user.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Tested-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-16-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -17,6 +17,7 @@
+ #include <linux/nvmem-provider.h>
+ #include <linux/gpio/consumer.h>
+ #include <linux/of.h>
++#include <linux/of_device.h>
+ #include <linux/slab.h>
+
+ struct nvmem_device {
+@@ -761,6 +762,13 @@ static struct nvmem_layout *nvmem_layout
+ if (!layout_np)
+ return NULL;
+
++ /*
++ * In case the nvmem device was built-in while the layout was built as a
++ * module, we shall manually request the layout driver loading otherwise
++ * we'll never have any match.
++ */
++ of_request_module(layout_np);
++
+ spin_lock(&nvmem_layout_lock);
+
+ list_for_each_entry(l, &nvmem_layouts, node) {
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0005-nvmem-core-add-per-cell-post-processing.patch b/target/linux/generic/backport-6.6/811-v6.4-0005-nvmem-core-add-per-cell-post-processing.patch
new file mode 100644
index 0000000000..53628cd4e4
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0005-nvmem-core-add-per-cell-post-processing.patch
@@ -0,0 +1,86 @@
+From 345ec382cd4b736c36e01f155d08c913b225b736 Mon Sep 17 00:00:00 2001
+From: Michael Walle <michael@walle.cc>
+Date: Tue, 4 Apr 2023 18:21:24 +0100
+Subject: [PATCH] nvmem: core: add per-cell post processing
+
+Instead of relying on the name the consumer is using for the cell, like
+it is done for the nvmem .cell_post_process configuration parameter,
+provide a per-cell post processing hook. This can then be populated by
+the NVMEM provider (or the NVMEM layout) when adding the cell.
+
+Signed-off-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-17-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 17 +++++++++++++++++
+ include/linux/nvmem-provider.h | 3 +++
+ 2 files changed, 20 insertions(+)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -54,6 +54,7 @@ struct nvmem_cell_entry {
+ int bytes;
+ int bit_offset;
+ int nbits;
++ nvmem_cell_post_process_t read_post_process;
+ struct device_node *np;
+ struct nvmem_device *nvmem;
+ struct list_head node;
+@@ -470,6 +471,7 @@ static int nvmem_cell_info_to_nvmem_cell
+ cell->offset = info->offset;
+ cell->bytes = info->bytes;
+ cell->name = info->name;
++ cell->read_post_process = info->read_post_process;
+
+ cell->bit_offset = info->bit_offset;
+ cell->nbits = info->nbits;
+@@ -1563,6 +1565,13 @@ static int __nvmem_cell_read(struct nvme
+ if (cell->bit_offset || cell->nbits)
+ nvmem_shift_read_buffer_in_place(cell, buf);
+
++ if (cell->read_post_process) {
++ rc = cell->read_post_process(nvmem->priv, id, index,
++ cell->offset, buf, cell->bytes);
++ if (rc)
++ return rc;
++ }
++
+ if (nvmem->cell_post_process) {
+ rc = nvmem->cell_post_process(nvmem->priv, id, index,
+ cell->offset, buf, cell->bytes);
+@@ -1671,6 +1680,14 @@ static int __nvmem_cell_entry_write(stru
+ (cell->bit_offset == 0 && len != cell->bytes))
+ return -EINVAL;
+
++ /*
++ * Any cells which have a read_post_process hook are read-only because
++ * we cannot reverse the operation and it might affect other cells,
++ * too.
++ */
++ if (cell->read_post_process)
++ return -EINVAL;
++
+ if (cell->bit_offset || cell->nbits) {
+ buf = nvmem_cell_prepare_write_buffer(cell, buf, len);
+ if (IS_ERR(buf))
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -54,6 +54,8 @@ struct nvmem_keepout {
+ * @bit_offset: Bit offset if cell is smaller than a byte.
+ * @nbits: Number of bits.
+ * @np: Optional device_node pointer.
++ * @read_post_process: Callback for optional post processing of cell data
++ * on reads.
+ */
+ struct nvmem_cell_info {
+ const char *name;
+@@ -62,6 +64,7 @@ struct nvmem_cell_info {
+ unsigned int bit_offset;
+ unsigned int nbits;
+ struct device_node *np;
++ nvmem_cell_post_process_t read_post_process;
+ };
+
+ /**
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0006-nvmem-core-allow-to-modify-a-cell-before-adding-it.patch b/target/linux/generic/backport-6.6/811-v6.4-0006-nvmem-core-allow-to-modify-a-cell-before-adding-it.patch
new file mode 100644
index 0000000000..32990148c8
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0006-nvmem-core-allow-to-modify-a-cell-before-adding-it.patch
@@ -0,0 +1,59 @@
+From de12c9691501ccba41a154c223869f82be4c12fd Mon Sep 17 00:00:00 2001
+From: Michael Walle <michael@walle.cc>
+Date: Tue, 4 Apr 2023 18:21:25 +0100
+Subject: [PATCH] nvmem: core: allow to modify a cell before adding it
+
+Provide a way to modify a cell before it will get added. This is useful
+to attach a custom post processing hook via a layout.
+
+Signed-off-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-18-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 4 ++++
+ include/linux/nvmem-provider.h | 5 +++++
+ 2 files changed, 9 insertions(+)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -695,6 +695,7 @@ static int nvmem_validate_keepouts(struc
+
+ static int nvmem_add_cells_from_of(struct nvmem_device *nvmem)
+ {
++ struct nvmem_layout *layout = nvmem->layout;
+ struct device *dev = &nvmem->dev;
+ struct device_node *child;
+ const __be32 *addr;
+@@ -724,6 +725,9 @@ static int nvmem_add_cells_from_of(struc
+
+ info.np = of_node_get(child);
+
++ if (layout && layout->fixup_cell_info)
++ layout->fixup_cell_info(nvmem, layout, &info);
++
+ ret = nvmem_add_one_cell(nvmem, &info);
+ kfree(info.name);
+ if (ret) {
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -155,6 +155,8 @@ struct nvmem_cell_table {
+ * @add_cells: Will be called if a nvmem device is found which
+ * has this layout. The function will add layout
+ * specific cells with nvmem_add_one_cell().
++ * @fixup_cell_info: Will be called before a cell is added. Can be
++ * used to modify the nvmem_cell_info.
+ * @owner: Pointer to struct module.
+ * @node: List node.
+ *
+@@ -168,6 +170,9 @@ struct nvmem_layout {
+ const struct of_device_id *of_match_table;
+ int (*add_cells)(struct device *dev, struct nvmem_device *nvmem,
+ struct nvmem_layout *layout);
++ void (*fixup_cell_info)(struct nvmem_device *nvmem,
++ struct nvmem_layout *layout,
++ struct nvmem_cell_info *cell);
+
+ /* private */
+ struct module *owner;
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0007-nvmem-imx-ocotp-replace-global-post-processing-with-.patch b/target/linux/generic/backport-6.6/811-v6.4-0007-nvmem-imx-ocotp-replace-global-post-processing-with-.patch
new file mode 100644
index 0000000000..2a5fa618ea
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0007-nvmem-imx-ocotp-replace-global-post-processing-with-.patch
@@ -0,0 +1,81 @@
+From 6c56a82d7895a213a43182a5d01a21a906a79847 Mon Sep 17 00:00:00 2001
+From: Michael Walle <michael@walle.cc>
+Date: Tue, 4 Apr 2023 18:21:26 +0100
+Subject: [PATCH] nvmem: imx-ocotp: replace global post processing with layouts
+
+In preparation of retiring the global post processing hook change this
+driver to use layouts. The layout will be supplied during registration
+and will be used to add the post processing hook to all added cells.
+
+Signed-off-by: Michael Walle <michael@walle.cc>
+Tested-by: Michael Walle <michael@walle.cc> # on kontron-pitx-imx8m
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-19-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/imx-ocotp.c | 30 +++++++++++++++++++-----------
+ 1 file changed, 19 insertions(+), 11 deletions(-)
+
+--- a/drivers/nvmem/imx-ocotp.c
++++ b/drivers/nvmem/imx-ocotp.c
+@@ -225,18 +225,13 @@ read_end:
+ static int imx_ocotp_cell_pp(void *context, const char *id, int index,
+ unsigned int offset, void *data, size_t bytes)
+ {
+- struct ocotp_priv *priv = context;
++ u8 *buf = data;
++ int i;
+
+ /* Deal with some post processing of nvmem cell data */
+- if (id && !strcmp(id, "mac-address")) {
+- if (priv->params->reverse_mac_address) {
+- u8 *buf = data;
+- int i;
+-
+- for (i = 0; i < bytes/2; i++)
+- swap(buf[i], buf[bytes - i - 1]);
+- }
+- }
++ if (id && !strcmp(id, "mac-address"))
++ for (i = 0; i < bytes / 2; i++)
++ swap(buf[i], buf[bytes - i - 1]);
+
+ return 0;
+ }
+@@ -488,7 +483,6 @@ static struct nvmem_config imx_ocotp_nvm
+ .stride = 1,
+ .reg_read = imx_ocotp_read,
+ .reg_write = imx_ocotp_write,
+- .cell_post_process = imx_ocotp_cell_pp,
+ };
+
+ static const struct ocotp_params imx6q_params = {
+@@ -595,6 +589,17 @@ static const struct of_device_id imx_oco
+ };
+ MODULE_DEVICE_TABLE(of, imx_ocotp_dt_ids);
+
++static void imx_ocotp_fixup_cell_info(struct nvmem_device *nvmem,
++ struct nvmem_layout *layout,
++ struct nvmem_cell_info *cell)
++{
++ cell->read_post_process = imx_ocotp_cell_pp;
++}
++
++struct nvmem_layout imx_ocotp_layout = {
++ .fixup_cell_info = imx_ocotp_fixup_cell_info,
++};
++
+ static int imx_ocotp_probe(struct platform_device *pdev)
+ {
+ struct device *dev = &pdev->dev;
+@@ -619,6 +624,9 @@ static int imx_ocotp_probe(struct platfo
+ imx_ocotp_nvmem_config.size = 4 * priv->params->nregs;
+ imx_ocotp_nvmem_config.dev = dev;
+ imx_ocotp_nvmem_config.priv = priv;
++ if (priv->params->reverse_mac_address)
++ imx_ocotp_nvmem_config.layout = &imx_ocotp_layout;
++
+ priv->config = &imx_ocotp_nvmem_config;
+
+ clk_prepare_enable(priv->clk);
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0008-nvmem-cell-drop-global-cell_post_process.patch b/target/linux/generic/backport-6.6/811-v6.4-0008-nvmem-cell-drop-global-cell_post_process.patch
new file mode 100644
index 0000000000..eac202b882
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0008-nvmem-cell-drop-global-cell_post_process.patch
@@ -0,0 +1,68 @@
+From 011e40a166fdaa65fb9946b7cd91efec85b70dbb Mon Sep 17 00:00:00 2001
+From: Michael Walle <michael@walle.cc>
+Date: Tue, 4 Apr 2023 18:21:27 +0100
+Subject: [PATCH] nvmem: cell: drop global cell_post_process
+
+There are no users anymore for the global cell_post_process callback
+anymore. New users should use proper nvmem layouts.
+
+Signed-off-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-20-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 9 ---------
+ include/linux/nvmem-provider.h | 2 --
+ 2 files changed, 11 deletions(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -39,7 +39,6 @@ struct nvmem_device {
+ unsigned int nkeepout;
+ nvmem_reg_read_t reg_read;
+ nvmem_reg_write_t reg_write;
+- nvmem_cell_post_process_t cell_post_process;
+ struct gpio_desc *wp_gpio;
+ struct nvmem_layout *layout;
+ void *priv;
+@@ -903,7 +902,6 @@ struct nvmem_device *nvmem_register(cons
+ nvmem->type = config->type;
+ nvmem->reg_read = config->reg_read;
+ nvmem->reg_write = config->reg_write;
+- nvmem->cell_post_process = config->cell_post_process;
+ nvmem->keepout = config->keepout;
+ nvmem->nkeepout = config->nkeepout;
+ if (config->of_node)
+@@ -1575,13 +1573,6 @@ static int __nvmem_cell_read(struct nvme
+ if (rc)
+ return rc;
+ }
+-
+- if (nvmem->cell_post_process) {
+- rc = nvmem->cell_post_process(nvmem->priv, id, index,
+- cell->offset, buf, cell->bytes);
+- if (rc)
+- return rc;
+- }
+
+ if (len)
+ *len = cell->bytes;
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -85,7 +85,6 @@ struct nvmem_cell_info {
+ * @no_of_node: Device should not use the parent's of_node even if it's !NULL.
+ * @reg_read: Callback to read data.
+ * @reg_write: Callback to write data.
+- * @cell_post_process: Callback for vendor specific post processing of cell data
+ * @size: Device size.
+ * @word_size: Minimum read/write access granularity.
+ * @stride: Minimum read/write access stride.
+@@ -118,7 +117,6 @@ struct nvmem_config {
+ bool no_of_node;
+ nvmem_reg_read_t reg_read;
+ nvmem_reg_write_t reg_write;
+- nvmem_cell_post_process_t cell_post_process;
+ int size;
+ int word_size;
+ int stride;
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0009-nvmem-core-provide-own-priv-pointer-in-post-process-.patch b/target/linux/generic/backport-6.6/811-v6.4-0009-nvmem-core-provide-own-priv-pointer-in-post-process-.patch
new file mode 100644
index 0000000000..46b30a2ed9
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0009-nvmem-core-provide-own-priv-pointer-in-post-process-.patch
@@ -0,0 +1,76 @@
+From 8a134fd9f9323f4c39ec27055b3d3723cfb5c1e9 Mon Sep 17 00:00:00 2001
+From: Michael Walle <michael@walle.cc>
+Date: Tue, 4 Apr 2023 18:21:28 +0100
+Subject: [PATCH] nvmem: core: provide own priv pointer in post process
+ callback
+
+It doesn't make any more sense to have a opaque pointer set up by the
+nvmem device. Usually, the layout isn't associated with a particular
+nvmem device. Instead, let the caller who set the post process callback
+provide the priv pointer.
+
+Signed-off-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-21-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 4 +++-
+ include/linux/nvmem-provider.h | 5 ++++-
+ 2 files changed, 7 insertions(+), 2 deletions(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -54,6 +54,7 @@ struct nvmem_cell_entry {
+ int bit_offset;
+ int nbits;
+ nvmem_cell_post_process_t read_post_process;
++ void *priv;
+ struct device_node *np;
+ struct nvmem_device *nvmem;
+ struct list_head node;
+@@ -471,6 +472,7 @@ static int nvmem_cell_info_to_nvmem_cell
+ cell->bytes = info->bytes;
+ cell->name = info->name;
+ cell->read_post_process = info->read_post_process;
++ cell->priv = info->priv;
+
+ cell->bit_offset = info->bit_offset;
+ cell->nbits = info->nbits;
+@@ -1568,7 +1570,7 @@ static int __nvmem_cell_read(struct nvme
+ nvmem_shift_read_buffer_in_place(cell, buf);
+
+ if (cell->read_post_process) {
+- rc = cell->read_post_process(nvmem->priv, id, index,
++ rc = cell->read_post_process(cell->priv, id, index,
+ cell->offset, buf, cell->bytes);
+ if (rc)
+ return rc;
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -20,7 +20,8 @@ typedef int (*nvmem_reg_write_t)(void *p
+ void *val, size_t bytes);
+ /* used for vendor specific post processing of cell data */
+ typedef int (*nvmem_cell_post_process_t)(void *priv, const char *id, int index,
+- unsigned int offset, void *buf, size_t bytes);
++ unsigned int offset, void *buf,
++ size_t bytes);
+
+ enum nvmem_type {
+ NVMEM_TYPE_UNKNOWN = 0,
+@@ -56,6 +57,7 @@ struct nvmem_keepout {
+ * @np: Optional device_node pointer.
+ * @read_post_process: Callback for optional post processing of cell data
+ * on reads.
++ * @priv: Opaque data passed to the read_post_process hook.
+ */
+ struct nvmem_cell_info {
+ const char *name;
+@@ -65,6 +67,7 @@ struct nvmem_cell_info {
+ unsigned int nbits;
+ struct device_node *np;
+ nvmem_cell_post_process_t read_post_process;
++ void *priv;
+ };
+
+ /**
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0010-nvmem-layouts-sl28vpd-Add-new-layout-driver.patch b/target/linux/generic/backport-6.6/811-v6.4-0010-nvmem-layouts-sl28vpd-Add-new-layout-driver.patch
new file mode 100644
index 0000000000..7d97658b60
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0010-nvmem-layouts-sl28vpd-Add-new-layout-driver.patch
@@ -0,0 +1,215 @@
+From d9fae023fe86069750092fc1c2f3a73e2fb18512 Mon Sep 17 00:00:00 2001
+From: Michael Walle <michael@walle.cc>
+Date: Tue, 4 Apr 2023 18:21:29 +0100
+Subject: [PATCH] nvmem: layouts: sl28vpd: Add new layout driver
+
+This layout applies to the VPD of the Kontron sl28 boards. The VPD only
+contains a base MAC address. Therefore, we have to add an individual
+offset to it. This is done by taking the second argument of the nvmem
+phandle into account. Also this let us checking the VPD version and the
+checksum.
+
+Signed-off-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-22-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/layouts/Kconfig | 9 ++
+ drivers/nvmem/layouts/Makefile | 2 +
+ drivers/nvmem/layouts/sl28vpd.c | 165 ++++++++++++++++++++++++++++++++
+ 3 files changed, 176 insertions(+)
+ create mode 100644 drivers/nvmem/layouts/sl28vpd.c
+
+--- a/drivers/nvmem/layouts/Kconfig
++++ b/drivers/nvmem/layouts/Kconfig
+@@ -2,4 +2,13 @@
+
+ menu "Layout Types"
+
++config NVMEM_LAYOUT_SL28_VPD
++ tristate "Kontron sl28 VPD layout support"
++ select CRC8
++ help
++ Say Y here if you want to support the VPD layout of the Kontron
++ SMARC-sAL28 boards.
++
++ If unsure, say N.
++
+ endmenu
+--- a/drivers/nvmem/layouts/Makefile
++++ b/drivers/nvmem/layouts/Makefile
+@@ -2,3 +2,5 @@
+ #
+ # Makefile for nvmem layouts.
+ #
++
++obj-$(CONFIG_NVMEM_LAYOUT_SL28_VPD) += sl28vpd.o
+--- /dev/null
++++ b/drivers/nvmem/layouts/sl28vpd.c
+@@ -0,0 +1,165 @@
++// SPDX-License-Identifier: GPL-2.0
++
++#include <linux/crc8.h>
++#include <linux/etherdevice.h>
++#include <linux/nvmem-consumer.h>
++#include <linux/nvmem-provider.h>
++#include <linux/of.h>
++#include <uapi/linux/if_ether.h>
++
++#define SL28VPD_MAGIC 'V'
++
++struct sl28vpd_header {
++ u8 magic;
++ u8 version;
++} __packed;
++
++struct sl28vpd_v1 {
++ struct sl28vpd_header header;
++ char serial_number[15];
++ u8 base_mac_address[ETH_ALEN];
++ u8 crc8;
++} __packed;
++
++static int sl28vpd_mac_address_pp(void *priv, const char *id, int index,
++ unsigned int offset, void *buf,
++ size_t bytes)
++{
++ if (bytes != ETH_ALEN)
++ return -EINVAL;
++
++ if (index < 0)
++ return -EINVAL;
++
++ if (!is_valid_ether_addr(buf))
++ return -EINVAL;
++
++ eth_addr_add(buf, index);
++
++ return 0;
++}
++
++static const struct nvmem_cell_info sl28vpd_v1_entries[] = {
++ {
++ .name = "serial-number",
++ .offset = offsetof(struct sl28vpd_v1, serial_number),
++ .bytes = sizeof_field(struct sl28vpd_v1, serial_number),
++ },
++ {
++ .name = "base-mac-address",
++ .offset = offsetof(struct sl28vpd_v1, base_mac_address),
++ .bytes = sizeof_field(struct sl28vpd_v1, base_mac_address),
++ .read_post_process = sl28vpd_mac_address_pp,
++ },
++};
++
++static int sl28vpd_v1_check_crc(struct device *dev, struct nvmem_device *nvmem)
++{
++ struct sl28vpd_v1 data_v1;
++ u8 table[CRC8_TABLE_SIZE];
++ int ret;
++ u8 crc;
++
++ crc8_populate_msb(table, 0x07);
++
++ ret = nvmem_device_read(nvmem, 0, sizeof(data_v1), &data_v1);
++ if (ret < 0)
++ return ret;
++ else if (ret != sizeof(data_v1))
++ return -EIO;
++
++ crc = crc8(table, (void *)&data_v1, sizeof(data_v1) - 1, 0);
++
++ if (crc != data_v1.crc8) {
++ dev_err(dev,
++ "Checksum is invalid (got %02x, expected %02x).\n",
++ crc, data_v1.crc8);
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
++static int sl28vpd_add_cells(struct device *dev, struct nvmem_device *nvmem,
++ struct nvmem_layout *layout)
++{
++ const struct nvmem_cell_info *pinfo;
++ struct nvmem_cell_info info = {0};
++ struct device_node *layout_np;
++ struct sl28vpd_header hdr;
++ int ret, i;
++
++ /* check header */
++ ret = nvmem_device_read(nvmem, 0, sizeof(hdr), &hdr);
++ if (ret < 0)
++ return ret;
++ else if (ret != sizeof(hdr))
++ return -EIO;
++
++ if (hdr.magic != SL28VPD_MAGIC) {
++ dev_err(dev, "Invalid magic value (%02x)\n", hdr.magic);
++ return -EINVAL;
++ }
++
++ if (hdr.version != 1) {
++ dev_err(dev, "Version %d is unsupported.\n", hdr.version);
++ return -EINVAL;
++ }
++
++ ret = sl28vpd_v1_check_crc(dev, nvmem);
++ if (ret)
++ return ret;
++
++ layout_np = of_nvmem_layout_get_container(nvmem);
++ if (!layout_np)
++ return -ENOENT;
++
++ for (i = 0; i < ARRAY_SIZE(sl28vpd_v1_entries); i++) {
++ pinfo = &sl28vpd_v1_entries[i];
++
++ info.name = pinfo->name;
++ info.offset = pinfo->offset;
++ info.bytes = pinfo->bytes;
++ info.read_post_process = pinfo->read_post_process;
++ info.np = of_get_child_by_name(layout_np, pinfo->name);
++
++ ret = nvmem_add_one_cell(nvmem, &info);
++ if (ret) {
++ of_node_put(layout_np);
++ return ret;
++ }
++ }
++
++ of_node_put(layout_np);
++
++ return 0;
++}
++
++static const struct of_device_id sl28vpd_of_match_table[] = {
++ { .compatible = "kontron,sl28-vpd" },
++ {},
++};
++MODULE_DEVICE_TABLE(of, sl28vpd_of_match_table);
++
++struct nvmem_layout sl28vpd_layout = {
++ .name = "sl28-vpd",
++ .of_match_table = sl28vpd_of_match_table,
++ .add_cells = sl28vpd_add_cells,
++};
++
++static int __init sl28vpd_init(void)
++{
++ return nvmem_layout_register(&sl28vpd_layout);
++}
++
++static void __exit sl28vpd_exit(void)
++{
++ nvmem_layout_unregister(&sl28vpd_layout);
++}
++
++module_init(sl28vpd_init);
++module_exit(sl28vpd_exit);
++
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Michael Walle <michael@walle.cc>");
++MODULE_DESCRIPTION("NVMEM layout driver for the VPD of Kontron sl28 boards");
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0011-nvmem-layouts-onie-tlv-Add-new-layout-driver.patch b/target/linux/generic/backport-6.6/811-v6.4-0011-nvmem-layouts-onie-tlv-Add-new-layout-driver.patch
new file mode 100644
index 0000000000..ca8b4bc069
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0011-nvmem-layouts-onie-tlv-Add-new-layout-driver.patch
@@ -0,0 +1,306 @@
+From d3c0d12f6474216bf386101e2449cc73e5c5b61d Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Tue, 4 Apr 2023 18:21:31 +0100
+Subject: [PATCH] nvmem: layouts: onie-tlv: Add new layout driver
+
+This layout applies on top of any non volatile storage device containing
+an ONIE table factory flashed. This table follows the tlv
+(type-length-value) organization described in the link below. We cannot
+afford using regular parsers because the content of these tables is
+manufacturer specific and must be dynamically discovered.
+
+Link: https://opencomputeproject.github.io/onie/design-spec/hw_requirements.html
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-24-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/layouts/Kconfig | 9 ++
+ drivers/nvmem/layouts/Makefile | 1 +
+ drivers/nvmem/layouts/onie-tlv.c | 257 +++++++++++++++++++++++++++++++
+ 3 files changed, 267 insertions(+)
+ create mode 100644 drivers/nvmem/layouts/onie-tlv.c
+
+--- a/drivers/nvmem/layouts/Kconfig
++++ b/drivers/nvmem/layouts/Kconfig
+@@ -11,4 +11,13 @@ config NVMEM_LAYOUT_SL28_VPD
+
+ If unsure, say N.
+
++config NVMEM_LAYOUT_ONIE_TLV
++ tristate "ONIE tlv support"
++ select CRC32
++ help
++ Say Y here if you want to support the Open Compute Project ONIE
++ Type-Length-Value standard table.
++
++ If unsure, say N.
++
+ endmenu
+--- a/drivers/nvmem/layouts/Makefile
++++ b/drivers/nvmem/layouts/Makefile
+@@ -4,3 +4,4 @@
+ #
+
+ obj-$(CONFIG_NVMEM_LAYOUT_SL28_VPD) += sl28vpd.o
++obj-$(CONFIG_NVMEM_LAYOUT_ONIE_TLV) += onie-tlv.o
+--- /dev/null
++++ b/drivers/nvmem/layouts/onie-tlv.c
+@@ -0,0 +1,257 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * ONIE tlv NVMEM cells provider
++ *
++ * Copyright (C) 2022 Open Compute Group ONIE
++ * Author: Miquel Raynal <miquel.raynal@bootlin.com>
++ * Based on the nvmem driver written by: Vadym Kochan <vadym.kochan@plvision.eu>
++ * Inspired by the first layout written by: Rafał Miłecki <rafal@milecki.pl>
++ */
++
++#include <linux/crc32.h>
++#include <linux/etherdevice.h>
++#include <linux/nvmem-consumer.h>
++#include <linux/nvmem-provider.h>
++#include <linux/of.h>
++
++#define ONIE_TLV_MAX_LEN 2048
++#define ONIE_TLV_CRC_FIELD_SZ 6
++#define ONIE_TLV_CRC_SZ 4
++#define ONIE_TLV_HDR_ID "TlvInfo"
++
++struct onie_tlv_hdr {
++ u8 id[8];
++ u8 version;
++ __be16 data_len;
++} __packed;
++
++struct onie_tlv {
++ u8 type;
++ u8 len;
++} __packed;
++
++static const char *onie_tlv_cell_name(u8 type)
++{
++ switch (type) {
++ case 0x21:
++ return "product-name";
++ case 0x22:
++ return "part-number";
++ case 0x23:
++ return "serial-number";
++ case 0x24:
++ return "mac-address";
++ case 0x25:
++ return "manufacture-date";
++ case 0x26:
++ return "device-version";
++ case 0x27:
++ return "label-revision";
++ case 0x28:
++ return "platform-name";
++ case 0x29:
++ return "onie-version";
++ case 0x2A:
++ return "num-macs";
++ case 0x2B:
++ return "manufacturer";
++ case 0x2C:
++ return "country-code";
++ case 0x2D:
++ return "vendor";
++ case 0x2E:
++ return "diag-version";
++ case 0x2F:
++ return "service-tag";
++ case 0xFD:
++ return "vendor-extension";
++ case 0xFE:
++ return "crc32";
++ default:
++ break;
++ }
++
++ return NULL;
++}
++
++static int onie_tlv_mac_read_cb(void *priv, const char *id, int index,
++ unsigned int offset, void *buf,
++ size_t bytes)
++{
++ eth_addr_add(buf, index);
++
++ return 0;
++}
++
++static nvmem_cell_post_process_t onie_tlv_read_cb(u8 type, u8 *buf)
++{
++ switch (type) {
++ case 0x24:
++ return &onie_tlv_mac_read_cb;
++ default:
++ break;
++ }
++
++ return NULL;
++}
++
++static int onie_tlv_add_cells(struct device *dev, struct nvmem_device *nvmem,
++ size_t data_len, u8 *data)
++{
++ struct nvmem_cell_info cell = {};
++ struct device_node *layout;
++ struct onie_tlv tlv;
++ unsigned int hdr_len = sizeof(struct onie_tlv_hdr);
++ unsigned int offset = 0;
++ int ret;
++
++ layout = of_nvmem_layout_get_container(nvmem);
++ if (!layout)
++ return -ENOENT;
++
++ while (offset < data_len) {
++ memcpy(&tlv, data + offset, sizeof(tlv));
++ if (offset + tlv.len >= data_len) {
++ dev_err(dev, "Out of bounds field (0x%x bytes at 0x%x)\n",
++ tlv.len, hdr_len + offset);
++ break;
++ }
++
++ cell.name = onie_tlv_cell_name(tlv.type);
++ if (!cell.name)
++ continue;
++
++ cell.offset = hdr_len + offset + sizeof(tlv.type) + sizeof(tlv.len);
++ cell.bytes = tlv.len;
++ cell.np = of_get_child_by_name(layout, cell.name);
++ cell.read_post_process = onie_tlv_read_cb(tlv.type, data + offset + sizeof(tlv));
++
++ ret = nvmem_add_one_cell(nvmem, &cell);
++ if (ret) {
++ of_node_put(layout);
++ return ret;
++ }
++
++ offset += sizeof(tlv) + tlv.len;
++ }
++
++ of_node_put(layout);
++
++ return 0;
++}
++
++static bool onie_tlv_hdr_is_valid(struct device *dev, struct onie_tlv_hdr *hdr)
++{
++ if (memcmp(hdr->id, ONIE_TLV_HDR_ID, sizeof(hdr->id))) {
++ dev_err(dev, "Invalid header\n");
++ return false;
++ }
++
++ if (hdr->version != 0x1) {
++ dev_err(dev, "Invalid version number\n");
++ return false;
++ }
++
++ return true;
++}
++
++static bool onie_tlv_crc_is_valid(struct device *dev, size_t table_len, u8 *table)
++{
++ struct onie_tlv crc_hdr;
++ u32 read_crc, calc_crc;
++ __be32 crc_be;
++
++ memcpy(&crc_hdr, table + table_len - ONIE_TLV_CRC_FIELD_SZ, sizeof(crc_hdr));
++ if (crc_hdr.type != 0xfe || crc_hdr.len != ONIE_TLV_CRC_SZ) {
++ dev_err(dev, "Invalid CRC field\n");
++ return false;
++ }
++
++ /* The table contains a JAMCRC, which is XOR'ed compared to the original
++ * CRC32 implementation as known in the Ethernet world.
++ */
++ memcpy(&crc_be, table + table_len - ONIE_TLV_CRC_SZ, ONIE_TLV_CRC_SZ);
++ read_crc = be32_to_cpu(crc_be);
++ calc_crc = crc32(~0, table, table_len - ONIE_TLV_CRC_SZ) ^ 0xFFFFFFFF;
++ if (read_crc != calc_crc) {
++ dev_err(dev, "Invalid CRC read: 0x%08x, expected: 0x%08x\n",
++ read_crc, calc_crc);
++ return false;
++ }
++
++ return true;
++}
++
++static int onie_tlv_parse_table(struct device *dev, struct nvmem_device *nvmem,
++ struct nvmem_layout *layout)
++{
++ struct onie_tlv_hdr hdr;
++ size_t table_len, data_len, hdr_len;
++ u8 *table, *data;
++ int ret;
++
++ ret = nvmem_device_read(nvmem, 0, sizeof(hdr), &hdr);
++ if (ret < 0)
++ return ret;
++
++ if (!onie_tlv_hdr_is_valid(dev, &hdr)) {
++ dev_err(dev, "Invalid ONIE TLV header\n");
++ return -EINVAL;
++ }
++
++ hdr_len = sizeof(hdr.id) + sizeof(hdr.version) + sizeof(hdr.data_len);
++ data_len = be16_to_cpu(hdr.data_len);
++ table_len = hdr_len + data_len;
++ if (table_len > ONIE_TLV_MAX_LEN) {
++ dev_err(dev, "Invalid ONIE TLV data length\n");
++ return -EINVAL;
++ }
++
++ table = devm_kmalloc(dev, table_len, GFP_KERNEL);
++ if (!table)
++ return -ENOMEM;
++
++ ret = nvmem_device_read(nvmem, 0, table_len, table);
++ if (ret != table_len)
++ return ret;
++
++ if (!onie_tlv_crc_is_valid(dev, table_len, table))
++ return -EINVAL;
++
++ data = table + hdr_len;
++ ret = onie_tlv_add_cells(dev, nvmem, data_len, data);
++ if (ret)
++ return ret;
++
++ return 0;
++}
++
++static const struct of_device_id onie_tlv_of_match_table[] = {
++ { .compatible = "onie,tlv-layout", },
++ {},
++};
++MODULE_DEVICE_TABLE(of, onie_tlv_of_match_table);
++
++static struct nvmem_layout onie_tlv_layout = {
++ .name = "ONIE tlv layout",
++ .of_match_table = onie_tlv_of_match_table,
++ .add_cells = onie_tlv_parse_table,
++};
++
++static int __init onie_tlv_init(void)
++{
++ return nvmem_layout_register(&onie_tlv_layout);
++}
++
++static void __exit onie_tlv_exit(void)
++{
++ nvmem_layout_unregister(&onie_tlv_layout);
++}
++
++module_init(onie_tlv_init);
++module_exit(onie_tlv_exit);
++
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
++MODULE_DESCRIPTION("NVMEM layout driver for Onie TLV table parsing");
++MODULE_ALIAS("NVMEM layout driver for Onie TLV table parsing");
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0012-nvmem-stm32-romem-mark-OF-related-data-as-maybe-unus.patch b/target/linux/generic/backport-6.6/811-v6.4-0012-nvmem-stm32-romem-mark-OF-related-data-as-maybe-unus.patch
new file mode 100644
index 0000000000..94a0911d73
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0012-nvmem-stm32-romem-mark-OF-related-data-as-maybe-unus.patch
@@ -0,0 +1,32 @@
+From a4fb434ef96ace5af758ca2c52c3a3f8f3abc87c Mon Sep 17 00:00:00 2001
+From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Date: Tue, 4 Apr 2023 18:21:34 +0100
+Subject: [PATCH] nvmem: stm32-romem: mark OF related data as maybe unused
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The driver can be compile tested with !CONFIG_OF making certain data
+unused:
+
+ drivers/nvmem/stm32-romem.c:271:34: error: ‘stm32_romem_of_match’ defined but not used [-Werror=unused-const-variable=]
+
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-27-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/stm32-romem.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/nvmem/stm32-romem.c
++++ b/drivers/nvmem/stm32-romem.c
+@@ -268,7 +268,7 @@ static const struct stm32_romem_cfg stm3
+ .ta = true,
+ };
+
+-static const struct of_device_id stm32_romem_of_match[] = {
++static const struct of_device_id stm32_romem_of_match[] __maybe_unused = {
+ { .compatible = "st,stm32f4-otp", }, {
+ .compatible = "st,stm32mp15-bsec",
+ .data = (void *)&stm32mp15_bsec_cfg,
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0013-nvmem-mtk-efuse-Support-postprocessing-for-GPU-speed.patch b/target/linux/generic/backport-6.6/811-v6.4-0013-nvmem-mtk-efuse-Support-postprocessing-for-GPU-speed.patch
new file mode 100644
index 0000000000..abda402bdd
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0013-nvmem-mtk-efuse-Support-postprocessing-for-GPU-speed.patch
@@ -0,0 +1,120 @@
+From de6e05097f7db066afb0ad4c88b730949f7b7749 Mon Sep 17 00:00:00 2001
+From: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Date: Tue, 4 Apr 2023 18:21:35 +0100
+Subject: [PATCH] nvmem: mtk-efuse: Support postprocessing for GPU speed
+ binning data
+
+On some MediaTek SoCs GPU speed binning data is available for read
+in the SoC's eFuse array but it has a format that is incompatible
+with what the OPP API expects, as we read a number from 0 to 7 but
+opp-supported-hw is expecting a bitmask to enable an OPP entry:
+being what we read limited to 0-7, it's straightforward to simply
+convert the value to BIT(value) as a post-processing action.
+
+So, introduce post-processing support and enable it by evaluating
+the newly introduced platform data's `uses_post_processing` member,
+currently enabled only for MT8186.
+
+Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-28-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/mtk-efuse.c | 53 +++++++++++++++++++++++++++++++++++++--
+ 1 file changed, 51 insertions(+), 2 deletions(-)
+
+--- a/drivers/nvmem/mtk-efuse.c
++++ b/drivers/nvmem/mtk-efuse.c
+@@ -10,6 +10,11 @@
+ #include <linux/io.h>
+ #include <linux/nvmem-provider.h>
+ #include <linux/platform_device.h>
++#include <linux/property.h>
++
++struct mtk_efuse_pdata {
++ bool uses_post_processing;
++};
+
+ struct mtk_efuse_priv {
+ void __iomem *base;
+@@ -29,6 +34,37 @@ static int mtk_reg_read(void *context,
+ return 0;
+ }
+
++static int mtk_efuse_gpu_speedbin_pp(void *context, const char *id, int index,
++ unsigned int offset, void *data, size_t bytes)
++{
++ u8 *val = data;
++
++ if (val[0] < 8)
++ val[0] = BIT(val[0]);
++
++ return 0;
++}
++
++static void mtk_efuse_fixup_cell_info(struct nvmem_device *nvmem,
++ struct nvmem_layout *layout,
++ struct nvmem_cell_info *cell)
++{
++ size_t sz = strlen(cell->name);
++
++ /*
++ * On some SoCs, the GPU speedbin is not read as bitmask but as
++ * a number with range [0-7] (max 3 bits): post process to use
++ * it in OPP tables to describe supported-hw.
++ */
++ if (cell->nbits <= 3 &&
++ strncmp(cell->name, "gpu-speedbin", min(sz, strlen("gpu-speedbin"))) == 0)
++ cell->read_post_process = mtk_efuse_gpu_speedbin_pp;
++}
++
++static struct nvmem_layout mtk_efuse_layout = {
++ .fixup_cell_info = mtk_efuse_fixup_cell_info,
++};
++
+ static int mtk_efuse_probe(struct platform_device *pdev)
+ {
+ struct device *dev = &pdev->dev;
+@@ -36,6 +72,7 @@ static int mtk_efuse_probe(struct platfo
+ struct nvmem_device *nvmem;
+ struct nvmem_config econfig = {};
+ struct mtk_efuse_priv *priv;
++ const struct mtk_efuse_pdata *pdata;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+@@ -45,20 +82,32 @@ static int mtk_efuse_probe(struct platfo
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
++ pdata = device_get_match_data(dev);
+ econfig.stride = 1;
+ econfig.word_size = 1;
+ econfig.reg_read = mtk_reg_read;
+ econfig.size = resource_size(res);
+ econfig.priv = priv;
+ econfig.dev = dev;
++ if (pdata->uses_post_processing)
++ econfig.layout = &mtk_efuse_layout;
+ nvmem = devm_nvmem_register(dev, &econfig);
+
+ return PTR_ERR_OR_ZERO(nvmem);
+ }
+
++static const struct mtk_efuse_pdata mtk_mt8186_efuse_pdata = {
++ .uses_post_processing = true,
++};
++
++static const struct mtk_efuse_pdata mtk_efuse_pdata = {
++ .uses_post_processing = false,
++};
++
+ static const struct of_device_id mtk_efuse_of_match[] = {
+- { .compatible = "mediatek,mt8173-efuse",},
+- { .compatible = "mediatek,efuse",},
++ { .compatible = "mediatek,mt8173-efuse", .data = &mtk_efuse_pdata },
++ { .compatible = "mediatek,mt8186-efuse", .data = &mtk_mt8186_efuse_pdata },
++ { .compatible = "mediatek,efuse", .data = &mtk_efuse_pdata },
+ {/* sentinel */},
+ };
+ MODULE_DEVICE_TABLE(of, mtk_efuse_of_match);
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0014-nvmem-bcm-ocotp-Use-devm_platform_ioremap_resource.patch b/target/linux/generic/backport-6.6/811-v6.4-0014-nvmem-bcm-ocotp-Use-devm_platform_ioremap_resource.patch
new file mode 100644
index 0000000000..200eb1928f
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0014-nvmem-bcm-ocotp-Use-devm_platform_ioremap_resource.patch
@@ -0,0 +1,39 @@
+From 1dc552fa33cf98af3e784dbc0500da93cae3b24a Mon Sep 17 00:00:00 2001
+From: Yang Li <yang.lee@linux.alibaba.com>
+Date: Tue, 4 Apr 2023 18:21:38 +0100
+Subject: [PATCH] nvmem: bcm-ocotp: Use devm_platform_ioremap_resource()
+
+According to commit 7945f929f1a7 ("drivers: provide
+devm_platform_ioremap_resource()"), convert platform_get_resource(),
+devm_ioremap_resource() to a single call to use
+devm_platform_ioremap_resource(), as this is exactly what this function
+does.
+
+Signed-off-by: Yang Li <yang.lee@linux.alibaba.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-31-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/bcm-ocotp.c | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+--- a/drivers/nvmem/bcm-ocotp.c
++++ b/drivers/nvmem/bcm-ocotp.c
+@@ -244,7 +244,6 @@ MODULE_DEVICE_TABLE(acpi, bcm_otpc_acpi_
+ static int bcm_otpc_probe(struct platform_device *pdev)
+ {
+ struct device *dev = &pdev->dev;
+- struct resource *res;
+ struct otpc_priv *priv;
+ struct nvmem_device *nvmem;
+ int err;
+@@ -259,8 +258,7 @@ static int bcm_otpc_probe(struct platfor
+ return -ENODEV;
+
+ /* Get OTP base address register. */
+- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+- priv->base = devm_ioremap_resource(dev, res);
++ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base)) {
+ dev_err(dev, "unable to map I/O memory\n");
+ return PTR_ERR(priv->base);
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0015-nvmem-nintendo-otp-Use-devm_platform_ioremap_resourc.patch b/target/linux/generic/backport-6.6/811-v6.4-0015-nvmem-nintendo-otp-Use-devm_platform_ioremap_resourc.patch
new file mode 100644
index 0000000000..890dacd08d
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0015-nvmem-nintendo-otp-Use-devm_platform_ioremap_resourc.patch
@@ -0,0 +1,39 @@
+From 649409990d2e93fac657be7c6960c28a2c601d65 Mon Sep 17 00:00:00 2001
+From: Yang Li <yang.lee@linux.alibaba.com>
+Date: Tue, 4 Apr 2023 18:21:39 +0100
+Subject: [PATCH] nvmem: nintendo-otp: Use devm_platform_ioremap_resource()
+
+According to commit 7945f929f1a7 ("drivers: provide
+devm_platform_ioremap_resource()"), convert platform_get_resource(),
+devm_ioremap_resource() to a single call to use
+devm_platform_ioremap_resource(), as this is exactly what this function
+does.
+
+Signed-off-by: Yang Li <yang.lee@linux.alibaba.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-32-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/nintendo-otp.c | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+--- a/drivers/nvmem/nintendo-otp.c
++++ b/drivers/nvmem/nintendo-otp.c
+@@ -76,7 +76,6 @@ static int nintendo_otp_probe(struct pla
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *of_id =
+ of_match_device(nintendo_otp_of_table, dev);
+- struct resource *res;
+ struct nvmem_device *nvmem;
+ struct nintendo_otp_priv *priv;
+
+@@ -92,8 +91,7 @@ static int nintendo_otp_probe(struct pla
+ if (!priv)
+ return -ENOMEM;
+
+- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+- priv->regs = devm_ioremap_resource(dev, res);
++ priv->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->regs))
+ return PTR_ERR(priv->regs);
+
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0016-nvmem-vf610-ocotp-Use-devm_platform_get_and_ioremap_.patch b/target/linux/generic/backport-6.6/811-v6.4-0016-nvmem-vf610-ocotp-Use-devm_platform_get_and_ioremap_.patch
new file mode 100644
index 0000000000..3f5d3c1ad4
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0016-nvmem-vf610-ocotp-Use-devm_platform_get_and_ioremap_.patch
@@ -0,0 +1,32 @@
+From c2367aa60d5e34d48582362c6de34b4131d92be7 Mon Sep 17 00:00:00 2001
+From: Yang Li <yang.lee@linux.alibaba.com>
+Date: Tue, 4 Apr 2023 18:21:40 +0100
+Subject: [PATCH] nvmem: vf610-ocotp: Use
+ devm_platform_get_and_ioremap_resource()
+
+According to commit 890cc39a8799 ("drivers: provide
+devm_platform_get_and_ioremap_resource()"), convert
+platform_get_resource(), devm_ioremap_resource() to a single
+call to devm_platform_get_and_ioremap_resource(), as this is exactly
+what this function does.
+
+Signed-off-by: Yang Li <yang.lee@linux.alibaba.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-33-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/vf610-ocotp.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/drivers/nvmem/vf610-ocotp.c
++++ b/drivers/nvmem/vf610-ocotp.c
+@@ -219,8 +219,7 @@ static int vf610_ocotp_probe(struct plat
+ if (!ocotp_dev)
+ return -ENOMEM;
+
+- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+- ocotp_dev->base = devm_ioremap_resource(dev, res);
++ ocotp_dev->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(ocotp_dev->base))
+ return PTR_ERR(ocotp_dev->base);
+
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0017-nvmem-core-support-specifying-both-cell-raw-data-pos.patch b/target/linux/generic/backport-6.6/811-v6.4-0017-nvmem-core-support-specifying-both-cell-raw-data-pos.patch
new file mode 100644
index 0000000000..eeb407e9bb
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0017-nvmem-core-support-specifying-both-cell-raw-data-pos.patch
@@ -0,0 +1,115 @@
+From 55d4980ce55b6bb4be66877de4dbec513911b988 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Tue, 4 Apr 2023 18:21:42 +0100
+Subject: [PATCH] nvmem: core: support specifying both: cell raw data & post
+ read lengths
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Callback .read_post_process() is designed to modify raw cell content
+before providing it to the consumer. So far we were dealing with
+modifications that didn't affect cell size (length). In some cases
+however cell content needs to be reformatted and resized.
+
+It's required e.g. to provide properly formatted MAC address in case
+it's stored in a non-binary format (e.g. using ASCII).
+
+There were few discussions how to optimally handle that. Following
+possible solutions were considered:
+1. Allow .read_post_process() to realloc (resize) content buffer
+2. Allow .read_post_process() to adjust (decrease) just buffer length
+3. Register NVMEM cells using post-read sizes
+
+The preferred solution was the last one. The problem is that simply
+adjusting "bytes" in NVMEM providers would result in core code NOT
+passing whole raw data to .read_post_process() callbacks. It means
+callback functions couldn't do their job without somehow manually
+reading original cell content on their own.
+
+This patch deals with that by registering NVMEM cells with both lengths:
+raw content one and post read one. It allows:
+1. Core code to read whole raw cell content
+2. Callbacks to return content they want
+
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-35-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 11 +++++++----
+ include/linux/nvmem-provider.h | 2 ++
+ 2 files changed, 9 insertions(+), 4 deletions(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -50,6 +50,7 @@ struct nvmem_device {
+ struct nvmem_cell_entry {
+ const char *name;
+ int offset;
++ size_t raw_len;
+ int bytes;
+ int bit_offset;
+ int nbits;
+@@ -469,6 +470,7 @@ static int nvmem_cell_info_to_nvmem_cell
+ {
+ cell->nvmem = nvmem;
+ cell->offset = info->offset;
++ cell->raw_len = info->raw_len ?: info->bytes;
+ cell->bytes = info->bytes;
+ cell->name = info->name;
+ cell->read_post_process = info->read_post_process;
+@@ -1560,7 +1562,7 @@ static int __nvmem_cell_read(struct nvme
+ {
+ int rc;
+
+- rc = nvmem_reg_read(nvmem, cell->offset, buf, cell->bytes);
++ rc = nvmem_reg_read(nvmem, cell->offset, buf, cell->raw_len);
+
+ if (rc)
+ return rc;
+@@ -1571,7 +1573,7 @@ static int __nvmem_cell_read(struct nvme
+
+ if (cell->read_post_process) {
+ rc = cell->read_post_process(cell->priv, id, index,
+- cell->offset, buf, cell->bytes);
++ cell->offset, buf, cell->raw_len);
+ if (rc)
+ return rc;
+ }
+@@ -1594,14 +1596,15 @@ static int __nvmem_cell_read(struct nvme
+ */
+ void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len)
+ {
+- struct nvmem_device *nvmem = cell->entry->nvmem;
++ struct nvmem_cell_entry *entry = cell->entry;
++ struct nvmem_device *nvmem = entry->nvmem;
+ u8 *buf;
+ int rc;
+
+ if (!nvmem)
+ return ERR_PTR(-EINVAL);
+
+- buf = kzalloc(cell->entry->bytes, GFP_KERNEL);
++ buf = kzalloc(max_t(size_t, entry->raw_len, entry->bytes), GFP_KERNEL);
+ if (!buf)
+ return ERR_PTR(-ENOMEM);
+
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -51,6 +51,7 @@ struct nvmem_keepout {
+ * struct nvmem_cell_info - NVMEM cell description
+ * @name: Name.
+ * @offset: Offset within the NVMEM device.
++ * @raw_len: Length of raw data (without post processing).
+ * @bytes: Length of the cell.
+ * @bit_offset: Bit offset if cell is smaller than a byte.
+ * @nbits: Number of bits.
+@@ -62,6 +63,7 @@ struct nvmem_keepout {
+ struct nvmem_cell_info {
+ const char *name;
+ unsigned int offset;
++ size_t raw_len;
+ unsigned int bytes;
+ unsigned int bit_offset;
+ unsigned int nbits;
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0018-nvmem-u-boot-env-post-process-ethaddr-env-variable.patch b/target/linux/generic/backport-6.6/811-v6.4-0018-nvmem-u-boot-env-post-process-ethaddr-env-variable.patch
new file mode 100644
index 0000000000..adde0ffc4b
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0018-nvmem-u-boot-env-post-process-ethaddr-env-variable.patch
@@ -0,0 +1,81 @@
+From c49f1a8af6bcf6d18576bca898f8083ca4b129e1 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Tue, 4 Apr 2023 18:21:43 +0100
+Subject: [PATCH] nvmem: u-boot-env: post-process "ethaddr" env variable
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+U-Boot environment variables are stored in ASCII format so "ethaddr"
+requires parsing into binary to make it work with Ethernet interfaces.
+
+This includes support for indexes to support #nvmem-cell-cells = <1>.
+
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-36-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/Kconfig | 1 +
+ drivers/nvmem/u-boot-env.c | 26 ++++++++++++++++++++++++++
+ 2 files changed, 27 insertions(+)
+
+--- a/drivers/nvmem/Kconfig
++++ b/drivers/nvmem/Kconfig
+@@ -340,6 +340,7 @@ config NVMEM_U_BOOT_ENV
+ tristate "U-Boot environment variables support"
+ depends on OF && MTD
+ select CRC32
++ select GENERIC_NET_UTILS
+ help
+ U-Boot stores its setup as environment variables. This driver adds
+ support for verifying & exporting such data. It also exposes variables
+--- a/drivers/nvmem/u-boot-env.c
++++ b/drivers/nvmem/u-boot-env.c
+@@ -4,6 +4,8 @@
+ */
+
+ #include <linux/crc32.h>
++#include <linux/etherdevice.h>
++#include <linux/if_ether.h>
+ #include <linux/mod_devicetable.h>
+ #include <linux/module.h>
+ #include <linux/mtd/mtd.h>
+@@ -70,6 +72,25 @@ static int u_boot_env_read(void *context
+ return 0;
+ }
+
++static int u_boot_env_read_post_process_ethaddr(void *context, const char *id, int index,
++ unsigned int offset, void *buf, size_t bytes)
++{
++ u8 mac[ETH_ALEN];
++
++ if (bytes != 3 * ETH_ALEN - 1)
++ return -EINVAL;
++
++ if (!mac_pton(buf, mac))
++ return -EINVAL;
++
++ if (index)
++ eth_addr_add(mac, index);
++
++ ether_addr_copy(buf, mac);
++
++ return 0;
++}
++
+ static int u_boot_env_add_cells(struct u_boot_env *priv, uint8_t *buf,
+ size_t data_offset, size_t data_len)
+ {
+@@ -101,6 +122,11 @@ static int u_boot_env_add_cells(struct u
+ priv->cells[idx].offset = data_offset + value - data;
+ priv->cells[idx].bytes = strlen(value);
+ priv->cells[idx].np = of_get_child_by_name(dev->of_node, priv->cells[idx].name);
++ if (!strcmp(var, "ethaddr")) {
++ priv->cells[idx].raw_len = strlen(value);
++ priv->cells[idx].bytes = ETH_ALEN;
++ priv->cells[idx].read_post_process = u_boot_env_read_post_process_ethaddr;
++ }
+ }
+
+ if (WARN_ON(idx != priv->ncells))
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0019-nvmem-Add-macro-to-register-nvmem-layout-drivers.patch b/target/linux/generic/backport-6.6/811-v6.4-0019-nvmem-Add-macro-to-register-nvmem-layout-drivers.patch
new file mode 100644
index 0000000000..7c6fe22b5f
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0019-nvmem-Add-macro-to-register-nvmem-layout-drivers.patch
@@ -0,0 +1,42 @@
+From 814c978f02db17f16e6aa2efa2a929372f06da09 Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Tue, 4 Apr 2023 18:21:44 +0100
+Subject: [PATCH] nvmem: Add macro to register nvmem layout drivers
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Provide a module_nvmem_layout_driver() macro at the end of the
+nvmem-provider.h header to reduce the boilerplate when registering nvmem
+layout drivers.
+
+Suggested-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Acked-by: Rafał Miłecki <rafal@milecki.pl>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-37-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/nvmem-provider.h | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -9,6 +9,7 @@
+ #ifndef _LINUX_NVMEM_PROVIDER_H
+ #define _LINUX_NVMEM_PROVIDER_H
+
++#include <linux/device/driver.h>
+ #include <linux/err.h>
+ #include <linux/errno.h>
+ #include <linux/gpio/consumer.h>
+@@ -242,4 +243,9 @@ nvmem_layout_get_match_data(struct nvmem
+ }
+
+ #endif /* CONFIG_NVMEM */
++
++#define module_nvmem_layout_driver(__layout_driver) \
++ module_driver(__layout_driver, nvmem_layout_register, \
++ nvmem_layout_unregister)
++
+ #endif /* ifndef _LINUX_NVMEM_PROVIDER_H */
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0020-nvmem-layouts-sl28vpd-Use-module_nvmem_layout_driver.patch b/target/linux/generic/backport-6.6/811-v6.4-0020-nvmem-layouts-sl28vpd-Use-module_nvmem_layout_driver.patch
new file mode 100644
index 0000000000..06646dd68b
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0020-nvmem-layouts-sl28vpd-Use-module_nvmem_layout_driver.patch
@@ -0,0 +1,39 @@
+From 0abdf99fe0c86252ba274703425f8d543d7e7f0d Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Tue, 4 Apr 2023 18:21:45 +0100
+Subject: [PATCH] nvmem: layouts: sl28vpd: Use module_nvmem_layout_driver()
+
+Stop open-coding the module init/exit functions. Use the
+module_nvmem_layout_driver() instead.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-38-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/layouts/sl28vpd.c | 14 +-------------
+ 1 file changed, 1 insertion(+), 13 deletions(-)
+
+--- a/drivers/nvmem/layouts/sl28vpd.c
++++ b/drivers/nvmem/layouts/sl28vpd.c
+@@ -146,19 +146,7 @@ struct nvmem_layout sl28vpd_layout = {
+ .of_match_table = sl28vpd_of_match_table,
+ .add_cells = sl28vpd_add_cells,
+ };
+-
+-static int __init sl28vpd_init(void)
+-{
+- return nvmem_layout_register(&sl28vpd_layout);
+-}
+-
+-static void __exit sl28vpd_exit(void)
+-{
+- nvmem_layout_unregister(&sl28vpd_layout);
+-}
+-
+-module_init(sl28vpd_init);
+-module_exit(sl28vpd_exit);
++module_nvmem_layout_driver(sl28vpd_layout);
+
+ MODULE_LICENSE("GPL");
+ MODULE_AUTHOR("Michael Walle <michael@walle.cc>");
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0021-nvmem-layouts-onie-tlv-Use-module_nvmem_layout_drive.patch b/target/linux/generic/backport-6.6/811-v6.4-0021-nvmem-layouts-onie-tlv-Use-module_nvmem_layout_drive.patch
new file mode 100644
index 0000000000..826f4378c2
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0021-nvmem-layouts-onie-tlv-Use-module_nvmem_layout_drive.patch
@@ -0,0 +1,39 @@
+From d119eb38faab61125aaa4f63c74eef61585cf34c Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Tue, 4 Apr 2023 18:21:46 +0100
+Subject: [PATCH] nvmem: layouts: onie-tlv: Use module_nvmem_layout_driver()
+
+Stop open-coding the module init/exit functions. Use the
+module_nvmem_layout_driver() instead.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-39-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/layouts/onie-tlv.c | 14 +-------------
+ 1 file changed, 1 insertion(+), 13 deletions(-)
+
+--- a/drivers/nvmem/layouts/onie-tlv.c
++++ b/drivers/nvmem/layouts/onie-tlv.c
+@@ -237,19 +237,7 @@ static struct nvmem_layout onie_tlv_layo
+ .of_match_table = onie_tlv_of_match_table,
+ .add_cells = onie_tlv_parse_table,
+ };
+-
+-static int __init onie_tlv_init(void)
+-{
+- return nvmem_layout_register(&onie_tlv_layout);
+-}
+-
+-static void __exit onie_tlv_exit(void)
+-{
+- nvmem_layout_unregister(&onie_tlv_layout);
+-}
+-
+-module_init(onie_tlv_init);
+-module_exit(onie_tlv_exit);
++module_nvmem_layout_driver(onie_tlv_layout);
+
+ MODULE_LICENSE("GPL");
+ MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0022-nvmem-layouts-onie-tlv-Drop-wrong-module-alias.patch b/target/linux/generic/backport-6.6/811-v6.4-0022-nvmem-layouts-onie-tlv-Drop-wrong-module-alias.patch
new file mode 100644
index 0000000000..f20db85ceb
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0022-nvmem-layouts-onie-tlv-Drop-wrong-module-alias.patch
@@ -0,0 +1,24 @@
+From 6b13e4b6a9a45028ac730e550380077df1845912 Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Tue, 4 Apr 2023 18:21:47 +0100
+Subject: [PATCH] nvmem: layouts: onie-tlv: Drop wrong module alias
+
+The MODULE_ALIAS macro is misused here as it carries the
+description. There is currently no relevant alias to provide so let's
+just drop it.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-40-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/layouts/onie-tlv.c | 1 -
+ 1 file changed, 1 deletion(-)
+
+--- a/drivers/nvmem/layouts/onie-tlv.c
++++ b/drivers/nvmem/layouts/onie-tlv.c
+@@ -242,4 +242,3 @@ module_nvmem_layout_driver(onie_tlv_layo
+ MODULE_LICENSE("GPL");
+ MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
+ MODULE_DESCRIPTION("NVMEM layout driver for Onie TLV table parsing");
+-MODULE_ALIAS("NVMEM layout driver for Onie TLV table parsing");
diff --git a/target/linux/generic/backport-6.6/811-v6.4-0023-nvmem-layouts-sl28vpd-set-varaiable-sl28vpd_layout-s.patch b/target/linux/generic/backport-6.6/811-v6.4-0023-nvmem-layouts-sl28vpd-set-varaiable-sl28vpd_layout-s.patch
new file mode 100644
index 0000000000..5cf847b57a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/811-v6.4-0023-nvmem-layouts-sl28vpd-set-varaiable-sl28vpd_layout-s.patch
@@ -0,0 +1,31 @@
+From a8642cd11635a35a5f1dc31857887900d6610778 Mon Sep 17 00:00:00 2001
+From: Tom Rix <trix@redhat.com>
+Date: Tue, 4 Apr 2023 18:21:48 +0100
+Subject: [PATCH] nvmem: layouts: sl28vpd: set varaiable sl28vpd_layout
+ storage-class-specifier to static
+
+smatch reports
+drivers/nvmem/layouts/sl28vpd.c:144:21: warning: symbol
+ 'sl28vpd_layout' was not declared. Should it be static?
+
+This variable is only used in one file so it should be static.
+
+Signed-off-by: Tom Rix <trix@redhat.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-41-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/layouts/sl28vpd.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/nvmem/layouts/sl28vpd.c
++++ b/drivers/nvmem/layouts/sl28vpd.c
+@@ -141,7 +141,7 @@ static const struct of_device_id sl28vpd
+ };
+ MODULE_DEVICE_TABLE(of, sl28vpd_of_match_table);
+
+-struct nvmem_layout sl28vpd_layout = {
++static struct nvmem_layout sl28vpd_layout = {
+ .name = "sl28-vpd",
+ .of_match_table = sl28vpd_of_match_table,
+ .add_cells = sl28vpd_add_cells,
diff --git a/target/linux/generic/backport-6.6/812-v6.2-firmware-nvram-bcm47xx-support-init-from-IO-memory.patch b/target/linux/generic/backport-6.6/812-v6.2-firmware-nvram-bcm47xx-support-init-from-IO-memory.patch
new file mode 100644
index 0000000000..13ee2d4229
--- /dev/null
+++ b/target/linux/generic/backport-6.6/812-v6.2-firmware-nvram-bcm47xx-support-init-from-IO-memory.patch
@@ -0,0 +1,100 @@
+From a5be5ce0e25439fae3cd42e3d775979547926812 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Thu, 3 Nov 2022 09:25:29 +0100
+Subject: [PATCH] firmware/nvram: bcm47xx: support init from IO memory
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Provide NVMEM content to the NVRAM driver from a simple
+memory resource. This is necessary to use NVRAM in a memory-
+mapped flash device. Patch taken from OpenWrts development
+tree.
+
+This patch makes it possible to use memory-mapped NVRAM
+on the D-Link DWL-8610AP and the D-Link DIR-890L.
+
+Cc: Hauke Mehrtens <hauke@hauke-m.de>
+Cc: linux-mips@vger.kernel.org
+Cc: Florian Fainelli <f.fainelli@gmail.com>
+Cc: bcm-kernel-feedback-list@broadcom.com
+Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+[Added an export for modules potentially using the init symbol]
+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
+Link: https://lore.kernel.org/r/20221103082529.359084-1-linus.walleij@linaro.org
+Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
+---
+ drivers/firmware/broadcom/bcm47xx_nvram.c | 18 ++++++++++++++++++
+ drivers/nvmem/brcm_nvram.c | 3 +++
+ include/linux/bcm47xx_nvram.h | 6 ++++++
+ 3 files changed, 27 insertions(+)
+
+--- a/drivers/firmware/broadcom/bcm47xx_nvram.c
++++ b/drivers/firmware/broadcom/bcm47xx_nvram.c
+@@ -110,6 +110,24 @@ found:
+ return 0;
+ }
+
++int bcm47xx_nvram_init_from_iomem(void __iomem *nvram_start, size_t res_size)
++{
++ if (nvram_len) {
++ pr_warn("nvram already initialized\n");
++ return -EEXIST;
++ }
++
++ if (!bcm47xx_nvram_is_valid(nvram_start)) {
++ pr_err("No valid NVRAM found\n");
++ return -ENOENT;
++ }
++
++ bcm47xx_nvram_copy(nvram_start, res_size);
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(bcm47xx_nvram_init_from_iomem);
++
+ /*
+ * On bcm47xx we need access to the NVRAM very early, so we can't use mtd
+ * subsystem to access flash. We can't even use platform device / driver to
+--- a/drivers/nvmem/brcm_nvram.c
++++ b/drivers/nvmem/brcm_nvram.c
+@@ -3,6 +3,7 @@
+ * Copyright (C) 2021 Rafał Miłecki <rafal@milecki.pl>
+ */
+
++#include <linux/bcm47xx_nvram.h>
+ #include <linux/io.h>
+ #include <linux/mod_devicetable.h>
+ #include <linux/module.h>
+@@ -139,6 +140,8 @@ static int brcm_nvram_probe(struct platf
+ if (err)
+ return err;
+
++ bcm47xx_nvram_init_from_iomem(priv->base, resource_size(res));
++
+ config.dev = dev;
+ config.cells = priv->cells;
+ config.ncells = priv->ncells;
+--- a/include/linux/bcm47xx_nvram.h
++++ b/include/linux/bcm47xx_nvram.h
+@@ -11,6 +11,7 @@
+ #include <linux/vmalloc.h>
+
+ #ifdef CONFIG_BCM47XX_NVRAM
++int bcm47xx_nvram_init_from_iomem(void __iomem *nvram_start, size_t res_size);
+ int bcm47xx_nvram_init_from_mem(u32 base, u32 lim);
+ int bcm47xx_nvram_getenv(const char *name, char *val, size_t val_len);
+ int bcm47xx_nvram_gpio_pin(const char *name);
+@@ -20,6 +21,11 @@ static inline void bcm47xx_nvram_release
+ vfree(nvram);
+ };
+ #else
++static inline int bcm47xx_nvram_init_from_iomem(void __iomem *nvram_start,
++ size_t res_size)
++{
++ return -ENOTSUPP;
++}
+ static inline int bcm47xx_nvram_init_from_mem(u32 base, u32 lim)
+ {
+ return -ENOTSUPP;
diff --git a/target/linux/generic/backport-6.6/813-v6.5-0001-nvmem-imx-ocotp-set-varaiable-imx_ocotp_layout-stora.patch b/target/linux/generic/backport-6.6/813-v6.5-0001-nvmem-imx-ocotp-set-varaiable-imx_ocotp_layout-stora.patch
new file mode 100644
index 0000000000..38cfccd5ef
--- /dev/null
+++ b/target/linux/generic/backport-6.6/813-v6.5-0001-nvmem-imx-ocotp-set-varaiable-imx_ocotp_layout-stora.patch
@@ -0,0 +1,31 @@
+From eebc6573ad940b62a87776db3917e912b4f52d78 Mon Sep 17 00:00:00 2001
+From: Tom Rix <trix@redhat.com>
+Date: Sun, 11 Jun 2023 15:03:05 +0100
+Subject: [PATCH] nvmem: imx-ocotp: set varaiable imx_ocotp_layout
+ storage-class-specifier to static
+
+smatch reports
+drivers/nvmem/imx-ocotp.c:599:21: warning: symbol
+ 'imx_ocotp_layout' was not declared. Should it be static?
+
+This variable is only used in one file so should be static.
+
+Signed-off-by: Tom Rix <trix@redhat.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Message-ID: <20230611140330.154222-2-srinivas.kandagatla@linaro.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/imx-ocotp.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/nvmem/imx-ocotp.c
++++ b/drivers/nvmem/imx-ocotp.c
+@@ -596,7 +596,7 @@ static void imx_ocotp_fixup_cell_info(st
+ cell->read_post_process = imx_ocotp_cell_pp;
+ }
+
+-struct nvmem_layout imx_ocotp_layout = {
++static struct nvmem_layout imx_ocotp_layout = {
+ .fixup_cell_info = imx_ocotp_fixup_cell_info,
+ };
+
diff --git a/target/linux/generic/backport-6.6/813-v6.5-0002-nvmem-imx-ocotp-Reverse-MAC-addresses-on-all-i.MX-de.patch b/target/linux/generic/backport-6.6/813-v6.5-0002-nvmem-imx-ocotp-Reverse-MAC-addresses-on-all-i.MX-de.patch
new file mode 100644
index 0000000000..7523e5ebf6
--- /dev/null
+++ b/target/linux/generic/backport-6.6/813-v6.5-0002-nvmem-imx-ocotp-Reverse-MAC-addresses-on-all-i.MX-de.patch
@@ -0,0 +1,71 @@
+From 8a00fc606312c68b98add8fe8e6f7a013ce29e78 Mon Sep 17 00:00:00 2001
+From: Alexander Stein <alexander.stein@ew.tq-group.com>
+Date: Sun, 11 Jun 2023 15:03:06 +0100
+Subject: [PATCH] nvmem: imx-ocotp: Reverse MAC addresses on all i.MX derivates
+
+Not just i.MX8M, but all i.MX6/7 (and subtypes) need to reverse the
+MAC address read from fuses. Exceptions are i.MX6SLL and i.MX7ULP which
+do not support ethernet at all.
+
+Fixes: d0221a780cbc ("nvmem: imx-ocotp: add support for post processing")
+Signed-off-by: Alexander Stein <alexander.stein@ew.tq-group.com>
+Tested-by: Richard Leitner <richard.leitner@skidata.com> # imx6q
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Message-ID: <20230611140330.154222-3-srinivas.kandagatla@linaro.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/imx-ocotp.c | 8 +-------
+ 1 file changed, 1 insertion(+), 7 deletions(-)
+
+--- a/drivers/nvmem/imx-ocotp.c
++++ b/drivers/nvmem/imx-ocotp.c
+@@ -97,7 +97,6 @@ struct ocotp_params {
+ unsigned int bank_address_words;
+ void (*set_timing)(struct ocotp_priv *priv);
+ struct ocotp_ctrl_reg ctrl;
+- bool reverse_mac_address;
+ };
+
+ static int imx_ocotp_wait_for_busy(struct ocotp_priv *priv, u32 flags)
+@@ -545,7 +544,6 @@ static const struct ocotp_params imx8mq_
+ .bank_address_words = 0,
+ .set_timing = imx_ocotp_set_imx6_timing,
+ .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT,
+- .reverse_mac_address = true,
+ };
+
+ static const struct ocotp_params imx8mm_params = {
+@@ -553,7 +551,6 @@ static const struct ocotp_params imx8mm_
+ .bank_address_words = 0,
+ .set_timing = imx_ocotp_set_imx6_timing,
+ .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT,
+- .reverse_mac_address = true,
+ };
+
+ static const struct ocotp_params imx8mn_params = {
+@@ -561,7 +558,6 @@ static const struct ocotp_params imx8mn_
+ .bank_address_words = 0,
+ .set_timing = imx_ocotp_set_imx6_timing,
+ .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT,
+- .reverse_mac_address = true,
+ };
+
+ static const struct ocotp_params imx8mp_params = {
+@@ -569,7 +565,6 @@ static const struct ocotp_params imx8mp_
+ .bank_address_words = 0,
+ .set_timing = imx_ocotp_set_imx6_timing,
+ .ctrl = IMX_OCOTP_BM_CTRL_8MP,
+- .reverse_mac_address = true,
+ };
+
+ static const struct of_device_id imx_ocotp_dt_ids[] = {
+@@ -624,8 +619,7 @@ static int imx_ocotp_probe(struct platfo
+ imx_ocotp_nvmem_config.size = 4 * priv->params->nregs;
+ imx_ocotp_nvmem_config.dev = dev;
+ imx_ocotp_nvmem_config.priv = priv;
+- if (priv->params->reverse_mac_address)
+- imx_ocotp_nvmem_config.layout = &imx_ocotp_layout;
++ imx_ocotp_nvmem_config.layout = &imx_ocotp_layout;
+
+ priv->config = &imx_ocotp_nvmem_config;
+
diff --git a/target/linux/generic/backport-6.6/813-v6.5-0003-nvmem-brcm_nvram-add-.read_post_process-for-MACs.patch b/target/linux/generic/backport-6.6/813-v6.5-0003-nvmem-brcm_nvram-add-.read_post_process-for-MACs.patch
new file mode 100644
index 0000000000..fb4d346a95
--- /dev/null
+++ b/target/linux/generic/backport-6.6/813-v6.5-0003-nvmem-brcm_nvram-add-.read_post_process-for-MACs.patch
@@ -0,0 +1,81 @@
+From 73bcd133c910bff3b6d3b3834d0d14be9444e90a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Sun, 11 Jun 2023 15:03:08 +0100
+Subject: [PATCH] nvmem: brcm_nvram: add .read_post_process() for MACs
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+1. Parse ASCII MAC format into byte based
+2. Calculate relative addresses based on index argument
+
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Message-ID: <20230611140330.154222-5-srinivas.kandagatla@linaro.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/Kconfig | 1 +
+ drivers/nvmem/brcm_nvram.c | 28 ++++++++++++++++++++++++++++
+ 2 files changed, 29 insertions(+)
+
+--- a/drivers/nvmem/Kconfig
++++ b/drivers/nvmem/Kconfig
+@@ -55,6 +55,7 @@ config NVMEM_BRCM_NVRAM
+ tristate "Broadcom's NVRAM support"
+ depends on ARCH_BCM_5301X || COMPILE_TEST
+ depends on HAS_IOMEM
++ select GENERIC_NET_UTILS
+ help
+ This driver provides support for Broadcom's NVRAM that can be accessed
+ using I/O mapping.
+--- a/drivers/nvmem/brcm_nvram.c
++++ b/drivers/nvmem/brcm_nvram.c
+@@ -4,6 +4,8 @@
+ */
+
+ #include <linux/bcm47xx_nvram.h>
++#include <linux/etherdevice.h>
++#include <linux/if_ether.h>
+ #include <linux/io.h>
+ #include <linux/mod_devicetable.h>
+ #include <linux/module.h>
+@@ -42,6 +44,25 @@ static int brcm_nvram_read(void *context
+ return 0;
+ }
+
++static int brcm_nvram_read_post_process_macaddr(void *context, const char *id, int index,
++ unsigned int offset, void *buf, size_t bytes)
++{
++ u8 mac[ETH_ALEN];
++
++ if (bytes != 3 * ETH_ALEN - 1)
++ return -EINVAL;
++
++ if (!mac_pton(buf, mac))
++ return -EINVAL;
++
++ if (index)
++ eth_addr_add(mac, index);
++
++ ether_addr_copy(buf, mac);
++
++ return 0;
++}
++
+ static int brcm_nvram_add_cells(struct brcm_nvram *priv, uint8_t *data,
+ size_t len)
+ {
+@@ -75,6 +96,13 @@ static int brcm_nvram_add_cells(struct b
+ priv->cells[idx].offset = value - (char *)data;
+ priv->cells[idx].bytes = strlen(value);
+ priv->cells[idx].np = of_get_child_by_name(dev->of_node, priv->cells[idx].name);
++ if (!strcmp(var, "et0macaddr") ||
++ !strcmp(var, "et1macaddr") ||
++ !strcmp(var, "et2macaddr")) {
++ priv->cells[idx].raw_len = strlen(value);
++ priv->cells[idx].bytes = ETH_ALEN;
++ priv->cells[idx].read_post_process = brcm_nvram_read_post_process_macaddr;
++ }
+ }
+
+ return 0;
diff --git a/target/linux/generic/backport-6.6/813-v6.5-0004-nvmem-rockchip-otp-Add-clks-and-reg_read-to-rockchip.patch b/target/linux/generic/backport-6.6/813-v6.5-0004-nvmem-rockchip-otp-Add-clks-and-reg_read-to-rockchip.patch
new file mode 100644
index 0000000000..3b6e6e57f5
--- /dev/null
+++ b/target/linux/generic/backport-6.6/813-v6.5-0004-nvmem-rockchip-otp-Add-clks-and-reg_read-to-rockchip.patch
@@ -0,0 +1,166 @@
+From 8dc61364164e79e44c07fa2ac0a7b6939f00d5db Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
+Date: Sun, 11 Jun 2023 15:03:13 +0100
+Subject: [PATCH] nvmem: rockchip-otp: Add clks and reg_read to rockchip_data
+
+In preparation to support new Rockchip OTP memory devices with different
+clock configurations and register layout, extend rockchip_data struct
+with the related members: clks, num_clks, reg_read.
+
+Additionally, to avoid managing redundant driver data, drop num_clks
+member from rockchip_otp struct and update all references to point to
+the equivalent member in rockchip_data.
+
+Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
+Tested-by: Vincent Legoll <vincent.legoll@gmail.com>
+Reviewed-by: Heiko Stuebner <heiko@sntech.de>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Message-ID: <20230611140330.154222-10-srinivas.kandagatla@linaro.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/rockchip-otp.c | 79 ++++++++++++++++++++++--------------
+ 1 file changed, 49 insertions(+), 30 deletions(-)
+
+--- a/drivers/nvmem/rockchip-otp.c
++++ b/drivers/nvmem/rockchip-otp.c
+@@ -54,21 +54,19 @@
+
+ #define OTPC_TIMEOUT 10000
+
++struct rockchip_data {
++ int size;
++ const char * const *clks;
++ int num_clks;
++ nvmem_reg_read_t reg_read;
++};
++
+ struct rockchip_otp {
+ struct device *dev;
+ void __iomem *base;
+- struct clk_bulk_data *clks;
+- int num_clks;
++ struct clk_bulk_data *clks;
+ struct reset_control *rst;
+-};
+-
+-/* list of required clocks */
+-static const char * const rockchip_otp_clocks[] = {
+- "otp", "apb_pclk", "phy",
+-};
+-
+-struct rockchip_data {
+- int size;
++ const struct rockchip_data *data;
+ };
+
+ static int rockchip_otp_reset(struct rockchip_otp *otp)
+@@ -132,29 +130,23 @@ static int rockchip_otp_ecc_enable(struc
+ return ret;
+ }
+
+-static int rockchip_otp_read(void *context, unsigned int offset,
+- void *val, size_t bytes)
++static int px30_otp_read(void *context, unsigned int offset,
++ void *val, size_t bytes)
+ {
+ struct rockchip_otp *otp = context;
+ u8 *buf = val;
+- int ret = 0;
+-
+- ret = clk_bulk_prepare_enable(otp->num_clks, otp->clks);
+- if (ret < 0) {
+- dev_err(otp->dev, "failed to prepare/enable clks\n");
+- return ret;
+- }
++ int ret;
+
+ ret = rockchip_otp_reset(otp);
+ if (ret) {
+ dev_err(otp->dev, "failed to reset otp phy\n");
+- goto disable_clks;
++ return ret;
+ }
+
+ ret = rockchip_otp_ecc_enable(otp, false);
+ if (ret < 0) {
+ dev_err(otp->dev, "rockchip_otp_ecc_enable err\n");
+- goto disable_clks;
++ return ret;
+ }
+
+ writel(OTPC_USE_USER | OTPC_USE_USER_MASK, otp->base + OTPC_USER_CTRL);
+@@ -174,8 +166,28 @@ static int rockchip_otp_read(void *conte
+
+ read_end:
+ writel(0x0 | OTPC_USE_USER_MASK, otp->base + OTPC_USER_CTRL);
+-disable_clks:
+- clk_bulk_disable_unprepare(otp->num_clks, otp->clks);
++
++ return ret;
++}
++
++static int rockchip_otp_read(void *context, unsigned int offset,
++ void *val, size_t bytes)
++{
++ struct rockchip_otp *otp = context;
++ int ret;
++
++ if (!otp->data || !otp->data->reg_read)
++ return -EINVAL;
++
++ ret = clk_bulk_prepare_enable(otp->data->num_clks, otp->clks);
++ if (ret < 0) {
++ dev_err(otp->dev, "failed to prepare/enable clks\n");
++ return ret;
++ }
++
++ ret = otp->data->reg_read(context, offset, val, bytes);
++
++ clk_bulk_disable_unprepare(otp->data->num_clks, otp->clks);
+
+ return ret;
+ }
+@@ -189,8 +201,15 @@ static struct nvmem_config otp_config =
+ .reg_read = rockchip_otp_read,
+ };
+
++static const char * const px30_otp_clocks[] = {
++ "otp", "apb_pclk", "phy",
++};
++
+ static const struct rockchip_data px30_data = {
+ .size = 0x40,
++ .clks = px30_otp_clocks,
++ .num_clks = ARRAY_SIZE(px30_otp_clocks),
++ .reg_read = px30_otp_read,
+ };
+
+ static const struct of_device_id rockchip_otp_match[] = {
+@@ -225,21 +244,21 @@ static int rockchip_otp_probe(struct pla
+ if (!otp)
+ return -ENOMEM;
+
++ otp->data = data;
+ otp->dev = dev;
+ otp->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(otp->base))
+ return PTR_ERR(otp->base);
+
+- otp->num_clks = ARRAY_SIZE(rockchip_otp_clocks);
+- otp->clks = devm_kcalloc(dev, otp->num_clks,
+- sizeof(*otp->clks), GFP_KERNEL);
++ otp->clks = devm_kcalloc(dev, data->num_clks, sizeof(*otp->clks),
++ GFP_KERNEL);
+ if (!otp->clks)
+ return -ENOMEM;
+
+- for (i = 0; i < otp->num_clks; ++i)
+- otp->clks[i].id = rockchip_otp_clocks[i];
++ for (i = 0; i < data->num_clks; ++i)
++ otp->clks[i].id = data->clks[i];
+
+- ret = devm_clk_bulk_get(dev, otp->num_clks, otp->clks);
++ ret = devm_clk_bulk_get(dev, data->num_clks, otp->clks);
+ if (ret)
+ return ret;
+
diff --git a/target/linux/generic/backport-6.6/813-v6.5-0005-nvmem-rockchip-otp-Generalize-rockchip_otp_wait_stat.patch b/target/linux/generic/backport-6.6/813-v6.5-0005-nvmem-rockchip-otp-Generalize-rockchip_otp_wait_stat.patch
new file mode 100644
index 0000000000..b5b66cfc5a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/813-v6.5-0005-nvmem-rockchip-otp-Generalize-rockchip_otp_wait_stat.patch
@@ -0,0 +1,62 @@
+From 30fd21cfb1e64ef20035559a8246f5fbf682c40e Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
+Date: Sun, 11 Jun 2023 15:03:14 +0100
+Subject: [PATCH] nvmem: rockchip-otp: Generalize rockchip_otp_wait_status()
+
+In preparation to support additional Rockchip OTP memory devices with
+different register layout, generalize rockchip_otp_wait_status() to
+accept a new parameter for specifying the offset of the status register.
+
+Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
+Tested-by: Vincent Legoll <vincent.legoll@gmail.com>
+Reviewed-by: Heiko Stuebner <heiko@sntech.de>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Message-ID: <20230611140330.154222-11-srinivas.kandagatla@linaro.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/rockchip-otp.c | 11 ++++++-----
+ 1 file changed, 6 insertions(+), 5 deletions(-)
+
+--- a/drivers/nvmem/rockchip-otp.c
++++ b/drivers/nvmem/rockchip-otp.c
+@@ -90,18 +90,19 @@ static int rockchip_otp_reset(struct roc
+ return 0;
+ }
+
+-static int rockchip_otp_wait_status(struct rockchip_otp *otp, u32 flag)
++static int rockchip_otp_wait_status(struct rockchip_otp *otp,
++ unsigned int reg, u32 flag)
+ {
+ u32 status = 0;
+ int ret;
+
+- ret = readl_poll_timeout_atomic(otp->base + OTPC_INT_STATUS, status,
++ ret = readl_poll_timeout_atomic(otp->base + reg, status,
+ (status & flag), 1, OTPC_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* clean int status */
+- writel(flag, otp->base + OTPC_INT_STATUS);
++ writel(flag, otp->base + reg);
+
+ return 0;
+ }
+@@ -123,7 +124,7 @@ static int rockchip_otp_ecc_enable(struc
+
+ writel(SBPI_ENABLE_MASK | SBPI_ENABLE, otp->base + OTPC_SBPI_CTRL);
+
+- ret = rockchip_otp_wait_status(otp, OTPC_SBPI_DONE);
++ ret = rockchip_otp_wait_status(otp, OTPC_INT_STATUS, OTPC_SBPI_DONE);
+ if (ret < 0)
+ dev_err(otp->dev, "timeout during ecc_enable\n");
+
+@@ -156,7 +157,7 @@ static int px30_otp_read(void *context,
+ otp->base + OTPC_USER_ADDR);
+ writel(OTPC_USER_FSM_ENABLE | OTPC_USER_FSM_ENABLE_MASK,
+ otp->base + OTPC_USER_ENABLE);
+- ret = rockchip_otp_wait_status(otp, OTPC_USER_DONE);
++ ret = rockchip_otp_wait_status(otp, OTPC_INT_STATUS, OTPC_USER_DONE);
+ if (ret < 0) {
+ dev_err(otp->dev, "timeout during read setup\n");
+ goto read_end;
diff --git a/target/linux/generic/backport-6.6/813-v6.5-0006-nvmem-rockchip-otp-Use-devm_reset_control_array_get_.patch b/target/linux/generic/backport-6.6/813-v6.5-0006-nvmem-rockchip-otp-Use-devm_reset_control_array_get_.patch
new file mode 100644
index 0000000000..3a17479d95
--- /dev/null
+++ b/target/linux/generic/backport-6.6/813-v6.5-0006-nvmem-rockchip-otp-Use-devm_reset_control_array_get_.patch
@@ -0,0 +1,31 @@
+From d325c9dd2b6e94040ca722ddcadcd6af358dd2be Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
+Date: Sun, 11 Jun 2023 15:03:15 +0100
+Subject: [PATCH] nvmem: rockchip-otp: Use
+ devm_reset_control_array_get_exclusive()
+
+In preparation to support new Rockchip OTP memory devices having
+specific reset configurations, switch devm_reset_control_get() to
+devm_reset_control_array_get_exclusive().
+
+Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
+Tested-by: Vincent Legoll <vincent.legoll@gmail.com>
+Reviewed-by: Heiko Stuebner <heiko@sntech.de>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Message-ID: <20230611140330.154222-12-srinivas.kandagatla@linaro.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/rockchip-otp.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/nvmem/rockchip-otp.c
++++ b/drivers/nvmem/rockchip-otp.c
+@@ -263,7 +263,7 @@ static int rockchip_otp_probe(struct pla
+ if (ret)
+ return ret;
+
+- otp->rst = devm_reset_control_get(dev, "phy");
++ otp->rst = devm_reset_control_array_get_exclusive(dev);
+ if (IS_ERR(otp->rst))
+ return PTR_ERR(otp->rst);
+
diff --git a/target/linux/generic/backport-6.6/813-v6.5-0007-nvmem-rockchip-otp-Improve-probe-error-handling.patch b/target/linux/generic/backport-6.6/813-v6.5-0007-nvmem-rockchip-otp-Improve-probe-error-handling.patch
new file mode 100644
index 0000000000..37cb927b10
--- /dev/null
+++ b/target/linux/generic/backport-6.6/813-v6.5-0007-nvmem-rockchip-otp-Improve-probe-error-handling.patch
@@ -0,0 +1,71 @@
+From 912517345b867a69542dc9f5c2cc3e9d8beaccf5 Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
+Date: Sun, 11 Jun 2023 15:03:16 +0100
+Subject: [PATCH] nvmem: rockchip-otp: Improve probe error handling
+
+Enhance error handling in the probe function by making use of
+dev_err_probe(), which ensures the error code is always printed, in
+addition to the specified error message.
+
+Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
+Tested-by: Vincent Legoll <vincent.legoll@gmail.com>
+Reviewed-by: Heiko Stuebner <heiko@sntech.de>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Message-ID: <20230611140330.154222-13-srinivas.kandagatla@linaro.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/rockchip-otp.c | 21 ++++++++++++---------
+ 1 file changed, 12 insertions(+), 9 deletions(-)
+
+--- a/drivers/nvmem/rockchip-otp.c
++++ b/drivers/nvmem/rockchip-otp.c
+@@ -235,10 +235,8 @@ static int rockchip_otp_probe(struct pla
+ int ret, i;
+
+ data = of_device_get_match_data(dev);
+- if (!data) {
+- dev_err(dev, "failed to get match data\n");
+- return -EINVAL;
+- }
++ if (!data)
++ return dev_err_probe(dev, -EINVAL, "failed to get match data\n");
+
+ otp = devm_kzalloc(&pdev->dev, sizeof(struct rockchip_otp),
+ GFP_KERNEL);
+@@ -249,7 +247,8 @@ static int rockchip_otp_probe(struct pla
+ otp->dev = dev;
+ otp->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(otp->base))
+- return PTR_ERR(otp->base);
++ return dev_err_probe(dev, PTR_ERR(otp->base),
++ "failed to ioremap resource\n");
+
+ otp->clks = devm_kcalloc(dev, data->num_clks, sizeof(*otp->clks),
+ GFP_KERNEL);
+@@ -261,18 +260,22 @@ static int rockchip_otp_probe(struct pla
+
+ ret = devm_clk_bulk_get(dev, data->num_clks, otp->clks);
+ if (ret)
+- return ret;
++ return dev_err_probe(dev, ret, "failed to get clocks\n");
+
+ otp->rst = devm_reset_control_array_get_exclusive(dev);
+ if (IS_ERR(otp->rst))
+- return PTR_ERR(otp->rst);
++ return dev_err_probe(dev, PTR_ERR(otp->rst),
++ "failed to get resets\n");
+
+ otp_config.size = data->size;
+ otp_config.priv = otp;
+ otp_config.dev = dev;
+- nvmem = devm_nvmem_register(dev, &otp_config);
+
+- return PTR_ERR_OR_ZERO(nvmem);
++ nvmem = devm_nvmem_register(dev, &otp_config);
++ if (IS_ERR(nvmem))
++ return dev_err_probe(dev, PTR_ERR(nvmem),
++ "failed to register nvmem device\n");
++ return 0;
+ }
+
+ static struct platform_driver rockchip_otp_driver = {
diff --git a/target/linux/generic/backport-6.6/813-v6.5-0008-nvmem-rockchip-otp-Add-support-for-RK3588.patch b/target/linux/generic/backport-6.6/813-v6.5-0008-nvmem-rockchip-otp-Add-support-for-RK3588.patch
new file mode 100644
index 0000000000..c1e2231c9e
--- /dev/null
+++ b/target/linux/generic/backport-6.6/813-v6.5-0008-nvmem-rockchip-otp-Add-support-for-RK3588.patch
@@ -0,0 +1,129 @@
+From 8ab099fafbbc8c9607c399d21a774784a6cb8b45 Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
+Date: Sun, 11 Jun 2023 15:03:17 +0100
+Subject: [PATCH] nvmem: rockchip-otp: Add support for RK3588
+
+Add support for the OTP memory device found on the Rockchip RK3588 SoC.
+
+While here, remove the unnecessary 'void *' casts in the OF device ID
+table.
+
+Co-developed-by: Finley Xiao <finley.xiao@rock-chips.com>
+Signed-off-by: Finley Xiao <finley.xiao@rock-chips.com>
+Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
+Tested-by: Vincent Legoll <vincent.legoll@gmail.com>
+Reviewed-by: Heiko Stuebner <heiko@sntech.de>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Message-ID: <20230611140330.154222-14-srinivas.kandagatla@linaro.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/rockchip-otp.c | 78 +++++++++++++++++++++++++++++++++++-
+ 1 file changed, 76 insertions(+), 2 deletions(-)
+
+--- a/drivers/nvmem/rockchip-otp.c
++++ b/drivers/nvmem/rockchip-otp.c
+@@ -54,6 +54,19 @@
+
+ #define OTPC_TIMEOUT 10000
+
++/* RK3588 Register */
++#define RK3588_OTPC_AUTO_CTRL 0x04
++#define RK3588_OTPC_AUTO_EN 0x08
++#define RK3588_OTPC_INT_ST 0x84
++#define RK3588_OTPC_DOUT0 0x20
++#define RK3588_NO_SECURE_OFFSET 0x300
++#define RK3588_NBYTES 4
++#define RK3588_BURST_NUM 1
++#define RK3588_BURST_SHIFT 8
++#define RK3588_ADDR_SHIFT 16
++#define RK3588_AUTO_EN BIT(0)
++#define RK3588_RD_DONE BIT(1)
++
+ struct rockchip_data {
+ int size;
+ const char * const *clks;
+@@ -171,6 +184,52 @@ read_end:
+ return ret;
+ }
+
++static int rk3588_otp_read(void *context, unsigned int offset,
++ void *val, size_t bytes)
++{
++ struct rockchip_otp *otp = context;
++ unsigned int addr_start, addr_end, addr_len;
++ int ret, i = 0;
++ u32 data;
++ u8 *buf;
++
++ addr_start = round_down(offset, RK3588_NBYTES) / RK3588_NBYTES;
++ addr_end = round_up(offset + bytes, RK3588_NBYTES) / RK3588_NBYTES;
++ addr_len = addr_end - addr_start;
++ addr_start += RK3588_NO_SECURE_OFFSET;
++
++ buf = kzalloc(array_size(addr_len, RK3588_NBYTES), GFP_KERNEL);
++ if (!buf)
++ return -ENOMEM;
++
++ while (addr_len--) {
++ writel((addr_start << RK3588_ADDR_SHIFT) |
++ (RK3588_BURST_NUM << RK3588_BURST_SHIFT),
++ otp->base + RK3588_OTPC_AUTO_CTRL);
++ writel(RK3588_AUTO_EN, otp->base + RK3588_OTPC_AUTO_EN);
++
++ ret = rockchip_otp_wait_status(otp, RK3588_OTPC_INT_ST,
++ RK3588_RD_DONE);
++ if (ret < 0) {
++ dev_err(otp->dev, "timeout during read setup\n");
++ goto read_end;
++ }
++
++ data = readl(otp->base + RK3588_OTPC_DOUT0);
++ memcpy(&buf[i], &data, RK3588_NBYTES);
++
++ i += RK3588_NBYTES;
++ addr_start++;
++ }
++
++ memcpy(val, buf + offset % RK3588_NBYTES, bytes);
++
++read_end:
++ kfree(buf);
++
++ return ret;
++}
++
+ static int rockchip_otp_read(void *context, unsigned int offset,
+ void *val, size_t bytes)
+ {
+@@ -213,14 +272,29 @@ static const struct rockchip_data px30_d
+ .reg_read = px30_otp_read,
+ };
+
++static const char * const rk3588_otp_clocks[] = {
++ "otp", "apb_pclk", "phy", "arb",
++};
++
++static const struct rockchip_data rk3588_data = {
++ .size = 0x400,
++ .clks = rk3588_otp_clocks,
++ .num_clks = ARRAY_SIZE(rk3588_otp_clocks),
++ .reg_read = rk3588_otp_read,
++};
++
+ static const struct of_device_id rockchip_otp_match[] = {
+ {
+ .compatible = "rockchip,px30-otp",
+- .data = (void *)&px30_data,
++ .data = &px30_data,
+ },
+ {
+ .compatible = "rockchip,rk3308-otp",
+- .data = (void *)&px30_data,
++ .data = &px30_data,
++ },
++ {
++ .compatible = "rockchip,rk3588-otp",
++ .data = &rk3588_data,
+ },
+ { /* sentinel */ },
+ };
diff --git a/target/linux/generic/backport-6.6/813-v6.5-0009-nvmem-zynqmp-Switch-xilinx.com-emails-to-amd.com.patch b/target/linux/generic/backport-6.6/813-v6.5-0009-nvmem-zynqmp-Switch-xilinx.com-emails-to-amd.com.patch
new file mode 100644
index 0000000000..220e3e9a05
--- /dev/null
+++ b/target/linux/generic/backport-6.6/813-v6.5-0009-nvmem-zynqmp-Switch-xilinx.com-emails-to-amd.com.patch
@@ -0,0 +1,26 @@
+From 9734408969e978a1c0d5d752be63dd638288e374 Mon Sep 17 00:00:00 2001
+From: Michal Simek <michal.simek@amd.com>
+Date: Sun, 11 Jun 2023 15:03:23 +0100
+Subject: [PATCH] nvmem: zynqmp: Switch @xilinx.com emails to @amd.com
+
+@xilinx.com is still working but better to switch to new amd.com after
+AMD/Xilinx acquisition.
+
+Signed-off-by: Michal Simek <michal.simek@amd.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Message-ID: <20230611140330.154222-20-srinivas.kandagatla@linaro.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/zynqmp_nvmem.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/nvmem/zynqmp_nvmem.c
++++ b/drivers/nvmem/zynqmp_nvmem.c
+@@ -76,6 +76,6 @@ static struct platform_driver zynqmp_nvm
+
+ module_platform_driver(zynqmp_nvmem_driver);
+
+-MODULE_AUTHOR("Michal Simek <michal.simek@xilinx.com>, Nava kishore Manne <navam@xilinx.com>");
++MODULE_AUTHOR("Michal Simek <michal.simek@amd.com>, Nava kishore Manne <nava.kishore.manne@amd.com>");
+ MODULE_DESCRIPTION("ZynqMP NVMEM driver");
+ MODULE_LICENSE("GPL");
diff --git a/target/linux/generic/backport-6.6/813-v6.5-0010-nvmem-imx-support-i.MX93-OCOTP.patch b/target/linux/generic/backport-6.6/813-v6.5-0010-nvmem-imx-support-i.MX93-OCOTP.patch
new file mode 100644
index 0000000000..f8e6be4241
--- /dev/null
+++ b/target/linux/generic/backport-6.6/813-v6.5-0010-nvmem-imx-support-i.MX93-OCOTP.patch
@@ -0,0 +1,230 @@
+From 22e9e6fcfb5042cb6d6c7874c459b034800092f1 Mon Sep 17 00:00:00 2001
+From: Peng Fan <peng.fan@nxp.com>
+Date: Sun, 11 Jun 2023 15:03:25 +0100
+Subject: [PATCH] nvmem: imx: support i.MX93 OCOTP
+
+Add i.MX93 OCOTP support. i.MX93 OCOTP has two parts: Fuse shadow
+block(fsb) and fuse managed by ELE. The FSB part could be directly
+accessed with MMIO, the ELE could only be accessed with ELE API.
+
+Currently the ELE API is not ready, so NULL function callback is used,
+but it was tested with downstream ELE API.
+
+Signed-off-by: Peng Fan <peng.fan@nxp.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Message-ID: <20230611140330.154222-22-srinivas.kandagatla@linaro.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/Kconfig | 9 ++
+ drivers/nvmem/Makefile | 2 +
+ drivers/nvmem/imx-ocotp-ele.c | 175 ++++++++++++++++++++++++++++++++++
+ 3 files changed, 186 insertions(+)
+ create mode 100644 drivers/nvmem/imx-ocotp-ele.c
+
+--- a/drivers/nvmem/Kconfig
++++ b/drivers/nvmem/Kconfig
+@@ -83,6 +83,15 @@ config NVMEM_IMX_OCOTP
+ This driver can also be built as a module. If so, the module
+ will be called nvmem-imx-ocotp.
+
++config NVMEM_IMX_OCOTP_ELE
++ tristate "i.MX On-Chip OTP Controller support"
++ depends on ARCH_MXC || COMPILE_TEST
++ depends on HAS_IOMEM
++ depends on OF
++ help
++ This is a driver for the On-Chip OTP Controller (OCOTP)
++ available on i.MX SoCs which has ELE.
++
+ config NVMEM_IMX_OCOTP_SCU
+ tristate "i.MX8 SCU On-Chip OTP Controller support"
+ depends on IMX_SCU
+--- a/drivers/nvmem/Makefile
++++ b/drivers/nvmem/Makefile
+@@ -18,6 +18,8 @@ obj-$(CONFIG_NVMEM_IMX_IIM) += nvmem-im
+ nvmem-imx-iim-y := imx-iim.o
+ obj-$(CONFIG_NVMEM_IMX_OCOTP) += nvmem-imx-ocotp.o
+ nvmem-imx-ocotp-y := imx-ocotp.o
++obj-$(CONFIG_NVMEM_IMX_OCOTP_ELE) += nvmem-imx-ocotp-ele.o
++nvmem-imx-ocotp-ele-y := imx-ocotp-ele.o
+ obj-$(CONFIG_NVMEM_IMX_OCOTP_SCU) += nvmem-imx-ocotp-scu.o
+ nvmem-imx-ocotp-scu-y := imx-ocotp-scu.o
+ obj-$(CONFIG_NVMEM_JZ4780_EFUSE) += nvmem_jz4780_efuse.o
+--- /dev/null
++++ b/drivers/nvmem/imx-ocotp-ele.c
+@@ -0,0 +1,175 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * i.MX9 OCOTP fusebox driver
++ *
++ * Copyright 2023 NXP
++ */
++
++#include <linux/device.h>
++#include <linux/io.h>
++#include <linux/module.h>
++#include <linux/nvmem-provider.h>
++#include <linux/of_device.h>
++#include <linux/platform_device.h>
++#include <linux/slab.h>
++
++enum fuse_type {
++ FUSE_FSB = 1,
++ FUSE_ELE = 2,
++ FUSE_INVALID = -1
++};
++
++struct ocotp_map_entry {
++ u32 start; /* start word */
++ u32 num; /* num words */
++ enum fuse_type type;
++};
++
++struct ocotp_devtype_data {
++ u32 reg_off;
++ char *name;
++ u32 size;
++ u32 num_entry;
++ u32 flag;
++ nvmem_reg_read_t reg_read;
++ struct ocotp_map_entry entry[];
++};
++
++struct imx_ocotp_priv {
++ struct device *dev;
++ void __iomem *base;
++ struct nvmem_config config;
++ struct mutex lock;
++ const struct ocotp_devtype_data *data;
++};
++
++static enum fuse_type imx_ocotp_fuse_type(void *context, u32 index)
++{
++ struct imx_ocotp_priv *priv = context;
++ const struct ocotp_devtype_data *data = priv->data;
++ u32 start, end;
++ int i;
++
++ for (i = 0; i < data->num_entry; i++) {
++ start = data->entry[i].start;
++ end = data->entry[i].start + data->entry[i].num;
++
++ if (index >= start && index < end)
++ return data->entry[i].type;
++ }
++
++ return FUSE_INVALID;
++}
++
++static int imx_ocotp_reg_read(void *context, unsigned int offset, void *val, size_t bytes)
++{
++ struct imx_ocotp_priv *priv = context;
++ void __iomem *reg = priv->base + priv->data->reg_off;
++ u32 count, index, num_bytes;
++ enum fuse_type type;
++ u32 *buf;
++ void *p;
++ int i;
++
++ index = offset;
++ num_bytes = round_up(bytes, 4);
++ count = num_bytes >> 2;
++
++ if (count > ((priv->data->size >> 2) - index))
++ count = (priv->data->size >> 2) - index;
++
++ p = kzalloc(num_bytes, GFP_KERNEL);
++ if (!p)
++ return -ENOMEM;
++
++ mutex_lock(&priv->lock);
++
++ buf = p;
++
++ for (i = index; i < (index + count); i++) {
++ type = imx_ocotp_fuse_type(context, i);
++ if (type == FUSE_INVALID || type == FUSE_ELE) {
++ *buf++ = 0;
++ continue;
++ }
++
++ *buf++ = readl_relaxed(reg + (i << 2));
++ }
++
++ memcpy(val, (u8 *)p, bytes);
++
++ mutex_unlock(&priv->lock);
++
++ kfree(p);
++
++ return 0;
++};
++
++static int imx_ele_ocotp_probe(struct platform_device *pdev)
++{
++ struct device *dev = &pdev->dev;
++ struct imx_ocotp_priv *priv;
++ struct nvmem_device *nvmem;
++
++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
++
++ priv->data = of_device_get_match_data(dev);
++
++ priv->base = devm_platform_ioremap_resource(pdev, 0);
++ if (IS_ERR(priv->base))
++ return PTR_ERR(priv->base);
++
++ priv->config.dev = dev;
++ priv->config.name = "ELE-OCOTP";
++ priv->config.id = NVMEM_DEVID_AUTO;
++ priv->config.owner = THIS_MODULE;
++ priv->config.size = priv->data->size;
++ priv->config.reg_read = priv->data->reg_read;
++ priv->config.word_size = 4;
++ priv->config.stride = 1;
++ priv->config.priv = priv;
++ priv->config.read_only = true;
++ mutex_init(&priv->lock);
++
++ nvmem = devm_nvmem_register(dev, &priv->config);
++ if (IS_ERR(nvmem))
++ return PTR_ERR(nvmem);
++
++ return 0;
++}
++
++static const struct ocotp_devtype_data imx93_ocotp_data = {
++ .reg_off = 0x8000,
++ .reg_read = imx_ocotp_reg_read,
++ .size = 2048,
++ .num_entry = 6,
++ .entry = {
++ { 0, 52, FUSE_FSB },
++ { 63, 1, FUSE_ELE},
++ { 128, 16, FUSE_ELE },
++ { 182, 1, FUSE_ELE },
++ { 188, 1, FUSE_ELE },
++ { 312, 200, FUSE_FSB }
++ },
++};
++
++static const struct of_device_id imx_ele_ocotp_dt_ids[] = {
++ { .compatible = "fsl,imx93-ocotp", .data = &imx93_ocotp_data, },
++ {},
++};
++MODULE_DEVICE_TABLE(of, imx_ele_ocotp_dt_ids);
++
++static struct platform_driver imx_ele_ocotp_driver = {
++ .driver = {
++ .name = "imx_ele_ocotp",
++ .of_match_table = imx_ele_ocotp_dt_ids,
++ },
++ .probe = imx_ele_ocotp_probe,
++};
++module_platform_driver(imx_ele_ocotp_driver);
++
++MODULE_DESCRIPTION("i.MX OCOTP/ELE driver");
++MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>");
++MODULE_LICENSE("GPL");
diff --git a/target/linux/generic/backport-6.6/813-v6.5-0011-nvmem-core-add-support-for-fixed-cells-layout.patch b/target/linux/generic/backport-6.6/813-v6.5-0011-nvmem-core-add-support-for-fixed-cells-layout.patch
new file mode 100644
index 0000000000..59b2f9fa2c
--- /dev/null
+++ b/target/linux/generic/backport-6.6/813-v6.5-0011-nvmem-core-add-support-for-fixed-cells-layout.patch
@@ -0,0 +1,96 @@
+From 27f699e578b1a72df89dfa3bc42e093a01dc8d10 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Sun, 11 Jun 2023 15:03:29 +0100
+Subject: [PATCH] nvmem: core: add support for fixed cells *layout*
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This adds support for the "fixed-layout" NVMEM layout binding. It allows
+defining NVMEM cells in a layout DT node named "nvmem-layout".
+
+While NVMEM subsystem supports layout drivers it has been discussed that
+"fixed-layout" may actually be supperted internally. It's because:
+1. It's a very basic layout
+2. It allows sharing code with legacy syntax parsing
+3. It's safer for soc_device_match() due to -EPROBE_DEFER
+4. This will make the syntax transition easier
+
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Reviewed-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Message-ID: <20230611140330.154222-26-srinivas.kandagatla@linaro.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 32 +++++++++++++++++++++++++++++---
+ 1 file changed, 29 insertions(+), 3 deletions(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -696,7 +696,7 @@ static int nvmem_validate_keepouts(struc
+ return 0;
+ }
+
+-static int nvmem_add_cells_from_of(struct nvmem_device *nvmem)
++static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np)
+ {
+ struct nvmem_layout *layout = nvmem->layout;
+ struct device *dev = &nvmem->dev;
+@@ -704,7 +704,7 @@ static int nvmem_add_cells_from_of(struc
+ const __be32 *addr;
+ int len, ret;
+
+- for_each_child_of_node(dev->of_node, child) {
++ for_each_child_of_node(np, child) {
+ struct nvmem_cell_info info = {0};
+
+ addr = of_get_property(child, "reg", &len);
+@@ -742,6 +742,28 @@ static int nvmem_add_cells_from_of(struc
+ return 0;
+ }
+
++static int nvmem_add_cells_from_legacy_of(struct nvmem_device *nvmem)
++{
++ return nvmem_add_cells_from_dt(nvmem, nvmem->dev.of_node);
++}
++
++static int nvmem_add_cells_from_fixed_layout(struct nvmem_device *nvmem)
++{
++ struct device_node *layout_np;
++ int err = 0;
++
++ layout_np = of_nvmem_layout_get_container(nvmem);
++ if (!layout_np)
++ return 0;
++
++ if (of_device_is_compatible(layout_np, "fixed-layout"))
++ err = nvmem_add_cells_from_dt(nvmem, layout_np);
++
++ of_node_put(layout_np);
++
++ return err;
++}
++
+ int __nvmem_layout_register(struct nvmem_layout *layout, struct module *owner)
+ {
+ layout->owner = owner;
+@@ -972,7 +994,7 @@ struct nvmem_device *nvmem_register(cons
+ if (rval)
+ goto err_remove_cells;
+
+- rval = nvmem_add_cells_from_of(nvmem);
++ rval = nvmem_add_cells_from_legacy_of(nvmem);
+ if (rval)
+ goto err_remove_cells;
+
+@@ -982,6 +1004,10 @@ struct nvmem_device *nvmem_register(cons
+ if (rval)
+ goto err_remove_cells;
+
++ rval = nvmem_add_cells_from_fixed_layout(nvmem);
++ if (rval)
++ goto err_remove_cells;
++
+ rval = nvmem_add_cells_from_layout(nvmem);
+ if (rval)
+ goto err_remove_cells;
diff --git a/target/linux/generic/backport-6.6/814-v6.6-0001-nvmem-sunxi_sid-Convert-to-devm_platform_ioremap_res.patch b/target/linux/generic/backport-6.6/814-v6.6-0001-nvmem-sunxi_sid-Convert-to-devm_platform_ioremap_res.patch
new file mode 100644
index 0000000000..bf7a816bb2
--- /dev/null
+++ b/target/linux/generic/backport-6.6/814-v6.6-0001-nvmem-sunxi_sid-Convert-to-devm_platform_ioremap_res.patch
@@ -0,0 +1,36 @@
+From 9ccfcbeb8f32ff89e99b36cb9cdebaa0d1b44ed1 Mon Sep 17 00:00:00 2001
+From: Yangtao Li <frank.li@vivo.com>
+Date: Wed, 23 Aug 2023 14:27:24 +0100
+Subject: [PATCH] nvmem: sunxi_sid: Convert to devm_platform_ioremap_resource()
+
+Use devm_platform_ioremap_resource() to simplify code.
+
+Signed-off-by: Yangtao Li <frank.li@vivo.com>
+Acked-by: Jernej Skrabec <jernej.skrabec@gmail.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230823132744.350618-3-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/sunxi_sid.c | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+--- a/drivers/nvmem/sunxi_sid.c
++++ b/drivers/nvmem/sunxi_sid.c
+@@ -125,7 +125,6 @@ static int sun8i_sid_read_by_reg(void *c
+ static int sunxi_sid_probe(struct platform_device *pdev)
+ {
+ struct device *dev = &pdev->dev;
+- struct resource *res;
+ struct nvmem_config *nvmem_cfg;
+ struct nvmem_device *nvmem;
+ struct sunxi_sid *sid;
+@@ -142,8 +141,7 @@ static int sunxi_sid_probe(struct platfo
+ return -EINVAL;
+ sid->value_offset = cfg->value_offset;
+
+- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+- sid->base = devm_ioremap_resource(dev, res);
++ sid->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(sid->base))
+ return PTR_ERR(sid->base);
+
diff --git a/target/linux/generic/backport-6.6/814-v6.6-0002-nvmem-brcm_nvram-Use-devm_platform_get_and_ioremap_r.patch b/target/linux/generic/backport-6.6/814-v6.6-0002-nvmem-brcm_nvram-Use-devm_platform_get_and_ioremap_r.patch
new file mode 100644
index 0000000000..f142d735de
--- /dev/null
+++ b/target/linux/generic/backport-6.6/814-v6.6-0002-nvmem-brcm_nvram-Use-devm_platform_get_and_ioremap_r.patch
@@ -0,0 +1,30 @@
+From cfadd0e7d9225566f320bc4dc716682be910be6c Mon Sep 17 00:00:00 2001
+From: Yangtao Li <frank.li@vivo.com>
+Date: Wed, 23 Aug 2023 14:27:25 +0100
+Subject: [PATCH] nvmem: brcm_nvram: Use
+ devm_platform_get_and_ioremap_resource()
+
+Convert platform_get_resource(), devm_ioremap_resource() to a single
+call to devm_platform_get_and_ioremap_resource(), as this is exactly
+what this function does.
+
+Signed-off-by: Yangtao Li <frank.li@vivo.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230823132744.350618-4-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/brcm_nvram.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/drivers/nvmem/brcm_nvram.c
++++ b/drivers/nvmem/brcm_nvram.c
+@@ -159,8 +159,7 @@ static int brcm_nvram_probe(struct platf
+ return -ENOMEM;
+ priv->dev = dev;
+
+- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+- priv->base = devm_ioremap_resource(dev, res);
++ priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
diff --git a/target/linux/generic/backport-6.6/814-v6.6-0003-nvmem-lpc18xx_otp-Convert-to-devm_platform_ioremap_r.patch b/target/linux/generic/backport-6.6/814-v6.6-0003-nvmem-lpc18xx_otp-Convert-to-devm_platform_ioremap_r.patch
new file mode 100644
index 0000000000..0395bbf8bc
--- /dev/null
+++ b/target/linux/generic/backport-6.6/814-v6.6-0003-nvmem-lpc18xx_otp-Convert-to-devm_platform_ioremap_r.patch
@@ -0,0 +1,34 @@
+From 0b49178e2b6b4aac3c7fa3ce8d8c02208a13b988 Mon Sep 17 00:00:00 2001
+From: Yangtao Li <frank.li@vivo.com>
+Date: Wed, 23 Aug 2023 14:27:26 +0100
+Subject: [PATCH] nvmem: lpc18xx_otp: Convert to
+ devm_platform_ioremap_resource()
+
+Use devm_platform_ioremap_resource() to simplify code.
+
+Signed-off-by: Yangtao Li <frank.li@vivo.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230823132744.350618-5-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/lpc18xx_otp.c | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+--- a/drivers/nvmem/lpc18xx_otp.c
++++ b/drivers/nvmem/lpc18xx_otp.c
+@@ -68,14 +68,12 @@ static int lpc18xx_otp_probe(struct plat
+ {
+ struct nvmem_device *nvmem;
+ struct lpc18xx_otp *otp;
+- struct resource *res;
+
+ otp = devm_kzalloc(&pdev->dev, sizeof(*otp), GFP_KERNEL);
+ if (!otp)
+ return -ENOMEM;
+
+- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+- otp->base = devm_ioremap_resource(&pdev->dev, res);
++ otp->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(otp->base))
+ return PTR_ERR(otp->base);
+
diff --git a/target/linux/generic/backport-6.6/814-v6.6-0004-nvmem-meson-mx-efuse-Convert-to-devm_platform_iorema.patch b/target/linux/generic/backport-6.6/814-v6.6-0004-nvmem-meson-mx-efuse-Convert-to-devm_platform_iorema.patch
new file mode 100644
index 0000000000..da077eb4b7
--- /dev/null
+++ b/target/linux/generic/backport-6.6/814-v6.6-0004-nvmem-meson-mx-efuse-Convert-to-devm_platform_iorema.patch
@@ -0,0 +1,36 @@
+From 0a223a097709b99a0ba738d6be5b4f52c04ffb64 Mon Sep 17 00:00:00 2001
+From: Yangtao Li <frank.li@vivo.com>
+Date: Wed, 23 Aug 2023 14:27:27 +0100
+Subject: [PATCH] nvmem: meson-mx-efuse: Convert to
+ devm_platform_ioremap_resource()
+
+Use devm_platform_ioremap_resource() to simplify code.
+
+Signed-off-by: Yangtao Li <frank.li@vivo.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230823132744.350618-6-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/meson-mx-efuse.c | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+--- a/drivers/nvmem/meson-mx-efuse.c
++++ b/drivers/nvmem/meson-mx-efuse.c
+@@ -194,7 +194,6 @@ static int meson_mx_efuse_probe(struct p
+ {
+ const struct meson_mx_efuse_platform_data *drvdata;
+ struct meson_mx_efuse *efuse;
+- struct resource *res;
+
+ drvdata = of_device_get_match_data(&pdev->dev);
+ if (!drvdata)
+@@ -204,8 +203,7 @@ static int meson_mx_efuse_probe(struct p
+ if (!efuse)
+ return -ENOMEM;
+
+- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+- efuse->base = devm_ioremap_resource(&pdev->dev, res);
++ efuse->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(efuse->base))
+ return PTR_ERR(efuse->base);
+
diff --git a/target/linux/generic/backport-6.6/814-v6.6-0005-nvmem-rockchip-efuse-Use-devm_platform_get_and_iorem.patch b/target/linux/generic/backport-6.6/814-v6.6-0005-nvmem-rockchip-efuse-Use-devm_platform_get_and_iorem.patch
new file mode 100644
index 0000000000..dc144ddfbd
--- /dev/null
+++ b/target/linux/generic/backport-6.6/814-v6.6-0005-nvmem-rockchip-efuse-Use-devm_platform_get_and_iorem.patch
@@ -0,0 +1,31 @@
+From 94904db28db49ac8fbb2a273d25156db26a3a985 Mon Sep 17 00:00:00 2001
+From: Yangtao Li <frank.li@vivo.com>
+Date: Wed, 23 Aug 2023 14:27:28 +0100
+Subject: [PATCH] nvmem: rockchip-efuse: Use
+ devm_platform_get_and_ioremap_resource()
+
+Convert platform_get_resource(), devm_ioremap_resource() to a single
+call to devm_platform_get_and_ioremap_resource(), as this is exactly
+what this function does.
+
+Signed-off-by: Yangtao Li <frank.li@vivo.com>
+Reviewed-by: Heiko Stuebner <heiko@sntech.de>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230823132744.350618-7-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/rockchip-efuse.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/drivers/nvmem/rockchip-efuse.c
++++ b/drivers/nvmem/rockchip-efuse.c
+@@ -267,8 +267,7 @@ static int rockchip_efuse_probe(struct p
+ if (!efuse)
+ return -ENOMEM;
+
+- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+- efuse->base = devm_ioremap_resource(dev, res);
++ efuse->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(efuse->base))
+ return PTR_ERR(efuse->base);
+
diff --git a/target/linux/generic/backport-6.6/814-v6.6-0006-nvmem-stm32-romem-Use-devm_platform_get_and_ioremap_.patch b/target/linux/generic/backport-6.6/814-v6.6-0006-nvmem-stm32-romem-Use-devm_platform_get_and_ioremap_.patch
new file mode 100644
index 0000000000..99e20939cc
--- /dev/null
+++ b/target/linux/generic/backport-6.6/814-v6.6-0006-nvmem-stm32-romem-Use-devm_platform_get_and_ioremap_.patch
@@ -0,0 +1,30 @@
+From 0a4a8c0d238fec1fa4b85591524ef42ad261cb97 Mon Sep 17 00:00:00 2001
+From: Yangtao Li <frank.li@vivo.com>
+Date: Wed, 23 Aug 2023 14:27:29 +0100
+Subject: [PATCH] nvmem: stm32-romem: Use
+ devm_platform_get_and_ioremap_resource()
+
+Convert platform_get_resource(), devm_ioremap_resource() to a single
+call to devm_platform_get_and_ioremap_resource(), as this is exactly
+what this function does.
+
+Signed-off-by: Yangtao Li <frank.li@vivo.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230823132744.350618-8-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/stm32-romem.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/drivers/nvmem/stm32-romem.c
++++ b/drivers/nvmem/stm32-romem.c
+@@ -196,8 +196,7 @@ static int stm32_romem_probe(struct plat
+ if (!priv)
+ return -ENOMEM;
+
+- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+- priv->base = devm_ioremap_resource(dev, res);
++ priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
diff --git a/target/linux/generic/backport-6.6/814-v6.6-0007-nvmem-qfprom-do-some-cleanup.patch b/target/linux/generic/backport-6.6/814-v6.6-0007-nvmem-qfprom-do-some-cleanup.patch
new file mode 100644
index 0000000000..6d93752e27
--- /dev/null
+++ b/target/linux/generic/backport-6.6/814-v6.6-0007-nvmem-qfprom-do-some-cleanup.patch
@@ -0,0 +1,59 @@
+From 0bc0d6dc2a9a05ae6729b4622f09782d9f230815 Mon Sep 17 00:00:00 2001
+From: Yangtao Li <frank.li@vivo.com>
+Date: Wed, 23 Aug 2023 14:27:30 +0100
+Subject: [PATCH] nvmem: qfprom: do some cleanup
+
+Use devm_platform_ioremap_resource() and
+devm_platform_get_and_ioremap_resource() to simplify code.
+BTW convert to use dev_err_probe() instead of open it.
+
+Signed-off-by: Yangtao Li <frank.li@vivo.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230823132744.350618-9-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/qfprom.c | 17 +++++------------
+ 1 file changed, 5 insertions(+), 12 deletions(-)
+
+--- a/drivers/nvmem/qfprom.c
++++ b/drivers/nvmem/qfprom.c
+@@ -374,8 +374,7 @@ static int qfprom_probe(struct platform_
+ return -ENOMEM;
+
+ /* The corrected section is always provided */
+- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+- priv->qfpcorrected = devm_ioremap_resource(dev, res);
++ priv->qfpcorrected = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(priv->qfpcorrected))
+ return PTR_ERR(priv->qfpcorrected);
+
+@@ -402,12 +401,10 @@ static int qfprom_probe(struct platform_
+ priv->qfpraw = devm_ioremap_resource(dev, res);
+ if (IS_ERR(priv->qfpraw))
+ return PTR_ERR(priv->qfpraw);
+- res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+- priv->qfpconf = devm_ioremap_resource(dev, res);
++ priv->qfpconf = devm_platform_ioremap_resource(pdev, 2);
+ if (IS_ERR(priv->qfpconf))
+ return PTR_ERR(priv->qfpconf);
+- res = platform_get_resource(pdev, IORESOURCE_MEM, 3);
+- priv->qfpsecurity = devm_ioremap_resource(dev, res);
++ priv->qfpsecurity = devm_platform_ioremap_resource(pdev, 3);
+ if (IS_ERR(priv->qfpsecurity))
+ return PTR_ERR(priv->qfpsecurity);
+
+@@ -427,12 +424,8 @@ static int qfprom_probe(struct platform_
+ return PTR_ERR(priv->vcc);
+
+ priv->secclk = devm_clk_get(dev, "core");
+- if (IS_ERR(priv->secclk)) {
+- ret = PTR_ERR(priv->secclk);
+- if (ret != -EPROBE_DEFER)
+- dev_err(dev, "Error getting clock: %d\n", ret);
+- return ret;
+- }
++ if (IS_ERR(priv->secclk))
++ return dev_err_probe(dev, PTR_ERR(priv->secclk), "Error getting clock\n");
+
+ /* Only enable writing if we have SoC data. */
+ if (priv->soc_data)
diff --git a/target/linux/generic/backport-6.6/814-v6.6-0008-nvmem-uniphier-Use-devm_platform_get_and_ioremap_res.patch b/target/linux/generic/backport-6.6/814-v6.6-0008-nvmem-uniphier-Use-devm_platform_get_and_ioremap_res.patch
new file mode 100644
index 0000000000..3e328a4c99
--- /dev/null
+++ b/target/linux/generic/backport-6.6/814-v6.6-0008-nvmem-uniphier-Use-devm_platform_get_and_ioremap_res.patch
@@ -0,0 +1,29 @@
+From 6ac41c556e22a0d7d267c9b9d48681d73af4b368 Mon Sep 17 00:00:00 2001
+From: Yangtao Li <frank.li@vivo.com>
+Date: Wed, 23 Aug 2023 14:27:31 +0100
+Subject: [PATCH] nvmem: uniphier: Use devm_platform_get_and_ioremap_resource()
+
+Convert platform_get_resource(), devm_ioremap_resource() to a single
+call to devm_platform_get_and_ioremap_resource(), as this is exactly
+what this function does.
+
+Signed-off-by: Yangtao Li <frank.li@vivo.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230823132744.350618-10-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/uniphier-efuse.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/drivers/nvmem/uniphier-efuse.c
++++ b/drivers/nvmem/uniphier-efuse.c
+@@ -41,8 +41,7 @@ static int uniphier_efuse_probe(struct p
+ if (!priv)
+ return -ENOMEM;
+
+- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+- priv->base = devm_ioremap_resource(dev, res);
++ priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
diff --git a/target/linux/generic/backport-6.6/814-v6.6-0009-nvmem-add-new-NXP-QorIQ-eFuse-driver.patch b/target/linux/generic/backport-6.6/814-v6.6-0009-nvmem-add-new-NXP-QorIQ-eFuse-driver.patch
new file mode 100644
index 0000000000..acbe18e270
--- /dev/null
+++ b/target/linux/generic/backport-6.6/814-v6.6-0009-nvmem-add-new-NXP-QorIQ-eFuse-driver.patch
@@ -0,0 +1,133 @@
+From c8efcf7a86ebf2ff48584d270b3070a7075bc345 Mon Sep 17 00:00:00 2001
+From: Richard Alpe <richard@bit42.se>
+Date: Mon, 10 Apr 2023 10:20:51 +0200
+Subject: [PATCH] nvmem: add new NXP QorIQ eFuse driver
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Add SFP (Security Fuse Processor) read support for NXP (Freescale)
+QorIQ series SOC's.
+
+This patch adds support for the T1023 SOC using the SFP offset from
+the existing T1023 device tree. In theory this should also work for
+T1024, T1014 and T1013 which uses the same SFP base offset.
+
+Signed-off-by: Richard Alpe <richard@bit42.se>
+Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+---
+ drivers/nvmem/Kconfig | 12 ++++++
+ drivers/nvmem/Makefile | 2 +
+ drivers/nvmem/qoriq-efuse.c | 78 +++++++++++++++++++++++++++++++++++++
+ 3 files changed, 92 insertions(+)
+ create mode 100644 drivers/nvmem/qoriq-efuse.c
+
+--- a/drivers/nvmem/Kconfig
++++ b/drivers/nvmem/Kconfig
+@@ -392,4 +392,16 @@ config NVMEM_ZYNQMP
+
+ If sure, say yes. If unsure, say no.
+
++config NVMEM_QORIQ_EFUSE
++ tristate "NXP QorIQ eFuse support"
++ depends on PPC_85xx || COMPILE_TEST
++ depends on HAS_IOMEM
++ help
++ This driver provides read support for the eFuses (SFP) on NXP QorIQ
++ series SoC's. This includes secure boot settings, the globally unique
++ NXP ID 'FUIDR' and the OEM unique ID 'OUIDR'.
++
++ This driver can also be built as a module. If so, the module
++ will be called nvmem_qoriq_efuse.
++
+ endif
+--- a/drivers/nvmem/Makefile
++++ b/drivers/nvmem/Makefile
+@@ -77,3 +77,5 @@ obj-$(CONFIG_NVMEM_VF610_OCOTP) += nvme
+ nvmem-vf610-ocotp-y := vf610-ocotp.o
+ obj-$(CONFIG_NVMEM_ZYNQMP) += nvmem_zynqmp_nvmem.o
+ nvmem_zynqmp_nvmem-y := zynqmp_nvmem.o
++obj-$(CONFIG_NVMEM_QORIQ_EFUSE) += nvmem-qoriq-efuse.o
++nvmem-qoriq-efuse-y := qoriq-efuse.o
+--- /dev/null
++++ b/drivers/nvmem/qoriq-efuse.c
+@@ -0,0 +1,78 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Copyright (C) 2023 Westermo Network Technologies AB
++ */
++
++#include <linux/device.h>
++#include <linux/io.h>
++#include <linux/module.h>
++#include <linux/mod_devicetable.h>
++#include <linux/nvmem-provider.h>
++#include <linux/platform_device.h>
++
++struct qoriq_efuse_priv {
++ void __iomem *base;
++};
++
++static int qoriq_efuse_read(void *context, unsigned int offset, void *val,
++ size_t bytes)
++{
++ struct qoriq_efuse_priv *priv = context;
++
++ /* .stride = 4 so offset is guaranteed to be aligned */
++ __ioread32_copy(val, priv->base + offset, bytes / 4);
++
++ /* Ignore trailing bytes (there shouldn't be any) */
++
++ return 0;
++}
++
++static int qoriq_efuse_probe(struct platform_device *pdev)
++{
++ struct nvmem_config config = {
++ .dev = &pdev->dev,
++ .read_only = true,
++ .reg_read = qoriq_efuse_read,
++ .stride = sizeof(u32),
++ .word_size = sizeof(u32),
++ .name = "qoriq_efuse_read",
++ .id = NVMEM_DEVID_AUTO,
++ .root_only = true,
++ };
++ struct qoriq_efuse_priv *priv;
++ struct nvmem_device *nvmem;
++ struct resource *res;
++
++ priv = devm_kzalloc(config.dev, sizeof(*priv), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
++
++ priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
++ if (IS_ERR(priv->base))
++ return PTR_ERR(priv->base);
++
++ config.size = resource_size(res);
++ config.priv = priv;
++ nvmem = devm_nvmem_register(config.dev, &config);
++
++ return PTR_ERR_OR_ZERO(nvmem);
++}
++
++static const struct of_device_id qoriq_efuse_of_match[] = {
++ { .compatible = "fsl,t1023-sfp", },
++ {/* sentinel */},
++};
++MODULE_DEVICE_TABLE(of, qoriq_efuse_of_match);
++
++static struct platform_driver qoriq_efuse_driver = {
++ .probe = qoriq_efuse_probe,
++ .driver = {
++ .name = "qoriq-efuse",
++ .of_match_table = qoriq_efuse_of_match,
++ },
++};
++module_platform_driver(qoriq_efuse_driver);
++
++MODULE_AUTHOR("Richard Alpe <richard.alpe@bit42.se>");
++MODULE_DESCRIPTION("NXP QorIQ Security Fuse Processor (SFP) Reader");
++MODULE_LICENSE("GPL");
diff --git a/target/linux/generic/backport-6.6/814-v6.6-0011-nvmem-Kconfig-Fix-typo-drive-driver.patch b/target/linux/generic/backport-6.6/814-v6.6-0011-nvmem-Kconfig-Fix-typo-drive-driver.patch
new file mode 100644
index 0000000000..67f30e34a2
--- /dev/null
+++ b/target/linux/generic/backport-6.6/814-v6.6-0011-nvmem-Kconfig-Fix-typo-drive-driver.patch
@@ -0,0 +1,37 @@
+From 9d53d595f688c9837e88a919229cc61a165c7b9e Mon Sep 17 00:00:00 2001
+From: Diederik de Haas <didi.debian@cknow.org>
+Date: Mon, 24 Jul 2023 13:36:22 +0200
+Subject: [PATCH] nvmem: Kconfig: Fix typo "drive" -> "driver"
+
+Fix typo where "driver" was meant instead of "drive".
+While at it, also capitalize "OTP".
+
+Signed-off-by: Diederik de Haas <didi.debian@cknow.org>
+Reviewed-by: Heiko Stuebner <heiko@sntech.de>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+---
+ drivers/nvmem/Kconfig | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/nvmem/Kconfig
++++ b/drivers/nvmem/Kconfig
+@@ -247,7 +247,7 @@ config NVMEM_ROCKCHIP_EFUSE
+ depends on ARCH_ROCKCHIP || COMPILE_TEST
+ depends on HAS_IOMEM
+ help
+- This is a simple drive to dump specified values of Rockchip SoC
++ This is a simple driver to dump specified values of Rockchip SoC
+ from eFuse, such as cpu-leakage.
+
+ This driver can also be built as a module. If so, the module
+@@ -258,8 +258,8 @@ config NVMEM_ROCKCHIP_OTP
+ depends on ARCH_ROCKCHIP || COMPILE_TEST
+ depends on HAS_IOMEM
+ help
+- This is a simple drive to dump specified values of Rockchip SoC
+- from otp, such as cpu-leakage.
++ This is a simple driver to dump specified values of Rockchip SoC
++ from OTP, such as cpu-leakage.
+
+ This driver can also be built as a module. If so, the module
+ will be called nvmem_rockchip_otp.
diff --git a/target/linux/generic/backport-6.6/814-v6.6-0012-nvmem-sec-qfprom-Add-Qualcomm-secure-QFPROM-support.patch b/target/linux/generic/backport-6.6/814-v6.6-0012-nvmem-sec-qfprom-Add-Qualcomm-secure-QFPROM-support.patch
new file mode 100644
index 0000000000..af4a2926dc
--- /dev/null
+++ b/target/linux/generic/backport-6.6/814-v6.6-0012-nvmem-sec-qfprom-Add-Qualcomm-secure-QFPROM-support.patch
@@ -0,0 +1,152 @@
+From 0a9ec38c47c1ca4528aa058e2b9ea61901a7e632 Mon Sep 17 00:00:00 2001
+From: Komal Bajaj <quic_kbajaj@quicinc.com>
+Date: Tue, 1 Aug 2023 12:10:25 +0530
+Subject: [PATCH] nvmem: sec-qfprom: Add Qualcomm secure QFPROM support
+
+For some of the Qualcomm SoC's, it is possible that
+some of the fuse regions or entire qfprom region is
+protected from non-secure access. In such situations,
+the OS will have to use secure calls to read the region.
+With that motivation, add secure qfprom driver.
+
+Signed-off-by: Komal Bajaj <quic_kbajaj@quicinc.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+---
+ drivers/nvmem/Kconfig | 13 ++++++
+ drivers/nvmem/Makefile | 2 +
+ drivers/nvmem/sec-qfprom.c | 96 ++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 111 insertions(+)
+ create mode 100644 drivers/nvmem/sec-qfprom.c
+
+--- a/drivers/nvmem/Kconfig
++++ b/drivers/nvmem/Kconfig
+@@ -226,6 +226,19 @@ config NVMEM_QCOM_QFPROM
+ This driver can also be built as a module. If so, the module
+ will be called nvmem_qfprom.
+
++config NVMEM_QCOM_SEC_QFPROM
++ tristate "QCOM SECURE QFPROM Support"
++ depends on ARCH_QCOM || COMPILE_TEST
++ depends on HAS_IOMEM
++ depends on OF
++ select QCOM_SCM
++ help
++ Say y here to enable secure QFPROM support. The secure QFPROM provides access
++ functions for QFPROM data to rest of the drivers via nvmem interface.
++
++ This driver can also be built as a module. If so, the module will be called
++ nvmem_sec_qfprom.
++
+ config NVMEM_RAVE_SP_EEPROM
+ tristate "Rave SP EEPROM Support"
+ depends on RAVE_SP_CORE
+--- a/drivers/nvmem/Makefile
++++ b/drivers/nvmem/Makefile
+@@ -46,6 +46,8 @@ obj-$(CONFIG_NVMEM_NINTENDO_OTP) += nvme
+ nvmem-nintendo-otp-y := nintendo-otp.o
+ obj-$(CONFIG_NVMEM_QCOM_QFPROM) += nvmem_qfprom.o
+ nvmem_qfprom-y := qfprom.o
++obj-$(CONFIG_NVMEM_QCOM_SEC_QFPROM) += nvmem_sec_qfprom.o
++nvmem_sec_qfprom-y := sec-qfprom.o
+ obj-$(CONFIG_NVMEM_RAVE_SP_EEPROM) += nvmem-rave-sp-eeprom.o
+ nvmem-rave-sp-eeprom-y := rave-sp-eeprom.o
+ obj-$(CONFIG_NVMEM_RMEM) += nvmem-rmem.o
+--- /dev/null
++++ b/drivers/nvmem/sec-qfprom.c
+@@ -0,0 +1,96 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (c) 2023, Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#include <linux/qcom_scm.h>
++#include <linux/mod_devicetable.h>
++#include <linux/nvmem-provider.h>
++#include <linux/platform_device.h>
++#include <linux/pm_runtime.h>
++
++/**
++ * struct sec_qfprom - structure holding secure qfprom attributes
++ *
++ * @base: starting physical address for secure qfprom corrected address space.
++ * @dev: qfprom device structure.
++ */
++struct sec_qfprom {
++ phys_addr_t base;
++ struct device *dev;
++};
++
++static int sec_qfprom_reg_read(void *context, unsigned int reg, void *_val, size_t bytes)
++{
++ struct sec_qfprom *priv = context;
++ unsigned int i;
++ u8 *val = _val;
++ u32 read_val;
++ u8 *tmp;
++
++ for (i = 0; i < bytes; i++, reg++) {
++ if (i == 0 || reg % 4 == 0) {
++ if (qcom_scm_io_readl(priv->base + (reg & ~3), &read_val)) {
++ dev_err(priv->dev, "Couldn't access fuse register\n");
++ return -EINVAL;
++ }
++ tmp = (u8 *)&read_val;
++ }
++
++ val[i] = tmp[reg & 3];
++ }
++
++ return 0;
++}
++
++static int sec_qfprom_probe(struct platform_device *pdev)
++{
++ struct nvmem_config econfig = {
++ .name = "sec-qfprom",
++ .stride = 1,
++ .word_size = 1,
++ .id = NVMEM_DEVID_AUTO,
++ .reg_read = sec_qfprom_reg_read,
++ };
++ struct device *dev = &pdev->dev;
++ struct nvmem_device *nvmem;
++ struct sec_qfprom *priv;
++ struct resource *res;
++
++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
++
++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
++ if (!res)
++ return -EINVAL;
++
++ priv->base = res->start;
++
++ econfig.size = resource_size(res);
++ econfig.dev = dev;
++ econfig.priv = priv;
++
++ priv->dev = dev;
++
++ nvmem = devm_nvmem_register(dev, &econfig);
++
++ return PTR_ERR_OR_ZERO(nvmem);
++}
++
++static const struct of_device_id sec_qfprom_of_match[] = {
++ { .compatible = "qcom,sec-qfprom" },
++ {/* sentinel */},
++};
++MODULE_DEVICE_TABLE(of, sec_qfprom_of_match);
++
++static struct platform_driver qfprom_driver = {
++ .probe = sec_qfprom_probe,
++ .driver = {
++ .name = "qcom_sec_qfprom",
++ .of_match_table = sec_qfprom_of_match,
++ },
++};
++module_platform_driver(qfprom_driver);
++MODULE_DESCRIPTION("Qualcomm Secure QFPROM driver");
++MODULE_LICENSE("GPL");
diff --git a/target/linux/generic/backport-6.6/814-v6.6-0013-nvmem-u-boot-env-Replace-zero-length-array-with-DECL.patch b/target/linux/generic/backport-6.6/814-v6.6-0013-nvmem-u-boot-env-Replace-zero-length-array-with-DECL.patch
new file mode 100644
index 0000000000..dab8ec2c24
--- /dev/null
+++ b/target/linux/generic/backport-6.6/814-v6.6-0013-nvmem-u-boot-env-Replace-zero-length-array-with-DECL.patch
@@ -0,0 +1,30 @@
+From c32f2186acc9abb4d766361255d7ddf07d15eeb2 Mon Sep 17 00:00:00 2001
+From: Atul Raut <rauji.raut@gmail.com>
+Date: Sun, 30 Jul 2023 15:39:15 -0700
+Subject: [PATCH] nvmem: u-boot-env:: Replace zero-length array with
+ DECLARE_FLEX_ARRAY() helper
+
+We are moving toward replacing zero-length arrays with C99 flexible-array
+members since they are deprecated. Therefore, the new DECLARE_FLEX_ARRAY()
+helper macro should be used to replace the zero-length array declaration.
+
+This fixes warnings such as:
+./drivers/nvmem/u-boot-env.c:50:9-13: WARNING use flexible-array member instead (https://www.kernel.org/doc/html/latest/process/deprecated.html#zero-length-and-one-element-arrays)
+
+Signed-off-by: Atul Raut <rauji.raut@gmail.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+---
+ drivers/nvmem/u-boot-env.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/nvmem/u-boot-env.c
++++ b/drivers/nvmem/u-boot-env.c
+@@ -47,7 +47,7 @@ struct u_boot_env_image_broadcom {
+ __le32 magic;
+ __le32 len;
+ __le32 crc32;
+- uint8_t data[0];
++ DECLARE_FLEX_ARRAY(uint8_t, data);
+ } __packed;
+
+ static int u_boot_env_read(void *context, unsigned int offset, void *val,
diff --git a/target/linux/generic/backport-6.6/814-v6.6-0014-nvmem-core-Create-all-cells-before-adding-the-nvmem-.patch b/target/linux/generic/backport-6.6/814-v6.6-0014-nvmem-core-Create-all-cells-before-adding-the-nvmem-.patch
new file mode 100644
index 0000000000..f9532f39c3
--- /dev/null
+++ b/target/linux/generic/backport-6.6/814-v6.6-0014-nvmem-core-Create-all-cells-before-adding-the-nvmem-.patch
@@ -0,0 +1,40 @@
+From 104af6a5b199eb4dc7970d1304aef38ac5a6ed54 Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Tue, 8 Aug 2023 08:29:26 +0200
+Subject: [PATCH] nvmem: core: Create all cells before adding the nvmem device
+
+Let's pack all the cells creation in one place, so they are all created
+before we add the nvmem device.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Reviewed-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+---
+ drivers/nvmem/core.c | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -998,17 +998,17 @@ struct nvmem_device *nvmem_register(cons
+ if (rval)
+ goto err_remove_cells;
+
+- dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name);
+-
+- rval = device_add(&nvmem->dev);
++ rval = nvmem_add_cells_from_fixed_layout(nvmem);
+ if (rval)
+ goto err_remove_cells;
+
+- rval = nvmem_add_cells_from_fixed_layout(nvmem);
++ rval = nvmem_add_cells_from_layout(nvmem);
+ if (rval)
+ goto err_remove_cells;
+
+- rval = nvmem_add_cells_from_layout(nvmem);
++ dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name);
++
++ rval = device_add(&nvmem->dev);
+ if (rval)
+ goto err_remove_cells;
+
diff --git a/target/linux/generic/backport-6.6/814-v6.6-0015-nvmem-core-Return-NULL-when-no-nvmem-layout-is-found.patch b/target/linux/generic/backport-6.6/814-v6.6-0015-nvmem-core-Return-NULL-when-no-nvmem-layout-is-found.patch
new file mode 100644
index 0000000000..8f64d0c28b
--- /dev/null
+++ b/target/linux/generic/backport-6.6/814-v6.6-0015-nvmem-core-Return-NULL-when-no-nvmem-layout-is-found.patch
@@ -0,0 +1,35 @@
+From 6c7f48ea2e663b679aa8e60d8d8e1e6306a644f9 Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Tue, 8 Aug 2023 08:29:27 +0200
+Subject: [PATCH] nvmem: core: Return NULL when no nvmem layout is found
+
+Currently, of_nvmem_layout_get_container() returns NULL on error, or an
+error pointer if either CONFIG_NVMEM or CONFIG_OF is turned off. We
+should likely avoid this kind of mix for two reasons: to clarify the
+intend and anyway fix the !CONFIG_OF which will likely always if we use
+this helper somewhere else. Let's just return NULL when no layout is
+found, we don't need an error value here.
+
+Link: https://staticthinking.wordpress.com/2022/08/01/mixing-error-pointers-and-null/
+Fixes: 266570f496b9 ("nvmem: core: introduce NVMEM layouts")
+Reported-by: kernel test robot <lkp@intel.com>
+Reported-by: Dan Carpenter <dan.carpenter@linaro.org>
+Closes: https://lore.kernel.org/r/202308030002.DnSFOrMB-lkp@intel.com/
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Reviewed-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+---
+ include/linux/nvmem-consumer.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/include/linux/nvmem-consumer.h
++++ b/include/linux/nvmem-consumer.h
+@@ -256,7 +256,7 @@ static inline struct nvmem_device *of_nv
+ static inline struct device_node *
+ of_nvmem_layout_get_container(struct nvmem_device *nvmem)
+ {
+- return ERR_PTR(-EOPNOTSUPP);
++ return NULL;
+ }
+ #endif /* CONFIG_NVMEM && CONFIG_OF */
+
diff --git a/target/linux/generic/backport-6.6/814-v6.6-0016-nvmem-core-Do-not-open-code-existing-functions.patch b/target/linux/generic/backport-6.6/814-v6.6-0016-nvmem-core-Do-not-open-code-existing-functions.patch
new file mode 100644
index 0000000000..28d8bba194
--- /dev/null
+++ b/target/linux/generic/backport-6.6/814-v6.6-0016-nvmem-core-Do-not-open-code-existing-functions.patch
@@ -0,0 +1,29 @@
+From b8257f61b4ddac6d7d0e19a5a4e8b07afb3b4ed3 Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Tue, 8 Aug 2023 08:29:28 +0200
+Subject: [PATCH] nvmem: core: Do not open-code existing functions
+
+Use of_nvmem_layout_get_container() instead of hardcoding it.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Reviewed-by: Michael Walle <michael@walle.cc>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+---
+ drivers/nvmem/core.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -786,10 +786,10 @@ EXPORT_SYMBOL_GPL(nvmem_layout_unregiste
+
+ static struct nvmem_layout *nvmem_layout_get(struct nvmem_device *nvmem)
+ {
+- struct device_node *layout_np, *np = nvmem->dev.of_node;
++ struct device_node *layout_np;
+ struct nvmem_layout *l, *layout = ERR_PTR(-EPROBE_DEFER);
+
+- layout_np = of_get_child_by_name(np, "nvmem-layout");
++ layout_np = of_nvmem_layout_get_container(nvmem);
+ if (!layout_np)
+ return NULL;
+
diff --git a/target/linux/generic/backport-6.6/814-v6.6-0017-nvmem-core-Notify-when-a-new-layout-is-registered.patch b/target/linux/generic/backport-6.6/814-v6.6-0017-nvmem-core-Notify-when-a-new-layout-is-registered.patch
new file mode 100644
index 0000000000..b62a0e18da
--- /dev/null
+++ b/target/linux/generic/backport-6.6/814-v6.6-0017-nvmem-core-Notify-when-a-new-layout-is-registered.patch
@@ -0,0 +1,44 @@
+From 0991afbe4b1805e7f0113ef10d7c5f0698a739e4 Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Tue, 8 Aug 2023 08:29:29 +0200
+Subject: [PATCH] nvmem: core: Notify when a new layout is registered
+
+Tell listeners a new layout was introduced and is now available.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+---
+ drivers/nvmem/core.c | 4 ++++
+ include/linux/nvmem-consumer.h | 2 ++
+ 2 files changed, 6 insertions(+)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -772,12 +772,16 @@ int __nvmem_layout_register(struct nvmem
+ list_add(&layout->node, &nvmem_layouts);
+ spin_unlock(&nvmem_layout_lock);
+
++ blocking_notifier_call_chain(&nvmem_notifier, NVMEM_LAYOUT_ADD, layout);
++
+ return 0;
+ }
+ EXPORT_SYMBOL_GPL(__nvmem_layout_register);
+
+ void nvmem_layout_unregister(struct nvmem_layout *layout)
+ {
++ blocking_notifier_call_chain(&nvmem_notifier, NVMEM_LAYOUT_REMOVE, layout);
++
+ spin_lock(&nvmem_layout_lock);
+ list_del(&layout->node);
+ spin_unlock(&nvmem_layout_lock);
+--- a/include/linux/nvmem-consumer.h
++++ b/include/linux/nvmem-consumer.h
+@@ -43,6 +43,8 @@ enum {
+ NVMEM_REMOVE,
+ NVMEM_CELL_ADD,
+ NVMEM_CELL_REMOVE,
++ NVMEM_LAYOUT_ADD,
++ NVMEM_LAYOUT_REMOVE,
+ };
+
+ #if IS_ENABLED(CONFIG_NVMEM)
diff --git a/target/linux/generic/backport-6.6/815-v6.6-1-leds-turris-omnia-Use-sysfs_emit-instead-of-sprintf.patch b/target/linux/generic/backport-6.6/815-v6.6-1-leds-turris-omnia-Use-sysfs_emit-instead-of-sprintf.patch
new file mode 100644
index 0000000000..e17be439b2
--- /dev/null
+++ b/target/linux/generic/backport-6.6/815-v6.6-1-leds-turris-omnia-Use-sysfs_emit-instead-of-sprintf.patch
@@ -0,0 +1,29 @@
+From a75b58a46423cfd9b1f73581f4bd2ac2ae743996 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
+Date: Wed, 2 Aug 2023 18:07:45 +0200
+Subject: [PATCH 1/6] leds: turris-omnia: Use sysfs_emit() instead of sprintf()
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Use the dedicated sysfs_emit() function instead of sprintf() in sysfs
+attribute accessor brightness_show().
+
+Signed-off-by: Marek Behún <kabel@kernel.org>
+Link: https://lore.kernel.org/r/20230802160748.11208-4-kabel@kernel.org
+Signed-off-by: Lee Jones <lee@kernel.org>
+---
+ drivers/leds/leds-turris-omnia.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/leds/leds-turris-omnia.c
++++ b/drivers/leds/leds-turris-omnia.c
+@@ -194,7 +194,7 @@ static ssize_t brightness_show(struct de
+ if (ret < 0)
+ return ret;
+
+- return sprintf(buf, "%d\n", ret);
++ return sysfs_emit(buf, "%d\n", ret);
+ }
+
+ static ssize_t brightness_store(struct device *dev, struct device_attribute *a,
diff --git a/target/linux/generic/backport-6.6/815-v6.7-2-leds-turris-omnia-Make-set_brightness-more-efficient.patch b/target/linux/generic/backport-6.6/815-v6.7-2-leds-turris-omnia-Make-set_brightness-more-efficient.patch
new file mode 100644
index 0000000000..d84ad67125
--- /dev/null
+++ b/target/linux/generic/backport-6.6/815-v6.7-2-leds-turris-omnia-Make-set_brightness-more-efficient.patch
@@ -0,0 +1,207 @@
+From a64c3c1357275b1fd61bc9734f638cdb5d8a8bbb Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
+Date: Mon, 18 Sep 2023 18:11:02 +0200
+Subject: [PATCH 4/6] leds: turris-omnia: Make set_brightness() more efficient
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Implement caching of the LED color and state values that are sent to MCU
+in order to make the set_brightness() operation more efficient by
+avoiding I2C transactions which are not needed.
+
+On Turris Omnia's MCU, which acts as the RGB LED controller, each LED
+has a RGB color, and a ON/OFF state, which are configurable via I2C
+commands CMD_LED_COLOR and CMD_LED_STATE.
+
+The CMD_LED_COLOR command sends 5 bytes and the CMD_LED_STATE command 2
+bytes over the I2C bus, which operates at 100 kHz. With I2C overhead
+this allows ~1670 color changing commands and ~3200 state changing
+commands per second (or around 1000 color + state changes per second).
+This may seem more than enough, but the issue is that the I2C bus is
+shared with another peripheral, the MCU. The MCU exposes an interrupt
+interface, and it can trigger hundreds of interrupts per second. Each
+time, we need to read the interrupt state register over this I2C bus.
+Whenever we are sending a LED color/state changing command, the
+interrupt reading is waiting.
+
+Currently, every time LED brightness or LED multi intensity is changed,
+we send a CMD_LED_STATE command, and if the computed color (brightness
+adjusted multi_intensity) is non-zero, we also send a CMD_LED_COLOR
+command.
+
+Consider for example the situation when we have a netdev trigger enabled
+for a LED. The netdev trigger does not change the LED color, only the
+brightness (either to 0 or to currently configured brightness), and so
+there is no need to send the CMD_LED_COLOR command. But each change of
+brightness to 0 sends one CMD_LED_STATE command, and each change of
+brightness to max_brightness sends one CMD_LED_STATE command and one
+CMD_LED_COLOR command:
+ set_brightness(0) -> CMD_LED_STATE
+ set_brightness(255) -> CMD_LED_STATE + CMD_LED_COLOR
+ (unnecessary)
+
+We can avoid the unnecessary I2C transactions if we cache the values of
+state and color that are sent to the controller. If the color does not
+change from the one previously sent, there is no need to do the
+CMD_LED_COLOR I2C transaction, and if the state does not change, there
+is no need to do the CMD_LED_STATE transaction.
+
+Because we need to make sure that our cached values are consistent with
+the controller state, add explicit setting of the LED color to white at
+probe time (this is the default setting when MCU resets, but does not
+necessarily need to be the case, for example if U-Boot played with the
+LED colors).
+
+Signed-off-by: Marek Behún <kabel@kernel.org>
+Link: https://lore.kernel.org/r/20230918161104.20860-3-kabel@kernel.org
+Signed-off-by: Lee Jones <lee@kernel.org>
+---
+ drivers/leds/leds-turris-omnia.c | 96 ++++++++++++++++++++++++++------
+ 1 file changed, 78 insertions(+), 18 deletions(-)
+
+--- a/drivers/leds/leds-turris-omnia.c
++++ b/drivers/leds/leds-turris-omnia.c
+@@ -30,6 +30,8 @@
+ struct omnia_led {
+ struct led_classdev_mc mc_cdev;
+ struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS];
++ u8 cached_channels[OMNIA_LED_NUM_CHANNELS];
++ bool on;
+ int reg;
+ };
+
+@@ -72,36 +74,82 @@ static int omnia_cmd_read_u8(const struc
+ return -EIO;
+ }
+
++static int omnia_led_send_color_cmd(const struct i2c_client *client,
++ struct omnia_led *led)
++{
++ char cmd[5];
++ int ret;
++
++ cmd[0] = CMD_LED_COLOR;
++ cmd[1] = led->reg;
++ cmd[2] = led->subled_info[0].brightness;
++ cmd[3] = led->subled_info[1].brightness;
++ cmd[4] = led->subled_info[2].brightness;
++
++ /* Send the color change command */
++ ret = i2c_master_send(client, cmd, 5);
++ if (ret < 0)
++ return ret;
++
++ /* Cache the RGB channel brightnesses */
++ for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i)
++ led->cached_channels[i] = led->subled_info[i].brightness;
++
++ return 0;
++}
++
++/* Determine if the computed RGB channels are different from the cached ones */
++static bool omnia_led_channels_changed(struct omnia_led *led)
++{
++ for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i)
++ if (led->subled_info[i].brightness != led->cached_channels[i])
++ return true;
++
++ return false;
++}
++
+ static int omnia_led_brightness_set_blocking(struct led_classdev *cdev,
+ enum led_brightness brightness)
+ {
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
+ struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
+ struct omnia_led *led = to_omnia_led(mc_cdev);
+- u8 buf[5], state;
+- int ret;
++ int err = 0;
+
+ mutex_lock(&leds->lock);
+
+- led_mc_calc_color_components(&led->mc_cdev, brightness);
++ /*
++ * Only recalculate RGB brightnesses from intensities if brightness is
++ * non-zero. Otherwise we won't be using them and we can save ourselves
++ * some software divisions (Omnia's CPU does not implement the division
++ * instruction).
++ */
++ if (brightness) {
++ led_mc_calc_color_components(mc_cdev, brightness);
++
++ /*
++ * Send color command only if brightness is non-zero and the RGB
++ * channel brightnesses changed.
++ */
++ if (omnia_led_channels_changed(led))
++ err = omnia_led_send_color_cmd(leds->client, led);
++ }
+
+- buf[0] = CMD_LED_COLOR;
+- buf[1] = led->reg;
+- buf[2] = mc_cdev->subled_info[0].brightness;
+- buf[3] = mc_cdev->subled_info[1].brightness;
+- buf[4] = mc_cdev->subled_info[2].brightness;
+-
+- state = CMD_LED_STATE_LED(led->reg);
+- if (buf[2] || buf[3] || buf[4])
+- state |= CMD_LED_STATE_ON;
+-
+- ret = omnia_cmd_write_u8(leds->client, CMD_LED_STATE, state);
+- if (ret >= 0 && (state & CMD_LED_STATE_ON))
+- ret = i2c_master_send(leds->client, buf, 5);
++ /* Send on/off state change only if (bool)brightness changed */
++ if (!err && !brightness != !led->on) {
++ u8 state = CMD_LED_STATE_LED(led->reg);
++
++ if (brightness)
++ state |= CMD_LED_STATE_ON;
++
++ err = omnia_cmd_write_u8(leds->client, CMD_LED_STATE, state);
++ if (!err)
++ led->on = !!brightness;
++ }
+
+ mutex_unlock(&leds->lock);
+
+- return ret;
++ return err;
+ }
+
+ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
+@@ -129,11 +177,15 @@ static int omnia_led_register(struct i2c
+ }
+
+ led->subled_info[0].color_index = LED_COLOR_ID_RED;
+- led->subled_info[0].channel = 0;
+ led->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+- led->subled_info[1].channel = 1;
+ led->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+- led->subled_info[2].channel = 2;
++
++ /* Initial color is white */
++ for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i) {
++ led->subled_info[i].intensity = 255;
++ led->subled_info[i].brightness = 255;
++ led->subled_info[i].channel = i;
++ }
+
+ led->mc_cdev.subled_info = led->subled_info;
+ led->mc_cdev.num_colors = OMNIA_LED_NUM_CHANNELS;
+@@ -162,6 +214,14 @@ static int omnia_led_register(struct i2c
+ return ret;
+ }
+
++ /* Set initial color and cache it */
++ ret = omnia_led_send_color_cmd(client, led);
++ if (ret < 0) {
++ dev_err(dev, "Cannot set LED %pOF initial color: %i\n", np,
++ ret);
++ return ret;
++ }
++
+ ret = devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev,
+ &init_data);
+ if (ret < 0) {
diff --git a/target/linux/generic/backport-6.6/815-v6.7-3-leds-turris-omnia-Support-HW-controlled-mode-via-pri.patch b/target/linux/generic/backport-6.6/815-v6.7-3-leds-turris-omnia-Support-HW-controlled-mode-via-pri.patch
new file mode 100644
index 0000000000..00773ab0f6
--- /dev/null
+++ b/target/linux/generic/backport-6.6/815-v6.7-3-leds-turris-omnia-Support-HW-controlled-mode-via-pri.patch
@@ -0,0 +1,202 @@
+From e965e0f6de60874fc0a0caed9a9e0122999e0c7b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
+Date: Mon, 18 Sep 2023 18:11:03 +0200
+Subject: [PATCH 5/6] leds: turris-omnia: Support HW controlled mode via
+ private trigger
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Add support for enabling MCU controlled mode of the Turris Omnia LEDs
+via a LED private trigger called "omnia-mcu". Recall that private LED
+triggers will only be listed in the sysfs trigger file for LEDs that
+support them (currently there is no user of this mechanism).
+
+When in MCU controlled mode, the user can still set LED color, but the
+blinking is done by MCU, which does different things for different LEDs:
+- WAN LED is blinked according to the LED[0] pin of the WAN PHY
+- LAN LEDs are blinked according to the LED[0] output of the
+ corresponding port of the LAN switch
+- PCIe LEDs are blinked according to the logical OR of the MiniPCIe port
+ LED pins
+
+In the future I want to make the netdev trigger to transparently offload
+the blinking to the HW if user sets compatible settings for the netdev
+trigger (for LEDs associated with network devices).
+There was some work on this already, and hopefully we will be able to
+complete it sometime, but for now there are still multiple blockers for
+this, and even if there weren't, we still would not be able to configure
+HW controlled mode for the LEDs associated with MiniPCIe ports.
+
+In the meantime let's support HW controlled mode via the private LED
+trigger mechanism. If, in the future, we manage to complete the netdev
+trigger offloading, we can still keep this private trigger for backwards
+compatibility, if needed.
+
+We also set "omnia-mcu" to cdev->default_trigger, so that the MCU keeps
+control until the user first wants to take over it. If a different
+default trigger is specified in device-tree via the
+'linux,default-trigger' property, LED class will overwrite
+cdev->default_trigger, and so the DT property will be respected.
+
+Signed-off-by: Marek Behún <kabel@kernel.org>
+Link: https://lore.kernel.org/r/20230918161104.20860-4-kabel@kernel.org
+Signed-off-by: Lee Jones <lee@kernel.org>
+---
+ drivers/leds/Kconfig | 1 +
+ drivers/leds/leds-turris-omnia.c | 98 +++++++++++++++++++++++++++++---
+ 2 files changed, 91 insertions(+), 8 deletions(-)
+
+--- a/drivers/leds/Kconfig
++++ b/drivers/leds/Kconfig
+@@ -164,6 +164,7 @@ config LEDS_TURRIS_OMNIA
+ depends on I2C
+ depends on MACH_ARMADA_38X || COMPILE_TEST
+ depends on OF
++ select LEDS_TRIGGERS
+ help
+ This option enables basic support for the LEDs found on the front
+ side of CZ.NIC's Turris Omnia router. There are 12 RGB LEDs on the
+--- a/drivers/leds/leds-turris-omnia.c
++++ b/drivers/leds/leds-turris-omnia.c
+@@ -31,7 +31,7 @@ struct omnia_led {
+ struct led_classdev_mc mc_cdev;
+ struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS];
+ u8 cached_channels[OMNIA_LED_NUM_CHANNELS];
+- bool on;
++ bool on, hwtrig;
+ int reg;
+ };
+
+@@ -120,12 +120,14 @@ static int omnia_led_brightness_set_bloc
+
+ /*
+ * Only recalculate RGB brightnesses from intensities if brightness is
+- * non-zero. Otherwise we won't be using them and we can save ourselves
+- * some software divisions (Omnia's CPU does not implement the division
+- * instruction).
++ * non-zero (if it is zero and the LED is in HW blinking mode, we use
++ * max_brightness as brightness). Otherwise we won't be using them and
++ * we can save ourselves some software divisions (Omnia's CPU does not
++ * implement the division instruction).
+ */
+- if (brightness) {
+- led_mc_calc_color_components(mc_cdev, brightness);
++ if (brightness || led->hwtrig) {
++ led_mc_calc_color_components(mc_cdev, brightness ?:
++ cdev->max_brightness);
+
+ /*
+ * Send color command only if brightness is non-zero and the RGB
+@@ -135,8 +137,11 @@ static int omnia_led_brightness_set_bloc
+ err = omnia_led_send_color_cmd(leds->client, led);
+ }
+
+- /* Send on/off state change only if (bool)brightness changed */
+- if (!err && !brightness != !led->on) {
++ /*
++ * Send on/off state change only if (bool)brightness changed and the LED
++ * is not being blinked by HW.
++ */
++ if (!err && !led->hwtrig && !brightness != !led->on) {
+ u8 state = CMD_LED_STATE_LED(led->reg);
+
+ if (brightness)
+@@ -152,6 +157,71 @@ static int omnia_led_brightness_set_bloc
+ return err;
+ }
+
++static struct led_hw_trigger_type omnia_hw_trigger_type;
++
++static int omnia_hwtrig_activate(struct led_classdev *cdev)
++{
++ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
++ struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
++ struct omnia_led *led = to_omnia_led(mc_cdev);
++ int err = 0;
++
++ mutex_lock(&leds->lock);
++
++ if (!led->on) {
++ /*
++ * If the LED is off (brightness was set to 0), the last
++ * configured color was not necessarily sent to the MCU.
++ * Recompute with max_brightness and send if needed.
++ */
++ led_mc_calc_color_components(mc_cdev, cdev->max_brightness);
++
++ if (omnia_led_channels_changed(led))
++ err = omnia_led_send_color_cmd(leds->client, led);
++ }
++
++ if (!err) {
++ /* Put the LED into MCU controlled mode */
++ err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE,
++ CMD_LED_MODE_LED(led->reg));
++ if (!err)
++ led->hwtrig = true;
++ }
++
++ mutex_unlock(&leds->lock);
++
++ return err;
++}
++
++static void omnia_hwtrig_deactivate(struct led_classdev *cdev)
++{
++ struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
++ struct omnia_led *led = to_omnia_led(lcdev_to_mccdev(cdev));
++ int err;
++
++ mutex_lock(&leds->lock);
++
++ led->hwtrig = false;
++
++ /* Put the LED into software mode */
++ err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE,
++ CMD_LED_MODE_LED(led->reg) |
++ CMD_LED_MODE_USER);
++
++ mutex_unlock(&leds->lock);
++
++ if (err < 0)
++ dev_err(cdev->dev, "Cannot put LED to software mode: %i\n",
++ err);
++}
++
++static struct led_trigger omnia_hw_trigger = {
++ .name = "omnia-mcu",
++ .activate = omnia_hwtrig_activate,
++ .deactivate = omnia_hwtrig_deactivate,
++ .trigger_type = &omnia_hw_trigger_type,
++};
++
+ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
+ struct device_node *np)
+ {
+@@ -195,6 +265,12 @@ static int omnia_led_register(struct i2c
+ cdev = &led->mc_cdev.led_cdev;
+ cdev->max_brightness = 255;
+ cdev->brightness_set_blocking = omnia_led_brightness_set_blocking;
++ cdev->trigger_type = &omnia_hw_trigger_type;
++ /*
++ * Use the omnia-mcu trigger as the default trigger. It may be rewritten
++ * by LED class from the linux,default-trigger property.
++ */
++ cdev->default_trigger = omnia_hw_trigger.name;
+
+ /* put the LED into software mode */
+ ret = omnia_cmd_write_u8(client, CMD_LED_MODE,
+@@ -309,6 +385,12 @@ static int omnia_leds_probe(struct i2c_c
+
+ mutex_init(&leds->lock);
+
++ ret = devm_led_trigger_register(dev, &omnia_hw_trigger);
++ if (ret < 0) {
++ dev_err(dev, "Cannot register private LED trigger: %d\n", ret);
++ return ret;
++ }
++
+ led = &leds->leds[0];
+ for_each_available_child_of_node(np, child) {
+ ret = omnia_led_register(client, led, child);
diff --git a/target/linux/generic/backport-6.6/815-v6.7-4-leds-turris-omnia-Add-support-for-enabling-disabling.patch b/target/linux/generic/backport-6.6/815-v6.7-4-leds-turris-omnia-Add-support-for-enabling-disabling.patch
new file mode 100644
index 0000000000..e98397922d
--- /dev/null
+++ b/target/linux/generic/backport-6.6/815-v6.7-4-leds-turris-omnia-Add-support-for-enabling-disabling.patch
@@ -0,0 +1,244 @@
+From 0efb3f9609d3de5a7d8c31e3835d7eb3e6adce79 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
+Date: Mon, 18 Sep 2023 18:11:04 +0200
+Subject: [PATCH 6/6] leds: turris-omnia: Add support for enabling/disabling HW
+ gamma correction
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+If the MCU on Turris Omnia is running newer firmware versions, the LED
+controller supports RGB gamma correction (and enables it by default for
+newer boards).
+
+Determine whether the gamma correction setting feature is supported and
+add the ability to set it via sysfs attribute file.
+
+Signed-off-by: Marek Behún <kabel@kernel.org>
+Link: https://lore.kernel.org/r/20230918161104.20860-5-kabel@kernel.org
+Signed-off-by: Lee Jones <lee@kernel.org>
+---
+ .../sysfs-class-led-driver-turris-omnia | 14 ++
+ drivers/leds/leds-turris-omnia.c | 137 +++++++++++++++---
+ 2 files changed, 134 insertions(+), 17 deletions(-)
+
+--- a/Documentation/ABI/testing/sysfs-class-led-driver-turris-omnia
++++ b/Documentation/ABI/testing/sysfs-class-led-driver-turris-omnia
+@@ -12,3 +12,17 @@ Description: (RW) On the front panel of
+ able to change this setting from software.
+
+ Format: %i
++
++What: /sys/class/leds/<led>/device/gamma_correction
++Date: August 2023
++KernelVersion: 6.6
++Contact: Marek Behún <kabel@kernel.org>
++Description: (RW) Newer versions of the microcontroller firmware of the
++ Turris Omnia router support gamma correction for the RGB LEDs.
++ This feature can be enabled/disabled by writing to this file.
++
++ If the feature is not supported because the MCU firmware is too
++ old, the file always reads as 0, and writing to the file results
++ in the EOPNOTSUPP error.
++
++ Format: %i
+--- a/drivers/leds/leds-turris-omnia.c
++++ b/drivers/leds/leds-turris-omnia.c
+@@ -15,17 +15,30 @@
+ #define OMNIA_BOARD_LEDS 12
+ #define OMNIA_LED_NUM_CHANNELS 3
+
+-#define CMD_LED_MODE 3
+-#define CMD_LED_MODE_LED(l) ((l) & 0x0f)
+-#define CMD_LED_MODE_USER 0x10
+-
+-#define CMD_LED_STATE 4
+-#define CMD_LED_STATE_LED(l) ((l) & 0x0f)
+-#define CMD_LED_STATE_ON 0x10
+-
+-#define CMD_LED_COLOR 5
+-#define CMD_LED_SET_BRIGHTNESS 7
+-#define CMD_LED_GET_BRIGHTNESS 8
++/* MCU controller commands at I2C address 0x2a */
++#define OMNIA_MCU_I2C_ADDR 0x2a
++
++#define CMD_GET_STATUS_WORD 0x01
++#define STS_FEATURES_SUPPORTED BIT(2)
++
++#define CMD_GET_FEATURES 0x10
++#define FEAT_LED_GAMMA_CORRECTION BIT(5)
++
++/* LED controller commands at I2C address 0x2b */
++#define CMD_LED_MODE 0x03
++#define CMD_LED_MODE_LED(l) ((l) & 0x0f)
++#define CMD_LED_MODE_USER 0x10
++
++#define CMD_LED_STATE 0x04
++#define CMD_LED_STATE_LED(l) ((l) & 0x0f)
++#define CMD_LED_STATE_ON 0x10
++
++#define CMD_LED_COLOR 0x05
++#define CMD_LED_SET_BRIGHTNESS 0x07
++#define CMD_LED_GET_BRIGHTNESS 0x08
++
++#define CMD_SET_GAMMA_CORRECTION 0x30
++#define CMD_GET_GAMMA_CORRECTION 0x31
+
+ struct omnia_led {
+ struct led_classdev_mc mc_cdev;
+@@ -40,6 +53,7 @@ struct omnia_led {
+ struct omnia_leds {
+ struct i2c_client *client;
+ struct mutex lock;
++ bool has_gamma_correction;
+ struct omnia_led leds[];
+ };
+
+@@ -50,30 +64,42 @@ static int omnia_cmd_write_u8(const stru
+ return i2c_master_send(client, buf, sizeof(buf));
+ }
+
+-static int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd)
++static int omnia_cmd_read_raw(struct i2c_adapter *adapter, u8 addr, u8 cmd,
++ void *reply, size_t len)
+ {
+ struct i2c_msg msgs[2];
+- u8 reply;
+ int ret;
+
+- msgs[0].addr = client->addr;
++ msgs[0].addr = addr;
+ msgs[0].flags = 0;
+ msgs[0].len = 1;
+ msgs[0].buf = &cmd;
+- msgs[1].addr = client->addr;
++ msgs[1].addr = addr;
+ msgs[1].flags = I2C_M_RD;
+- msgs[1].len = 1;
+- msgs[1].buf = &reply;
++ msgs[1].len = len;
++ msgs[1].buf = reply;
+
+- ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
++ ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs));
+ if (likely(ret == ARRAY_SIZE(msgs)))
+- return reply;
++ return len;
+ else if (ret < 0)
+ return ret;
+ else
+ return -EIO;
+ }
+
++static int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd)
++{
++ u8 reply;
++ int ret;
++
++ ret = omnia_cmd_read_raw(client->adapter, client->addr, cmd, &reply, 1);
++ if (ret < 0)
++ return ret;
++
++ return reply;
++}
++
+ static int omnia_led_send_color_cmd(const struct i2c_client *client,
+ struct omnia_led *led)
+ {
+@@ -352,12 +378,74 @@ static ssize_t brightness_store(struct d
+ }
+ static DEVICE_ATTR_RW(brightness);
+
++static ssize_t gamma_correction_show(struct device *dev,
++ struct device_attribute *a, char *buf)
++{
++ struct i2c_client *client = to_i2c_client(dev);
++ struct omnia_leds *leds = i2c_get_clientdata(client);
++ int ret;
++
++ if (leds->has_gamma_correction) {
++ ret = omnia_cmd_read_u8(client, CMD_GET_GAMMA_CORRECTION);
++ if (ret < 0)
++ return ret;
++ } else {
++ ret = 0;
++ }
++
++ return sysfs_emit(buf, "%d\n", !!ret);
++}
++
++static ssize_t gamma_correction_store(struct device *dev,
++ struct device_attribute *a,
++ const char *buf, size_t count)
++{
++ struct i2c_client *client = to_i2c_client(dev);
++ struct omnia_leds *leds = i2c_get_clientdata(client);
++ bool val;
++ int ret;
++
++ if (!leds->has_gamma_correction)
++ return -EOPNOTSUPP;
++
++ if (kstrtobool(buf, &val) < 0)
++ return -EINVAL;
++
++ ret = omnia_cmd_write_u8(client, CMD_SET_GAMMA_CORRECTION, val);
++
++ return ret < 0 ? ret : count;
++}
++static DEVICE_ATTR_RW(gamma_correction);
++
+ static struct attribute *omnia_led_controller_attrs[] = {
+ &dev_attr_brightness.attr,
++ &dev_attr_gamma_correction.attr,
+ NULL,
+ };
+ ATTRIBUTE_GROUPS(omnia_led_controller);
+
++static int omnia_mcu_get_features(const struct i2c_client *client)
++{
++ u16 reply;
++ int err;
++
++ err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR,
++ CMD_GET_STATUS_WORD, &reply, sizeof(reply));
++ if (err < 0)
++ return err;
++
++ /* Check whether MCU firmware supports the CMD_GET_FEAUTRES command */
++ if (!(le16_to_cpu(reply) & STS_FEATURES_SUPPORTED))
++ return 0;
++
++ err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR,
++ CMD_GET_FEATURES, &reply, sizeof(reply));
++ if (err < 0)
++ return err;
++
++ return le16_to_cpu(reply);
++}
++
+ static int omnia_leds_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+ {
+@@ -383,6 +471,21 @@ static int omnia_leds_probe(struct i2c_c
+ leds->client = client;
+ i2c_set_clientdata(client, leds);
+
++ ret = omnia_mcu_get_features(client);
++ if (ret < 0) {
++ dev_err(dev, "Cannot determine MCU supported features: %d\n",
++ ret);
++ return ret;
++ }
++
++ leds->has_gamma_correction = ret & FEAT_LED_GAMMA_CORRECTION;
++ if (!leds->has_gamma_correction) {
++ dev_info(dev,
++ "Your board's MCU firmware does not support the LED gamma correction feature.\n");
++ dev_info(dev,
++ "Consider upgrading MCU firmware with the omnia-mcutool utility.\n");
++ }
++
+ mutex_init(&leds->lock);
+
+ ret = devm_led_trigger_register(dev, &omnia_hw_trigger);
diff --git a/target/linux/generic/backport-6.6/815-v6.7-5-leds-turris-omnia-Fix-brightness-setting-and-trigger.patch b/target/linux/generic/backport-6.6/815-v6.7-5-leds-turris-omnia-Fix-brightness-setting-and-trigger.patch
new file mode 100644
index 0000000000..b0cebdcf14
--- /dev/null
+++ b/target/linux/generic/backport-6.6/815-v6.7-5-leds-turris-omnia-Fix-brightness-setting-and-trigger.patch
@@ -0,0 +1,167 @@
+From ffec49d391c5f0195360912b216aa24dbc9b53c8 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
+Date: Mon, 16 Oct 2023 16:15:38 +0200
+Subject: [PATCH] leds: turris-omnia: Fix brightness setting and trigger
+ activating
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+I have improperly refactored commits
+ 4d5ed2621c24 ("leds: turris-omnia: Make set_brightness() more efficient")
+and
+ aaf38273cf76 ("leds: turris-omnia: Support HW controlled mode via private trigger")
+after Lee requested a change in API semantics of the new functions I
+introduced in commit
+ 28350bc0ac77 ("leds: turris-omnia: Do not use SMBUS calls").
+
+Before the change, the function omnia_cmd_write_u8() returned 0 on
+success, and afterwards it returned a positive value (number of bytes
+written). The latter version was applied, but the following commits did
+not properly account for this change.
+
+This results in non-functional LED's .brightness_set_blocking() and
+trigger's .activate() methods.
+
+The main reasoning behind the semantics change was that read/write
+methods should return the number of read/written bytes on success.
+It was pointed to me [1] that this is not always true (for example the
+regmap API does not do so), and since the driver never uses this number
+of read/written bytes information, I decided to fix this issue by
+changing the functions to the original semantics (return 0 on success).
+
+[1] https://lore.kernel.org/linux-gpio/ZQnn+Gi0xVlsGCYA@smile.fi.intel.com/
+
+Fixes: 28350bc0ac77 ("leds: turris-omnia: Do not use SMBUS calls")
+Signed-off-by: Marek Behún <kabel@kernel.org>
+---
+ drivers/leds/leds-turris-omnia.c | 37 +++++++++++++++++---------------
+ 1 file changed, 20 insertions(+), 17 deletions(-)
+
+--- a/drivers/leds/leds-turris-omnia.c
++++ b/drivers/leds/leds-turris-omnia.c
+@@ -60,8 +60,11 @@ struct omnia_leds {
+ static int omnia_cmd_write_u8(const struct i2c_client *client, u8 cmd, u8 val)
+ {
+ u8 buf[2] = { cmd, val };
++ int ret;
++
++ ret = i2c_master_send(client, buf, sizeof(buf));
+
+- return i2c_master_send(client, buf, sizeof(buf));
++ return ret < 0 ? ret : 0;
+ }
+
+ static int omnia_cmd_read_raw(struct i2c_adapter *adapter, u8 addr, u8 cmd,
+@@ -81,7 +84,7 @@ static int omnia_cmd_read_raw(struct i2c
+
+ ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs));
+ if (likely(ret == ARRAY_SIZE(msgs)))
+- return len;
++ return 0;
+ else if (ret < 0)
+ return ret;
+ else
+@@ -91,11 +94,11 @@ static int omnia_cmd_read_raw(struct i2c
+ static int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd)
+ {
+ u8 reply;
+- int ret;
++ int err;
+
+- ret = omnia_cmd_read_raw(client->adapter, client->addr, cmd, &reply, 1);
+- if (ret < 0)
+- return ret;
++ err = omnia_cmd_read_raw(client->adapter, client->addr, cmd, &reply, 1);
++ if (err)
++ return err;
+
+ return reply;
+ }
+@@ -236,7 +239,7 @@ static void omnia_hwtrig_deactivate(stru
+
+ mutex_unlock(&leds->lock);
+
+- if (err < 0)
++ if (err)
+ dev_err(cdev->dev, "Cannot put LED to software mode: %i\n",
+ err);
+ }
+@@ -302,7 +305,7 @@ static int omnia_led_register(struct i2c
+ ret = omnia_cmd_write_u8(client, CMD_LED_MODE,
+ CMD_LED_MODE_LED(led->reg) |
+ CMD_LED_MODE_USER);
+- if (ret < 0) {
++ if (ret) {
+ dev_err(dev, "Cannot set LED %pOF to software mode: %i\n", np,
+ ret);
+ return ret;
+@@ -311,7 +314,7 @@ static int omnia_led_register(struct i2c
+ /* disable the LED */
+ ret = omnia_cmd_write_u8(client, CMD_LED_STATE,
+ CMD_LED_STATE_LED(led->reg));
+- if (ret < 0) {
++ if (ret) {
+ dev_err(dev, "Cannot set LED %pOF brightness: %i\n", np, ret);
+ return ret;
+ }
+@@ -364,7 +367,7 @@ static ssize_t brightness_store(struct d
+ {
+ struct i2c_client *client = to_i2c_client(dev);
+ unsigned long brightness;
+- int ret;
++ int err;
+
+ if (kstrtoul(buf, 10, &brightness))
+ return -EINVAL;
+@@ -372,9 +375,9 @@ static ssize_t brightness_store(struct d
+ if (brightness > 100)
+ return -EINVAL;
+
+- ret = omnia_cmd_write_u8(client, CMD_LED_SET_BRIGHTNESS, brightness);
++ err = omnia_cmd_write_u8(client, CMD_LED_SET_BRIGHTNESS, brightness);
+
+- return ret < 0 ? ret : count;
++ return err ?: count;
+ }
+ static DEVICE_ATTR_RW(brightness);
+
+@@ -403,7 +406,7 @@ static ssize_t gamma_correction_store(st
+ struct i2c_client *client = to_i2c_client(dev);
+ struct omnia_leds *leds = i2c_get_clientdata(client);
+ bool val;
+- int ret;
++ int err;
+
+ if (!leds->has_gamma_correction)
+ return -EOPNOTSUPP;
+@@ -411,9 +414,9 @@ static ssize_t gamma_correction_store(st
+ if (kstrtobool(buf, &val) < 0)
+ return -EINVAL;
+
+- ret = omnia_cmd_write_u8(client, CMD_SET_GAMMA_CORRECTION, val);
++ err = omnia_cmd_write_u8(client, CMD_SET_GAMMA_CORRECTION, val);
+
+- return ret < 0 ? ret : count;
++ return err ?: count;
+ }
+ static DEVICE_ATTR_RW(gamma_correction);
+
+@@ -431,7 +434,7 @@ static int omnia_mcu_get_features(const
+
+ err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR,
+ CMD_GET_STATUS_WORD, &reply, sizeof(reply));
+- if (err < 0)
++ if (err)
+ return err;
+
+ /* Check whether MCU firmware supports the CMD_GET_FEAUTRES command */
+@@ -440,7 +443,7 @@ static int omnia_mcu_get_features(const
+
+ err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR,
+ CMD_GET_FEATURES, &reply, sizeof(reply));
+- if (err < 0)
++ if (err)
+ return err;
+
+ return le16_to_cpu(reply);
diff --git a/target/linux/generic/backport-6.6/816-v6.7-0001-nvmem-qfprom-Mark-core-clk-as-optional.patch b/target/linux/generic/backport-6.6/816-v6.7-0001-nvmem-qfprom-Mark-core-clk-as-optional.patch
new file mode 100644
index 0000000000..66d4028140
--- /dev/null
+++ b/target/linux/generic/backport-6.6/816-v6.7-0001-nvmem-qfprom-Mark-core-clk-as-optional.patch
@@ -0,0 +1,37 @@
+From 16724d6ea40a2c9315f5a0d81005dfa4d7a6da24 Mon Sep 17 00:00:00 2001
+From: Luca Weiss <luca.weiss@fairphone.com>
+Date: Fri, 20 Oct 2023 11:55:40 +0100
+Subject: [PATCH] nvmem: qfprom: Mark core clk as optional
+
+On some platforms like sc7280 on non-ChromeOS devices the core clock
+cannot be touched by Linux so we cannot provide it. Mark it as optional
+as accessing qfprom for reading works without it but we still prohibit
+writing if we cannot provide the clock.
+
+Signed-off-by: Luca Weiss <luca.weiss@fairphone.com>
+Reviewed-by: Douglas Anderson <dianders@chromium.org>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20231020105545.216052-2-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/qfprom.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/nvmem/qfprom.c
++++ b/drivers/nvmem/qfprom.c
+@@ -423,12 +423,12 @@ static int qfprom_probe(struct platform_
+ if (IS_ERR(priv->vcc))
+ return PTR_ERR(priv->vcc);
+
+- priv->secclk = devm_clk_get(dev, "core");
++ priv->secclk = devm_clk_get_optional(dev, "core");
+ if (IS_ERR(priv->secclk))
+ return dev_err_probe(dev, PTR_ERR(priv->secclk), "Error getting clock\n");
+
+- /* Only enable writing if we have SoC data. */
+- if (priv->soc_data)
++ /* Only enable writing if we have SoC data and a valid clock */
++ if (priv->soc_data && priv->secclk)
+ econfig.reg_write = qfprom_reg_write;
+ }
+
diff --git a/target/linux/generic/backport-6.6/816-v6.7-0002-nvmem-add-explicit-config-option-to-read-old-syntax-.patch b/target/linux/generic/backport-6.6/816-v6.7-0002-nvmem-add-explicit-config-option-to-read-old-syntax-.patch
new file mode 100644
index 0000000000..35b15776fb
--- /dev/null
+++ b/target/linux/generic/backport-6.6/816-v6.7-0002-nvmem-add-explicit-config-option-to-read-old-syntax-.patch
@@ -0,0 +1,330 @@
+From 2cc3b37f5b6df8189d55d0e812d9658ce256dfec Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Fri, 20 Oct 2023 11:55:41 +0100
+Subject: [PATCH] nvmem: add explicit config option to read old syntax fixed OF
+ cells
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Binding for fixed NVMEM cells defined directly as NVMEM device subnodes
+has been deprecated. It has been replaced by the "fixed-layout" NVMEM
+layout binding.
+
+New syntax is meant to be clearer and should help avoiding imprecise
+bindings.
+
+NVMEM subsystem already supports the new binding. It should be a good
+idea to limit support for old syntax to existing drivers that actually
+support & use it (we can't break backward compatibility!). That way we
+additionally encourage new bindings & drivers to ignore deprecated
+binding.
+
+It wasn't clear (to me) if rtc and w1 code actually uses old syntax
+fixed cells. I enabled them to don't risk any breakage.
+
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+[for meson-{efuse,mx-efuse}.c]
+Acked-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+[for mtk-efuse.c, nvmem/core.c, nvmem-provider.h]
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+[MT8192, MT8195 Chromebooks]
+Tested-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+[for microchip-otpc.c]
+Reviewed-by: Claudiu Beznea <claudiu.beznea@microchip.com>
+[SAMA7G5-EK]
+Tested-by: Claudiu Beznea <claudiu.beznea@microchip.com>
+Acked-by: Jernej Skrabec <jernej.skrabec@gmail.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20231020105545.216052-3-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/mtd/mtdcore.c | 2 ++
+ drivers/nvmem/apple-efuses.c | 1 +
+ drivers/nvmem/core.c | 8 +++++---
+ drivers/nvmem/imx-ocotp-scu.c | 1 +
+ drivers/nvmem/imx-ocotp.c | 1 +
+ drivers/nvmem/meson-efuse.c | 1 +
+ drivers/nvmem/meson-mx-efuse.c | 1 +
+ drivers/nvmem/microchip-otpc.c | 1 +
+ drivers/nvmem/mtk-efuse.c | 1 +
+ drivers/nvmem/qcom-spmi-sdam.c | 1 +
+ drivers/nvmem/qfprom.c | 1 +
+ drivers/nvmem/rave-sp-eeprom.c | 1 +
+ drivers/nvmem/rockchip-efuse.c | 1 +
+ drivers/nvmem/sc27xx-efuse.c | 1 +
+ drivers/nvmem/sec-qfprom.c | 1 +
+ drivers/nvmem/sprd-efuse.c | 1 +
+ drivers/nvmem/stm32-romem.c | 1 +
+ drivers/nvmem/sunplus-ocotp.c | 1 +
+ drivers/nvmem/sunxi_sid.c | 1 +
+ drivers/nvmem/uniphier-efuse.c | 1 +
+ drivers/nvmem/zynqmp_nvmem.c | 1 +
+ drivers/rtc/nvmem.c | 1 +
+ drivers/w1/slaves/w1_ds250x.c | 1 +
+ include/linux/nvmem-provider.h | 2 ++
+ 24 files changed, 30 insertions(+), 3 deletions(-)
+
+--- a/drivers/mtd/mtdcore.c
++++ b/drivers/mtd/mtdcore.c
+@@ -523,6 +523,7 @@ static int mtd_nvmem_add(struct mtd_info
+ config.dev = &mtd->dev;
+ config.name = dev_name(&mtd->dev);
+ config.owner = THIS_MODULE;
++ config.add_legacy_fixed_of_cells = of_device_is_compatible(node, "nvmem-cells");
+ config.reg_read = mtd_nvmem_reg_read;
+ config.size = mtd->size;
+ config.word_size = 1;
+@@ -891,6 +892,7 @@ static struct nvmem_device *mtd_otp_nvme
+ config.name = compatible;
+ config.id = NVMEM_DEVID_AUTO;
+ config.owner = THIS_MODULE;
++ config.add_legacy_fixed_of_cells = true;
+ config.type = NVMEM_TYPE_OTP;
+ config.root_only = true;
+ config.ignore_wp = true;
+--- a/drivers/nvmem/apple-efuses.c
++++ b/drivers/nvmem/apple-efuses.c
+@@ -36,6 +36,7 @@ static int apple_efuses_probe(struct pla
+ struct resource *res;
+ struct nvmem_config config = {
+ .dev = &pdev->dev,
++ .add_legacy_fixed_of_cells = true,
+ .read_only = true,
+ .reg_read = apple_efuses_read,
+ .stride = sizeof(u32),
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -998,9 +998,11 @@ struct nvmem_device *nvmem_register(cons
+ if (rval)
+ goto err_remove_cells;
+
+- rval = nvmem_add_cells_from_legacy_of(nvmem);
+- if (rval)
+- goto err_remove_cells;
++ if (config->add_legacy_fixed_of_cells) {
++ rval = nvmem_add_cells_from_legacy_of(nvmem);
++ if (rval)
++ goto err_remove_cells;
++ }
+
+ rval = nvmem_add_cells_from_fixed_layout(nvmem);
+ if (rval)
+--- a/drivers/nvmem/imx-ocotp-scu.c
++++ b/drivers/nvmem/imx-ocotp-scu.c
+@@ -220,6 +220,7 @@ static int imx_scu_ocotp_write(void *con
+
+ static struct nvmem_config imx_scu_ocotp_nvmem_config = {
+ .name = "imx-scu-ocotp",
++ .add_legacy_fixed_of_cells = true,
+ .read_only = false,
+ .word_size = 4,
+ .stride = 1,
+--- a/drivers/nvmem/imx-ocotp.c
++++ b/drivers/nvmem/imx-ocotp.c
+@@ -616,6 +616,7 @@ static int imx_ocotp_probe(struct platfo
+ return PTR_ERR(priv->clk);
+
+ priv->params = of_device_get_match_data(&pdev->dev);
++ imx_ocotp_nvmem_config.add_legacy_fixed_of_cells = true;
+ imx_ocotp_nvmem_config.size = 4 * priv->params->nregs;
+ imx_ocotp_nvmem_config.dev = dev;
+ imx_ocotp_nvmem_config.priv = priv;
+--- a/drivers/nvmem/meson-efuse.c
++++ b/drivers/nvmem/meson-efuse.c
+@@ -93,6 +93,7 @@ static int meson_efuse_probe(struct plat
+
+ econfig->dev = dev;
+ econfig->name = dev_name(dev);
++ econfig->add_legacy_fixed_of_cells = true;
+ econfig->stride = 1;
+ econfig->word_size = 1;
+ econfig->reg_read = meson_efuse_read;
+--- a/drivers/nvmem/meson-mx-efuse.c
++++ b/drivers/nvmem/meson-mx-efuse.c
+@@ -211,6 +211,7 @@ static int meson_mx_efuse_probe(struct p
+ efuse->config.owner = THIS_MODULE;
+ efuse->config.dev = &pdev->dev;
+ efuse->config.priv = efuse;
++ efuse->config.add_legacy_fixed_of_cells = true;
+ efuse->config.stride = drvdata->word_size;
+ efuse->config.word_size = drvdata->word_size;
+ efuse->config.size = SZ_512;
+--- a/drivers/nvmem/microchip-otpc.c
++++ b/drivers/nvmem/microchip-otpc.c
+@@ -261,6 +261,7 @@ static int mchp_otpc_probe(struct platfo
+ return ret;
+
+ mchp_nvmem_config.dev = otpc->dev;
++ mchp_nvmem_config.add_legacy_fixed_of_cells = true;
+ mchp_nvmem_config.size = size;
+ mchp_nvmem_config.priv = otpc;
+ nvmem = devm_nvmem_register(&pdev->dev, &mchp_nvmem_config);
+--- a/drivers/nvmem/mtk-efuse.c
++++ b/drivers/nvmem/mtk-efuse.c
+@@ -83,6 +83,7 @@ static int mtk_efuse_probe(struct platfo
+ return PTR_ERR(priv->base);
+
+ pdata = device_get_match_data(dev);
++ econfig.add_legacy_fixed_of_cells = true;
+ econfig.stride = 1;
+ econfig.word_size = 1;
+ econfig.reg_read = mtk_reg_read;
+--- a/drivers/nvmem/qcom-spmi-sdam.c
++++ b/drivers/nvmem/qcom-spmi-sdam.c
+@@ -142,6 +142,7 @@ static int sdam_probe(struct platform_de
+ sdam->sdam_config.name = "spmi_sdam";
+ sdam->sdam_config.id = NVMEM_DEVID_AUTO;
+ sdam->sdam_config.owner = THIS_MODULE;
++ sdam->sdam_config.add_legacy_fixed_of_cells = true;
+ sdam->sdam_config.stride = 1;
+ sdam->sdam_config.word_size = 1;
+ sdam->sdam_config.reg_read = sdam_read;
+--- a/drivers/nvmem/qfprom.c
++++ b/drivers/nvmem/qfprom.c
+@@ -357,6 +357,7 @@ static int qfprom_probe(struct platform_
+ {
+ struct nvmem_config econfig = {
+ .name = "qfprom",
++ .add_legacy_fixed_of_cells = true,
+ .stride = 1,
+ .word_size = 1,
+ .id = NVMEM_DEVID_AUTO,
+--- a/drivers/nvmem/rave-sp-eeprom.c
++++ b/drivers/nvmem/rave-sp-eeprom.c
+@@ -328,6 +328,7 @@ static int rave_sp_eeprom_probe(struct p
+ of_property_read_string(np, "zii,eeprom-name", &config.name);
+ config.priv = eeprom;
+ config.dev = dev;
++ config.add_legacy_fixed_of_cells = true;
+ config.size = size;
+ config.reg_read = rave_sp_eeprom_reg_read;
+ config.reg_write = rave_sp_eeprom_reg_write;
+--- a/drivers/nvmem/rockchip-efuse.c
++++ b/drivers/nvmem/rockchip-efuse.c
+@@ -205,6 +205,7 @@ static int rockchip_rk3399_efuse_read(vo
+
+ static struct nvmem_config econfig = {
+ .name = "rockchip-efuse",
++ .add_legacy_fixed_of_cells = true,
+ .stride = 1,
+ .word_size = 1,
+ .read_only = true,
+--- a/drivers/nvmem/sc27xx-efuse.c
++++ b/drivers/nvmem/sc27xx-efuse.c
+@@ -248,6 +248,7 @@ static int sc27xx_efuse_probe(struct pla
+ econfig.reg_read = sc27xx_efuse_read;
+ econfig.priv = efuse;
+ econfig.dev = &pdev->dev;
++ econfig.add_legacy_fixed_of_cells = true;
+ nvmem = devm_nvmem_register(&pdev->dev, &econfig);
+ if (IS_ERR(nvmem)) {
+ dev_err(&pdev->dev, "failed to register nvmem config\n");
+--- a/drivers/nvmem/sec-qfprom.c
++++ b/drivers/nvmem/sec-qfprom.c
+@@ -47,6 +47,7 @@ static int sec_qfprom_probe(struct platf
+ {
+ struct nvmem_config econfig = {
+ .name = "sec-qfprom",
++ .add_legacy_fixed_of_cells = true,
+ .stride = 1,
+ .word_size = 1,
+ .id = NVMEM_DEVID_AUTO,
+--- a/drivers/nvmem/sprd-efuse.c
++++ b/drivers/nvmem/sprd-efuse.c
+@@ -408,6 +408,7 @@ static int sprd_efuse_probe(struct platf
+ econfig.read_only = false;
+ econfig.name = "sprd-efuse";
+ econfig.size = efuse->data->blk_nums * SPRD_EFUSE_BLOCK_WIDTH;
++ econfig.add_legacy_fixed_of_cells = true;
+ econfig.reg_read = sprd_efuse_read;
+ econfig.reg_write = sprd_efuse_write;
+ econfig.priv = efuse;
+--- a/drivers/nvmem/stm32-romem.c
++++ b/drivers/nvmem/stm32-romem.c
+@@ -207,6 +207,7 @@ static int stm32_romem_probe(struct plat
+ priv->cfg.priv = priv;
+ priv->cfg.owner = THIS_MODULE;
+ priv->cfg.type = NVMEM_TYPE_OTP;
++ priv->cfg.add_legacy_fixed_of_cells = true;
+
+ priv->lower = 0;
+
+--- a/drivers/nvmem/sunplus-ocotp.c
++++ b/drivers/nvmem/sunplus-ocotp.c
+@@ -145,6 +145,7 @@ disable_clk:
+
+ static struct nvmem_config sp_ocotp_nvmem_config = {
+ .name = "sp-ocotp",
++ .add_legacy_fixed_of_cells = true,
+ .read_only = true,
+ .word_size = 1,
+ .size = QAC628_OTP_SIZE,
+--- a/drivers/nvmem/sunxi_sid.c
++++ b/drivers/nvmem/sunxi_sid.c
+@@ -154,6 +154,7 @@ static int sunxi_sid_probe(struct platfo
+ nvmem_cfg->dev = dev;
+ nvmem_cfg->name = "sunxi-sid";
+ nvmem_cfg->type = NVMEM_TYPE_OTP;
++ nvmem_cfg->add_legacy_fixed_of_cells = true;
+ nvmem_cfg->read_only = true;
+ nvmem_cfg->size = cfg->size;
+ nvmem_cfg->word_size = 1;
+--- a/drivers/nvmem/uniphier-efuse.c
++++ b/drivers/nvmem/uniphier-efuse.c
+@@ -52,6 +52,7 @@ static int uniphier_efuse_probe(struct p
+ econfig.size = resource_size(res);
+ econfig.priv = priv;
+ econfig.dev = dev;
++ econfig.add_legacy_fixed_of_cells = true;
+ nvmem = devm_nvmem_register(dev, &econfig);
+
+ return PTR_ERR_OR_ZERO(nvmem);
+--- a/drivers/nvmem/zynqmp_nvmem.c
++++ b/drivers/nvmem/zynqmp_nvmem.c
+@@ -58,6 +58,7 @@ static int zynqmp_nvmem_probe(struct pla
+
+ priv->dev = dev;
+ econfig.dev = dev;
++ econfig.add_legacy_fixed_of_cells = true;
+ econfig.reg_read = zynqmp_nvmem_read;
+ econfig.priv = priv;
+
+--- a/drivers/rtc/nvmem.c
++++ b/drivers/rtc/nvmem.c
+@@ -21,6 +21,7 @@ int devm_rtc_nvmem_register(struct rtc_d
+
+ nvmem_config->dev = dev;
+ nvmem_config->owner = rtc->owner;
++ nvmem_config->add_legacy_fixed_of_cells = true;
+ nvmem = devm_nvmem_register(dev, nvmem_config);
+ if (IS_ERR(nvmem))
+ dev_err(dev, "failed to register nvmem device for RTC\n");
+--- a/drivers/w1/slaves/w1_ds250x.c
++++ b/drivers/w1/slaves/w1_ds250x.c
+@@ -168,6 +168,7 @@ static int w1_eprom_add_slave(struct w1_
+ struct nvmem_device *nvmem;
+ struct nvmem_config nvmem_cfg = {
+ .dev = &sl->dev,
++ .add_legacy_fixed_of_cells = true,
+ .reg_read = w1_nvmem_read,
+ .type = NVMEM_TYPE_OTP,
+ .read_only = true,
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -82,6 +82,7 @@ struct nvmem_cell_info {
+ * @owner: Pointer to exporter module. Used for refcounting.
+ * @cells: Optional array of pre-defined NVMEM cells.
+ * @ncells: Number of elements in cells.
++ * @add_legacy_fixed_of_cells: Read fixed NVMEM cells from old OF syntax.
+ * @keepout: Optional array of keepout ranges (sorted ascending by start).
+ * @nkeepout: Number of elements in the keepout array.
+ * @type: Type of the nvmem storage
+@@ -112,6 +113,7 @@ struct nvmem_config {
+ struct module *owner;
+ const struct nvmem_cell_info *cells;
+ int ncells;
++ bool add_legacy_fixed_of_cells;
+ const struct nvmem_keepout *keepout;
+ unsigned int nkeepout;
+ enum nvmem_type type;
diff --git a/target/linux/generic/backport-6.6/816-v6.7-0003-nvmem-Use-device_get_match_data.patch b/target/linux/generic/backport-6.6/816-v6.7-0003-nvmem-Use-device_get_match_data.patch
new file mode 100644
index 0000000000..84c0293982
--- /dev/null
+++ b/target/linux/generic/backport-6.6/816-v6.7-0003-nvmem-Use-device_get_match_data.patch
@@ -0,0 +1,77 @@
+From 0720219f4d34a88a9badb4de70cfad7585687d48 Mon Sep 17 00:00:00 2001
+From: Rob Herring <robh@kernel.org>
+Date: Fri, 20 Oct 2023 11:55:45 +0100
+Subject: [PATCH] nvmem: Use device_get_match_data()
+
+Use preferred device_get_match_data() instead of of_match_device() to
+get the driver match data. With this, adjust the includes to explicitly
+include the correct headers.
+
+Signed-off-by: Rob Herring <robh@kernel.org>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20231020105545.216052-7-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/mxs-ocotp.c | 10 ++++------
+ drivers/nvmem/stm32-romem.c | 7 ++++---
+ 2 files changed, 8 insertions(+), 9 deletions(-)
+
+--- a/drivers/nvmem/mxs-ocotp.c
++++ b/drivers/nvmem/mxs-ocotp.c
+@@ -13,8 +13,9 @@
+ #include <linux/io.h>
+ #include <linux/module.h>
+ #include <linux/nvmem-provider.h>
+-#include <linux/of_device.h>
++#include <linux/of.h>
+ #include <linux/platform_device.h>
++#include <linux/property.h>
+ #include <linux/slab.h>
+ #include <linux/stmp_device.h>
+
+@@ -140,11 +141,10 @@ static int mxs_ocotp_probe(struct platfo
+ struct device *dev = &pdev->dev;
+ const struct mxs_data *data;
+ struct mxs_ocotp *otp;
+- const struct of_device_id *match;
+ int ret;
+
+- match = of_match_device(dev->driver->of_match_table, dev);
+- if (!match || !match->data)
++ data = device_get_match_data(dev);
++ if (!data)
+ return -EINVAL;
+
+ otp = devm_kzalloc(dev, sizeof(*otp), GFP_KERNEL);
+@@ -169,8 +169,6 @@ static int mxs_ocotp_probe(struct platfo
+ if (ret)
+ return ret;
+
+- data = match->data;
+-
+ ocotp_config.size = data->size;
+ ocotp_config.priv = otp;
+ ocotp_config.dev = dev;
+--- a/drivers/nvmem/stm32-romem.c
++++ b/drivers/nvmem/stm32-romem.c
+@@ -10,7 +10,9 @@
+ #include <linux/io.h>
+ #include <linux/module.h>
+ #include <linux/nvmem-provider.h>
+-#include <linux/of_device.h>
++#include <linux/of.h>
++#include <linux/platform_device.h>
++#include <linux/property.h>
+ #include <linux/tee_drv.h>
+
+ #include "stm32-bsec-optee-ta.h"
+@@ -211,8 +213,7 @@ static int stm32_romem_probe(struct plat
+
+ priv->lower = 0;
+
+- cfg = (const struct stm32_romem_cfg *)
+- of_match_device(dev->driver->of_match_table, dev)->data;
++ cfg = device_get_match_data(dev);
+ if (!cfg) {
+ priv->cfg.read_only = true;
+ priv->cfg.size = resource_size(res);
diff --git a/target/linux/generic/backport-6.6/816-v6.7-0004-Revert-nvmem-add-new-config-option.patch b/target/linux/generic/backport-6.6/816-v6.7-0004-Revert-nvmem-add-new-config-option.patch
new file mode 100644
index 0000000000..7d80ad37f1
--- /dev/null
+++ b/target/linux/generic/backport-6.6/816-v6.7-0004-Revert-nvmem-add-new-config-option.patch
@@ -0,0 +1,77 @@
+From f4cf4e5db331a5ce69e3f0b21d322cac0f4e4b5d Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Mon, 23 Oct 2023 12:27:59 +0200
+Subject: [PATCH] Revert "nvmem: add new config option"
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This reverts commit 517f14d9cf3533d5ab4fded195ab6f80a92e378f.
+
+Config option "no_of_node" is no longer needed since adding a more
+explicit and targeted option "add_legacy_fixed_of_cells".
+
+That "no_of_node" config option was needed *earlier* to help mtd's case.
+
+DT nodes of MTD partitions (that are also NVMEM devices) may contain
+subnodes. Those SHOULD NOT be treated as NVMEM fixed cells.
+
+To prevent NVMEM core code from parsing subnodes a "no_of_node" option
+was added (and set to true in mtd) to make for_each_child_of_node() in
+NVMEM a no-op. That was a bit hacky because it was messing with
+"of_node" pointer to achieve some side-effect.
+
+With the introduction of "add_legacy_fixed_of_cells" config option
+things got more explicit. MTD subsystem simply tells NVMEM when to look
+for fixed cells and there is no need to hack "of_node" pointer anymore.
+
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Reviewed-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20231023102759.31529-1-zajec5@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/mtd/mtdcore.c | 1 -
+ drivers/nvmem/core.c | 2 +-
+ include/linux/nvmem-provider.h | 2 --
+ 3 files changed, 1 insertion(+), 4 deletions(-)
+
+--- a/drivers/mtd/mtdcore.c
++++ b/drivers/mtd/mtdcore.c
+@@ -531,7 +531,6 @@ static int mtd_nvmem_add(struct mtd_info
+ config.read_only = true;
+ config.root_only = true;
+ config.ignore_wp = true;
+- config.no_of_node = !of_device_is_compatible(node, "nvmem-cells");
+ config.priv = mtd;
+
+ mtd->nvmem = nvmem_register(&config);
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -936,7 +936,7 @@ struct nvmem_device *nvmem_register(cons
+ nvmem->nkeepout = config->nkeepout;
+ if (config->of_node)
+ nvmem->dev.of_node = config->of_node;
+- else if (!config->no_of_node)
++ else
+ nvmem->dev.of_node = config->dev->of_node;
+
+ switch (config->id) {
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -89,7 +89,6 @@ struct nvmem_cell_info {
+ * @read_only: Device is read-only.
+ * @root_only: Device is accessibly to root only.
+ * @of_node: If given, this will be used instead of the parent's of_node.
+- * @no_of_node: Device should not use the parent's of_node even if it's !NULL.
+ * @reg_read: Callback to read data.
+ * @reg_write: Callback to write data.
+ * @size: Device size.
+@@ -122,7 +121,6 @@ struct nvmem_config {
+ bool ignore_wp;
+ struct nvmem_layout *layout;
+ struct device_node *of_node;
+- bool no_of_node;
+ nvmem_reg_read_t reg_read;
+ nvmem_reg_write_t reg_write;
+ int size;
diff --git a/target/linux/generic/backport-6.6/816-v6.7-0005-nvmem-Do-not-expect-fixed-layouts-to-grab-a-layout-d.patch b/target/linux/generic/backport-6.6/816-v6.7-0005-nvmem-Do-not-expect-fixed-layouts-to-grab-a-layout-d.patch
new file mode 100644
index 0000000000..bd5ceaabf7
--- /dev/null
+++ b/target/linux/generic/backport-6.6/816-v6.7-0005-nvmem-Do-not-expect-fixed-layouts-to-grab-a-layout-d.patch
@@ -0,0 +1,45 @@
+From b7c1e53751cb3990153084f31c41f25fde3b629c Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Fri, 24 Nov 2023 20:38:14 +0100
+Subject: [PATCH] nvmem: Do not expect fixed layouts to grab a layout driver
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Two series lived in parallel for some time, which led to this situation:
+- The nvmem-layout container is used for dynamic layouts
+- We now expect fixed layouts to also use the nvmem-layout container but
+this does not require any additional driver, the support is built-in the
+nvmem core.
+
+Ensure we don't refuse to probe for wrong reasons.
+
+Fixes: 27f699e578b1 ("nvmem: core: add support for fixed cells *layout*")
+Cc: stable@vger.kernel.org
+Reported-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Tested-by: Rafał Miłecki <rafal@milecki.pl>
+Tested-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
+Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
+
+Link: https://lore.kernel.org/r/20231124193814.360552-1-miquel.raynal@bootlin.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -797,6 +797,12 @@ static struct nvmem_layout *nvmem_layout
+ if (!layout_np)
+ return NULL;
+
++ /* Fixed layouts don't have a matching driver */
++ if (of_device_is_compatible(layout_np, "fixed-layout")) {
++ of_node_put(layout_np);
++ return NULL;
++ }
++
+ /*
+ * In case the nvmem device was built-in while the layout was built as a
+ * module, we shall manually request the layout driver loading otherwise
diff --git a/target/linux/generic/backport-6.6/816-v6.7-0006-nvmem-brcm_nvram-store-a-copy-of-NVRAM-content.patch b/target/linux/generic/backport-6.6/816-v6.7-0006-nvmem-brcm_nvram-store-a-copy-of-NVRAM-content.patch
new file mode 100644
index 0000000000..d49a20599d
--- /dev/null
+++ b/target/linux/generic/backport-6.6/816-v6.7-0006-nvmem-brcm_nvram-store-a-copy-of-NVRAM-content.patch
@@ -0,0 +1,261 @@
+From 1e37bf84afacd5ba17b7a13a18ca2bc78aff05c0 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Fri, 15 Dec 2023 11:13:58 +0000
+Subject: [PATCH] nvmem: brcm_nvram: store a copy of NVRAM content
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This driver uses MMIO access for reading NVRAM from a flash device.
+Underneath there is a flash controller that reads data and provides
+mapping window.
+
+Using MMIO interface affects controller configuration and may break real
+controller driver. It was reported by multiple users of devices with
+NVRAM stored on NAND.
+
+Modify driver to read & cache NVRAM content during init and use that
+copy to provide NVMEM data when requested. On NAND flashes due to their
+alignment NVRAM partitions can be quite big (1 MiB and more) while
+actual NVRAM content stays quite small (usually 16 to 32 KiB). To avoid
+allocating so much memory check for actual data length.
+
+Link: https://lore.kernel.org/linux-mtd/CACna6rwf3_9QVjYcM+847biTX=K0EoWXuXcSMkJO1Vy_5vmVqA@mail.gmail.com/
+Fixes: 3fef9ed0627a ("nvmem: brcm_nvram: new driver exposing Broadcom's NVRAM")
+Cc: <Stable@vger.kernel.org>
+Cc: Arınç ÜNAL <arinc.unal@arinc9.com>
+Cc: Florian Fainelli <florian.fainelli@broadcom.com>
+Cc: Scott Branden <scott.branden@broadcom.com>
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Acked-by: Arınç ÜNAL <arinc.unal@arinc9.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20231215111358.316727-3-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/brcm_nvram.c | 134 ++++++++++++++++++++++++++-----------
+ 1 file changed, 94 insertions(+), 40 deletions(-)
+
+--- a/drivers/nvmem/brcm_nvram.c
++++ b/drivers/nvmem/brcm_nvram.c
+@@ -17,9 +17,23 @@
+
+ #define NVRAM_MAGIC "FLSH"
+
++/**
++ * struct brcm_nvram - driver state internal struct
++ *
++ * @dev: NVMEM device pointer
++ * @nvmem_size: Size of the whole space available for NVRAM
++ * @data: NVRAM data copy stored to avoid poking underlaying flash controller
++ * @data_len: NVRAM data size
++ * @padding_byte: Padding value used to fill remaining space
++ * @cells: Array of discovered NVMEM cells
++ * @ncells: Number of elements in cells
++ */
+ struct brcm_nvram {
+ struct device *dev;
+- void __iomem *base;
++ size_t nvmem_size;
++ uint8_t *data;
++ size_t data_len;
++ uint8_t padding_byte;
+ struct nvmem_cell_info *cells;
+ int ncells;
+ };
+@@ -36,10 +50,47 @@ static int brcm_nvram_read(void *context
+ size_t bytes)
+ {
+ struct brcm_nvram *priv = context;
+- u8 *dst = val;
++ size_t to_copy;
++
++ if (offset + bytes > priv->data_len)
++ to_copy = max_t(ssize_t, (ssize_t)priv->data_len - offset, 0);
++ else
++ to_copy = bytes;
++
++ memcpy(val, priv->data + offset, to_copy);
++
++ memset((uint8_t *)val + to_copy, priv->padding_byte, bytes - to_copy);
++
++ return 0;
++}
++
++static int brcm_nvram_copy_data(struct brcm_nvram *priv, struct platform_device *pdev)
++{
++ struct resource *res;
++ void __iomem *base;
++
++ base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
++ if (IS_ERR(base))
++ return PTR_ERR(base);
++
++ priv->nvmem_size = resource_size(res);
++
++ priv->padding_byte = readb(base + priv->nvmem_size - 1);
++ for (priv->data_len = priv->nvmem_size;
++ priv->data_len;
++ priv->data_len--) {
++ if (readb(base + priv->data_len - 1) != priv->padding_byte)
++ break;
++ }
++ WARN(priv->data_len > SZ_128K, "Unexpected (big) NVRAM size: %zu B\n", priv->data_len);
+
+- while (bytes--)
+- *dst++ = readb(priv->base + offset++);
++ priv->data = devm_kzalloc(priv->dev, priv->data_len, GFP_KERNEL);
++ if (!priv->data)
++ return -ENOMEM;
++
++ memcpy_fromio(priv->data, base, priv->data_len);
++
++ bcm47xx_nvram_init_from_iomem(base, priv->data_len);
+
+ return 0;
+ }
+@@ -67,8 +118,13 @@ static int brcm_nvram_add_cells(struct b
+ size_t len)
+ {
+ struct device *dev = priv->dev;
+- char *var, *value, *eq;
++ char *var, *value;
++ uint8_t tmp;
+ int idx;
++ int err = 0;
++
++ tmp = priv->data[len - 1];
++ priv->data[len - 1] = '\0';
+
+ priv->ncells = 0;
+ for (var = data + sizeof(struct brcm_nvram_header);
+@@ -78,67 +134,68 @@ static int brcm_nvram_add_cells(struct b
+ }
+
+ priv->cells = devm_kcalloc(dev, priv->ncells, sizeof(*priv->cells), GFP_KERNEL);
+- if (!priv->cells)
+- return -ENOMEM;
++ if (!priv->cells) {
++ err = -ENOMEM;
++ goto out;
++ }
+
+ for (var = data + sizeof(struct brcm_nvram_header), idx = 0;
+ var < (char *)data + len && *var;
+ var = value + strlen(value) + 1, idx++) {
++ char *eq, *name;
++
+ eq = strchr(var, '=');
+ if (!eq)
+ break;
+ *eq = '\0';
++ name = devm_kstrdup(dev, var, GFP_KERNEL);
++ *eq = '=';
++ if (!name) {
++ err = -ENOMEM;
++ goto out;
++ }
+ value = eq + 1;
+
+- priv->cells[idx].name = devm_kstrdup(dev, var, GFP_KERNEL);
+- if (!priv->cells[idx].name)
+- return -ENOMEM;
++ priv->cells[idx].name = name;
+ priv->cells[idx].offset = value - (char *)data;
+ priv->cells[idx].bytes = strlen(value);
+ priv->cells[idx].np = of_get_child_by_name(dev->of_node, priv->cells[idx].name);
+- if (!strcmp(var, "et0macaddr") ||
+- !strcmp(var, "et1macaddr") ||
+- !strcmp(var, "et2macaddr")) {
++ if (!strcmp(name, "et0macaddr") ||
++ !strcmp(name, "et1macaddr") ||
++ !strcmp(name, "et2macaddr")) {
+ priv->cells[idx].raw_len = strlen(value);
+ priv->cells[idx].bytes = ETH_ALEN;
+ priv->cells[idx].read_post_process = brcm_nvram_read_post_process_macaddr;
+ }
+ }
+
+- return 0;
++out:
++ priv->data[len - 1] = tmp;
++ return err;
+ }
+
+ static int brcm_nvram_parse(struct brcm_nvram *priv)
+ {
++ struct brcm_nvram_header *header = (struct brcm_nvram_header *)priv->data;
+ struct device *dev = priv->dev;
+- struct brcm_nvram_header header;
+- uint8_t *data;
+ size_t len;
+ int err;
+
+- memcpy_fromio(&header, priv->base, sizeof(header));
+-
+- if (memcmp(header.magic, NVRAM_MAGIC, 4)) {
++ if (memcmp(header->magic, NVRAM_MAGIC, 4)) {
+ dev_err(dev, "Invalid NVRAM magic\n");
+ return -EINVAL;
+ }
+
+- len = le32_to_cpu(header.len);
+-
+- data = kzalloc(len, GFP_KERNEL);
+- if (!data)
+- return -ENOMEM;
+-
+- memcpy_fromio(data, priv->base, len);
+- data[len - 1] = '\0';
+-
+- err = brcm_nvram_add_cells(priv, data, len);
+- if (err) {
+- dev_err(dev, "Failed to add cells: %d\n", err);
+- return err;
++ len = le32_to_cpu(header->len);
++ if (len > priv->nvmem_size) {
++ dev_err(dev, "NVRAM length (%zd) exceeds mapped size (%zd)\n", len,
++ priv->nvmem_size);
++ return -EINVAL;
+ }
+
+- kfree(data);
++ err = brcm_nvram_add_cells(priv, priv->data, len);
++ if (err)
++ dev_err(dev, "Failed to add cells: %d\n", err);
+
+ return 0;
+ }
+@@ -150,7 +207,6 @@ static int brcm_nvram_probe(struct platf
+ .reg_read = brcm_nvram_read,
+ };
+ struct device *dev = &pdev->dev;
+- struct resource *res;
+ struct brcm_nvram *priv;
+ int err;
+
+@@ -159,21 +215,19 @@ static int brcm_nvram_probe(struct platf
+ return -ENOMEM;
+ priv->dev = dev;
+
+- priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+- if (IS_ERR(priv->base))
+- return PTR_ERR(priv->base);
++ err = brcm_nvram_copy_data(priv, pdev);
++ if (err)
++ return err;
+
+ err = brcm_nvram_parse(priv);
+ if (err)
+ return err;
+
+- bcm47xx_nvram_init_from_iomem(priv->base, resource_size(res));
+-
+ config.dev = dev;
+ config.cells = priv->cells;
+ config.ncells = priv->ncells;
+ config.priv = priv;
+- config.size = resource_size(res);
++ config.size = priv->nvmem_size;
+
+ return PTR_ERR_OR_ZERO(devm_nvmem_register(dev, &config));
+ }
diff --git a/target/linux/generic/backport-6.6/818-v6.8-of-device-Export-of_device_make_bus_id.patch b/target/linux/generic/backport-6.6/818-v6.8-of-device-Export-of_device_make_bus_id.patch
new file mode 100644
index 0000000000..564fe9822d
--- /dev/null
+++ b/target/linux/generic/backport-6.6/818-v6.8-of-device-Export-of_device_make_bus_id.patch
@@ -0,0 +1,140 @@
+From 7f38b70042fcaa49219045bd1a9a2836e27a58ac Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Fri, 15 Dec 2023 11:15:27 +0000
+Subject: [PATCH] of: device: Export of_device_make_bus_id()
+
+This helper is really handy to create unique device names based on their
+device tree path, we may need it outside of the OF core (in the NVMEM
+subsystem) so let's export it. As this helper has nothing patform
+specific, let's move it to of/device.c instead of of/platform.c so we
+can add its prototype to of_device.h.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Acked-by: Rob Herring <robh@kernel.org>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20231215111536.316972-2-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/of/device.c | 41 +++++++++++++++++++++++++++++++++++++++
+ drivers/of/platform.c | 40 --------------------------------------
+ include/linux/of_device.h | 6 ++++++
+ 3 files changed, 47 insertions(+), 40 deletions(-)
+
+--- a/drivers/of/device.c
++++ b/drivers/of/device.c
+@@ -395,3 +395,44 @@ int of_device_uevent_modalias(struct dev
+ return 0;
+ }
+ EXPORT_SYMBOL_GPL(of_device_uevent_modalias);
++
++/**
++ * of_device_make_bus_id - Use the device node data to assign a unique name
++ * @dev: pointer to device structure that is linked to a device tree node
++ *
++ * This routine will first try using the translated bus address to
++ * derive a unique name. If it cannot, then it will prepend names from
++ * parent nodes until a unique name can be derived.
++ */
++void of_device_make_bus_id(struct device *dev)
++{
++ struct device_node *node = dev->of_node;
++ const __be32 *reg;
++ u64 addr;
++ u32 mask;
++
++ /* Construct the name, using parent nodes if necessary to ensure uniqueness */
++ while (node->parent) {
++ /*
++ * If the address can be translated, then that is as much
++ * uniqueness as we need. Make it the first component and return
++ */
++ reg = of_get_property(node, "reg", NULL);
++ if (reg && (addr = of_translate_address(node, reg)) != OF_BAD_ADDR) {
++ if (!of_property_read_u32(node, "mask", &mask))
++ dev_set_name(dev, dev_name(dev) ? "%llx.%x.%pOFn:%s" : "%llx.%x.%pOFn",
++ addr, ffs(mask) - 1, node, dev_name(dev));
++
++ else
++ dev_set_name(dev, dev_name(dev) ? "%llx.%pOFn:%s" : "%llx.%pOFn",
++ addr, node, dev_name(dev));
++ return;
++ }
++
++ /* format arguments only used if dev_name() resolves to NULL */
++ dev_set_name(dev, dev_name(dev) ? "%s:%s" : "%s",
++ kbasename(node->full_name), dev_name(dev));
++ node = node->parent;
++ }
++}
++EXPORT_SYMBOL_GPL(of_device_make_bus_id);
+--- a/drivers/of/platform.c
++++ b/drivers/of/platform.c
+@@ -64,46 +64,6 @@ EXPORT_SYMBOL(of_find_device_by_node);
+ */
+
+ /**
+- * of_device_make_bus_id - Use the device node data to assign a unique name
+- * @dev: pointer to device structure that is linked to a device tree node
+- *
+- * This routine will first try using the translated bus address to
+- * derive a unique name. If it cannot, then it will prepend names from
+- * parent nodes until a unique name can be derived.
+- */
+-static void of_device_make_bus_id(struct device *dev)
+-{
+- struct device_node *node = dev->of_node;
+- const __be32 *reg;
+- u64 addr;
+- u32 mask;
+-
+- /* Construct the name, using parent nodes if necessary to ensure uniqueness */
+- while (node->parent) {
+- /*
+- * If the address can be translated, then that is as much
+- * uniqueness as we need. Make it the first component and return
+- */
+- reg = of_get_property(node, "reg", NULL);
+- if (reg && (addr = of_translate_address(node, reg)) != OF_BAD_ADDR) {
+- if (!of_property_read_u32(node, "mask", &mask))
+- dev_set_name(dev, dev_name(dev) ? "%llx.%x.%pOFn:%s" : "%llx.%x.%pOFn",
+- addr, ffs(mask) - 1, node, dev_name(dev));
+-
+- else
+- dev_set_name(dev, dev_name(dev) ? "%llx.%pOFn:%s" : "%llx.%pOFn",
+- addr, node, dev_name(dev));
+- return;
+- }
+-
+- /* format arguments only used if dev_name() resolves to NULL */
+- dev_set_name(dev, dev_name(dev) ? "%s:%s" : "%s",
+- kbasename(node->full_name), dev_name(dev));
+- node = node->parent;
+- }
+-}
+-
+-/**
+ * of_device_alloc - Allocate and initialize an of_device
+ * @np: device node to assign to device
+ * @bus_id: Name to assign to the device. May be null to use default name.
+--- a/include/linux/of_device.h
++++ b/include/linux/of_device.h
+@@ -56,6 +56,9 @@ static inline int of_dma_configure(struc
+ {
+ return of_dma_configure_id(dev, np, force_dma, NULL);
+ }
++
++void of_device_make_bus_id(struct device *dev);
++
+ #else /* CONFIG_OF */
+
+ static inline int of_driver_match_device(struct device *dev,
+@@ -113,6 +116,9 @@ static inline int of_dma_configure(struc
+ {
+ return 0;
+ }
++
++static inline void of_device_make_bus_id(struct device *dev) {}
++
+ #endif /* CONFIG_OF */
+
+ #endif /* _LINUX_OF_DEVICE_H */
diff --git a/target/linux/generic/backport-6.6/819-v6.8-0001-nvmem-Move-of_nvmem_layout_get_container-in-another-.patch b/target/linux/generic/backport-6.6/819-v6.8-0001-nvmem-Move-of_nvmem_layout_get_container-in-another-.patch
new file mode 100644
index 0000000000..2093fac8a1
--- /dev/null
+++ b/target/linux/generic/backport-6.6/819-v6.8-0001-nvmem-Move-of_nvmem_layout_get_container-in-another-.patch
@@ -0,0 +1,95 @@
+From 4a1a40233b4a9fc159a5c7a27dc34c5c7bc5be55 Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Fri, 15 Dec 2023 11:15:28 +0000
+Subject: [PATCH] nvmem: Move of_nvmem_layout_get_container() in another header
+
+nvmem-consumer.h is included by consumer devices, extracting data from
+NVMEM devices whereas nvmem-provider.h is included by devices providing
+NVMEM content.
+
+The only users of of_nvmem_layout_get_container() outside of the core
+are layout drivers, so better move its prototype to nvmem-provider.h.
+
+While we do so, we also move the kdoc associated with the function to
+the header rather than the .c file.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20231215111536.316972-3-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 8 --------
+ include/linux/nvmem-consumer.h | 7 -------
+ include/linux/nvmem-provider.h | 21 +++++++++++++++++++++
+ 3 files changed, 21 insertions(+), 15 deletions(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -848,14 +848,6 @@ static int nvmem_add_cells_from_layout(s
+ }
+
+ #if IS_ENABLED(CONFIG_OF)
+-/**
+- * of_nvmem_layout_get_container() - Get OF node to layout container.
+- *
+- * @nvmem: nvmem device.
+- *
+- * Return: a node pointer with refcount incremented or NULL if no
+- * container exists. Use of_node_put() on it when done.
+- */
+ struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem)
+ {
+ return of_get_child_by_name(nvmem->dev.of_node, "nvmem-layout");
+--- a/include/linux/nvmem-consumer.h
++++ b/include/linux/nvmem-consumer.h
+@@ -241,7 +241,6 @@ struct nvmem_cell *of_nvmem_cell_get(str
+ const char *id);
+ struct nvmem_device *of_nvmem_device_get(struct device_node *np,
+ const char *name);
+-struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem);
+ #else
+ static inline struct nvmem_cell *of_nvmem_cell_get(struct device_node *np,
+ const char *id)
+@@ -254,12 +253,6 @@ static inline struct nvmem_device *of_nv
+ {
+ return ERR_PTR(-EOPNOTSUPP);
+ }
+-
+-static inline struct device_node *
+-of_nvmem_layout_get_container(struct nvmem_device *nvmem)
+-{
+- return NULL;
+-}
+ #endif /* CONFIG_NVMEM && CONFIG_OF */
+
+ #endif /* ifndef _LINUX_NVMEM_CONSUMER_H */
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -244,6 +244,27 @@ nvmem_layout_get_match_data(struct nvmem
+
+ #endif /* CONFIG_NVMEM */
+
++#if IS_ENABLED(CONFIG_NVMEM) && IS_ENABLED(CONFIG_OF)
++
++/**
++ * of_nvmem_layout_get_container() - Get OF node of layout container
++ *
++ * @nvmem: nvmem device
++ *
++ * Return: a node pointer with refcount incremented or NULL if no
++ * container exists. Use of_node_put() on it when done.
++ */
++struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem);
++
++#else /* CONFIG_NVMEM && CONFIG_OF */
++
++static inline struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem)
++{
++ return NULL;
++}
++
++#endif /* CONFIG_NVMEM && CONFIG_OF */
++
+ #define module_nvmem_layout_driver(__layout_driver) \
+ module_driver(__layout_driver, nvmem_layout_register, \
+ nvmem_layout_unregister)
diff --git a/target/linux/generic/backport-6.6/819-v6.8-0002-nvmem-Create-a-header-for-internal-sharing.patch b/target/linux/generic/backport-6.6/819-v6.8-0002-nvmem-Create-a-header-for-internal-sharing.patch
new file mode 100644
index 0000000000..e722109f91
--- /dev/null
+++ b/target/linux/generic/backport-6.6/819-v6.8-0002-nvmem-Create-a-header-for-internal-sharing.patch
@@ -0,0 +1,91 @@
+From ec9c08a1cb8dc5e8e003f95f5f62de41dde235bb Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Fri, 15 Dec 2023 11:15:29 +0000
+Subject: [PATCH] nvmem: Create a header for internal sharing
+
+Before adding all the NVMEM layout bus infrastructure to the core, let's
+move the main nvmem_device structure in an internal header, only
+available to the core. This way all the additional code can be added in
+a dedicated file in order to keep the current core file tidy.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20231215111536.316972-4-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 24 +-----------------------
+ drivers/nvmem/internals.h | 35 +++++++++++++++++++++++++++++++++++
+ 2 files changed, 36 insertions(+), 23 deletions(-)
+ create mode 100644 drivers/nvmem/internals.h
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -20,29 +20,7 @@
+ #include <linux/of_device.h>
+ #include <linux/slab.h>
+
+-struct nvmem_device {
+- struct module *owner;
+- struct device dev;
+- int stride;
+- int word_size;
+- int id;
+- struct kref refcnt;
+- size_t size;
+- bool read_only;
+- bool root_only;
+- int flags;
+- enum nvmem_type type;
+- struct bin_attribute eeprom;
+- struct device *base_dev;
+- struct list_head cells;
+- const struct nvmem_keepout *keepout;
+- unsigned int nkeepout;
+- nvmem_reg_read_t reg_read;
+- nvmem_reg_write_t reg_write;
+- struct gpio_desc *wp_gpio;
+- struct nvmem_layout *layout;
+- void *priv;
+-};
++#include "internals.h"
+
+ #define to_nvmem_device(d) container_of(d, struct nvmem_device, dev)
+
+--- /dev/null
++++ b/drivers/nvmem/internals.h
+@@ -0,0 +1,35 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++
++#ifndef _LINUX_NVMEM_INTERNALS_H
++#define _LINUX_NVMEM_INTERNALS_H
++
++#include <linux/device.h>
++#include <linux/nvmem-consumer.h>
++#include <linux/nvmem-provider.h>
++
++struct nvmem_device {
++ struct module *owner;
++ struct device dev;
++ struct list_head node;
++ int stride;
++ int word_size;
++ int id;
++ struct kref refcnt;
++ size_t size;
++ bool read_only;
++ bool root_only;
++ int flags;
++ enum nvmem_type type;
++ struct bin_attribute eeprom;
++ struct device *base_dev;
++ struct list_head cells;
++ const struct nvmem_keepout *keepout;
++ unsigned int nkeepout;
++ nvmem_reg_read_t reg_read;
++ nvmem_reg_write_t reg_write;
++ struct gpio_desc *wp_gpio;
++ struct nvmem_layout *layout;
++ void *priv;
++};
++
++#endif /* ifndef _LINUX_NVMEM_INTERNALS_H */
diff --git a/target/linux/generic/backport-6.6/819-v6.8-0003-nvmem-Simplify-the-add_cells-hook.patch b/target/linux/generic/backport-6.6/819-v6.8-0003-nvmem-Simplify-the-add_cells-hook.patch
new file mode 100644
index 0000000000..db2d8c1b46
--- /dev/null
+++ b/target/linux/generic/backport-6.6/819-v6.8-0003-nvmem-Simplify-the-add_cells-hook.patch
@@ -0,0 +1,79 @@
+From 1b7c298a4ecbc28cc6ee94005734bff55eb83d22 Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Fri, 15 Dec 2023 11:15:30 +0000
+Subject: [PATCH] nvmem: Simplify the ->add_cells() hook
+
+The layout entry is not used and will anyway be made useless by the new
+layout bus infrastructure coming next, so drop it. While at it, clarify
+the kdoc entry.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20231215111536.316972-5-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 2 +-
+ drivers/nvmem/layouts/onie-tlv.c | 3 +--
+ drivers/nvmem/layouts/sl28vpd.c | 3 +--
+ include/linux/nvmem-provider.h | 8 +++-----
+ 4 files changed, 6 insertions(+), 10 deletions(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -817,7 +817,7 @@ static int nvmem_add_cells_from_layout(s
+ int ret;
+
+ if (layout && layout->add_cells) {
+- ret = layout->add_cells(&nvmem->dev, nvmem, layout);
++ ret = layout->add_cells(&nvmem->dev, nvmem);
+ if (ret)
+ return ret;
+ }
+--- a/drivers/nvmem/layouts/onie-tlv.c
++++ b/drivers/nvmem/layouts/onie-tlv.c
+@@ -182,8 +182,7 @@ static bool onie_tlv_crc_is_valid(struct
+ return true;
+ }
+
+-static int onie_tlv_parse_table(struct device *dev, struct nvmem_device *nvmem,
+- struct nvmem_layout *layout)
++static int onie_tlv_parse_table(struct device *dev, struct nvmem_device *nvmem)
+ {
+ struct onie_tlv_hdr hdr;
+ size_t table_len, data_len, hdr_len;
+--- a/drivers/nvmem/layouts/sl28vpd.c
++++ b/drivers/nvmem/layouts/sl28vpd.c
+@@ -80,8 +80,7 @@ static int sl28vpd_v1_check_crc(struct d
+ return 0;
+ }
+
+-static int sl28vpd_add_cells(struct device *dev, struct nvmem_device *nvmem,
+- struct nvmem_layout *layout)
++static int sl28vpd_add_cells(struct device *dev, struct nvmem_device *nvmem)
+ {
+ const struct nvmem_cell_info *pinfo;
+ struct nvmem_cell_info info = {0};
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -156,9 +156,8 @@ struct nvmem_cell_table {
+ *
+ * @name: Layout name.
+ * @of_match_table: Open firmware match table.
+- * @add_cells: Will be called if a nvmem device is found which
+- * has this layout. The function will add layout
+- * specific cells with nvmem_add_one_cell().
++ * @add_cells: Called to populate the layout using
++ * nvmem_add_one_cell().
+ * @fixup_cell_info: Will be called before a cell is added. Can be
+ * used to modify the nvmem_cell_info.
+ * @owner: Pointer to struct module.
+@@ -172,8 +171,7 @@ struct nvmem_cell_table {
+ struct nvmem_layout {
+ const char *name;
+ const struct of_device_id *of_match_table;
+- int (*add_cells)(struct device *dev, struct nvmem_device *nvmem,
+- struct nvmem_layout *layout);
++ int (*add_cells)(struct device *dev, struct nvmem_device *nvmem);
+ void (*fixup_cell_info)(struct nvmem_device *nvmem,
+ struct nvmem_layout *layout,
+ struct nvmem_cell_info *cell);
diff --git a/target/linux/generic/backport-6.6/819-v6.8-0004-nvmem-Move-and-rename-fixup_cell_info.patch b/target/linux/generic/backport-6.6/819-v6.8-0004-nvmem-Move-and-rename-fixup_cell_info.patch
new file mode 100644
index 0000000000..65aa37f834
--- /dev/null
+++ b/target/linux/generic/backport-6.6/819-v6.8-0004-nvmem-Move-and-rename-fixup_cell_info.patch
@@ -0,0 +1,169 @@
+From 1172460e716784ac7e1049a537bdca8edbf97360 Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Fri, 15 Dec 2023 11:15:31 +0000
+Subject: [PATCH] nvmem: Move and rename ->fixup_cell_info()
+
+This hook is meant to be used by any provider and instantiating a layout
+just for this is useless. Let's instead move this hook to the nvmem
+device and add it to the config structure to be easily shared by the
+providers.
+
+While at moving this hook, rename it ->fixup_dt_cell_info() to clarify
+its main intended purpose.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20231215111536.316972-6-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 6 +++---
+ drivers/nvmem/imx-ocotp.c | 11 +++--------
+ drivers/nvmem/internals.h | 2 ++
+ drivers/nvmem/mtk-efuse.c | 11 +++--------
+ include/linux/nvmem-provider.h | 9 ++++-----
+ 5 files changed, 15 insertions(+), 24 deletions(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -676,7 +676,6 @@ static int nvmem_validate_keepouts(struc
+
+ static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np)
+ {
+- struct nvmem_layout *layout = nvmem->layout;
+ struct device *dev = &nvmem->dev;
+ struct device_node *child;
+ const __be32 *addr;
+@@ -706,8 +705,8 @@ static int nvmem_add_cells_from_dt(struc
+
+ info.np = of_node_get(child);
+
+- if (layout && layout->fixup_cell_info)
+- layout->fixup_cell_info(nvmem, layout, &info);
++ if (nvmem->fixup_dt_cell_info)
++ nvmem->fixup_dt_cell_info(nvmem, &info);
+
+ ret = nvmem_add_one_cell(nvmem, &info);
+ kfree(info.name);
+@@ -896,6 +895,7 @@ struct nvmem_device *nvmem_register(cons
+
+ kref_init(&nvmem->refcnt);
+ INIT_LIST_HEAD(&nvmem->cells);
++ nvmem->fixup_dt_cell_info = config->fixup_dt_cell_info;
+
+ nvmem->owner = config->owner;
+ if (!nvmem->owner && config->dev->driver)
+--- a/drivers/nvmem/imx-ocotp.c
++++ b/drivers/nvmem/imx-ocotp.c
+@@ -584,17 +584,12 @@ static const struct of_device_id imx_oco
+ };
+ MODULE_DEVICE_TABLE(of, imx_ocotp_dt_ids);
+
+-static void imx_ocotp_fixup_cell_info(struct nvmem_device *nvmem,
+- struct nvmem_layout *layout,
+- struct nvmem_cell_info *cell)
++static void imx_ocotp_fixup_dt_cell_info(struct nvmem_device *nvmem,
++ struct nvmem_cell_info *cell)
+ {
+ cell->read_post_process = imx_ocotp_cell_pp;
+ }
+
+-static struct nvmem_layout imx_ocotp_layout = {
+- .fixup_cell_info = imx_ocotp_fixup_cell_info,
+-};
+-
+ static int imx_ocotp_probe(struct platform_device *pdev)
+ {
+ struct device *dev = &pdev->dev;
+@@ -620,7 +615,7 @@ static int imx_ocotp_probe(struct platfo
+ imx_ocotp_nvmem_config.size = 4 * priv->params->nregs;
+ imx_ocotp_nvmem_config.dev = dev;
+ imx_ocotp_nvmem_config.priv = priv;
+- imx_ocotp_nvmem_config.layout = &imx_ocotp_layout;
++ imx_ocotp_nvmem_config.fixup_dt_cell_info = &imx_ocotp_fixup_dt_cell_info;
+
+ priv->config = &imx_ocotp_nvmem_config;
+
+--- a/drivers/nvmem/internals.h
++++ b/drivers/nvmem/internals.h
+@@ -23,6 +23,8 @@ struct nvmem_device {
+ struct bin_attribute eeprom;
+ struct device *base_dev;
+ struct list_head cells;
++ void (*fixup_dt_cell_info)(struct nvmem_device *nvmem,
++ struct nvmem_cell_info *cell);
+ const struct nvmem_keepout *keepout;
+ unsigned int nkeepout;
+ nvmem_reg_read_t reg_read;
+--- a/drivers/nvmem/mtk-efuse.c
++++ b/drivers/nvmem/mtk-efuse.c
+@@ -45,9 +45,8 @@ static int mtk_efuse_gpu_speedbin_pp(voi
+ return 0;
+ }
+
+-static void mtk_efuse_fixup_cell_info(struct nvmem_device *nvmem,
+- struct nvmem_layout *layout,
+- struct nvmem_cell_info *cell)
++static void mtk_efuse_fixup_dt_cell_info(struct nvmem_device *nvmem,
++ struct nvmem_cell_info *cell)
+ {
+ size_t sz = strlen(cell->name);
+
+@@ -61,10 +60,6 @@ static void mtk_efuse_fixup_cell_info(st
+ cell->read_post_process = mtk_efuse_gpu_speedbin_pp;
+ }
+
+-static struct nvmem_layout mtk_efuse_layout = {
+- .fixup_cell_info = mtk_efuse_fixup_cell_info,
+-};
+-
+ static int mtk_efuse_probe(struct platform_device *pdev)
+ {
+ struct device *dev = &pdev->dev;
+@@ -91,7 +86,7 @@ static int mtk_efuse_probe(struct platfo
+ econfig.priv = priv;
+ econfig.dev = dev;
+ if (pdata->uses_post_processing)
+- econfig.layout = &mtk_efuse_layout;
++ econfig.fixup_dt_cell_info = &mtk_efuse_fixup_dt_cell_info;
+ nvmem = devm_nvmem_register(dev, &econfig);
+
+ return PTR_ERR_OR_ZERO(nvmem);
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -83,6 +83,8 @@ struct nvmem_cell_info {
+ * @cells: Optional array of pre-defined NVMEM cells.
+ * @ncells: Number of elements in cells.
+ * @add_legacy_fixed_of_cells: Read fixed NVMEM cells from old OF syntax.
++ * @fixup_dt_cell_info: Will be called before a cell is added. Can be
++ * used to modify the nvmem_cell_info.
+ * @keepout: Optional array of keepout ranges (sorted ascending by start).
+ * @nkeepout: Number of elements in the keepout array.
+ * @type: Type of the nvmem storage
+@@ -113,6 +115,8 @@ struct nvmem_config {
+ const struct nvmem_cell_info *cells;
+ int ncells;
+ bool add_legacy_fixed_of_cells;
++ void (*fixup_dt_cell_info)(struct nvmem_device *nvmem,
++ struct nvmem_cell_info *cell);
+ const struct nvmem_keepout *keepout;
+ unsigned int nkeepout;
+ enum nvmem_type type;
+@@ -158,8 +162,6 @@ struct nvmem_cell_table {
+ * @of_match_table: Open firmware match table.
+ * @add_cells: Called to populate the layout using
+ * nvmem_add_one_cell().
+- * @fixup_cell_info: Will be called before a cell is added. Can be
+- * used to modify the nvmem_cell_info.
+ * @owner: Pointer to struct module.
+ * @node: List node.
+ *
+@@ -172,9 +174,6 @@ struct nvmem_layout {
+ const char *name;
+ const struct of_device_id *of_match_table;
+ int (*add_cells)(struct device *dev, struct nvmem_device *nvmem);
+- void (*fixup_cell_info)(struct nvmem_device *nvmem,
+- struct nvmem_layout *layout,
+- struct nvmem_cell_info *cell);
+
+ /* private */
+ struct module *owner;
diff --git a/target/linux/generic/backport-6.6/819-v6.8-0005-nvmem-core-Rework-layouts-to-become-regular-devices.patch b/target/linux/generic/backport-6.6/819-v6.8-0005-nvmem-core-Rework-layouts-to-become-regular-devices.patch
new file mode 100644
index 0000000000..1881332340
--- /dev/null
+++ b/target/linux/generic/backport-6.6/819-v6.8-0005-nvmem-core-Rework-layouts-to-become-regular-devices.patch
@@ -0,0 +1,763 @@
+From fc29fd821d9ac2ae3d32a722fac39ce874efb883 Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Fri, 15 Dec 2023 11:15:32 +0000
+Subject: [PATCH] nvmem: core: Rework layouts to become regular devices
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Current layout support was initially written without modules support in
+mind. When the requirement for module support rose, the existing base
+was improved to adopt modularization support, but kind of a design flaw
+was introduced. With the existing implementation, when a storage device
+registers into NVMEM, the core tries to hook a layout (if any) and
+populates its cells immediately. This means, if the hardware description
+expects a layout to be hooked up, but no driver was provided for that,
+the storage medium will fail to probe and try later from
+scratch. Even if we consider that the hardware description shall be
+correct, we could still probe the storage device (especially if it
+contains the rootfs).
+
+One way to overcome this situation is to consider the layouts as
+devices, and leverage the native notifier mechanism. When a new NVMEM
+device is registered, we can populate its nvmem-layout child, if any,
+and wait for the matching to be done in order to get the cells (the
+waiting can be easily done with the NVMEM notifiers). If the layout
+driver is compiled as a module, it should automatically be loaded. This
+way, there is no strong order to enforce, any NVMEM device creation
+or NVMEM layout driver insertion will be observed as a new event which
+may lead to the creation of additional cells, without disturbing the
+probes with costly (and sometimes endless) deferrals.
+
+In order to achieve that goal we create a new bus for the nvmem-layouts
+with minimal logic to match nvmem-layout devices with nvmem-layout
+drivers. All this infrastructure code is created in the layouts.c file.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Tested-by: Rafał Miłecki <rafal@milecki.pl>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20231215111536.316972-7-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/Kconfig | 1 +
+ drivers/nvmem/Makefile | 2 +
+ drivers/nvmem/core.c | 170 ++++++++++----------------
+ drivers/nvmem/internals.h | 21 ++++
+ drivers/nvmem/layouts.c | 201 +++++++++++++++++++++++++++++++
+ drivers/nvmem/layouts/Kconfig | 8 ++
+ drivers/nvmem/layouts/onie-tlv.c | 24 +++-
+ drivers/nvmem/layouts/sl28vpd.c | 24 +++-
+ include/linux/nvmem-provider.h | 38 +++---
+ 9 files changed, 354 insertions(+), 135 deletions(-)
+ create mode 100644 drivers/nvmem/layouts.c
+
+--- a/drivers/nvmem/Kconfig
++++ b/drivers/nvmem/Kconfig
+@@ -1,6 +1,7 @@
+ # SPDX-License-Identifier: GPL-2.0-only
+ menuconfig NVMEM
+ bool "NVMEM Support"
++ imply NVMEM_LAYOUTS
+ help
+ Support for NVMEM(Non Volatile Memory) devices like EEPROM, EFUSES...
+
+--- a/drivers/nvmem/Makefile
++++ b/drivers/nvmem/Makefile
+@@ -5,6 +5,8 @@
+
+ obj-$(CONFIG_NVMEM) += nvmem_core.o
+ nvmem_core-y := core.o
++obj-$(CONFIG_NVMEM_LAYOUTS) += nvmem_layouts.o
++nvmem_layouts-y := layouts.o
+ obj-y += layouts/
+
+ # Devices
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -56,9 +56,6 @@ static LIST_HEAD(nvmem_lookup_list);
+
+ static BLOCKING_NOTIFIER_HEAD(nvmem_notifier);
+
+-static DEFINE_SPINLOCK(nvmem_layout_lock);
+-static LIST_HEAD(nvmem_layouts);
+-
+ static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
+ void *val, size_t bytes)
+ {
+@@ -741,97 +738,22 @@ static int nvmem_add_cells_from_fixed_la
+ return err;
+ }
+
+-int __nvmem_layout_register(struct nvmem_layout *layout, struct module *owner)
++int nvmem_layout_register(struct nvmem_layout *layout)
+ {
+- layout->owner = owner;
+-
+- spin_lock(&nvmem_layout_lock);
+- list_add(&layout->node, &nvmem_layouts);
+- spin_unlock(&nvmem_layout_lock);
+-
+- blocking_notifier_call_chain(&nvmem_notifier, NVMEM_LAYOUT_ADD, layout);
++ if (!layout->add_cells)
++ return -EINVAL;
+
+- return 0;
++ /* Populate the cells */
++ return layout->add_cells(&layout->nvmem->dev, layout->nvmem);
+ }
+-EXPORT_SYMBOL_GPL(__nvmem_layout_register);
++EXPORT_SYMBOL_GPL(nvmem_layout_register);
+
+ void nvmem_layout_unregister(struct nvmem_layout *layout)
+ {
+- blocking_notifier_call_chain(&nvmem_notifier, NVMEM_LAYOUT_REMOVE, layout);
+-
+- spin_lock(&nvmem_layout_lock);
+- list_del(&layout->node);
+- spin_unlock(&nvmem_layout_lock);
++ /* Keep the API even with an empty stub in case we need it later */
+ }
+ EXPORT_SYMBOL_GPL(nvmem_layout_unregister);
+
+-static struct nvmem_layout *nvmem_layout_get(struct nvmem_device *nvmem)
+-{
+- struct device_node *layout_np;
+- struct nvmem_layout *l, *layout = ERR_PTR(-EPROBE_DEFER);
+-
+- layout_np = of_nvmem_layout_get_container(nvmem);
+- if (!layout_np)
+- return NULL;
+-
+- /* Fixed layouts don't have a matching driver */
+- if (of_device_is_compatible(layout_np, "fixed-layout")) {
+- of_node_put(layout_np);
+- return NULL;
+- }
+-
+- /*
+- * In case the nvmem device was built-in while the layout was built as a
+- * module, we shall manually request the layout driver loading otherwise
+- * we'll never have any match.
+- */
+- of_request_module(layout_np);
+-
+- spin_lock(&nvmem_layout_lock);
+-
+- list_for_each_entry(l, &nvmem_layouts, node) {
+- if (of_match_node(l->of_match_table, layout_np)) {
+- if (try_module_get(l->owner))
+- layout = l;
+-
+- break;
+- }
+- }
+-
+- spin_unlock(&nvmem_layout_lock);
+- of_node_put(layout_np);
+-
+- return layout;
+-}
+-
+-static void nvmem_layout_put(struct nvmem_layout *layout)
+-{
+- if (layout)
+- module_put(layout->owner);
+-}
+-
+-static int nvmem_add_cells_from_layout(struct nvmem_device *nvmem)
+-{
+- struct nvmem_layout *layout = nvmem->layout;
+- int ret;
+-
+- if (layout && layout->add_cells) {
+- ret = layout->add_cells(&nvmem->dev, nvmem);
+- if (ret)
+- return ret;
+- }
+-
+- return 0;
+-}
+-
+-#if IS_ENABLED(CONFIG_OF)
+-struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem)
+-{
+- return of_get_child_by_name(nvmem->dev.of_node, "nvmem-layout");
+-}
+-EXPORT_SYMBOL_GPL(of_nvmem_layout_get_container);
+-#endif
+-
+ const void *nvmem_layout_get_match_data(struct nvmem_device *nvmem,
+ struct nvmem_layout *layout)
+ {
+@@ -839,7 +761,7 @@ const void *nvmem_layout_get_match_data(
+ const struct of_device_id *match;
+
+ layout_np = of_nvmem_layout_get_container(nvmem);
+- match = of_match_node(layout->of_match_table, layout_np);
++ match = of_match_node(layout->dev.driver->of_match_table, layout_np);
+
+ return match ? match->data : NULL;
+ }
+@@ -951,19 +873,6 @@ struct nvmem_device *nvmem_register(cons
+ goto err_put_device;
+ }
+
+- /*
+- * If the driver supplied a layout by config->layout, the module
+- * pointer will be NULL and nvmem_layout_put() will be a noop.
+- */
+- nvmem->layout = config->layout ?: nvmem_layout_get(nvmem);
+- if (IS_ERR(nvmem->layout)) {
+- rval = PTR_ERR(nvmem->layout);
+- nvmem->layout = NULL;
+-
+- if (rval == -EPROBE_DEFER)
+- goto err_teardown_compat;
+- }
+-
+ if (config->cells) {
+ rval = nvmem_add_cells(nvmem, config->cells, config->ncells);
+ if (rval)
+@@ -984,24 +893,24 @@ struct nvmem_device *nvmem_register(cons
+ if (rval)
+ goto err_remove_cells;
+
+- rval = nvmem_add_cells_from_layout(nvmem);
+- if (rval)
+- goto err_remove_cells;
+-
+ dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name);
+
+ rval = device_add(&nvmem->dev);
+ if (rval)
+ goto err_remove_cells;
+
++ rval = nvmem_populate_layout(nvmem);
++ if (rval)
++ goto err_remove_dev;
++
+ blocking_notifier_call_chain(&nvmem_notifier, NVMEM_ADD, nvmem);
+
+ return nvmem;
+
++err_remove_dev:
++ device_del(&nvmem->dev);
+ err_remove_cells:
+ nvmem_device_remove_all_cells(nvmem);
+- nvmem_layout_put(nvmem->layout);
+-err_teardown_compat:
+ if (config->compat)
+ nvmem_sysfs_remove_compat(nvmem, config);
+ err_put_device:
+@@ -1023,7 +932,7 @@ static void nvmem_device_release(struct
+ device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom);
+
+ nvmem_device_remove_all_cells(nvmem);
+- nvmem_layout_put(nvmem->layout);
++ nvmem_destroy_layout(nvmem);
+ device_unregister(&nvmem->dev);
+ }
+
+@@ -1325,6 +1234,12 @@ nvmem_cell_get_from_lookup(struct device
+ return cell;
+ }
+
++static void nvmem_layout_module_put(struct nvmem_device *nvmem)
++{
++ if (nvmem->layout && nvmem->layout->dev.driver)
++ module_put(nvmem->layout->dev.driver->owner);
++}
++
+ #if IS_ENABLED(CONFIG_OF)
+ static struct nvmem_cell_entry *
+ nvmem_find_cell_entry_by_node(struct nvmem_device *nvmem, struct device_node *np)
+@@ -1343,6 +1258,18 @@ nvmem_find_cell_entry_by_node(struct nvm
+ return cell;
+ }
+
++static int nvmem_layout_module_get_optional(struct nvmem_device *nvmem)
++{
++ if (!nvmem->layout)
++ return 0;
++
++ if (!nvmem->layout->dev.driver ||
++ !try_module_get(nvmem->layout->dev.driver->owner))
++ return -EPROBE_DEFER;
++
++ return 0;
++}
++
+ /**
+ * of_nvmem_cell_get() - Get a nvmem cell from given device node and cell id
+ *
+@@ -1405,16 +1332,29 @@ struct nvmem_cell *of_nvmem_cell_get(str
+ return ERR_CAST(nvmem);
+ }
+
++ ret = nvmem_layout_module_get_optional(nvmem);
++ if (ret) {
++ of_node_put(cell_np);
++ __nvmem_device_put(nvmem);
++ return ERR_PTR(ret);
++ }
++
+ cell_entry = nvmem_find_cell_entry_by_node(nvmem, cell_np);
+ of_node_put(cell_np);
+ if (!cell_entry) {
+ __nvmem_device_put(nvmem);
+- return ERR_PTR(-ENOENT);
++ nvmem_layout_module_put(nvmem);
++ if (nvmem->layout)
++ return ERR_PTR(-EPROBE_DEFER);
++ else
++ return ERR_PTR(-ENOENT);
+ }
+
+ cell = nvmem_create_cell(cell_entry, id, cell_index);
+- if (IS_ERR(cell))
++ if (IS_ERR(cell)) {
+ __nvmem_device_put(nvmem);
++ nvmem_layout_module_put(nvmem);
++ }
+
+ return cell;
+ }
+@@ -1528,6 +1468,7 @@ void nvmem_cell_put(struct nvmem_cell *c
+
+ kfree(cell);
+ __nvmem_device_put(nvmem);
++ nvmem_layout_module_put(nvmem);
+ }
+ EXPORT_SYMBOL_GPL(nvmem_cell_put);
+
+@@ -2105,11 +2046,22 @@ EXPORT_SYMBOL_GPL(nvmem_dev_name);
+
+ static int __init nvmem_init(void)
+ {
+- return bus_register(&nvmem_bus_type);
++ int ret;
++
++ ret = bus_register(&nvmem_bus_type);
++ if (ret)
++ return ret;
++
++ ret = nvmem_layout_bus_register();
++ if (ret)
++ bus_unregister(&nvmem_bus_type);
++
++ return ret;
+ }
+
+ static void __exit nvmem_exit(void)
+ {
++ nvmem_layout_bus_unregister();
+ bus_unregister(&nvmem_bus_type);
+ }
+
+--- a/drivers/nvmem/internals.h
++++ b/drivers/nvmem/internals.h
+@@ -34,4 +34,25 @@ struct nvmem_device {
+ void *priv;
+ };
+
++#if IS_ENABLED(CONFIG_OF)
++int nvmem_layout_bus_register(void);
++void nvmem_layout_bus_unregister(void);
++int nvmem_populate_layout(struct nvmem_device *nvmem);
++void nvmem_destroy_layout(struct nvmem_device *nvmem);
++#else /* CONFIG_OF */
++static inline int nvmem_layout_bus_register(void)
++{
++ return 0;
++}
++
++static inline void nvmem_layout_bus_unregister(void) {}
++
++static inline int nvmem_populate_layout(struct nvmem_device *nvmem)
++{
++ return 0;
++}
++
++static inline void nvmem_destroy_layout(struct nvmem_device *nvmem) { }
++#endif /* CONFIG_OF */
++
+ #endif /* ifndef _LINUX_NVMEM_INTERNALS_H */
+--- /dev/null
++++ b/drivers/nvmem/layouts.c
+@@ -0,0 +1,201 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * NVMEM layout bus handling
++ *
++ * Copyright (C) 2023 Bootlin
++ * Author: Miquel Raynal <miquel.raynal@bootlin.com
++ */
++
++#include <linux/device.h>
++#include <linux/dma-mapping.h>
++#include <linux/nvmem-consumer.h>
++#include <linux/nvmem-provider.h>
++#include <linux/of.h>
++#include <linux/of_device.h>
++#include <linux/of_irq.h>
++
++#include "internals.h"
++
++#define to_nvmem_layout_driver(drv) \
++ (container_of((drv), struct nvmem_layout_driver, driver))
++#define to_nvmem_layout_device(_dev) \
++ container_of((_dev), struct nvmem_layout, dev)
++
++static int nvmem_layout_bus_match(struct device *dev, struct device_driver *drv)
++{
++ return of_driver_match_device(dev, drv);
++}
++
++static int nvmem_layout_bus_probe(struct device *dev)
++{
++ struct nvmem_layout_driver *drv = to_nvmem_layout_driver(dev->driver);
++ struct nvmem_layout *layout = to_nvmem_layout_device(dev);
++
++ if (!drv->probe || !drv->remove)
++ return -EINVAL;
++
++ return drv->probe(layout);
++}
++
++static void nvmem_layout_bus_remove(struct device *dev)
++{
++ struct nvmem_layout_driver *drv = to_nvmem_layout_driver(dev->driver);
++ struct nvmem_layout *layout = to_nvmem_layout_device(dev);
++
++ return drv->remove(layout);
++}
++
++static struct bus_type nvmem_layout_bus_type = {
++ .name = "nvmem-layout",
++ .match = nvmem_layout_bus_match,
++ .probe = nvmem_layout_bus_probe,
++ .remove = nvmem_layout_bus_remove,
++};
++
++int nvmem_layout_driver_register(struct nvmem_layout_driver *drv)
++{
++ drv->driver.bus = &nvmem_layout_bus_type;
++
++ return driver_register(&drv->driver);
++}
++EXPORT_SYMBOL_GPL(nvmem_layout_driver_register);
++
++void nvmem_layout_driver_unregister(struct nvmem_layout_driver *drv)
++{
++ driver_unregister(&drv->driver);
++}
++EXPORT_SYMBOL_GPL(nvmem_layout_driver_unregister);
++
++static void nvmem_layout_release_device(struct device *dev)
++{
++ struct nvmem_layout *layout = to_nvmem_layout_device(dev);
++
++ of_node_put(layout->dev.of_node);
++ kfree(layout);
++}
++
++static int nvmem_layout_create_device(struct nvmem_device *nvmem,
++ struct device_node *np)
++{
++ struct nvmem_layout *layout;
++ struct device *dev;
++ int ret;
++
++ layout = kzalloc(sizeof(*layout), GFP_KERNEL);
++ if (!layout)
++ return -ENOMEM;
++
++ /* Create a bidirectional link */
++ layout->nvmem = nvmem;
++ nvmem->layout = layout;
++
++ /* Device model registration */
++ dev = &layout->dev;
++ device_initialize(dev);
++ dev->parent = &nvmem->dev;
++ dev->bus = &nvmem_layout_bus_type;
++ dev->release = nvmem_layout_release_device;
++ dev->coherent_dma_mask = DMA_BIT_MASK(32);
++ dev->dma_mask = &dev->coherent_dma_mask;
++ device_set_node(dev, of_fwnode_handle(of_node_get(np)));
++ of_device_make_bus_id(dev);
++ of_msi_configure(dev, dev->of_node);
++
++ ret = device_add(dev);
++ if (ret) {
++ put_device(dev);
++ return ret;
++ }
++
++ return 0;
++}
++
++static const struct of_device_id of_nvmem_layout_skip_table[] = {
++ { .compatible = "fixed-layout", },
++ {}
++};
++
++static int nvmem_layout_bus_populate(struct nvmem_device *nvmem,
++ struct device_node *layout_dn)
++{
++ int ret;
++
++ /* Make sure it has a compatible property */
++ if (!of_get_property(layout_dn, "compatible", NULL)) {
++ pr_debug("%s() - skipping %pOF, no compatible prop\n",
++ __func__, layout_dn);
++ return 0;
++ }
++
++ /* Fixed layouts are parsed manually somewhere else for now */
++ if (of_match_node(of_nvmem_layout_skip_table, layout_dn)) {
++ pr_debug("%s() - skipping %pOF node\n", __func__, layout_dn);
++ return 0;
++ }
++
++ if (of_node_check_flag(layout_dn, OF_POPULATED_BUS)) {
++ pr_debug("%s() - skipping %pOF, already populated\n",
++ __func__, layout_dn);
++
++ return 0;
++ }
++
++ /* NVMEM layout buses expect only a single device representing the layout */
++ ret = nvmem_layout_create_device(nvmem, layout_dn);
++ if (ret)
++ return ret;
++
++ of_node_set_flag(layout_dn, OF_POPULATED_BUS);
++
++ return 0;
++}
++
++struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem)
++{
++ return of_get_child_by_name(nvmem->dev.of_node, "nvmem-layout");
++}
++EXPORT_SYMBOL_GPL(of_nvmem_layout_get_container);
++
++/*
++ * Returns the number of devices populated, 0 if the operation was not relevant
++ * for this nvmem device, an error code otherwise.
++ */
++int nvmem_populate_layout(struct nvmem_device *nvmem)
++{
++ struct device_node *layout_dn;
++ int ret;
++
++ layout_dn = of_nvmem_layout_get_container(nvmem);
++ if (!layout_dn)
++ return 0;
++
++ /* Populate the layout device */
++ device_links_supplier_sync_state_pause();
++ ret = nvmem_layout_bus_populate(nvmem, layout_dn);
++ device_links_supplier_sync_state_resume();
++
++ of_node_put(layout_dn);
++ return ret;
++}
++
++void nvmem_destroy_layout(struct nvmem_device *nvmem)
++{
++ struct device *dev;
++
++ if (!nvmem->layout)
++ return;
++
++ dev = &nvmem->layout->dev;
++ of_node_clear_flag(dev->of_node, OF_POPULATED_BUS);
++ device_unregister(dev);
++}
++
++int nvmem_layout_bus_register(void)
++{
++ return bus_register(&nvmem_layout_bus_type);
++}
++
++void nvmem_layout_bus_unregister(void)
++{
++ bus_unregister(&nvmem_layout_bus_type);
++}
+--- a/drivers/nvmem/layouts/Kconfig
++++ b/drivers/nvmem/layouts/Kconfig
+@@ -1,5 +1,11 @@
+ # SPDX-License-Identifier: GPL-2.0
+
++config NVMEM_LAYOUTS
++ bool
++ depends on OF
++
++if NVMEM_LAYOUTS
++
+ menu "Layout Types"
+
+ config NVMEM_LAYOUT_SL28_VPD
+@@ -21,3 +27,5 @@ config NVMEM_LAYOUT_ONIE_TLV
+ If unsure, say N.
+
+ endmenu
++
++endif
+--- a/drivers/nvmem/layouts/onie-tlv.c
++++ b/drivers/nvmem/layouts/onie-tlv.c
+@@ -225,16 +225,32 @@ static int onie_tlv_parse_table(struct d
+ return 0;
+ }
+
++static int onie_tlv_probe(struct nvmem_layout *layout)
++{
++ layout->add_cells = onie_tlv_parse_table;
++
++ return nvmem_layout_register(layout);
++}
++
++static void onie_tlv_remove(struct nvmem_layout *layout)
++{
++ nvmem_layout_unregister(layout);
++}
++
+ static const struct of_device_id onie_tlv_of_match_table[] = {
+ { .compatible = "onie,tlv-layout", },
+ {},
+ };
+ MODULE_DEVICE_TABLE(of, onie_tlv_of_match_table);
+
+-static struct nvmem_layout onie_tlv_layout = {
+- .name = "ONIE tlv layout",
+- .of_match_table = onie_tlv_of_match_table,
+- .add_cells = onie_tlv_parse_table,
++static struct nvmem_layout_driver onie_tlv_layout = {
++ .driver = {
++ .owner = THIS_MODULE,
++ .name = "onie-tlv-layout",
++ .of_match_table = onie_tlv_of_match_table,
++ },
++ .probe = onie_tlv_probe,
++ .remove = onie_tlv_remove,
+ };
+ module_nvmem_layout_driver(onie_tlv_layout);
+
+--- a/drivers/nvmem/layouts/sl28vpd.c
++++ b/drivers/nvmem/layouts/sl28vpd.c
+@@ -134,16 +134,32 @@ static int sl28vpd_add_cells(struct devi
+ return 0;
+ }
+
++static int sl28vpd_probe(struct nvmem_layout *layout)
++{
++ layout->add_cells = sl28vpd_add_cells;
++
++ return nvmem_layout_register(layout);
++}
++
++static void sl28vpd_remove(struct nvmem_layout *layout)
++{
++ nvmem_layout_unregister(layout);
++}
++
+ static const struct of_device_id sl28vpd_of_match_table[] = {
+ { .compatible = "kontron,sl28-vpd" },
+ {},
+ };
+ MODULE_DEVICE_TABLE(of, sl28vpd_of_match_table);
+
+-static struct nvmem_layout sl28vpd_layout = {
+- .name = "sl28-vpd",
+- .of_match_table = sl28vpd_of_match_table,
+- .add_cells = sl28vpd_add_cells,
++static struct nvmem_layout_driver sl28vpd_layout = {
++ .driver = {
++ .owner = THIS_MODULE,
++ .name = "kontron-sl28vpd-layout",
++ .of_match_table = sl28vpd_of_match_table,
++ },
++ .probe = sl28vpd_probe,
++ .remove = sl28vpd_remove,
+ };
+ module_nvmem_layout_driver(sl28vpd_layout);
+
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -9,6 +9,7 @@
+ #ifndef _LINUX_NVMEM_PROVIDER_H
+ #define _LINUX_NVMEM_PROVIDER_H
+
++#include <linux/device.h>
+ #include <linux/device/driver.h>
+ #include <linux/err.h>
+ #include <linux/errno.h>
+@@ -158,12 +159,11 @@ struct nvmem_cell_table {
+ /**
+ * struct nvmem_layout - NVMEM layout definitions
+ *
+- * @name: Layout name.
+- * @of_match_table: Open firmware match table.
+- * @add_cells: Called to populate the layout using
+- * nvmem_add_one_cell().
+- * @owner: Pointer to struct module.
+- * @node: List node.
++ * @dev: Device-model layout device.
++ * @nvmem: The underlying NVMEM device
++ * @add_cells: Will be called if a nvmem device is found which
++ * has this layout. The function will add layout
++ * specific cells with nvmem_add_one_cell().
+ *
+ * A nvmem device can hold a well defined structure which can just be
+ * evaluated during runtime. For example a TLV list, or a list of "name=val"
+@@ -171,13 +171,15 @@ struct nvmem_cell_table {
+ * cells.
+ */
+ struct nvmem_layout {
+- const char *name;
+- const struct of_device_id *of_match_table;
++ struct device dev;
++ struct nvmem_device *nvmem;
+ int (*add_cells)(struct device *dev, struct nvmem_device *nvmem);
++};
+
+- /* private */
+- struct module *owner;
+- struct list_head node;
++struct nvmem_layout_driver {
++ struct device_driver driver;
++ int (*probe)(struct nvmem_layout *layout);
++ void (*remove)(struct nvmem_layout *layout);
+ };
+
+ #if IS_ENABLED(CONFIG_NVMEM)
+@@ -194,11 +196,15 @@ void nvmem_del_cell_table(struct nvmem_c
+ int nvmem_add_one_cell(struct nvmem_device *nvmem,
+ const struct nvmem_cell_info *info);
+
+-int __nvmem_layout_register(struct nvmem_layout *layout, struct module *owner);
+-#define nvmem_layout_register(layout) \
+- __nvmem_layout_register(layout, THIS_MODULE)
++int nvmem_layout_register(struct nvmem_layout *layout);
+ void nvmem_layout_unregister(struct nvmem_layout *layout);
+
++int nvmem_layout_driver_register(struct nvmem_layout_driver *drv);
++void nvmem_layout_driver_unregister(struct nvmem_layout_driver *drv);
++#define module_nvmem_layout_driver(__nvmem_layout_driver) \
++ module_driver(__nvmem_layout_driver, nvmem_layout_driver_register, \
++ nvmem_layout_driver_unregister)
++
+ const void *nvmem_layout_get_match_data(struct nvmem_device *nvmem,
+ struct nvmem_layout *layout);
+
+@@ -262,8 +268,4 @@ static inline struct device_node *of_nvm
+
+ #endif /* CONFIG_NVMEM && CONFIG_OF */
+
+-#define module_nvmem_layout_driver(__layout_driver) \
+- module_driver(__layout_driver, nvmem_layout_register, \
+- nvmem_layout_unregister)
+-
+ #endif /* ifndef _LINUX_NVMEM_PROVIDER_H */
diff --git a/target/linux/generic/backport-6.6/819-v6.8-0006-nvmem-core-Expose-cells-through-sysfs.patch b/target/linux/generic/backport-6.6/819-v6.8-0006-nvmem-core-Expose-cells-through-sysfs.patch
new file mode 100644
index 0000000000..89872bec2e
--- /dev/null
+++ b/target/linux/generic/backport-6.6/819-v6.8-0006-nvmem-core-Expose-cells-through-sysfs.patch
@@ -0,0 +1,240 @@
+From 0331c611949fffdf486652450901a4dc52bc5cca Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Fri, 15 Dec 2023 11:15:34 +0000
+Subject: [PATCH] nvmem: core: Expose cells through sysfs
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The binary content of nvmem devices is available to the user so in the
+easiest cases, finding the content of a cell is rather easy as it is
+just a matter of looking at a known and fixed offset. However, nvmem
+layouts have been recently introduced to cope with more advanced
+situations, where the offset and size of the cells is not known in
+advance or is dynamic. When using layouts, more advanced parsers are
+used by the kernel in order to give direct access to the content of each
+cell, regardless of its position/size in the underlying
+device. Unfortunately, these information are not accessible by users,
+unless by fully re-implementing the parser logic in userland.
+
+Let's expose the cells and their content through sysfs to avoid these
+situations. Of course the relevant NVMEM sysfs Kconfig option must be
+enabled for this support to be available.
+
+Not all nvmem devices expose cells. Indeed, the .bin_attrs attribute
+group member will be filled at runtime only when relevant and will
+remain empty otherwise. In this case, as the cells attribute group will
+be empty, it will not lead to any additional folder/file creation.
+
+Exposed cells are read-only. There is, in practice, everything in the
+core to support a write path, but as I don't see any need for that, I
+prefer to keep the interface simple (and probably safer). The interface
+is documented as being in the "testing" state which means we can later
+add a write attribute if though relevant.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Tested-by: Rafał Miłecki <rafal@milecki.pl>
+Tested-by: Chen-Yu Tsai <wenst@chromium.org>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20231215111536.316972-9-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 135 +++++++++++++++++++++++++++++++++++++-
+ drivers/nvmem/internals.h | 1 +
+ 2 files changed, 135 insertions(+), 1 deletion(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -300,6 +300,43 @@ static umode_t nvmem_bin_attr_is_visible
+ return nvmem_bin_attr_get_umode(nvmem);
+ }
+
++static struct nvmem_cell *nvmem_create_cell(struct nvmem_cell_entry *entry,
++ const char *id, int index);
++
++static ssize_t nvmem_cell_attr_read(struct file *filp, struct kobject *kobj,
++ struct bin_attribute *attr, char *buf,
++ loff_t pos, size_t count)
++{
++ struct nvmem_cell_entry *entry;
++ struct nvmem_cell *cell = NULL;
++ size_t cell_sz, read_len;
++ void *content;
++
++ entry = attr->private;
++ cell = nvmem_create_cell(entry, entry->name, 0);
++ if (IS_ERR(cell))
++ return PTR_ERR(cell);
++
++ if (!cell)
++ return -EINVAL;
++
++ content = nvmem_cell_read(cell, &cell_sz);
++ if (IS_ERR(content)) {
++ read_len = PTR_ERR(content);
++ goto destroy_cell;
++ }
++
++ read_len = min_t(unsigned int, cell_sz - pos, count);
++ memcpy(buf, content + pos, read_len);
++ kfree(content);
++
++destroy_cell:
++ kfree_const(cell->id);
++ kfree(cell);
++
++ return read_len;
++}
++
+ /* default read/write permissions */
+ static struct bin_attribute bin_attr_rw_nvmem = {
+ .attr = {
+@@ -321,11 +358,21 @@ static const struct attribute_group nvme
+ .is_bin_visible = nvmem_bin_attr_is_visible,
+ };
+
++/* Cell attributes will be dynamically allocated */
++static struct attribute_group nvmem_cells_group = {
++ .name = "cells",
++};
++
+ static const struct attribute_group *nvmem_dev_groups[] = {
+ &nvmem_bin_group,
+ NULL,
+ };
+
++static const struct attribute_group *nvmem_cells_groups[] = {
++ &nvmem_cells_group,
++ NULL,
++};
++
+ static struct bin_attribute bin_attr_nvmem_eeprom_compat = {
+ .attr = {
+ .name = "eeprom",
+@@ -381,6 +428,68 @@ static void nvmem_sysfs_remove_compat(st
+ device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom);
+ }
+
++static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem)
++{
++ struct bin_attribute **cells_attrs, *attrs;
++ struct nvmem_cell_entry *entry;
++ unsigned int ncells = 0, i = 0;
++ int ret = 0;
++
++ mutex_lock(&nvmem_mutex);
++
++ if (list_empty(&nvmem->cells) || nvmem->sysfs_cells_populated) {
++ nvmem_cells_group.bin_attrs = NULL;
++ goto unlock_mutex;
++ }
++
++ /* Allocate an array of attributes with a sentinel */
++ ncells = list_count_nodes(&nvmem->cells);
++ cells_attrs = devm_kcalloc(&nvmem->dev, ncells + 1,
++ sizeof(struct bin_attribute *), GFP_KERNEL);
++ if (!cells_attrs) {
++ ret = -ENOMEM;
++ goto unlock_mutex;
++ }
++
++ attrs = devm_kcalloc(&nvmem->dev, ncells, sizeof(struct bin_attribute), GFP_KERNEL);
++ if (!attrs) {
++ ret = -ENOMEM;
++ goto unlock_mutex;
++ }
++
++ /* Initialize each attribute to take the name and size of the cell */
++ list_for_each_entry(entry, &nvmem->cells, node) {
++ sysfs_bin_attr_init(&attrs[i]);
++ attrs[i].attr.name = devm_kasprintf(&nvmem->dev, GFP_KERNEL,
++ "%s@%x", entry->name,
++ entry->offset);
++ attrs[i].attr.mode = 0444;
++ attrs[i].size = entry->bytes;
++ attrs[i].read = &nvmem_cell_attr_read;
++ attrs[i].private = entry;
++ if (!attrs[i].attr.name) {
++ ret = -ENOMEM;
++ goto unlock_mutex;
++ }
++
++ cells_attrs[i] = &attrs[i];
++ i++;
++ }
++
++ nvmem_cells_group.bin_attrs = cells_attrs;
++
++ ret = devm_device_add_groups(&nvmem->dev, nvmem_cells_groups);
++ if (ret)
++ goto unlock_mutex;
++
++ nvmem->sysfs_cells_populated = true;
++
++unlock_mutex:
++ mutex_unlock(&nvmem_mutex);
++
++ return ret;
++}
++
+ #else /* CONFIG_NVMEM_SYSFS */
+
+ static int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem,
+@@ -740,11 +849,25 @@ static int nvmem_add_cells_from_fixed_la
+
+ int nvmem_layout_register(struct nvmem_layout *layout)
+ {
++ int ret;
++
+ if (!layout->add_cells)
+ return -EINVAL;
+
+ /* Populate the cells */
+- return layout->add_cells(&layout->nvmem->dev, layout->nvmem);
++ ret = layout->add_cells(&layout->nvmem->dev, layout->nvmem);
++ if (ret)
++ return ret;
++
++#ifdef CONFIG_NVMEM_SYSFS
++ ret = nvmem_populate_sysfs_cells(layout->nvmem);
++ if (ret) {
++ nvmem_device_remove_all_cells(layout->nvmem);
++ return ret;
++ }
++#endif
++
++ return 0;
+ }
+ EXPORT_SYMBOL_GPL(nvmem_layout_register);
+
+@@ -903,10 +1026,20 @@ struct nvmem_device *nvmem_register(cons
+ if (rval)
+ goto err_remove_dev;
+
++#ifdef CONFIG_NVMEM_SYSFS
++ rval = nvmem_populate_sysfs_cells(nvmem);
++ if (rval)
++ goto err_destroy_layout;
++#endif
++
+ blocking_notifier_call_chain(&nvmem_notifier, NVMEM_ADD, nvmem);
+
+ return nvmem;
+
++#ifdef CONFIG_NVMEM_SYSFS
++err_destroy_layout:
++ nvmem_destroy_layout(nvmem);
++#endif
+ err_remove_dev:
+ device_del(&nvmem->dev);
+ err_remove_cells:
+--- a/drivers/nvmem/internals.h
++++ b/drivers/nvmem/internals.h
+@@ -32,6 +32,7 @@ struct nvmem_device {
+ struct gpio_desc *wp_gpio;
+ struct nvmem_layout *layout;
+ void *priv;
++ bool sysfs_cells_populated;
+ };
+
+ #if IS_ENABLED(CONFIG_OF)
diff --git a/target/linux/generic/backport-6.6/819-v6.8-0007-nvmem-stm32-add-support-for-STM32MP25-BSEC-to-contro.patch b/target/linux/generic/backport-6.6/819-v6.8-0007-nvmem-stm32-add-support-for-STM32MP25-BSEC-to-contro.patch
new file mode 100644
index 0000000000..f686222f88
--- /dev/null
+++ b/target/linux/generic/backport-6.6/819-v6.8-0007-nvmem-stm32-add-support-for-STM32MP25-BSEC-to-contro.patch
@@ -0,0 +1,65 @@
+From f0ac5b23039610619ca4a4805528553ecb6bc815 Mon Sep 17 00:00:00 2001
+From: Patrick Delaunay <patrick.delaunay@foss.st.com>
+Date: Fri, 15 Dec 2023 11:15:36 +0000
+Subject: [PATCH] nvmem: stm32: add support for STM32MP25 BSEC to control OTP
+ data
+
+On STM32MP25, OTP area may be read/written by using BSEC (boot, security
+and OTP control). The BSEC internal peripheral is only managed by the
+secure world.
+
+The 12 Kbits of OTP (effective) are organized into the following regions:
+- lower OTP (OTP0 to OTP127) = 4096 lower OTP bits,
+ bitwise (1-bit) programmable
+- mid OTP (OTP128 to OTP255) = 4096 middle OTP bits,
+ bulk (32-bit) programmable
+- upper OTP (OTP256 to OTP383) = 4096 upper OTP bits,
+ bulk (32-bit) programmable,
+ only accessible when BSEC is in closed state.
+
+As HWKEY and ECIES key are only accessible by ROM code;
+only 368 OTP words are managed in this driver (OTP0 to OTP267).
+
+This patch adds the STM32MP25 configuration for reading and writing
+the OTP data using the OP-TEE BSEC TA services.
+
+Signed-off-by: Patrick Delaunay <patrick.delaunay@foss.st.com>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20231215111536.316972-11-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/stm32-romem.c | 16 ++++++++++++++++
+ 1 file changed, 16 insertions(+)
+
+--- a/drivers/nvmem/stm32-romem.c
++++ b/drivers/nvmem/stm32-romem.c
+@@ -269,6 +269,19 @@ static const struct stm32_romem_cfg stm3
+ .ta = true,
+ };
+
++/*
++ * STM32MP25 BSEC OTP: 3 regions of 32-bits data words
++ * lower OTP (OTP0 to OTP127), bitwise (1-bit) programmable
++ * mid OTP (OTP128 to OTP255), bulk (32-bit) programmable
++ * upper OTP (OTP256 to OTP383), bulk (32-bit) programmable
++ * but no access to HWKEY and ECIES key: limited at OTP367
++ */
++static const struct stm32_romem_cfg stm32mp25_bsec_cfg = {
++ .size = 368 * 4,
++ .lower = 127,
++ .ta = true,
++};
++
+ static const struct of_device_id stm32_romem_of_match[] __maybe_unused = {
+ { .compatible = "st,stm32f4-otp", }, {
+ .compatible = "st,stm32mp15-bsec",
+@@ -276,6 +289,9 @@ static const struct of_device_id stm32_r
+ }, {
+ .compatible = "st,stm32mp13-bsec",
+ .data = (void *)&stm32mp13_bsec_cfg,
++ }, {
++ .compatible = "st,stm32mp25-bsec",
++ .data = (void *)&stm32mp25_bsec_cfg,
+ },
+ { /* sentinel */ },
+ };
diff --git a/target/linux/generic/backport-6.6/819-v6.8-0008-nvmem-layouts-refactor-.add_cells-callback-arguments.patch b/target/linux/generic/backport-6.6/819-v6.8-0008-nvmem-layouts-refactor-.add_cells-callback-arguments.patch
new file mode 100644
index 0000000000..1bf3ba35b6
--- /dev/null
+++ b/target/linux/generic/backport-6.6/819-v6.8-0008-nvmem-layouts-refactor-.add_cells-callback-arguments.patch
@@ -0,0 +1,94 @@
+From 401df0d4f4098ecc9c5278da2f50756d62e5b37d Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Tue, 19 Dec 2023 13:01:03 +0100
+Subject: [PATCH] nvmem: layouts: refactor .add_cells() callback arguments
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Simply pass whole "struct nvmem_layout" instead of single variables.
+There is nothing in "struct nvmem_layout" that we have to hide from
+layout drivers. They also access it during .probe() and .remove().
+
+Thanks to this change:
+
+1. API gets more consistent
+ All layouts drivers callbacks get the same argument
+
+2. Layouts get correct device
+ Before this change NVMEM core code was passing NVMEM device instead
+ of layout device. That resulted in:
+ * Confusing prints
+ * Calling devm_*() helpers on wrong device
+ * Helpers like of_device_get_match_data() dereferencing NULLs
+
+3. It gets possible to get match data
+ First of all nvmem_layout_get_match_data() requires passing "struct
+ nvmem_layout" which .add_cells() callback didn't have before this. It
+ doesn't matter much as it's rather useless now anyway (and will be
+ dropped).
+ What's more important however is that of_device_get_match_data() can
+ be used now thanks to owning a proper device pointer.
+
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Reviewed-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Reviewed-by: Michael Walle <michael@walle.cc>
+Link: https://lore.kernel.org/r/20231219120104.3422-1-zajec5@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 2 +-
+ drivers/nvmem/layouts/onie-tlv.c | 4 +++-
+ drivers/nvmem/layouts/sl28vpd.c | 4 +++-
+ include/linux/nvmem-provider.h | 2 +-
+ 4 files changed, 8 insertions(+), 4 deletions(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -855,7 +855,7 @@ int nvmem_layout_register(struct nvmem_l
+ return -EINVAL;
+
+ /* Populate the cells */
+- ret = layout->add_cells(&layout->nvmem->dev, layout->nvmem);
++ ret = layout->add_cells(layout);
+ if (ret)
+ return ret;
+
+--- a/drivers/nvmem/layouts/onie-tlv.c
++++ b/drivers/nvmem/layouts/onie-tlv.c
+@@ -182,8 +182,10 @@ static bool onie_tlv_crc_is_valid(struct
+ return true;
+ }
+
+-static int onie_tlv_parse_table(struct device *dev, struct nvmem_device *nvmem)
++static int onie_tlv_parse_table(struct nvmem_layout *layout)
+ {
++ struct nvmem_device *nvmem = layout->nvmem;
++ struct device *dev = &layout->dev;
+ struct onie_tlv_hdr hdr;
+ size_t table_len, data_len, hdr_len;
+ u8 *table, *data;
+--- a/drivers/nvmem/layouts/sl28vpd.c
++++ b/drivers/nvmem/layouts/sl28vpd.c
+@@ -80,8 +80,10 @@ static int sl28vpd_v1_check_crc(struct d
+ return 0;
+ }
+
+-static int sl28vpd_add_cells(struct device *dev, struct nvmem_device *nvmem)
++static int sl28vpd_add_cells(struct nvmem_layout *layout)
+ {
++ struct nvmem_device *nvmem = layout->nvmem;
++ struct device *dev = &layout->dev;
+ const struct nvmem_cell_info *pinfo;
+ struct nvmem_cell_info info = {0};
+ struct device_node *layout_np;
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -173,7 +173,7 @@ struct nvmem_cell_table {
+ struct nvmem_layout {
+ struct device dev;
+ struct nvmem_device *nvmem;
+- int (*add_cells)(struct device *dev, struct nvmem_device *nvmem);
++ int (*add_cells)(struct nvmem_layout *layout);
+ };
+
+ struct nvmem_layout_driver {
diff --git a/target/linux/generic/backport-6.6/819-v6.8-0009-nvmem-drop-nvmem_layout_get_match_data.patch b/target/linux/generic/backport-6.6/819-v6.8-0009-nvmem-drop-nvmem_layout_get_match_data.patch
new file mode 100644
index 0000000000..514b5f2de5
--- /dev/null
+++ b/target/linux/generic/backport-6.6/819-v6.8-0009-nvmem-drop-nvmem_layout_get_match_data.patch
@@ -0,0 +1,72 @@
+From 43f60e3fb62edc7bd8891de8779fb422f4ae23ae Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Tue, 19 Dec 2023 13:01:04 +0100
+Subject: [PATCH] nvmem: drop nvmem_layout_get_match_data()
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Thanks for layouts refactoring we now have "struct device" associated
+with layout. Also its OF pointer points directly to the "nvmem-layout"
+DT node.
+
+All it takes to get match data is a generic of_device_get_match_data().
+
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Reviewed-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Reviewed-by: Michael Walle <michael@walle.cc>
+Link: https://lore.kernel.org/r/20231219120104.3422-2-zajec5@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 13 -------------
+ include/linux/nvmem-provider.h | 10 ----------
+ 2 files changed, 23 deletions(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -877,19 +877,6 @@ void nvmem_layout_unregister(struct nvme
+ }
+ EXPORT_SYMBOL_GPL(nvmem_layout_unregister);
+
+-const void *nvmem_layout_get_match_data(struct nvmem_device *nvmem,
+- struct nvmem_layout *layout)
+-{
+- struct device_node __maybe_unused *layout_np;
+- const struct of_device_id *match;
+-
+- layout_np = of_nvmem_layout_get_container(nvmem);
+- match = of_match_node(layout->dev.driver->of_match_table, layout_np);
+-
+- return match ? match->data : NULL;
+-}
+-EXPORT_SYMBOL_GPL(nvmem_layout_get_match_data);
+-
+ /**
+ * nvmem_register() - Register a nvmem device for given nvmem_config.
+ * Also creates a binary entry in /sys/bus/nvmem/devices/dev-name/nvmem
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -205,9 +205,6 @@ void nvmem_layout_driver_unregister(stru
+ module_driver(__nvmem_layout_driver, nvmem_layout_driver_register, \
+ nvmem_layout_driver_unregister)
+
+-const void *nvmem_layout_get_match_data(struct nvmem_device *nvmem,
+- struct nvmem_layout *layout);
+-
+ #else
+
+ static inline struct nvmem_device *nvmem_register(const struct nvmem_config *c)
+@@ -238,13 +235,6 @@ static inline int nvmem_layout_register(
+
+ static inline void nvmem_layout_unregister(struct nvmem_layout *layout) {}
+
+-static inline const void *
+-nvmem_layout_get_match_data(struct nvmem_device *nvmem,
+- struct nvmem_layout *layout)
+-{
+- return NULL;
+-}
+-
+ #endif /* CONFIG_NVMEM */
+
+ #if IS_ENABLED(CONFIG_NVMEM) && IS_ENABLED(CONFIG_OF)
diff --git a/target/linux/generic/backport-6.6/819-v6.8-0010-nvmem-core-add-nvmem_dev_size-helper.patch b/target/linux/generic/backport-6.6/819-v6.8-0010-nvmem-core-add-nvmem_dev_size-helper.patch
new file mode 100644
index 0000000000..aa0bbaa0c5
--- /dev/null
+++ b/target/linux/generic/backport-6.6/819-v6.8-0010-nvmem-core-add-nvmem_dev_size-helper.patch
@@ -0,0 +1,53 @@
+From 33cf42e68efc8ff529a7eee08a4f0ba8c8d0a207 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Thu, 21 Dec 2023 18:34:17 +0100
+Subject: [PATCH] nvmem: core: add nvmem_dev_size() helper
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This is required by layouts that need to read whole NVMEM content. It's
+especially useful for NVMEM devices without hardcoded layout (like
+U-Boot environment data block).
+
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Reviewed-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Link: https://lore.kernel.org/r/20231221173421.13737-2-zajec5@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 13 +++++++++++++
+ include/linux/nvmem-consumer.h | 1 +
+ 2 files changed, 14 insertions(+)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -2164,6 +2164,19 @@ const char *nvmem_dev_name(struct nvmem_
+ }
+ EXPORT_SYMBOL_GPL(nvmem_dev_name);
+
++/**
++ * nvmem_dev_size() - Get the size of a given nvmem device.
++ *
++ * @nvmem: nvmem device.
++ *
++ * Return: size of the nvmem device.
++ */
++size_t nvmem_dev_size(struct nvmem_device *nvmem)
++{
++ return nvmem->size;
++}
++EXPORT_SYMBOL_GPL(nvmem_dev_size);
++
+ static int __init nvmem_init(void)
+ {
+ int ret;
+--- a/include/linux/nvmem-consumer.h
++++ b/include/linux/nvmem-consumer.h
+@@ -81,6 +81,7 @@ int nvmem_device_cell_write(struct nvmem
+ struct nvmem_cell_info *info, void *buf);
+
+ const char *nvmem_dev_name(struct nvmem_device *nvmem);
++size_t nvmem_dev_size(struct nvmem_device *nvmem);
+
+ void nvmem_add_cell_lookups(struct nvmem_cell_lookup *entries,
+ size_t nentries);
diff --git a/target/linux/generic/backport-6.6/819-v6.8-0011-nvmem-u-boot-env-use-nvmem_add_one_cell-nvmem-subsys.patch b/target/linux/generic/backport-6.6/819-v6.8-0011-nvmem-u-boot-env-use-nvmem_add_one_cell-nvmem-subsys.patch
new file mode 100644
index 0000000000..fc826f3f7e
--- /dev/null
+++ b/target/linux/generic/backport-6.6/819-v6.8-0011-nvmem-u-boot-env-use-nvmem_add_one_cell-nvmem-subsys.patch
@@ -0,0 +1,126 @@
+From 7c8979b42b1a9c5604f431ba804928e55919263c Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Thu, 21 Dec 2023 18:34:18 +0100
+Subject: [PATCH] nvmem: u-boot-env: use nvmem_add_one_cell() nvmem subsystem
+ helper
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Simplify adding NVMEM cells.
+
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Reviewed-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Link: https://lore.kernel.org/r/20231221173421.13737-3-zajec5@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/u-boot-env.c | 55 +++++++++++++++-----------------------
+ 1 file changed, 21 insertions(+), 34 deletions(-)
+
+--- a/drivers/nvmem/u-boot-env.c
++++ b/drivers/nvmem/u-boot-env.c
+@@ -23,13 +23,10 @@ enum u_boot_env_format {
+
+ struct u_boot_env {
+ struct device *dev;
++ struct nvmem_device *nvmem;
+ enum u_boot_env_format format;
+
+ struct mtd_info *mtd;
+-
+- /* Cells */
+- struct nvmem_cell_info *cells;
+- int ncells;
+ };
+
+ struct u_boot_env_image_single {
+@@ -94,43 +91,36 @@ static int u_boot_env_read_post_process_
+ static int u_boot_env_add_cells(struct u_boot_env *priv, uint8_t *buf,
+ size_t data_offset, size_t data_len)
+ {
++ struct nvmem_device *nvmem = priv->nvmem;
+ struct device *dev = priv->dev;
+ char *data = buf + data_offset;
+ char *var, *value, *eq;
+- int idx;
+-
+- priv->ncells = 0;
+- for (var = data; var < data + data_len && *var; var += strlen(var) + 1)
+- priv->ncells++;
+-
+- priv->cells = devm_kcalloc(dev, priv->ncells, sizeof(*priv->cells), GFP_KERNEL);
+- if (!priv->cells)
+- return -ENOMEM;
+
+- for (var = data, idx = 0;
++ for (var = data;
+ var < data + data_len && *var;
+- var = value + strlen(value) + 1, idx++) {
++ var = value + strlen(value) + 1) {
++ struct nvmem_cell_info info = {};
++
+ eq = strchr(var, '=');
+ if (!eq)
+ break;
+ *eq = '\0';
+ value = eq + 1;
+
+- priv->cells[idx].name = devm_kstrdup(dev, var, GFP_KERNEL);
+- if (!priv->cells[idx].name)
++ info.name = devm_kstrdup(dev, var, GFP_KERNEL);
++ if (!info.name)
+ return -ENOMEM;
+- priv->cells[idx].offset = data_offset + value - data;
+- priv->cells[idx].bytes = strlen(value);
+- priv->cells[idx].np = of_get_child_by_name(dev->of_node, priv->cells[idx].name);
++ info.offset = data_offset + value - data;
++ info.bytes = strlen(value);
++ info.np = of_get_child_by_name(dev->of_node, info.name);
+ if (!strcmp(var, "ethaddr")) {
+- priv->cells[idx].raw_len = strlen(value);
+- priv->cells[idx].bytes = ETH_ALEN;
+- priv->cells[idx].read_post_process = u_boot_env_read_post_process_ethaddr;
++ info.raw_len = strlen(value);
++ info.bytes = ETH_ALEN;
++ info.read_post_process = u_boot_env_read_post_process_ethaddr;
+ }
+- }
+
+- if (WARN_ON(idx != priv->ncells))
+- priv->ncells = idx;
++ nvmem_add_one_cell(nvmem, &info);
++ }
+
+ return 0;
+ }
+@@ -209,7 +199,6 @@ static int u_boot_env_probe(struct platf
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct u_boot_env *priv;
+- int err;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+@@ -224,17 +213,15 @@ static int u_boot_env_probe(struct platf
+ return PTR_ERR(priv->mtd);
+ }
+
+- err = u_boot_env_parse(priv);
+- if (err)
+- return err;
+-
+ config.dev = dev;
+- config.cells = priv->cells;
+- config.ncells = priv->ncells;
+ config.priv = priv;
+ config.size = priv->mtd->size;
+
+- return PTR_ERR_OR_ZERO(devm_nvmem_register(dev, &config));
++ priv->nvmem = devm_nvmem_register(dev, &config);
++ if (IS_ERR(priv->nvmem))
++ return PTR_ERR(priv->nvmem);
++
++ return u_boot_env_parse(priv);
+ }
+
+ static const struct of_device_id u_boot_env_of_match_table[] = {
diff --git a/target/linux/generic/backport-6.6/819-v6.8-0012-nvmem-u-boot-env-use-nvmem-device-helpers.patch b/target/linux/generic/backport-6.6/819-v6.8-0012-nvmem-u-boot-env-use-nvmem-device-helpers.patch
new file mode 100644
index 0000000000..70abc7cf14
--- /dev/null
+++ b/target/linux/generic/backport-6.6/819-v6.8-0012-nvmem-u-boot-env-use-nvmem-device-helpers.patch
@@ -0,0 +1,81 @@
+From a832556d23c5a11115f300011a5874d6107a0d62 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Thu, 21 Dec 2023 18:34:19 +0100
+Subject: [PATCH] nvmem: u-boot-env: use nvmem device helpers
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Use nvmem_dev_size() and nvmem_device_read() to make this driver less
+mtd dependent.
+
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Reviewed-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Link: https://lore.kernel.org/r/20231221173421.13737-4-zajec5@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/u-boot-env.c | 23 +++++++++++++++--------
+ 1 file changed, 15 insertions(+), 8 deletions(-)
+
+--- a/drivers/nvmem/u-boot-env.c
++++ b/drivers/nvmem/u-boot-env.c
+@@ -127,27 +127,34 @@ static int u_boot_env_add_cells(struct u
+
+ static int u_boot_env_parse(struct u_boot_env *priv)
+ {
++ struct nvmem_device *nvmem = priv->nvmem;
+ struct device *dev = priv->dev;
+ size_t crc32_data_offset;
+ size_t crc32_data_len;
+ size_t crc32_offset;
+ size_t data_offset;
+ size_t data_len;
++ size_t dev_size;
+ uint32_t crc32;
+ uint32_t calc;
+- size_t bytes;
+ uint8_t *buf;
++ int bytes;
+ int err;
+
+- buf = kcalloc(1, priv->mtd->size, GFP_KERNEL);
++ dev_size = nvmem_dev_size(nvmem);
++
++ buf = kcalloc(1, dev_size, GFP_KERNEL);
+ if (!buf) {
+ err = -ENOMEM;
+ goto err_out;
+ }
+
+- err = mtd_read(priv->mtd, 0, priv->mtd->size, &bytes, buf);
+- if ((err && !mtd_is_bitflip(err)) || bytes != priv->mtd->size) {
+- dev_err(dev, "Failed to read from mtd: %d\n", err);
++ bytes = nvmem_device_read(nvmem, 0, dev_size, buf);
++ if (bytes < 0) {
++ err = bytes;
++ goto err_kfree;
++ } else if (bytes != dev_size) {
++ err = -EIO;
+ goto err_kfree;
+ }
+
+@@ -169,8 +176,8 @@ static int u_boot_env_parse(struct u_boo
+ break;
+ }
+ crc32 = le32_to_cpu(*(__le32 *)(buf + crc32_offset));
+- crc32_data_len = priv->mtd->size - crc32_data_offset;
+- data_len = priv->mtd->size - data_offset;
++ crc32_data_len = dev_size - crc32_data_offset;
++ data_len = dev_size - data_offset;
+
+ calc = crc32(~0, buf + crc32_data_offset, crc32_data_len) ^ ~0L;
+ if (calc != crc32) {
+@@ -179,7 +186,7 @@ static int u_boot_env_parse(struct u_boo
+ goto err_kfree;
+ }
+
+- buf[priv->mtd->size - 1] = '\0';
++ buf[dev_size - 1] = '\0';
+ err = u_boot_env_add_cells(priv, buf, data_offset, data_len);
+ if (err)
+ dev_err(dev, "Failed to add cells: %d\n", err);
diff --git a/target/linux/generic/backport-6.6/819-v6.8-0013-nvmem-u-boot-env-improve-coding-style.patch b/target/linux/generic/backport-6.6/819-v6.8-0013-nvmem-u-boot-env-improve-coding-style.patch
new file mode 100644
index 0000000000..273cfed874
--- /dev/null
+++ b/target/linux/generic/backport-6.6/819-v6.8-0013-nvmem-u-boot-env-improve-coding-style.patch
@@ -0,0 +1,62 @@
+From 6bafe07c930676d6430be471310958070816a595 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Thu, 21 Dec 2023 18:34:20 +0100
+Subject: [PATCH] nvmem: u-boot-env: improve coding style
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+1. Prefer kzalloc() over kcalloc()
+ See memory-allocation.rst which says: "to be on the safe side it's
+ best to use routines that set memory to zero, like kzalloc()"
+2. Drop dev_err() for u_boot_env_add_cells() fail
+ It can fail only on -ENOMEM. We don't want to print error then.
+3. Add extra "crc32_addr" variable
+ It makes code reading header's crc32 easier to understand / review.
+
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+Reviewed-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Link: https://lore.kernel.org/r/20231221173421.13737-5-zajec5@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/u-boot-env.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+--- a/drivers/nvmem/u-boot-env.c
++++ b/drivers/nvmem/u-boot-env.c
+@@ -132,6 +132,7 @@ static int u_boot_env_parse(struct u_boo
+ size_t crc32_data_offset;
+ size_t crc32_data_len;
+ size_t crc32_offset;
++ __le32 *crc32_addr;
+ size_t data_offset;
+ size_t data_len;
+ size_t dev_size;
+@@ -143,7 +144,7 @@ static int u_boot_env_parse(struct u_boo
+
+ dev_size = nvmem_dev_size(nvmem);
+
+- buf = kcalloc(1, dev_size, GFP_KERNEL);
++ buf = kzalloc(dev_size, GFP_KERNEL);
+ if (!buf) {
+ err = -ENOMEM;
+ goto err_out;
+@@ -175,7 +176,8 @@ static int u_boot_env_parse(struct u_boo
+ data_offset = offsetof(struct u_boot_env_image_broadcom, data);
+ break;
+ }
+- crc32 = le32_to_cpu(*(__le32 *)(buf + crc32_offset));
++ crc32_addr = (__le32 *)(buf + crc32_offset);
++ crc32 = le32_to_cpu(*crc32_addr);
+ crc32_data_len = dev_size - crc32_data_offset;
+ data_len = dev_size - data_offset;
+
+@@ -188,8 +190,6 @@ static int u_boot_env_parse(struct u_boo
+
+ buf[dev_size - 1] = '\0';
+ err = u_boot_env_add_cells(priv, buf, data_offset, data_len);
+- if (err)
+- dev_err(dev, "Failed to add cells: %d\n", err);
+
+ err_kfree:
+ kfree(buf);
diff --git a/target/linux/generic/backport-6.6/820-v6.4-net-phy-fix-circular-LEDS_CLASS-dependencies.patch b/target/linux/generic/backport-6.6/820-v6.4-net-phy-fix-circular-LEDS_CLASS-dependencies.patch
new file mode 100644
index 0000000000..3b68403690
--- /dev/null
+++ b/target/linux/generic/backport-6.6/820-v6.4-net-phy-fix-circular-LEDS_CLASS-dependencies.patch
@@ -0,0 +1,65 @@
+From 4bb7aac70b5d8a4bddf4ee0791b834f9f56883d2 Mon Sep 17 00:00:00 2001
+From: Arnd Bergmann <arnd@arndb.de>
+Date: Thu, 20 Apr 2023 10:45:51 +0200
+Subject: [PATCH] net: phy: fix circular LEDS_CLASS dependencies
+
+The CONFIG_PHYLIB symbol is selected by a number of device drivers that
+need PHY support, but it now has a dependency on CONFIG_LEDS_CLASS,
+which may not be enabled, causing build failures.
+
+Avoid the risk of missing and circular dependencies by guarding the
+phylib LED support itself in another Kconfig symbol that can only be
+enabled if the dependency is met.
+
+This could be made a hidden symbol and always enabled when both CONFIG_OF
+and CONFIG_LEDS_CLASS are reachable from the phylib, but there may be an
+advantage in having users see this option when they have a misconfigured
+kernel without built-in LED support.
+
+Fixes: 01e5b728e9e4 ("net: phy: Add a binding for PHY LEDs")
+Signed-off-by: Arnd Bergmann <arnd@arndb.de>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/20230420084624.3005701-1-arnd@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/Kconfig | 9 ++++++++-
+ drivers/net/phy/phy_device.c | 3 ++-
+ 2 files changed, 10 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -18,7 +18,6 @@ menuconfig PHYLIB
+ depends on NETDEVICES
+ select MDIO_DEVICE
+ select MDIO_DEVRES
+- depends on LEDS_CLASS || LEDS_CLASS=n
+ help
+ Ethernet controllers are usually attached to PHY
+ devices. This option provides infrastructure for
+@@ -45,6 +44,14 @@ config LED_TRIGGER_PHY
+ <Speed in megabits>Mbps OR <Speed in gigabits>Gbps OR link
+ for any speed known to the PHY.
+
++config PHYLIB_LEDS
++ bool "Support probing LEDs from device tree"
++ depends on LEDS_CLASS=y || LEDS_CLASS=PHYLIB
++ depends on OF
++ default y
++ help
++ When LED class support is enabled, phylib can automatically
++ probe LED setting from device tree.
+
+ config FIXED_PHY
+ tristate "MDIO Bus/PHY emulation with fixed speed/link PHYs"
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -3314,7 +3314,8 @@ static int phy_probe(struct device *dev)
+ /* Get the LEDs from the device tree, and instantiate standard
+ * LEDs for them.
+ */
+- err = of_phy_leds(phydev);
++ if (IS_ENABLED(CONFIG_PHYLIB_LEDS))
++ err = of_phy_leds(phydev);
+
+ out:
+ /* Re-assert the reset signal on error */
diff --git a/target/linux/generic/backport-6.6/821-v6.4-net-phy-Fix-reading-LED-reg-property.patch b/target/linux/generic/backport-6.6/821-v6.4-net-phy-Fix-reading-LED-reg-property.patch
new file mode 100644
index 0000000000..622bb9e94a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/821-v6.4-net-phy-Fix-reading-LED-reg-property.patch
@@ -0,0 +1,43 @@
+From aed8fdad2152d946add50bec00a6b07c457bdcdf Mon Sep 17 00:00:00 2001
+From: Alexander Stein <alexander.stein@ew.tq-group.com>
+Date: Mon, 24 Apr 2023 16:16:48 +0200
+Subject: [PATCH] net: phy: Fix reading LED reg property
+
+'reg' is always encoded in 32 bits, thus it has to be read using the
+function with the corresponding bit width.
+
+Fixes: 01e5b728e9e4 ("net: phy: Add a binding for PHY LEDs")
+Signed-off-by: Alexander Stein <alexander.stein@ew.tq-group.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Link: https://lore.kernel.org/r/20230424141648.317944-1-alexander.stein@ew.tq-group.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phy_device.c | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -3075,6 +3075,7 @@ static int of_phy_led(struct phy_device
+ struct led_init_data init_data = {};
+ struct led_classdev *cdev;
+ struct phy_led *phyled;
++ u32 index;
+ int err;
+
+ phyled = devm_kzalloc(dev, sizeof(*phyled), GFP_KERNEL);
+@@ -3084,10 +3085,13 @@ static int of_phy_led(struct phy_device
+ cdev = &phyled->led_cdev;
+ phyled->phydev = phydev;
+
+- err = of_property_read_u8(led, "reg", &phyled->index);
++ err = of_property_read_u32(led, "reg", &index);
+ if (err)
+ return err;
++ if (index > U8_MAX)
++ return -EINVAL;
+
++ phyled->index = index;
+ if (phydev->drv->led_brightness_set)
+ cdev->brightness_set_blocking = phy_led_set_brightness;
+ if (phydev->drv->led_blink_set)
diff --git a/target/linux/generic/backport-6.6/822-v6.4-net-phy-Manual-remove-LEDs-to-ensure-correct-orderin.patch b/target/linux/generic/backport-6.6/822-v6.4-net-phy-Manual-remove-LEDs-to-ensure-correct-orderin.patch
new file mode 100644
index 0000000000..80197e963b
--- /dev/null
+++ b/target/linux/generic/backport-6.6/822-v6.4-net-phy-Manual-remove-LEDs-to-ensure-correct-orderin.patch
@@ -0,0 +1,67 @@
+From c938ab4da0eb1620ae3243b0b24c572ddfc318fc Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Sat, 17 Jun 2023 17:55:00 +0200
+Subject: [PATCH] net: phy: Manual remove LEDs to ensure correct ordering
+
+If the core is left to remove the LEDs via devm_, it is performed too
+late, after the PHY driver is removed from the PHY. This results in
+dereferencing a NULL pointer when the LED core tries to turn the LED
+off before destroying the LED.
+
+Manually unregister the LEDs at a safe point in phy_remove.
+
+Cc: stable@vger.kernel.org
+Reported-by: Florian Fainelli <f.fainelli@gmail.com>
+Suggested-by: Florian Fainelli <f.fainelli@gmail.com>
+Fixes: 01e5b728e9e4 ("net: phy: Add a binding for PHY LEDs")
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/phy_device.c | 15 ++++++++++++++-
+ 1 file changed, 14 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -3068,6 +3068,15 @@ static int phy_led_blink_set(struct led_
+ return err;
+ }
+
++static void phy_leds_unregister(struct phy_device *phydev)
++{
++ struct phy_led *phyled;
++
++ list_for_each_entry(phyled, &phydev->leds, list) {
++ led_classdev_unregister(&phyled->led_cdev);
++ }
++}
++
+ static int of_phy_led(struct phy_device *phydev,
+ struct device_node *led)
+ {
+@@ -3101,7 +3110,7 @@ static int of_phy_led(struct phy_device
+ init_data.fwnode = of_fwnode_handle(led);
+ init_data.devname_mandatory = true;
+
+- err = devm_led_classdev_register_ext(dev, cdev, &init_data);
++ err = led_classdev_register_ext(dev, cdev, &init_data);
+ if (err)
+ return err;
+
+@@ -3130,6 +3139,7 @@ static int of_phy_leds(struct phy_device
+ err = of_phy_led(phydev, led);
+ if (err) {
+ of_node_put(led);
++ phy_leds_unregister(phydev);
+ return err;
+ }
+ }
+@@ -3335,6 +3345,9 @@ static int phy_remove(struct device *dev
+
+ cancel_delayed_work_sync(&phydev->state_queue);
+
++ if (IS_ENABLED(CONFIG_PHYLIB_LEDS))
++ phy_leds_unregister(phydev);
++
+ phydev->state = PHY_DOWN;
+
+ sfp_bus_del_upstream(phydev->sfp_bus);
diff --git a/target/linux/generic/backport-6.6/824-v6.5-leds-trigger-netdev-Remove-NULL-check-before-dev_-pu.patch b/target/linux/generic/backport-6.6/824-v6.5-leds-trigger-netdev-Remove-NULL-check-before-dev_-pu.patch
new file mode 100644
index 0000000000..609115b04a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/824-v6.5-leds-trigger-netdev-Remove-NULL-check-before-dev_-pu.patch
@@ -0,0 +1,44 @@
+From af7320ecae0ce646fd2c4a88341a3fbc243553da Mon Sep 17 00:00:00 2001
+From: Yang Li <yang.lee@linux.alibaba.com>
+Date: Thu, 11 May 2023 15:08:20 +0800
+Subject: [PATCH] leds: trigger: netdev: Remove NULL check before dev_{put,
+ hold}
+
+The call netdev_{put, hold} of dev_{put, hold} will check NULL,
+so there is no need to check before using dev_{put, hold},
+remove it to silence the warnings:
+
+./drivers/leds/trigger/ledtrig-netdev.c:291:3-10: WARNING: NULL check before dev_{put, hold} functions is not needed.
+./drivers/leds/trigger/ledtrig-netdev.c:401:2-9: WARNING: NULL check before dev_{put, hold} functions is not needed.
+
+Reported-by: Abaci Robot <abaci@linux.alibaba.com>
+Closes: https://bugzilla.openanolis.cn/show_bug.cgi?id=4929
+Signed-off-by: Yang Li <yang.lee@linux.alibaba.com>
+Link: https://lore.kernel.org/r/20230511070820.52731-1-yang.lee@linux.alibaba.com
+Signed-off-by: Lee Jones <lee@kernel.org>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 6 ++----
+ 1 file changed, 2 insertions(+), 4 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -462,8 +462,7 @@ static int netdev_trig_notify(struct not
+ get_device_state(trigger_data);
+ fallthrough;
+ case NETDEV_REGISTER:
+- if (trigger_data->net_dev)
+- dev_put(trigger_data->net_dev);
++ dev_put(trigger_data->net_dev);
+ dev_hold(dev);
+ trigger_data->net_dev = dev;
+ break;
+@@ -594,8 +593,7 @@ static void netdev_trig_deactivate(struc
+
+ cancel_delayed_work_sync(&trigger_data->work);
+
+- if (trigger_data->net_dev)
+- dev_put(trigger_data->net_dev);
++ dev_put(trigger_data->net_dev);
+
+ kfree(trigger_data);
+ }
diff --git a/target/linux/generic/backport-6.6/825-v6.5-leds-trigger-netdev-uninitialized-variable-in-netdev.patch b/target/linux/generic/backport-6.6/825-v6.5-leds-trigger-netdev-uninitialized-variable-in-netdev.patch
new file mode 100644
index 0000000000..2f7a6ced90
--- /dev/null
+++ b/target/linux/generic/backport-6.6/825-v6.5-leds-trigger-netdev-uninitialized-variable-in-netdev.patch
@@ -0,0 +1,31 @@
+From 97c5209b3d374a25ebdb4c2ea9e9c1b121768da0 Mon Sep 17 00:00:00 2001
+From: Dan Carpenter <dan.carpenter@linaro.org>
+Date: Wed, 14 Jun 2023 10:03:59 +0300
+Subject: [PATCH] leds: trigger: netdev: uninitialized variable in
+ netdev_trig_activate()
+
+The qca8k_cled_hw_control_get() function which implements ->hw_control_get
+sets the appropriate bits but does not clear them. This leads to an
+uninitialized variable bug. Fix this by setting mode to zero at the
+start.
+
+Fixes: e0256648c831 ("net: dsa: qca8k: implement hw_control ops")
+Signed-off-by: Dan Carpenter <dan.carpenter@linaro.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Acked-by: Lee Jones <lee@kernel.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -538,7 +538,7 @@ static void netdev_trig_work(struct work
+ static int netdev_trig_activate(struct led_classdev *led_cdev)
+ {
+ struct led_netdev_data *trigger_data;
+- unsigned long mode;
++ unsigned long mode = 0;
+ struct device *dev;
+ int rc;
+
diff --git a/target/linux/generic/backport-6.6/826-v6.6-01-led-trig-netdev-Fix-requesting-offload-device.patch b/target/linux/generic/backport-6.6/826-v6.6-01-led-trig-netdev-Fix-requesting-offload-device.patch
new file mode 100644
index 0000000000..dedf84c64b
--- /dev/null
+++ b/target/linux/generic/backport-6.6/826-v6.6-01-led-trig-netdev-Fix-requesting-offload-device.patch
@@ -0,0 +1,57 @@
+From 7df1f14c04cbb1950e79c19793420f87227c3e80 Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Tue, 8 Aug 2023 23:04:33 +0200
+Subject: [PATCH 1/4] led: trig: netdev: Fix requesting offload device
+
+When the netdev trigger is activates, it tries to determine what
+device the LED blinks for, and what the current blink mode is.
+
+The documentation for hw_control_get() says:
+
+ * Return 0 on success, a negative error number on failing parsing the
+ * initial mode. Error from this function is NOT FATAL as the device
+ * may be in a not supported initial state by the attached LED trigger.
+ */
+
+For the Marvell PHY and the Armada 370-rd board, the initial LED blink
+mode is not supported by the trigger, so it returns an error. This
+resulted in not getting the device the LED is blinking for. As a
+result, the device is unknown and offloaded is never performed.
+
+Change to condition to always get the device if offloading is
+supported, and reduce the scope of testing for an error from
+hw_control_get() to skip setting trigger internal state if there is an
+error.
+
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/20230808210436.838995-2-andrew@lunn.ch
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -564,15 +564,17 @@ static int netdev_trig_activate(struct l
+ /* Check if hw control is active by default on the LED.
+ * Init already enabled mode in hw control.
+ */
+- if (supports_hw_control(led_cdev) &&
+- !led_cdev->hw_control_get(led_cdev, &mode)) {
++ if (supports_hw_control(led_cdev)) {
+ dev = led_cdev->hw_control_get_device(led_cdev);
+ if (dev) {
+ const char *name = dev_name(dev);
+
+ set_device_name(trigger_data, name, strlen(name));
+ trigger_data->hw_control = true;
+- trigger_data->mode = mode;
++
++ rc = led_cdev->hw_control_get(led_cdev, &mode);
++ if (!rc)
++ trigger_data->mode = mode;
+ }
+ }
+
diff --git a/target/linux/generic/backport-6.6/826-v6.6-02-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch b/target/linux/generic/backport-6.6/826-v6.6-02-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch
new file mode 100644
index 0000000000..505513a53f
--- /dev/null
+++ b/target/linux/generic/backport-6.6/826-v6.6-02-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch
@@ -0,0 +1,149 @@
+From 1dcc03c9a7a824a31eaaecdfaa03542fb25feb6c Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Tue, 8 Aug 2023 23:04:34 +0200
+Subject: [PATCH 2/4] net: phy: phy_device: Call into the PHY driver to set LED
+ offload
+
+Linux LEDs can be requested to perform hardware accelerated blinking
+to indicate link, RX, TX etc. Pass the rules for blinking to the PHY
+driver, if it implements the ops needed to determine if a given
+pattern can be offloaded, to offload it, and what the current offload
+is. Additionally implement the op needed to get what device the LED is
+for.
+
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/20230808210436.838995-3-andrew@lunn.ch
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phy_device.c | 68 ++++++++++++++++++++++++++++++++++++
+ include/linux/phy.h | 33 +++++++++++++++++
+ 2 files changed, 101 insertions(+)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -3068,6 +3068,61 @@ static int phy_led_blink_set(struct led_
+ return err;
+ }
+
++static __maybe_unused struct device *
++phy_led_hw_control_get_device(struct led_classdev *led_cdev)
++{
++ struct phy_led *phyled = to_phy_led(led_cdev);
++ struct phy_device *phydev = phyled->phydev;
++
++ if (phydev->attached_dev)
++ return &phydev->attached_dev->dev;
++ return NULL;
++}
++
++static int __maybe_unused
++phy_led_hw_control_get(struct led_classdev *led_cdev,
++ unsigned long *rules)
++{
++ struct phy_led *phyled = to_phy_led(led_cdev);
++ struct phy_device *phydev = phyled->phydev;
++ int err;
++
++ mutex_lock(&phydev->lock);
++ err = phydev->drv->led_hw_control_get(phydev, phyled->index, rules);
++ mutex_unlock(&phydev->lock);
++
++ return err;
++}
++
++static int __maybe_unused
++phy_led_hw_control_set(struct led_classdev *led_cdev,
++ unsigned long rules)
++{
++ struct phy_led *phyled = to_phy_led(led_cdev);
++ struct phy_device *phydev = phyled->phydev;
++ int err;
++
++ mutex_lock(&phydev->lock);
++ err = phydev->drv->led_hw_control_set(phydev, phyled->index, rules);
++ mutex_unlock(&phydev->lock);
++
++ return err;
++}
++
++static __maybe_unused int phy_led_hw_is_supported(struct led_classdev *led_cdev,
++ unsigned long rules)
++{
++ struct phy_led *phyled = to_phy_led(led_cdev);
++ struct phy_device *phydev = phyled->phydev;
++ int err;
++
++ mutex_lock(&phydev->lock);
++ err = phydev->drv->led_hw_is_supported(phydev, phyled->index, rules);
++ mutex_unlock(&phydev->lock);
++
++ return err;
++}
++
+ static void phy_leds_unregister(struct phy_device *phydev)
+ {
+ struct phy_led *phyled;
+@@ -3105,6 +3160,19 @@ static int of_phy_led(struct phy_device
+ cdev->brightness_set_blocking = phy_led_set_brightness;
+ if (phydev->drv->led_blink_set)
+ cdev->blink_set = phy_led_blink_set;
++
++#ifdef CONFIG_LEDS_TRIGGERS
++ if (phydev->drv->led_hw_is_supported &&
++ phydev->drv->led_hw_control_set &&
++ phydev->drv->led_hw_control_get) {
++ cdev->hw_control_is_supported = phy_led_hw_is_supported;
++ cdev->hw_control_set = phy_led_hw_control_set;
++ cdev->hw_control_get = phy_led_hw_control_get;
++ cdev->hw_control_trigger = "netdev";
++ }
++
++ cdev->hw_control_get_device = phy_led_hw_control_get_device;
++#endif
+ cdev->max_brightness = 1;
+ init_data.devicename = dev_name(&phydev->mdio.dev);
+ init_data.fwnode = of_fwnode_handle(led);
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -1026,6 +1026,39 @@ struct phy_driver {
+ int (*led_blink_set)(struct phy_device *dev, u8 index,
+ unsigned long *delay_on,
+ unsigned long *delay_off);
++ /**
++ * @led_hw_is_supported: Can the HW support the given rules.
++ * @dev: PHY device which has the LED
++ * @index: Which LED of the PHY device
++ * @rules The core is interested in these rules
++ *
++ * Return 0 if yes, -EOPNOTSUPP if not, or an error code.
++ */
++ int (*led_hw_is_supported)(struct phy_device *dev, u8 index,
++ unsigned long rules);
++ /**
++ * @led_hw_control_set: Set the HW to control the LED
++ * @dev: PHY device which has the LED
++ * @index: Which LED of the PHY device
++ * @rules The rules used to control the LED
++ *
++ * Returns 0, or a an error code.
++ */
++ int (*led_hw_control_set)(struct phy_device *dev, u8 index,
++ unsigned long rules);
++ /**
++ * @led_hw_control_get: Get how the HW is controlling the LED
++ * @dev: PHY device which has the LED
++ * @index: Which LED of the PHY device
++ * @rules Pointer to the rules used to control the LED
++ *
++ * Set *@rules to how the HW is currently blinking. Returns 0
++ * on success, or a error code if the current blinking cannot
++ * be represented in rules, or some other error happens.
++ */
++ int (*led_hw_control_get)(struct phy_device *dev, u8 index,
++ unsigned long *rules);
++
+ };
+ #define to_phy_driver(d) container_of(to_mdio_common_driver(d), \
+ struct phy_driver, mdiodrv)
diff --git a/target/linux/generic/backport-6.6/826-v6.6-03-net-phy-marvell-Add-support-for-offloading-LED-blink.patch b/target/linux/generic/backport-6.6/826-v6.6-03-net-phy-marvell-Add-support-for-offloading-LED-blink.patch
new file mode 100644
index 0000000000..1300583b66
--- /dev/null
+++ b/target/linux/generic/backport-6.6/826-v6.6-03-net-phy-marvell-Add-support-for-offloading-LED-blink.patch
@@ -0,0 +1,344 @@
+From 460b0b648fab24f576c481424e0de5479ffb9786 Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Tue, 8 Aug 2023 23:04:35 +0200
+Subject: [PATCH 3/4] net: phy: marvell: Add support for offloading LED
+ blinking
+
+Add the code needed to indicate if a given blinking pattern can be
+offloaded, to offload a pattern and to try to return the current
+pattern.
+
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/20230808210436.838995-4-andrew@lunn.ch
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/marvell.c | 281 ++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 281 insertions(+)
+
+--- a/drivers/net/phy/marvell.c
++++ b/drivers/net/phy/marvell.c
+@@ -2893,6 +2893,272 @@ static int m88e1318_led_blink_set(struct
+ MII_88E1318S_PHY_LED_FUNC, reg);
+ }
+
++struct marvell_led_rules {
++ int mode;
++ unsigned long rules;
++};
++
++static const struct marvell_led_rules marvell_led0[] = {
++ {
++ .mode = 0,
++ .rules = BIT(TRIGGER_NETDEV_LINK),
++ },
++ {
++ .mode = 1,
++ .rules = (BIT(TRIGGER_NETDEV_LINK) |
++ BIT(TRIGGER_NETDEV_RX) |
++ BIT(TRIGGER_NETDEV_TX)),
++ },
++ {
++ .mode = 3,
++ .rules = (BIT(TRIGGER_NETDEV_RX) |
++ BIT(TRIGGER_NETDEV_TX)),
++ },
++ {
++ .mode = 4,
++ .rules = (BIT(TRIGGER_NETDEV_RX) |
++ BIT(TRIGGER_NETDEV_TX)),
++ },
++ {
++ .mode = 5,
++ .rules = BIT(TRIGGER_NETDEV_TX),
++ },
++ {
++ .mode = 6,
++ .rules = BIT(TRIGGER_NETDEV_LINK),
++ },
++ {
++ .mode = 7,
++ .rules = BIT(TRIGGER_NETDEV_LINK_1000),
++ },
++ {
++ .mode = 8,
++ .rules = 0,
++ },
++};
++
++static const struct marvell_led_rules marvell_led1[] = {
++ {
++ .mode = 1,
++ .rules = (BIT(TRIGGER_NETDEV_LINK) |
++ BIT(TRIGGER_NETDEV_RX) |
++ BIT(TRIGGER_NETDEV_TX)),
++ },
++ {
++ .mode = 2,
++ .rules = (BIT(TRIGGER_NETDEV_LINK) |
++ BIT(TRIGGER_NETDEV_RX)),
++ },
++ {
++ .mode = 3,
++ .rules = (BIT(TRIGGER_NETDEV_RX) |
++ BIT(TRIGGER_NETDEV_TX)),
++ },
++ {
++ .mode = 4,
++ .rules = (BIT(TRIGGER_NETDEV_RX) |
++ BIT(TRIGGER_NETDEV_TX)),
++ },
++ {
++ .mode = 6,
++ .rules = (BIT(TRIGGER_NETDEV_LINK_100) |
++ BIT(TRIGGER_NETDEV_LINK_1000)),
++ },
++ {
++ .mode = 7,
++ .rules = BIT(TRIGGER_NETDEV_LINK_100),
++ },
++ {
++ .mode = 8,
++ .rules = 0,
++ },
++};
++
++static const struct marvell_led_rules marvell_led2[] = {
++ {
++ .mode = 0,
++ .rules = BIT(TRIGGER_NETDEV_LINK),
++ },
++ {
++ .mode = 1,
++ .rules = (BIT(TRIGGER_NETDEV_LINK) |
++ BIT(TRIGGER_NETDEV_RX) |
++ BIT(TRIGGER_NETDEV_TX)),
++ },
++ {
++ .mode = 3,
++ .rules = (BIT(TRIGGER_NETDEV_RX) |
++ BIT(TRIGGER_NETDEV_TX)),
++ },
++ {
++ .mode = 4,
++ .rules = (BIT(TRIGGER_NETDEV_RX) |
++ BIT(TRIGGER_NETDEV_TX)),
++ },
++ {
++ .mode = 5,
++ .rules = BIT(TRIGGER_NETDEV_TX),
++ },
++ {
++ .mode = 6,
++ .rules = (BIT(TRIGGER_NETDEV_LINK_10) |
++ BIT(TRIGGER_NETDEV_LINK_1000)),
++ },
++ {
++ .mode = 7,
++ .rules = BIT(TRIGGER_NETDEV_LINK_10),
++ },
++ {
++ .mode = 8,
++ .rules = 0,
++ },
++};
++
++static int marvell_find_led_mode(unsigned long rules,
++ const struct marvell_led_rules *marvell_rules,
++ int count,
++ int *mode)
++{
++ int i;
++
++ for (i = 0; i < count; i++) {
++ if (marvell_rules[i].rules == rules) {
++ *mode = marvell_rules[i].mode;
++ return 0;
++ }
++ }
++ return -EOPNOTSUPP;
++}
++
++static int marvell_get_led_mode(u8 index, unsigned long rules, int *mode)
++{
++ int ret;
++
++ switch (index) {
++ case 0:
++ ret = marvell_find_led_mode(rules, marvell_led0,
++ ARRAY_SIZE(marvell_led0), mode);
++ break;
++ case 1:
++ ret = marvell_find_led_mode(rules, marvell_led1,
++ ARRAY_SIZE(marvell_led1), mode);
++ break;
++ case 2:
++ ret = marvell_find_led_mode(rules, marvell_led2,
++ ARRAY_SIZE(marvell_led2), mode);
++ break;
++ default:
++ ret = -EINVAL;
++ }
++
++ return ret;
++}
++
++static int marvell_find_led_rules(unsigned long *rules,
++ const struct marvell_led_rules *marvell_rules,
++ int count,
++ int mode)
++{
++ int i;
++
++ for (i = 0; i < count; i++) {
++ if (marvell_rules[i].mode == mode) {
++ *rules = marvell_rules[i].rules;
++ return 0;
++ }
++ }
++ return -EOPNOTSUPP;
++}
++
++static int marvell_get_led_rules(u8 index, unsigned long *rules, int mode)
++{
++ int ret;
++
++ switch (index) {
++ case 0:
++ ret = marvell_find_led_rules(rules, marvell_led0,
++ ARRAY_SIZE(marvell_led0), mode);
++ break;
++ case 1:
++ ret = marvell_find_led_rules(rules, marvell_led1,
++ ARRAY_SIZE(marvell_led1), mode);
++ break;
++ case 2:
++ ret = marvell_find_led_rules(rules, marvell_led2,
++ ARRAY_SIZE(marvell_led2), mode);
++ break;
++ default:
++ ret = -EOPNOTSUPP;
++ }
++
++ return ret;
++}
++
++static int m88e1318_led_hw_is_supported(struct phy_device *phydev, u8 index,
++ unsigned long rules)
++{
++ int mode, ret;
++
++ switch (index) {
++ case 0:
++ case 1:
++ case 2:
++ ret = marvell_get_led_mode(index, rules, &mode);
++ break;
++ default:
++ ret = -EINVAL;
++ }
++
++ return ret;
++}
++
++static int m88e1318_led_hw_control_set(struct phy_device *phydev, u8 index,
++ unsigned long rules)
++{
++ int mode, ret, reg;
++
++ switch (index) {
++ case 0:
++ case 1:
++ case 2:
++ ret = marvell_get_led_mode(index, rules, &mode);
++ break;
++ default:
++ ret = -EINVAL;
++ }
++
++ if (ret < 0)
++ return ret;
++
++ reg = phy_read_paged(phydev, MII_MARVELL_LED_PAGE,
++ MII_88E1318S_PHY_LED_FUNC);
++ if (reg < 0)
++ return reg;
++
++ reg &= ~(0xf << (4 * index));
++ reg |= mode << (4 * index);
++ return phy_write_paged(phydev, MII_MARVELL_LED_PAGE,
++ MII_88E1318S_PHY_LED_FUNC, reg);
++}
++
++static int m88e1318_led_hw_control_get(struct phy_device *phydev, u8 index,
++ unsigned long *rules)
++{
++ int mode, reg;
++
++ if (index > 2)
++ return -EINVAL;
++
++ reg = phy_read_paged(phydev, MII_MARVELL_LED_PAGE,
++ MII_88E1318S_PHY_LED_FUNC);
++ if (reg < 0)
++ return reg;
++
++ mode = (reg >> (4 * index)) & 0xf;
++
++ return marvell_get_led_rules(index, rules, mode);
++}
++
+ static int marvell_probe(struct phy_device *phydev)
+ {
+ struct marvell_priv *priv;
+@@ -3144,6 +3410,9 @@ static struct phy_driver marvell_drivers
+ .get_stats = marvell_get_stats,
+ .led_brightness_set = m88e1318_led_brightness_set,
+ .led_blink_set = m88e1318_led_blink_set,
++ .led_hw_is_supported = m88e1318_led_hw_is_supported,
++ .led_hw_control_set = m88e1318_led_hw_control_set,
++ .led_hw_control_get = m88e1318_led_hw_control_get,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1145,
+@@ -3252,6 +3521,9 @@ static struct phy_driver marvell_drivers
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
+ .led_brightness_set = m88e1318_led_brightness_set,
+ .led_blink_set = m88e1318_led_blink_set,
++ .led_hw_is_supported = m88e1318_led_hw_is_supported,
++ .led_hw_control_set = m88e1318_led_hw_control_set,
++ .led_hw_control_get = m88e1318_led_hw_control_get,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1540,
+@@ -3280,6 +3552,9 @@ static struct phy_driver marvell_drivers
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
+ .led_brightness_set = m88e1318_led_brightness_set,
+ .led_blink_set = m88e1318_led_blink_set,
++ .led_hw_is_supported = m88e1318_led_hw_is_supported,
++ .led_hw_control_set = m88e1318_led_hw_control_set,
++ .led_hw_control_get = m88e1318_led_hw_control_get,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1545,
+@@ -3308,6 +3583,9 @@ static struct phy_driver marvell_drivers
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
+ .led_brightness_set = m88e1318_led_brightness_set,
+ .led_blink_set = m88e1318_led_blink_set,
++ .led_hw_is_supported = m88e1318_led_hw_is_supported,
++ .led_hw_control_set = m88e1318_led_hw_control_set,
++ .led_hw_control_get = m88e1318_led_hw_control_get,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E3016,
+@@ -3451,6 +3729,9 @@ static struct phy_driver marvell_drivers
+ .set_tunable = m88e1540_set_tunable,
+ .led_brightness_set = m88e1318_led_brightness_set,
+ .led_blink_set = m88e1318_led_blink_set,
++ .led_hw_is_supported = m88e1318_led_hw_is_supported,
++ .led_hw_control_set = m88e1318_led_hw_control_set,
++ .led_hw_control_get = m88e1318_led_hw_control_get,
+ },
+ };
+
diff --git a/target/linux/generic/backport-6.6/826-v6.6-04-leds-trig-netdev-Disable-offload-on-deactivation-of-.patch b/target/linux/generic/backport-6.6/826-v6.6-04-leds-trig-netdev-Disable-offload-on-deactivation-of-.patch
new file mode 100644
index 0000000000..d6a69aa9ca
--- /dev/null
+++ b/target/linux/generic/backport-6.6/826-v6.6-04-leds-trig-netdev-Disable-offload-on-deactivation-of-.patch
@@ -0,0 +1,31 @@
+From e8fbcc47a8e935f36f044d85f21a99acecbd7bfb Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Tue, 8 Aug 2023 23:04:36 +0200
+Subject: [PATCH 4/4] leds: trig-netdev: Disable offload on deactivation of
+ trigger
+
+Ensure that the offloading of blinking is stopped when the trigger is
+deactivated. Calling led_set_brightness() is documented as stopping
+offload and setting the LED to a constant brightness.
+
+Suggested-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/20230808210436.838995-5-andrew@lunn.ch
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -595,6 +595,8 @@ static void netdev_trig_deactivate(struc
+
+ cancel_delayed_work_sync(&trigger_data->work);
+
++ led_set_brightness(led_cdev, LED_OFF);
++
+ dev_put(trigger_data->net_dev);
+
+ kfree(trigger_data);
diff --git a/target/linux/generic/backport-6.6/827-v6.3-0001-of-base-add-of_parse_phandle_with_optional_args.patch b/target/linux/generic/backport-6.6/827-v6.3-0001-of-base-add-of_parse_phandle_with_optional_args.patch
new file mode 100644
index 0000000000..f568c3f6ce
--- /dev/null
+++ b/target/linux/generic/backport-6.6/827-v6.3-0001-of-base-add-of_parse_phandle_with_optional_args.patch
@@ -0,0 +1,58 @@
+From c5d264d4b527c96ae8903376a4b195df47b05203 Mon Sep 17 00:00:00 2001
+From: Michael Walle <michael@walle.cc>
+Date: Mon, 6 Feb 2023 13:43:43 +0000
+Subject: [PATCH] of: base: add of_parse_phandle_with_optional_args()
+
+Add a new variant of the of_parse_phandle_with_args() which treats the
+cells name as optional. If it's missing, it is assumed that the phandle
+has no arguments.
+
+Up until now, a nvmem node didn't have any arguments, so all the device
+trees haven't any '#*-cells' property. But there is a need for an
+additional argument for the phandle, for which we need a '#*-cells'
+property. Therefore, we need to support nvmem nodes with and without
+this property.
+
+Signed-off-by: Michael Walle <michael@walle.cc>
+Reviewed-by: Rob Herring <robh@kernel.org>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230206134356.839737-10-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/of.h | 25 +++++++++++++++++++++++++
+ 1 file changed, 25 insertions(+)
+
+--- a/include/linux/of.h
++++ b/include/linux/of.h
+@@ -1009,6 +1009,31 @@ static inline int of_parse_phandle_with_
+ }
+
+ /**
++ * of_parse_phandle_with_optional_args() - Find a node pointed by phandle in a list
++ * @np: pointer to a device tree node containing a list
++ * @list_name: property name that contains a list
++ * @cells_name: property name that specifies phandles' arguments count
++ * @index: index of a phandle to parse out
++ * @out_args: optional pointer to output arguments structure (will be filled)
++ *
++ * Same as of_parse_phandle_with_args() except that if the cells_name property
++ * is not found, cell_count of 0 is assumed.
++ *
++ * This is used to useful, if you have a phandle which didn't have arguments
++ * before and thus doesn't have a '#*-cells' property but is now migrated to
++ * having arguments while retaining backwards compatibility.
++ */
++static inline int of_parse_phandle_with_optional_args(const struct device_node *np,
++ const char *list_name,
++ const char *cells_name,
++ int index,
++ struct of_phandle_args *out_args)
++{
++ return __of_parse_phandle_with_args(np, list_name, cells_name,
++ 0, index, out_args);
++}
++
++/**
+ * of_property_count_u8_elems - Count the number of u8 elements in a property
+ *
+ * @np: device node from which the property value is to be read.
diff --git a/target/linux/generic/backport-6.6/827-v6.3-0002-of-property-make-.-cells-optional-for-simple-props.patch b/target/linux/generic/backport-6.6/827-v6.3-0002-of-property-make-.-cells-optional-for-simple-props.patch
new file mode 100644
index 0000000000..66c29126b6
--- /dev/null
+++ b/target/linux/generic/backport-6.6/827-v6.3-0002-of-property-make-.-cells-optional-for-simple-props.patch
@@ -0,0 +1,34 @@
+From ff24fed10ba414d19579e26e60b126fad2f2bb07 Mon Sep 17 00:00:00 2001
+From: Michael Walle <michael@walle.cc>
+Date: Mon, 6 Feb 2023 13:43:44 +0000
+Subject: [PATCH] of: property: make #.*-cells optional for simple props
+
+Sometimes, future bindings for phandles will get additional arguments.
+Thus the target node of the phandle will need a new #.*-cells property.
+To be backwards compatible, this needs to be optional.
+
+Prepare the DEFINE_SIMPLE_PROPS() to handle the cells name as optional.
+
+Signed-off-by: Michael Walle <michael@walle.cc>
+Tested-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Reviewed-by: Rob Herring <robh@kernel.org>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230206134356.839737-11-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/of/property.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/of/property.c
++++ b/drivers/of/property.c
+@@ -1146,8 +1146,8 @@ static struct device_node *parse_prop_ce
+ if (strcmp(prop_name, list_name))
+ return NULL;
+
+- if (of_parse_phandle_with_args(np, list_name, cells_name, index,
+- &sup_args))
++ if (__of_parse_phandle_with_args(np, list_name, cells_name, 0, index,
++ &sup_args))
+ return NULL;
+
+ return sup_args.np;
diff --git a/target/linux/generic/backport-6.6/827-v6.3-0003-of-property-add-nvmem-cell-cells-property.patch b/target/linux/generic/backport-6.6/827-v6.3-0003-of-property-add-nvmem-cell-cells-property.patch
new file mode 100644
index 0000000000..07354d7da7
--- /dev/null
+++ b/target/linux/generic/backport-6.6/827-v6.3-0003-of-property-add-nvmem-cell-cells-property.patch
@@ -0,0 +1,30 @@
+From e2d8172043d2e50df19fcd59c11e5593de8188d7 Mon Sep 17 00:00:00 2001
+From: Michael Walle <michael@walle.cc>
+Date: Mon, 6 Feb 2023 13:43:45 +0000
+Subject: [PATCH] of: property: add #nvmem-cell-cells property
+
+Bindings describe the new '#nvmem-cell-cells' property. Now that the
+arguments count property is optional, we just add this property to the
+nvmem-cells.
+
+Signed-off-by: Michael Walle <michael@walle.cc>
+Tested-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Reviewed-by: Rob Herring <robh@kernel.org>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230206134356.839737-12-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/of/property.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/of/property.c
++++ b/drivers/of/property.c
+@@ -1251,7 +1251,7 @@ DEFINE_SIMPLE_PROP(dmas, "dmas", "#dma-c
+ DEFINE_SIMPLE_PROP(power_domains, "power-domains", "#power-domain-cells")
+ DEFINE_SIMPLE_PROP(hwlocks, "hwlocks", "#hwlock-cells")
+ DEFINE_SIMPLE_PROP(extcon, "extcon", NULL)
+-DEFINE_SIMPLE_PROP(nvmem_cells, "nvmem-cells", NULL)
++DEFINE_SIMPLE_PROP(nvmem_cells, "nvmem-cells", "#nvmem-cell-cells")
+ DEFINE_SIMPLE_PROP(phys, "phys", "#phy-cells")
+ DEFINE_SIMPLE_PROP(wakeup_parent, "wakeup-parent", NULL)
+ DEFINE_SIMPLE_PROP(pinctrl0, "pinctrl-0", NULL)
diff --git a/target/linux/generic/backport-6.6/827-v6.3-0004-of-device-Ignore-modalias-of-reused-nodes.patch b/target/linux/generic/backport-6.6/827-v6.3-0004-of-device-Ignore-modalias-of-reused-nodes.patch
new file mode 100644
index 0000000000..8dc370618e
--- /dev/null
+++ b/target/linux/generic/backport-6.6/827-v6.3-0004-of-device-Ignore-modalias-of-reused-nodes.patch
@@ -0,0 +1,37 @@
+From 553bd29700145e1849698985e9800f14e967da49 Mon Sep 17 00:00:00 2001
+From: Alexander Stein <alexander.stein@ew.tq-group.com>
+Date: Tue, 7 Feb 2023 12:05:29 +0100
+Subject: [PATCH] of: device: Ignore modalias of reused nodes
+
+If of_node is reused, do not use that node's modalias. This will hide
+the name of the actual device. This is rather prominent in USB glue
+drivers creating a platform device for the host controller.
+
+Signed-off-by: Alexander Stein <alexander.stein@ew.tq-group.com>
+Reviewed-by: Rob Herring <robh@kernel.org>
+Link: https://lore.kernel.org/r/20230207110531.1060252-2-alexander.stein@ew.tq-group.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/of/device.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/of/device.c
++++ b/drivers/of/device.c
+@@ -256,7 +256,7 @@ static ssize_t of_device_get_modalias(st
+ ssize_t csize;
+ ssize_t tsize;
+
+- if ((!dev) || (!dev->of_node))
++ if ((!dev) || (!dev->of_node) || dev->of_node_reused)
+ return -ENODEV;
+
+ /* Name & Type */
+@@ -379,7 +379,7 @@ int of_device_uevent_modalias(struct dev
+ {
+ int sl;
+
+- if ((!dev) || (!dev->of_node))
++ if ((!dev) || (!dev->of_node) || dev->of_node_reused)
+ return -ENODEV;
+
+ /* Devicetree modalias is tricky, we add it in 2 steps */
diff --git a/target/linux/generic/backport-6.6/827-v6.3-0005-of-device-Do-not-ignore-error-code-in-of_device_ueve.patch b/target/linux/generic/backport-6.6/827-v6.3-0005-of-device-Do-not-ignore-error-code-in-of_device_ueve.patch
new file mode 100644
index 0000000000..dcdc2313ba
--- /dev/null
+++ b/target/linux/generic/backport-6.6/827-v6.3-0005-of-device-Do-not-ignore-error-code-in-of_device_ueve.patch
@@ -0,0 +1,29 @@
+From 2295bed9bebe8d1eef276194fed5b5fbe89c5363 Mon Sep 17 00:00:00 2001
+From: Alexander Stein <alexander.stein@ew.tq-group.com>
+Date: Tue, 7 Feb 2023 12:05:30 +0100
+Subject: [PATCH] of: device: Do not ignore error code in
+ of_device_uevent_modalias
+
+of_device_get_modalias might return an error code, propagate that one.
+Otherwise the negative, signed integer is propagated to unsigned integer
+for the comparison resulting in a huge 'sl' size.
+
+Signed-off-by: Alexander Stein <alexander.stein@ew.tq-group.com>
+Reviewed-by: Rob Herring <robh@kernel.org>
+Link: https://lore.kernel.org/r/20230207110531.1060252-3-alexander.stein@ew.tq-group.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/of/device.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/drivers/of/device.c
++++ b/drivers/of/device.c
+@@ -388,6 +388,8 @@ int of_device_uevent_modalias(struct dev
+
+ sl = of_device_get_modalias(dev, &env->buf[env->buflen-1],
+ sizeof(env->buf) - env->buflen);
++ if (sl < 0)
++ return sl;
+ if (sl >= (sizeof(env->buf) - env->buflen))
+ return -ENOMEM;
+ env->buflen += sl;
diff --git a/target/linux/generic/backport-6.6/828-v6.4-0002-of-Update-of_device_get_modalias.patch b/target/linux/generic/backport-6.6/828-v6.4-0002-of-Update-of_device_get_modalias.patch
new file mode 100644
index 0000000000..280ed9085c
--- /dev/null
+++ b/target/linux/generic/backport-6.6/828-v6.4-0002-of-Update-of_device_get_modalias.patch
@@ -0,0 +1,103 @@
+From 5c3d15e127ebfc0754cd18def7124633b6d46672 Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Tue, 4 Apr 2023 18:21:15 +0100
+Subject: [PATCH] of: Update of_device_get_modalias()
+
+This function only needs a "struct device_node" to work, but for
+convenience the author (and only user) of this helper did use a "struct
+device" and put it in device.c.
+
+Let's convert this helper to take a "struct device node" instead. This
+change asks for two additional changes: renaming it "of_modalias()"
+to fit the current naming, and moving it outside of device.c which will
+be done in a follow-up commit.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Reviewed-by: Rob Herring <robh@kernel.org>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-8-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/of/device.c | 29 +++++++++++++++++------------
+ 1 file changed, 17 insertions(+), 12 deletions(-)
+
+--- a/drivers/of/device.c
++++ b/drivers/of/device.c
+@@ -248,7 +248,7 @@ const void *of_device_get_match_data(con
+ }
+ EXPORT_SYMBOL(of_device_get_match_data);
+
+-static ssize_t of_device_get_modalias(struct device *dev, char *str, ssize_t len)
++static ssize_t of_modalias(const struct device_node *np, char *str, ssize_t len)
+ {
+ const char *compat;
+ char *c;
+@@ -256,19 +256,16 @@ static ssize_t of_device_get_modalias(st
+ ssize_t csize;
+ ssize_t tsize;
+
+- if ((!dev) || (!dev->of_node) || dev->of_node_reused)
+- return -ENODEV;
+-
+ /* Name & Type */
+ /* %p eats all alphanum characters, so %c must be used here */
+- csize = snprintf(str, len, "of:N%pOFn%c%s", dev->of_node, 'T',
+- of_node_get_device_type(dev->of_node));
++ csize = snprintf(str, len, "of:N%pOFn%c%s", np, 'T',
++ of_node_get_device_type(np));
+ tsize = csize;
+ len -= csize;
+ if (str)
+ str += csize;
+
+- of_property_for_each_string(dev->of_node, "compatible", p, compat) {
++ of_property_for_each_string(np, "compatible", p, compat) {
+ csize = strlen(compat) + 1;
+ tsize += csize;
+ if (csize > len)
+@@ -293,7 +290,10 @@ int of_device_request_module(struct devi
+ ssize_t size;
+ int ret;
+
+- size = of_device_get_modalias(dev, NULL, 0);
++ if (!dev || !dev->of_node)
++ return -ENODEV;
++
++ size = of_modalias(dev->of_node, NULL, 0);
+ if (size < 0)
+ return size;
+
+@@ -304,7 +304,7 @@ int of_device_request_module(struct devi
+ if (!str)
+ return -ENOMEM;
+
+- of_device_get_modalias(dev, str, size);
++ of_modalias(dev->of_node, str, size);
+ str[size - 1] = '\0';
+ ret = request_module(str);
+ kfree(str);
+@@ -321,7 +321,12 @@ EXPORT_SYMBOL_GPL(of_device_request_modu
+ */
+ ssize_t of_device_modalias(struct device *dev, char *str, ssize_t len)
+ {
+- ssize_t sl = of_device_get_modalias(dev, str, len - 2);
++ ssize_t sl;
++
++ if (!dev || !dev->of_node || dev->of_node_reused)
++ return -ENODEV;
++
++ sl = of_modalias(dev->of_node, str, len - 2);
+ if (sl < 0)
+ return sl;
+ if (sl > len - 2)
+@@ -386,8 +391,8 @@ int of_device_uevent_modalias(struct dev
+ if (add_uevent_var(env, "MODALIAS="))
+ return -ENOMEM;
+
+- sl = of_device_get_modalias(dev, &env->buf[env->buflen-1],
+- sizeof(env->buf) - env->buflen);
++ sl = of_modalias(dev->of_node, &env->buf[env->buflen-1],
++ sizeof(env->buf) - env->buflen);
+ if (sl < 0)
+ return sl;
+ if (sl >= (sizeof(env->buf) - env->buflen))
diff --git a/target/linux/generic/backport-6.6/828-v6.4-0003-of-Rename-of_modalias_node.patch b/target/linux/generic/backport-6.6/828-v6.4-0003-of-Rename-of_modalias_node.patch
new file mode 100644
index 0000000000..671556fbaa
--- /dev/null
+++ b/target/linux/generic/backport-6.6/828-v6.4-0003-of-Rename-of_modalias_node.patch
@@ -0,0 +1,173 @@
+From 673aa1ed1c9b6710bf24e3f0957d85e2f46c77db Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Tue, 4 Apr 2023 18:21:16 +0100
+Subject: [PATCH] of: Rename of_modalias_node()
+
+This helper does not produce a real modalias, but tries to get the
+"product" compatible part of the "vendor,product" compatibles only. It
+is far from creating a purely useful modalias string and does not seem
+to be used like that directly anyway, so let's try to give this helper a
+more meaningful name before moving there a real modalias helper (already
+existing under of/device.c).
+
+Also update the various documentations to refer to the strings as
+"aliases" rather than "modaliases" which has a real meaning in the Linux
+kernel.
+
+There is no functional change.
+
+Cc: Rafael J. Wysocki <rafael@kernel.org>
+Cc: Len Brown <lenb@kernel.org>
+Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
+Cc: Maxime Ripard <mripard@kernel.org>
+Cc: Thomas Zimmermann <tzimmermann@suse.de>
+Cc: Sebastian Reichel <sre@kernel.org>
+Cc: Wolfram Sang <wsa@kernel.org>
+Cc: Mark Brown <broonie@kernel.org>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Reviewed-by: Rob Herring <robh@kernel.org>
+Acked-by: Mark Brown <broonie@kernel.org>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Acked-by: Sebastian Reichel <sre@kernel.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-9-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/acpi/bus.c | 7 ++++---
+ drivers/gpu/drm/drm_mipi_dsi.c | 2 +-
+ drivers/hsi/hsi_core.c | 2 +-
+ drivers/i2c/busses/i2c-powermac.c | 2 +-
+ drivers/i2c/i2c-core-of.c | 2 +-
+ drivers/of/base.c | 18 +++++++++++-------
+ drivers/spi/spi.c | 4 ++--
+ include/linux/of.h | 3 ++-
+ 8 files changed, 23 insertions(+), 17 deletions(-)
+
+--- a/drivers/acpi/bus.c
++++ b/drivers/acpi/bus.c
+@@ -806,9 +806,10 @@ static bool acpi_of_modalias(struct acpi
+ * @modalias: Pointer to buffer that modalias value will be copied into
+ * @len: Length of modalias buffer
+ *
+- * This is a counterpart of of_modalias_node() for struct acpi_device objects.
+- * If there is a compatible string for @adev, it will be copied to @modalias
+- * with the vendor prefix stripped; otherwise, @default_id will be used.
++ * This is a counterpart of of_alias_from_compatible() for struct acpi_device
++ * objects. If there is a compatible string for @adev, it will be copied to
++ * @modalias with the vendor prefix stripped; otherwise, @default_id will be
++ * used.
+ */
+ void acpi_set_modalias(struct acpi_device *adev, const char *default_id,
+ char *modalias, size_t len)
+--- a/drivers/gpu/drm/drm_mipi_dsi.c
++++ b/drivers/gpu/drm/drm_mipi_dsi.c
+@@ -160,7 +160,7 @@ of_mipi_dsi_device_add(struct mipi_dsi_h
+ int ret;
+ u32 reg;
+
+- if (of_modalias_node(node, info.type, sizeof(info.type)) < 0) {
++ if (of_alias_from_compatible(node, info.type, sizeof(info.type)) < 0) {
+ drm_err(host, "modalias failure on %pOF\n", node);
+ return ERR_PTR(-EINVAL);
+ }
+--- a/drivers/hsi/hsi_core.c
++++ b/drivers/hsi/hsi_core.c
+@@ -207,7 +207,7 @@ static void hsi_add_client_from_dt(struc
+ if (!cl)
+ return;
+
+- err = of_modalias_node(client, name, sizeof(name));
++ err = of_alias_from_compatible(client, name, sizeof(name));
+ if (err)
+ goto err;
+
+--- a/drivers/i2c/busses/i2c-powermac.c
++++ b/drivers/i2c/busses/i2c-powermac.c
+@@ -284,7 +284,7 @@ static bool i2c_powermac_get_type(struct
+ */
+
+ /* First try proper modalias */
+- if (of_modalias_node(node, tmp, sizeof(tmp)) >= 0) {
++ if (of_alias_from_compatible(node, tmp, sizeof(tmp)) >= 0) {
+ snprintf(type, type_size, "MAC,%s", tmp);
+ return true;
+ }
+--- a/drivers/i2c/i2c-core-of.c
++++ b/drivers/i2c/i2c-core-of.c
+@@ -27,7 +27,7 @@ int of_i2c_get_board_info(struct device
+
+ memset(info, 0, sizeof(*info));
+
+- if (of_modalias_node(node, info->type, sizeof(info->type)) < 0) {
++ if (of_alias_from_compatible(node, info->type, sizeof(info->type)) < 0) {
+ dev_err(dev, "of_i2c: modalias failure on %pOF\n", node);
+ return -EINVAL;
+ }
+--- a/drivers/of/base.c
++++ b/drivers/of/base.c
+@@ -1208,19 +1208,23 @@ struct device_node *of_find_matching_nod
+ EXPORT_SYMBOL(of_find_matching_node_and_match);
+
+ /**
+- * of_modalias_node - Lookup appropriate modalias for a device node
++ * of_alias_from_compatible - Lookup appropriate alias for a device node
++ * depending on compatible
+ * @node: pointer to a device tree node
+- * @modalias: Pointer to buffer that modalias value will be copied into
+- * @len: Length of modalias value
++ * @alias: Pointer to buffer that alias value will be copied into
++ * @len: Length of alias value
+ *
+ * Based on the value of the compatible property, this routine will attempt
+- * to choose an appropriate modalias value for a particular device tree node.
++ * to choose an appropriate alias value for a particular device tree node.
+ * It does this by stripping the manufacturer prefix (as delimited by a ',')
+ * from the first entry in the compatible list property.
+ *
++ * Note: The matching on just the "product" side of the compatible is a relic
++ * from I2C and SPI. Please do not add any new user.
++ *
+ * Return: This routine returns 0 on success, <0 on failure.
+ */
+-int of_modalias_node(struct device_node *node, char *modalias, int len)
++int of_alias_from_compatible(const struct device_node *node, char *alias, int len)
+ {
+ const char *compatible, *p;
+ int cplen;
+@@ -1229,10 +1233,10 @@ int of_modalias_node(struct device_node
+ if (!compatible || strlen(compatible) > cplen)
+ return -ENODEV;
+ p = strchr(compatible, ',');
+- strscpy(modalias, p ? p + 1 : compatible, len);
++ strscpy(alias, p ? p + 1 : compatible, len);
+ return 0;
+ }
+-EXPORT_SYMBOL_GPL(of_modalias_node);
++EXPORT_SYMBOL_GPL(of_alias_from_compatible);
+
+ /**
+ * of_find_node_by_phandle - Find a node given a phandle
+--- a/drivers/spi/spi.c
++++ b/drivers/spi/spi.c
+@@ -2330,8 +2330,8 @@ of_register_spi_device(struct spi_contro
+ }
+
+ /* Select device driver */
+- rc = of_modalias_node(nc, spi->modalias,
+- sizeof(spi->modalias));
++ rc = of_alias_from_compatible(nc, spi->modalias,
++ sizeof(spi->modalias));
+ if (rc < 0) {
+ dev_err(&ctlr->dev, "cannot find modalias for %pOF\n", nc);
+ goto err_out;
+--- a/include/linux/of.h
++++ b/include/linux/of.h
+@@ -362,7 +362,8 @@ extern int of_n_addr_cells(struct device
+ extern int of_n_size_cells(struct device_node *np);
+ extern const struct of_device_id *of_match_node(
+ const struct of_device_id *matches, const struct device_node *node);
+-extern int of_modalias_node(struct device_node *node, char *modalias, int len);
++extern int of_alias_from_compatible(const struct device_node *node, char *alias,
++ int len);
+ extern void of_print_phandle_args(const char *msg, const struct of_phandle_args *args);
+ extern int __of_parse_phandle_with_args(const struct device_node *np,
+ const char *list_name, const char *cells_name, int cell_count,
diff --git a/target/linux/generic/backport-6.6/828-v6.4-0004-of-Move-of_modalias-to-module.c.patch b/target/linux/generic/backport-6.6/828-v6.4-0004-of-Move-of_modalias-to-module.c.patch
new file mode 100644
index 0000000000..39a84161a2
--- /dev/null
+++ b/target/linux/generic/backport-6.6/828-v6.4-0004-of-Move-of_modalias-to-module.c.patch
@@ -0,0 +1,160 @@
+From bd7a7ed774afd1a4174df34227626c95573be517 Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Tue, 4 Apr 2023 18:21:17 +0100
+Subject: [PATCH] of: Move of_modalias() to module.c
+
+Create a specific .c file for OF related module handling.
+Move of_modalias() inside as a first step.
+
+The helper is exposed through of.h even though it is only used by core
+files because the users from device.c will soon be split into an OF-only
+helper in module.c as well as a device-oriented inline helper in
+of_device.h. Putting this helper in of_private.h would require to
+include of_private.h from of_device.h, which is not acceptable.
+
+Suggested-by: Rob Herring <robh+dt@kernel.org>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Reviewed-by: Rob Herring <robh@kernel.org>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-10-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/of/Makefile | 2 +-
+ drivers/of/device.c | 37 -------------------------------------
+ drivers/of/module.c | 44 ++++++++++++++++++++++++++++++++++++++++++++
+ include/linux/of.h | 9 +++++++++
+ 4 files changed, 54 insertions(+), 38 deletions(-)
+ create mode 100644 drivers/of/module.c
+
+--- a/drivers/of/Makefile
++++ b/drivers/of/Makefile
+@@ -1,5 +1,5 @@
+ # SPDX-License-Identifier: GPL-2.0
+-obj-y = base.o device.o platform.o property.o
++obj-y = base.o device.o module.o platform.o property.o
+ obj-$(CONFIG_OF_KOBJ) += kobj.o
+ obj-$(CONFIG_OF_DYNAMIC) += dynamic.o
+ obj-$(CONFIG_OF_FLATTREE) += fdt.o
+--- a/drivers/of/device.c
++++ b/drivers/of/device.c
+@@ -1,5 +1,4 @@
+ // SPDX-License-Identifier: GPL-2.0
+-#include <linux/string.h>
+ #include <linux/kernel.h>
+ #include <linux/of.h>
+ #include <linux/of_device.h>
+@@ -248,42 +247,6 @@ const void *of_device_get_match_data(con
+ }
+ EXPORT_SYMBOL(of_device_get_match_data);
+
+-static ssize_t of_modalias(const struct device_node *np, char *str, ssize_t len)
+-{
+- const char *compat;
+- char *c;
+- struct property *p;
+- ssize_t csize;
+- ssize_t tsize;
+-
+- /* Name & Type */
+- /* %p eats all alphanum characters, so %c must be used here */
+- csize = snprintf(str, len, "of:N%pOFn%c%s", np, 'T',
+- of_node_get_device_type(np));
+- tsize = csize;
+- len -= csize;
+- if (str)
+- str += csize;
+-
+- of_property_for_each_string(np, "compatible", p, compat) {
+- csize = strlen(compat) + 1;
+- tsize += csize;
+- if (csize > len)
+- continue;
+-
+- csize = snprintf(str, len, "C%s", compat);
+- for (c = str; c; ) {
+- c = strchr(c, ' ');
+- if (c)
+- *c++ = '_';
+- }
+- len -= csize;
+- str += csize;
+- }
+-
+- return tsize;
+-}
+-
+ int of_device_request_module(struct device *dev)
+ {
+ char *str;
+--- /dev/null
++++ b/drivers/of/module.c
+@@ -0,0 +1,44 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Linux kernel module helpers.
++ */
++
++#include <linux/of.h>
++#include <linux/slab.h>
++#include <linux/string.h>
++
++ssize_t of_modalias(const struct device_node *np, char *str, ssize_t len)
++{
++ const char *compat;
++ char *c;
++ struct property *p;
++ ssize_t csize;
++ ssize_t tsize;
++
++ /* Name & Type */
++ /* %p eats all alphanum characters, so %c must be used here */
++ csize = snprintf(str, len, "of:N%pOFn%c%s", np, 'T',
++ of_node_get_device_type(np));
++ tsize = csize;
++ len -= csize;
++ if (str)
++ str += csize;
++
++ of_property_for_each_string(np, "compatible", p, compat) {
++ csize = strlen(compat) + 1;
++ tsize += csize;
++ if (csize > len)
++ continue;
++
++ csize = snprintf(str, len, "C%s", compat);
++ for (c = str; c; ) {
++ c = strchr(c, ' ');
++ if (c)
++ *c++ = '_';
++ }
++ len -= csize;
++ str += csize;
++ }
++
++ return tsize;
++}
+--- a/include/linux/of.h
++++ b/include/linux/of.h
+@@ -374,6 +374,9 @@ extern int of_parse_phandle_with_args_ma
+ extern int of_count_phandle_with_args(const struct device_node *np,
+ const char *list_name, const char *cells_name);
+
++/* module functions */
++extern ssize_t of_modalias(const struct device_node *np, char *str, ssize_t len);
++
+ /* phandle iterator functions */
+ extern int of_phandle_iterator_init(struct of_phandle_iterator *it,
+ const struct device_node *np,
+@@ -731,6 +734,12 @@ static inline int of_count_phandle_with_
+ return -ENOSYS;
+ }
+
++static inline ssize_t of_modalias(const struct device_node *np, char *str,
++ ssize_t len)
++{
++ return -ENODEV;
++}
++
+ static inline int of_phandle_iterator_init(struct of_phandle_iterator *it,
+ const struct device_node *np,
+ const char *list_name,
diff --git a/target/linux/generic/backport-6.6/828-v6.4-0005-of-Move-the-request-module-helper-logic-to-module.c.patch b/target/linux/generic/backport-6.6/828-v6.4-0005-of-Move-the-request-module-helper-logic-to-module.c.patch
new file mode 100644
index 0000000000..046c1df561
--- /dev/null
+++ b/target/linux/generic/backport-6.6/828-v6.4-0005-of-Move-the-request-module-helper-logic-to-module.c.patch
@@ -0,0 +1,131 @@
+From e6506f06d5e82765666902ccf9e9162f3e31d518 Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Tue, 4 Apr 2023 18:21:18 +0100
+Subject: [PATCH] of: Move the request module helper logic to module.c
+
+Depending on device.c for pure OF handling is considered
+backwards. Let's extract the content of of_device_request_module() to
+have the real logic under module.c.
+
+The next step will be to convert users of of_device_request_module() to
+use the new helper.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Reviewed-by: Rob Herring <robh@kernel.org>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20230404172148.82422-11-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/of/device.c | 25 ++-----------------------
+ drivers/of/module.c | 30 ++++++++++++++++++++++++++++++
+ include/linux/of.h | 6 ++++++
+ 3 files changed, 38 insertions(+), 23 deletions(-)
+
+--- a/drivers/of/device.c
++++ b/drivers/of/device.c
+@@ -8,7 +8,6 @@
+ #include <linux/dma-direct.h> /* for bus_dma_region */
+ #include <linux/dma-map-ops.h>
+ #include <linux/init.h>
+-#include <linux/module.h>
+ #include <linux/mod_devicetable.h>
+ #include <linux/slab.h>
+ #include <linux/platform_device.h>
+@@ -249,30 +248,10 @@ EXPORT_SYMBOL(of_device_get_match_data);
+
+ int of_device_request_module(struct device *dev)
+ {
+- char *str;
+- ssize_t size;
+- int ret;
+-
+- if (!dev || !dev->of_node)
++ if (!dev)
+ return -ENODEV;
+
+- size = of_modalias(dev->of_node, NULL, 0);
+- if (size < 0)
+- return size;
+-
+- /* Reserve an additional byte for the trailing '\0' */
+- size++;
+-
+- str = kmalloc(size, GFP_KERNEL);
+- if (!str)
+- return -ENOMEM;
+-
+- of_modalias(dev->of_node, str, size);
+- str[size - 1] = '\0';
+- ret = request_module(str);
+- kfree(str);
+-
+- return ret;
++ return of_request_module(dev->of_node);
+ }
+ EXPORT_SYMBOL_GPL(of_device_request_module);
+
+--- a/drivers/of/module.c
++++ b/drivers/of/module.c
+@@ -4,6 +4,7 @@
+ */
+
+ #include <linux/of.h>
++#include <linux/module.h>
+ #include <linux/slab.h>
+ #include <linux/string.h>
+
+@@ -42,3 +43,32 @@ ssize_t of_modalias(const struct device_
+
+ return tsize;
+ }
++
++int of_request_module(const struct device_node *np)
++{
++ char *str;
++ ssize_t size;
++ int ret;
++
++ if (!np)
++ return -ENODEV;
++
++ size = of_modalias(np, NULL, 0);
++ if (size < 0)
++ return size;
++
++ /* Reserve an additional byte for the trailing '\0' */
++ size++;
++
++ str = kmalloc(size, GFP_KERNEL);
++ if (!str)
++ return -ENOMEM;
++
++ of_modalias(np, str, size);
++ str[size - 1] = '\0';
++ ret = request_module(str);
++ kfree(str);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(of_request_module);
+--- a/include/linux/of.h
++++ b/include/linux/of.h
+@@ -376,6 +376,7 @@ extern int of_count_phandle_with_args(co
+
+ /* module functions */
+ extern ssize_t of_modalias(const struct device_node *np, char *str, ssize_t len);
++extern int of_request_module(const struct device_node *np);
+
+ /* phandle iterator functions */
+ extern int of_phandle_iterator_init(struct of_phandle_iterator *it,
+@@ -739,6 +740,11 @@ static inline ssize_t of_modalias(const
+ {
+ return -ENODEV;
+ }
++
++static inline int of_request_module(const struct device_node *np)
++{
++ return -ENODEV;
++}
+
+ static inline int of_phandle_iterator_init(struct of_phandle_iterator *it,
+ const struct device_node *np,
diff --git a/target/linux/generic/backport-6.6/830-00-v6.2-dt-bindings-arm-qcom-document-qcom-msm-id-and-qcom-b.patch b/target/linux/generic/backport-6.6/830-00-v6.2-dt-bindings-arm-qcom-document-qcom-msm-id-and-qcom-b.patch
new file mode 100644
index 0000000000..3319f431ba
--- /dev/null
+++ b/target/linux/generic/backport-6.6/830-00-v6.2-dt-bindings-arm-qcom-document-qcom-msm-id-and-qcom-b.patch
@@ -0,0 +1,207 @@
+From 77faa07c185c969e742cbb3e6aa487a11b0b616c Mon Sep 17 00:00:00 2001
+From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Date: Tue, 30 Aug 2022 09:57:42 +0300
+Subject: [PATCH] dt-bindings: arm: qcom: document qcom,msm-id and
+ qcom,board-id
+
+The top level qcom,msm-id and qcom,board-id properties are utilized by
+bootloaders on Qualcomm MSM platforms to determine which device tree
+should be used and passed to the kernel.
+
+The commit b32e592d3c28 ("devicetree: bindings: Document qcom board
+compatible format") from 2015 was a consensus during discussion about
+upstreaming qcom,msm-id and qcom,board-id fields. There are however still
+problems with that consensus:
+1. It was reached 7 years ago but it turned out its implementation did
+ not reach all possible products.
+
+2. Initially additional tool (dtbTool) was needed for parsing these
+ fields to create a QCDT image consisting of multiple DTBs, later the
+ bootloaders were improved and they use these qcom,msm-id and
+ qcom,board-id properties directly.
+
+3. Extracting relevant information from the board compatible requires
+ this additional tool (dtbTool), which makes the build process more
+ complicated and not easily reproducible (DTBs are modified after the
+ kernel build).
+
+4. Some versions of Qualcomm bootloaders expect these properties even
+ when booting with a single DTB. The community is stuck with these
+ bootloaders thus they require properties in the DTBs.
+
+Since several upstreamed Qualcomm SoC-based boards require these
+properties to properly boot and the properties are reportedly used by
+bootloaders, document them along with the bindings header with constants
+used by: bootloader, some DTS and socinfo driver.
+
+Link: https://lore.kernel.org/r/a3c932d1-a102-ce18-deea-18cbbd05ecab@linaro.org/
+Co-developed-by: Kumar Gala <galak@codeaurora.org>
+Signed-off-by: Kumar Gala <galak@codeaurora.org>
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
+Reviewed-by: Rob Herring <robh@kernel.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20220830065744.161163-2-krzysztof.kozlowski@linaro.org
+---
+ include/dt-bindings/arm/qcom,ids.h | 155 +++++++++++++++++++++++++++++
+ 1 file changed, 155 insertions(+)
+ create mode 100644 include/dt-bindings/arm/qcom,ids.h
+
+--- /dev/null
++++ b/include/dt-bindings/arm/qcom,ids.h
+@@ -0,0 +1,155 @@
++/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */
++/*
++ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
++ * Copyright (c) 2022 Linaro Ltd
++ * Author: Krzysztof Kozlowski <krzk@kernel.org> based on previous work of Kumar Gala.
++ */
++#ifndef _DT_BINDINGS_ARM_QCOM_IDS_H
++#define _DT_BINDINGS_ARM_QCOM_IDS_H
++
++/*
++ * The MSM chipset and hardware revision used by Qualcomm bootloaders, DTS for
++ * older chipsets (qcom,msm-id) and in socinfo driver:
++ */
++#define QCOM_ID_MSM8960 87
++#define QCOM_ID_APQ8064 109
++#define QCOM_ID_MSM8660A 122
++#define QCOM_ID_MSM8260A 123
++#define QCOM_ID_APQ8060A 124
++#define QCOM_ID_MSM8974 126
++#define QCOM_ID_MPQ8064 130
++#define QCOM_ID_MSM8960AB 138
++#define QCOM_ID_APQ8060AB 139
++#define QCOM_ID_MSM8260AB 140
++#define QCOM_ID_MSM8660AB 141
++#define QCOM_ID_MSM8626 145
++#define QCOM_ID_MSM8610 147
++#define QCOM_ID_APQ8064AB 153
++#define QCOM_ID_MSM8226 158
++#define QCOM_ID_MSM8526 159
++#define QCOM_ID_MSM8110 161
++#define QCOM_ID_MSM8210 162
++#define QCOM_ID_MSM8810 163
++#define QCOM_ID_MSM8212 164
++#define QCOM_ID_MSM8612 165
++#define QCOM_ID_MSM8112 166
++#define QCOM_ID_MSM8225Q 168
++#define QCOM_ID_MSM8625Q 169
++#define QCOM_ID_MSM8125Q 170
++#define QCOM_ID_APQ8064AA 172
++#define QCOM_ID_APQ8084 178
++#define QCOM_ID_APQ8074 184
++#define QCOM_ID_MSM8274 185
++#define QCOM_ID_MSM8674 186
++#define QCOM_ID_MSM8974PRO_AC 194
++#define QCOM_ID_MSM8126 198
++#define QCOM_ID_APQ8026 199
++#define QCOM_ID_MSM8926 200
++#define QCOM_ID_MSM8326 205
++#define QCOM_ID_MSM8916 206
++#define QCOM_ID_MSM8994 207
++#define QCOM_ID_APQ8074PRO_AA 208
++#define QCOM_ID_APQ8074PRO_AB 209
++#define QCOM_ID_APQ8074PRO_AC 210
++#define QCOM_ID_MSM8274PRO_AA 211
++#define QCOM_ID_MSM8274PRO_AB 212
++#define QCOM_ID_MSM8274PRO_AC 213
++#define QCOM_ID_MSM8674PRO_AA 214
++#define QCOM_ID_MSM8674PRO_AB 215
++#define QCOM_ID_MSM8674PRO_AC 216
++#define QCOM_ID_MSM8974PRO_AA 217
++#define QCOM_ID_MSM8974PRO_AB 218
++#define QCOM_ID_APQ8028 219
++#define QCOM_ID_MSM8128 220
++#define QCOM_ID_MSM8228 221
++#define QCOM_ID_MSM8528 222
++#define QCOM_ID_MSM8628 223
++#define QCOM_ID_MSM8928 224
++#define QCOM_ID_MSM8510 225
++#define QCOM_ID_MSM8512 226
++#define QCOM_ID_MSM8936 233
++#define QCOM_ID_MSM8939 239
++#define QCOM_ID_APQ8036 240
++#define QCOM_ID_APQ8039 241
++#define QCOM_ID_MSM8996 246
++#define QCOM_ID_APQ8016 247
++#define QCOM_ID_MSM8216 248
++#define QCOM_ID_MSM8116 249
++#define QCOM_ID_MSM8616 250
++#define QCOM_ID_MSM8992 251
++#define QCOM_ID_APQ8094 253
++#define QCOM_ID_MDM9607 290
++#define QCOM_ID_APQ8096 291
++#define QCOM_ID_MSM8998 292
++#define QCOM_ID_MSM8953 293
++#define QCOM_ID_MDM8207 296
++#define QCOM_ID_MDM9207 297
++#define QCOM_ID_MDM9307 298
++#define QCOM_ID_MDM9628 299
++#define QCOM_ID_APQ8053 304
++#define QCOM_ID_MSM8996SG 305
++#define QCOM_ID_MSM8996AU 310
++#define QCOM_ID_APQ8096AU 311
++#define QCOM_ID_APQ8096SG 312
++#define QCOM_ID_SDM660 317
++#define QCOM_ID_SDM630 318
++#define QCOM_ID_APQ8098 319
++#define QCOM_ID_SDM845 321
++#define QCOM_ID_MDM9206 322
++#define QCOM_ID_IPQ8074 323
++#define QCOM_ID_SDA660 324
++#define QCOM_ID_SDM658 325
++#define QCOM_ID_SDA658 326
++#define QCOM_ID_SDA630 327
++#define QCOM_ID_SDM450 338
++#define QCOM_ID_SDA845 341
++#define QCOM_ID_IPQ8072 342
++#define QCOM_ID_IPQ8076 343
++#define QCOM_ID_IPQ8078 344
++#define QCOM_ID_SDM636 345
++#define QCOM_ID_SDA636 346
++#define QCOM_ID_SDM632 349
++#define QCOM_ID_SDA632 350
++#define QCOM_ID_SDA450 351
++#define QCOM_ID_SM8250 356
++#define QCOM_ID_IPQ8070 375
++#define QCOM_ID_IPQ8071 376
++#define QCOM_ID_IPQ8072A 389
++#define QCOM_ID_IPQ8074A 390
++#define QCOM_ID_IPQ8076A 391
++#define QCOM_ID_IPQ8078A 392
++#define QCOM_ID_SM6125 394
++#define QCOM_ID_IPQ8070A 395
++#define QCOM_ID_IPQ8071A 396
++#define QCOM_ID_IPQ6018 402
++#define QCOM_ID_IPQ6028 403
++#define QCOM_ID_IPQ6000 421
++#define QCOM_ID_IPQ6010 422
++#define QCOM_ID_SC7180 425
++#define QCOM_ID_SM6350 434
++#define QCOM_ID_SM8350 439
++#define QCOM_ID_SC8280XP 449
++#define QCOM_ID_IPQ6005 453
++#define QCOM_ID_QRB5165 455
++#define QCOM_ID_SM8450 457
++#define QCOM_ID_SM7225 459
++#define QCOM_ID_SA8295P 460
++#define QCOM_ID_SA8540P 461
++#define QCOM_ID_SM8450_2 480
++#define QCOM_ID_SM8450_3 482
++#define QCOM_ID_SC7280 487
++#define QCOM_ID_SC7180P 495
++#define QCOM_ID_SM6375 507
++
++/*
++ * The board type and revision information, used by Qualcomm bootloaders and
++ * DTS for older chipsets (qcom,board-id):
++ */
++#define QCOM_BOARD_ID(a, major, minor) \
++ (((major & 0xff) << 16) | ((minor & 0xff) << 8) | QCOM_BOARD_ID_##a)
++
++#define QCOM_BOARD_ID_MTP 8
++#define QCOM_BOARD_ID_DRAGONBOARD 10
++#define QCOM_BOARD_ID_SBC 24
++
++#endif /* _DT_BINDINGS_ARM_QCOM_IDS_H */
diff --git a/target/linux/generic/backport-6.6/830-01-v6.5-soc-qcom-socinfo-move-SMEM-item-struct-and-defines-t.patch b/target/linux/generic/backport-6.6/830-01-v6.5-soc-qcom-socinfo-move-SMEM-item-struct-and-defines-t.patch
new file mode 100644
index 0000000000..394087a27a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/830-01-v6.5-soc-qcom-socinfo-move-SMEM-item-struct-and-defines-t.patch
@@ -0,0 +1,171 @@
+From 7cbff3c3f867ff3b24de674f44ca03f54e416a37 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Sat, 31 Dec 2022 00:27:42 +0100
+Subject: [PATCH] soc: qcom: socinfo: move SMEM item struct and defines to a
+ header
+
+Move SMEM item struct and related defines to a header in order to be able
+to reuse them in the Qualcomm NVMEM CPUFreq driver instead of duplicating
+them.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230526204802.3081168-1-robimarko@gmail.com
+---
+ drivers/soc/qcom/socinfo.c | 58 +--------------------------
+ include/linux/soc/qcom/socinfo.h | 67 ++++++++++++++++++++++++++++++++
+ 2 files changed, 68 insertions(+), 57 deletions(-)
+ create mode 100644 include/linux/soc/qcom/socinfo.h
+
+--- a/drivers/soc/qcom/socinfo.c
++++ b/drivers/soc/qcom/socinfo.c
+@@ -11,6 +11,7 @@
+ #include <linux/random.h>
+ #include <linux/slab.h>
+ #include <linux/soc/qcom/smem.h>
++#include <linux/soc/qcom/socinfo.h>
+ #include <linux/string.h>
+ #include <linux/sys_soc.h>
+ #include <linux/types.h>
+@@ -25,15 +26,6 @@
+ #define SOCINFO_MINOR(ver) ((ver) & 0xffff)
+ #define SOCINFO_VERSION(maj, min) ((((maj) & 0xffff) << 16)|((min) & 0xffff))
+
+-#define SMEM_SOCINFO_BUILD_ID_LENGTH 32
+-#define SMEM_SOCINFO_CHIP_ID_LENGTH 32
+-
+-/*
+- * SMEM item id, used to acquire handles to respective
+- * SMEM region.
+- */
+-#define SMEM_HW_SW_BUILD_ID 137
+-
+ #ifdef CONFIG_DEBUG_FS
+ #define SMEM_IMAGE_VERSION_BLOCKS_COUNT 32
+ #define SMEM_IMAGE_VERSION_SIZE 4096
+@@ -116,54 +108,6 @@ static const char *const pmic_models[] =
+ };
+ #endif /* CONFIG_DEBUG_FS */
+
+-/* Socinfo SMEM item structure */
+-struct socinfo {
+- __le32 fmt;
+- __le32 id;
+- __le32 ver;
+- char build_id[SMEM_SOCINFO_BUILD_ID_LENGTH];
+- /* Version 2 */
+- __le32 raw_id;
+- __le32 raw_ver;
+- /* Version 3 */
+- __le32 hw_plat;
+- /* Version 4 */
+- __le32 plat_ver;
+- /* Version 5 */
+- __le32 accessory_chip;
+- /* Version 6 */
+- __le32 hw_plat_subtype;
+- /* Version 7 */
+- __le32 pmic_model;
+- __le32 pmic_die_rev;
+- /* Version 8 */
+- __le32 pmic_model_1;
+- __le32 pmic_die_rev_1;
+- __le32 pmic_model_2;
+- __le32 pmic_die_rev_2;
+- /* Version 9 */
+- __le32 foundry_id;
+- /* Version 10 */
+- __le32 serial_num;
+- /* Version 11 */
+- __le32 num_pmics;
+- __le32 pmic_array_offset;
+- /* Version 12 */
+- __le32 chip_family;
+- __le32 raw_device_family;
+- __le32 raw_device_num;
+- /* Version 13 */
+- __le32 nproduct_id;
+- char chip_id[SMEM_SOCINFO_CHIP_ID_LENGTH];
+- /* Version 14 */
+- __le32 num_clusters;
+- __le32 ncluster_array_offset;
+- __le32 num_defective_parts;
+- __le32 ndefective_parts_array_offset;
+- /* Version 15 */
+- __le32 nmodem_supported;
+-};
+-
+ #ifdef CONFIG_DEBUG_FS
+ struct socinfo_params {
+ u32 raw_device_family;
+--- /dev/null
++++ b/include/linux/soc/qcom/socinfo.h
+@@ -0,0 +1,67 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Copyright (c) 2009-2017, The Linux Foundation. All rights reserved.
++ * Copyright (c) 2017-2019, Linaro Ltd.
++ */
++
++#ifndef __QCOM_SOCINFO_H__
++#define __QCOM_SOCINFO_H__
++
++/*
++ * SMEM item id, used to acquire handles to respective
++ * SMEM region.
++ */
++#define SMEM_HW_SW_BUILD_ID 137
++
++#define SMEM_SOCINFO_BUILD_ID_LENGTH 32
++#define SMEM_SOCINFO_CHIP_ID_LENGTH 32
++
++/* Socinfo SMEM item structure */
++struct socinfo {
++ __le32 fmt;
++ __le32 id;
++ __le32 ver;
++ char build_id[SMEM_SOCINFO_BUILD_ID_LENGTH];
++ /* Version 2 */
++ __le32 raw_id;
++ __le32 raw_ver;
++ /* Version 3 */
++ __le32 hw_plat;
++ /* Version 4 */
++ __le32 plat_ver;
++ /* Version 5 */
++ __le32 accessory_chip;
++ /* Version 6 */
++ __le32 hw_plat_subtype;
++ /* Version 7 */
++ __le32 pmic_model;
++ __le32 pmic_die_rev;
++ /* Version 8 */
++ __le32 pmic_model_1;
++ __le32 pmic_die_rev_1;
++ __le32 pmic_model_2;
++ __le32 pmic_die_rev_2;
++ /* Version 9 */
++ __le32 foundry_id;
++ /* Version 10 */
++ __le32 serial_num;
++ /* Version 11 */
++ __le32 num_pmics;
++ __le32 pmic_array_offset;
++ /* Version 12 */
++ __le32 chip_family;
++ __le32 raw_device_family;
++ __le32 raw_device_num;
++ /* Version 13 */
++ __le32 nproduct_id;
++ char chip_id[SMEM_SOCINFO_CHIP_ID_LENGTH];
++ /* Version 14 */
++ __le32 num_clusters;
++ __le32 ncluster_array_offset;
++ __le32 num_defective_parts;
++ __le32 ndefective_parts_array_offset;
++ /* Version 15 */
++ __le32 nmodem_supported;
++};
++
++#endif
diff --git a/target/linux/generic/backport-6.6/830-02-v6.5-soc-qcom-smem-Switch-to-EXPORT_SYMBOL_GPL.patch b/target/linux/generic/backport-6.6/830-02-v6.5-soc-qcom-smem-Switch-to-EXPORT_SYMBOL_GPL.patch
new file mode 100644
index 0000000000..7c7c3f3635
--- /dev/null
+++ b/target/linux/generic/backport-6.6/830-02-v6.5-soc-qcom-smem-Switch-to-EXPORT_SYMBOL_GPL.patch
@@ -0,0 +1,55 @@
+From 9f1bbff157a69db7684f5da2f73b2325c638a90e Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 26 May 2023 22:47:59 +0200
+Subject: [PATCH] soc: qcom: smem: Switch to EXPORT_SYMBOL_GPL()
+
+SMEM has been GPL licensed from the start, and there is no reason to use
+EXPORT_SYMBOL() so switch to the GPL version.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Reviewed-by: Trilok Soni <quic_tsoni@quicinc.com>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230526204802.3081168-2-robimarko@gmail.com
+---
+ drivers/soc/qcom/smem.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+--- a/drivers/soc/qcom/smem.c
++++ b/drivers/soc/qcom/smem.c
+@@ -500,7 +500,7 @@ int qcom_smem_alloc(unsigned host, unsig
+
+ return ret;
+ }
+-EXPORT_SYMBOL(qcom_smem_alloc);
++EXPORT_SYMBOL_GPL(qcom_smem_alloc);
+
+ static void *qcom_smem_get_global(struct qcom_smem *smem,
+ unsigned item,
+@@ -674,7 +674,7 @@ void *qcom_smem_get(unsigned host, unsig
+ return ptr;
+
+ }
+-EXPORT_SYMBOL(qcom_smem_get);
++EXPORT_SYMBOL_GPL(qcom_smem_get);
+
+ /**
+ * qcom_smem_get_free_space() - retrieve amount of free space in a partition
+@@ -719,7 +719,7 @@ int qcom_smem_get_free_space(unsigned ho
+
+ return ret;
+ }
+-EXPORT_SYMBOL(qcom_smem_get_free_space);
++EXPORT_SYMBOL_GPL(qcom_smem_get_free_space);
+
+ static bool addr_in_range(void __iomem *base, size_t size, void *addr)
+ {
+@@ -770,7 +770,7 @@ phys_addr_t qcom_smem_virt_to_phys(void
+
+ return 0;
+ }
+-EXPORT_SYMBOL(qcom_smem_virt_to_phys);
++EXPORT_SYMBOL_GPL(qcom_smem_virt_to_phys);
+
+ static int qcom_smem_get_sbl_version(struct qcom_smem *smem)
+ {
diff --git a/target/linux/generic/backport-6.6/830-03-v6.5-soc-qcom-smem-introduce-qcom_smem_get_soc_id.patch b/target/linux/generic/backport-6.6/830-03-v6.5-soc-qcom-smem-introduce-qcom_smem_get_soc_id.patch
new file mode 100644
index 0000000000..4a816bb0f9
--- /dev/null
+++ b/target/linux/generic/backport-6.6/830-03-v6.5-soc-qcom-smem-introduce-qcom_smem_get_soc_id.patch
@@ -0,0 +1,70 @@
+From c3ecf2602a32d9b9e5fc997076c0d2836495c085 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 26 May 2023 22:48:00 +0200
+Subject: [PATCH] soc: qcom: smem: introduce qcom_smem_get_soc_id()
+
+Introduce a helper to return the SoC SMEM ID, which is used to identify the
+exact SoC model as there may be differences in the same SoC family.
+
+Currently, cpufreq-nvmem does this completely in the driver and there has
+been more interest expresed for other drivers to use this information so
+lets expose a common helper to prevent redoing it in individual drivers
+since this field is present on every SMEM table version.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230526204802.3081168-3-robimarko@gmail.com
+---
+ drivers/soc/qcom/smem.c | 23 +++++++++++++++++++++++
+ include/linux/soc/qcom/smem.h | 2 ++
+ 2 files changed, 25 insertions(+)
+
+--- a/drivers/soc/qcom/smem.c
++++ b/drivers/soc/qcom/smem.c
+@@ -14,6 +14,7 @@
+ #include <linux/sizes.h>
+ #include <linux/slab.h>
+ #include <linux/soc/qcom/smem.h>
++#include <linux/soc/qcom/socinfo.h>
+
+ /*
+ * The Qualcomm shared memory system is a allocate only heap structure that
+@@ -772,6 +773,28 @@ phys_addr_t qcom_smem_virt_to_phys(void
+ }
+ EXPORT_SYMBOL_GPL(qcom_smem_virt_to_phys);
+
++/**
++ * qcom_smem_get_soc_id() - return the SoC ID
++ * @id: On success, we return the SoC ID here.
++ *
++ * Look up SoC ID from HW/SW build ID and return it.
++ *
++ * Return: 0 on success, negative errno on failure.
++ */
++int qcom_smem_get_soc_id(u32 *id)
++{
++ struct socinfo *info;
++
++ info = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_HW_SW_BUILD_ID, NULL);
++ if (IS_ERR(info))
++ return PTR_ERR(info);
++
++ *id = __le32_to_cpu(info->id);
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(qcom_smem_get_soc_id);
++
+ static int qcom_smem_get_sbl_version(struct qcom_smem *smem)
+ {
+ struct smem_header *header;
+--- a/include/linux/soc/qcom/smem.h
++++ b/include/linux/soc/qcom/smem.h
+@@ -11,4 +11,6 @@ int qcom_smem_get_free_space(unsigned ho
+
+ phys_addr_t qcom_smem_virt_to_phys(void *p);
+
++int qcom_smem_get_soc_id(u32 *id);
++
+ #endif
diff --git a/target/linux/generic/backport-6.6/830-04-v6.5-cpufreq-qcom-nvmem-use-SoC-ID-s-from-bindings.patch b/target/linux/generic/backport-6.6/830-04-v6.5-cpufreq-qcom-nvmem-use-SoC-ID-s-from-bindings.patch
new file mode 100644
index 0000000000..9560122ccd
--- /dev/null
+++ b/target/linux/generic/backport-6.6/830-04-v6.5-cpufreq-qcom-nvmem-use-SoC-ID-s-from-bindings.patch
@@ -0,0 +1,51 @@
+From 2b8634d1468ff498cc91b6adf993c27ae6fa079d Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 26 May 2023 22:48:01 +0200
+Subject: [PATCH] cpufreq: qcom-nvmem: use SoC ID-s from bindings
+
+SMEM SoC ID-s are now stored in DT bindings so lets use those instead of
+defining them in the driver again.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Reviewed-by: Bjorn Andersson <bjorn.andersson@linaro.org>
+Acked-by: Viresh Kumar <viresh.kumar@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230526204802.3081168-4-robimarko@gmail.com
+---
+ drivers/cpufreq/qcom-cpufreq-nvmem.c | 15 +++++----------
+ 1 file changed, 5 insertions(+), 10 deletions(-)
+
+--- a/drivers/cpufreq/qcom-cpufreq-nvmem.c
++++ b/drivers/cpufreq/qcom-cpufreq-nvmem.c
+@@ -31,12 +31,7 @@
+
+ #define MSM_ID_SMEM 137
+
+-enum _msm_id {
+- MSM8996V3 = 0xF6ul,
+- APQ8096V3 = 0x123ul,
+- MSM8996SG = 0x131ul,
+- APQ8096SG = 0x138ul,
+-};
++#include <dt-bindings/arm/qcom,ids.h>
+
+ enum _msm8996_version {
+ MSM8996_V3,
+@@ -154,12 +149,12 @@ static enum _msm8996_version qcom_cpufre
+ msm_id++;
+
+ switch ((enum _msm_id)*msm_id) {
+- case MSM8996V3:
+- case APQ8096V3:
++ case QCOM_ID_MSM8996:
++ case QCOM_ID_APQ8096:
+ version = MSM8996_V3;
+ break;
+- case MSM8996SG:
+- case APQ8096SG:
++ case QCOM_ID_MSM8996SG:
++ case QCOM_ID_APQ8096SG:
+ version = MSM8996_SG;
+ break;
+ default:
diff --git a/target/linux/generic/backport-6.6/830-05-v6.5-cpufreq-qcom-nvmem-use-helper-to-get-SMEM-SoC-ID.patch b/target/linux/generic/backport-6.6/830-05-v6.5-cpufreq-qcom-nvmem-use-helper-to-get-SMEM-SoC-ID.patch
new file mode 100644
index 0000000000..4f37d672ba
--- /dev/null
+++ b/target/linux/generic/backport-6.6/830-05-v6.5-cpufreq-qcom-nvmem-use-helper-to-get-SMEM-SoC-ID.patch
@@ -0,0 +1,109 @@
+From e7992615acacc27baeec310197108143afc77337 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 26 May 2023 22:48:02 +0200
+Subject: [PATCH] cpufreq: qcom-nvmem: use helper to get SMEM SoC ID
+
+Now that SMEM exports a helper to get the SMEM SoC ID lets utilize it.
+Currently qcom_cpufreq_get_msm_id() is encoding the returned SMEM SoC ID
+into an enum, however there is no reason to do so and we can just match
+directly on the SMEM SoC ID as returned by qcom_smem_get_soc_id().
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Acked-by: Viresh Kumar <viresh.kumar@linaro.org>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230526204802.3081168-5-robimarko@gmail.com
+---
+ drivers/cpufreq/qcom-cpufreq-nvmem.c | 56 +++++-----------------------
+ 1 file changed, 10 insertions(+), 46 deletions(-)
+
+--- a/drivers/cpufreq/qcom-cpufreq-nvmem.c
++++ b/drivers/cpufreq/qcom-cpufreq-nvmem.c
+@@ -29,16 +29,8 @@
+ #include <linux/slab.h>
+ #include <linux/soc/qcom/smem.h>
+
+-#define MSM_ID_SMEM 137
+-
+ #include <dt-bindings/arm/qcom,ids.h>
+
+-enum _msm8996_version {
+- MSM8996_V3,
+- MSM8996_SG,
+- NUM_OF_MSM8996_VERSIONS,
+-};
+-
+ struct qcom_cpufreq_drv;
+
+ struct qcom_cpufreq_match_data {
+@@ -135,60 +127,32 @@ static void get_krait_bin_format_b(struc
+ dev_dbg(cpu_dev, "PVS version: %d\n", *pvs_ver);
+ }
+
+-static enum _msm8996_version qcom_cpufreq_get_msm_id(void)
+-{
+- size_t len;
+- u32 *msm_id;
+- enum _msm8996_version version;
+-
+- msm_id = qcom_smem_get(QCOM_SMEM_HOST_ANY, MSM_ID_SMEM, &len);
+- if (IS_ERR(msm_id))
+- return NUM_OF_MSM8996_VERSIONS;
+-
+- /* The first 4 bytes are format, next to them is the actual msm-id */
+- msm_id++;
+-
+- switch ((enum _msm_id)*msm_id) {
+- case QCOM_ID_MSM8996:
+- case QCOM_ID_APQ8096:
+- version = MSM8996_V3;
+- break;
+- case QCOM_ID_MSM8996SG:
+- case QCOM_ID_APQ8096SG:
+- version = MSM8996_SG;
+- break;
+- default:
+- version = NUM_OF_MSM8996_VERSIONS;
+- }
+-
+- return version;
+-}
+-
+ static int qcom_cpufreq_kryo_name_version(struct device *cpu_dev,
+ struct nvmem_cell *speedbin_nvmem,
+ char **pvs_name,
+ struct qcom_cpufreq_drv *drv)
+ {
+ size_t len;
++ u32 msm_id;
+ u8 *speedbin;
+- enum _msm8996_version msm8996_version;
++ int ret;
+ *pvs_name = NULL;
+
+- msm8996_version = qcom_cpufreq_get_msm_id();
+- if (NUM_OF_MSM8996_VERSIONS == msm8996_version) {
+- dev_err(cpu_dev, "Not Snapdragon 820/821!");
+- return -ENODEV;
+- }
++ ret = qcom_smem_get_soc_id(&msm_id);
++ if (ret)
++ return ret;
+
+ speedbin = nvmem_cell_read(speedbin_nvmem, &len);
+ if (IS_ERR(speedbin))
+ return PTR_ERR(speedbin);
+
+- switch (msm8996_version) {
+- case MSM8996_V3:
++ switch (msm_id) {
++ case QCOM_ID_MSM8996:
++ case QCOM_ID_APQ8096:
+ drv->versions = 1 << (unsigned int)(*speedbin);
+ break;
+- case MSM8996_SG:
++ case QCOM_ID_MSM8996SG:
++ case QCOM_ID_APQ8096SG:
+ drv->versions = 1 << ((unsigned int)(*speedbin) + 4);
+ break;
+ default:
diff --git a/target/linux/generic/backport-6.6/831-v6.7-rtc-rtc7301-Support-byte-addressed-IO.patch b/target/linux/generic/backport-6.6/831-v6.7-rtc-rtc7301-Support-byte-addressed-IO.patch
new file mode 100644
index 0000000000..ddda6e4e78
--- /dev/null
+++ b/target/linux/generic/backport-6.6/831-v6.7-rtc-rtc7301-Support-byte-addressed-IO.patch
@@ -0,0 +1,93 @@
+From edd25a77e69b7c546c28077e5dffe72c54c0afe8 Mon Sep 17 00:00:00 2001
+From: Linus Walleij <linus.walleij@linaro.org>
+Date: Thu, 21 Sep 2023 22:18:12 +0200
+Subject: [PATCH 2/4] rtc: rtc7301: Support byte-addressed IO
+
+The old RTC7301 driver in OpenWrt used byte access, but the
+current mainline Linux driver uses 32bit word access.
+
+Make this configurable using device properties using the
+standard property "reg-io-width" in e.g. device tree.
+
+This is needed for the USRobotics USR8200 which has the
+chip connected using byte accesses.
+
+Debugging and testing by Howard Harte.
+
+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
+---
+ drivers/rtc/rtc-r7301.c | 35 +++++++++++++++++++++++++++++++++--
+ 1 file changed, 33 insertions(+), 2 deletions(-)
+
+--- a/drivers/rtc/rtc-r7301.c
++++ b/drivers/rtc/rtc-r7301.c
+@@ -14,6 +14,7 @@
+ #include <linux/module.h>
+ #include <linux/mod_devicetable.h>
+ #include <linux/delay.h>
++#include <linux/property.h>
+ #include <linux/regmap.h>
+ #include <linux/platform_device.h>
+ #include <linux/rtc.h>
+@@ -55,12 +56,23 @@ struct rtc7301_priv {
+ u8 bank;
+ };
+
+-static const struct regmap_config rtc7301_regmap_config = {
++/*
++ * When the device is memory-mapped, some platforms pack the registers into
++ * 32-bit access using the lower 8 bits at each 4-byte stride, while others
++ * expose them as simply consecutive bytes.
++ */
++static const struct regmap_config rtc7301_regmap_32_config = {
+ .reg_bits = 32,
+ .val_bits = 8,
+ .reg_stride = 4,
+ };
+
++static const struct regmap_config rtc7301_regmap_8_config = {
++ .reg_bits = 8,
++ .val_bits = 8,
++ .reg_stride = 1,
++};
++
+ static u8 rtc7301_read(struct rtc7301_priv *priv, unsigned int reg)
+ {
+ int reg_stride = regmap_get_reg_stride(priv->regmap);
+@@ -356,7 +368,9 @@ static int __init rtc7301_rtc_probe(stru
+ void __iomem *regs;
+ struct rtc7301_priv *priv;
+ struct rtc_device *rtc;
++ static const struct regmap_config *mapconf;
+ int ret;
++ u32 val;
+
+ priv = devm_kzalloc(&dev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+@@ -366,8 +380,25 @@ static int __init rtc7301_rtc_probe(stru
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
++ ret = device_property_read_u32(&dev->dev, "reg-io-width", &val);
++ if (ret)
++ /* Default to 32bit accesses */
++ val = 4;
++
++ switch (val) {
++ case 1:
++ mapconf = &rtc7301_regmap_8_config;
++ break;
++ case 4:
++ mapconf = &rtc7301_regmap_32_config;
++ break;
++ default:
++ dev_err(&dev->dev, "invalid reg-io-width %d\n", val);
++ return -EINVAL;
++ }
++
+ priv->regmap = devm_regmap_init_mmio(&dev->dev, regs,
+- &rtc7301_regmap_config);
++ mapconf);
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
diff --git a/target/linux/generic/backport-6.6/832-v6.7-net-phy-amd-Support-the-Altima-AMI101L.patch b/target/linux/generic/backport-6.6/832-v6.7-net-phy-amd-Support-the-Altima-AMI101L.patch
new file mode 100644
index 0000000000..2969462838
--- /dev/null
+++ b/target/linux/generic/backport-6.6/832-v6.7-net-phy-amd-Support-the-Altima-AMI101L.patch
@@ -0,0 +1,82 @@
+From 49e5663b505070424e18099841943f34342aa405 Mon Sep 17 00:00:00 2001
+From: Linus Walleij <linus.walleij@linaro.org>
+Date: Sun, 24 Sep 2023 01:09:01 +0200
+Subject: [PATCH] net: phy: amd: Support the Altima AMI101L
+
+The Altima AC101L is obviously compatible with the AMD PHY,
+as seen by reading the datasheet.
+
+Datasheet: https://docs.broadcom.com/doc/AC101L-DS05-405-RDS.pdf
+
+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
+---
+ drivers/net/phy/Kconfig | 4 ++--
+ drivers/net/phy/amd.c | 33 +++++++++++++++++++++++----------
+ 2 files changed, 25 insertions(+), 12 deletions(-)
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -72,9 +72,9 @@ config SFP
+ comment "MII PHY device drivers"
+
+ config AMD_PHY
+- tristate "AMD PHYs"
++ tristate "AMD and Altima PHYs"
+ help
+- Currently supports the am79c874
++ Currently supports the AMD am79c874 and Altima AC101L.
+
+ config MESON_GXL_PHY
+ tristate "Amlogic Meson GXL Internal PHY"
+--- a/drivers/net/phy/amd.c
++++ b/drivers/net/phy/amd.c
+@@ -13,6 +13,7 @@
+ #include <linux/mii.h>
+ #include <linux/phy.h>
+
++#define PHY_ID_AC101L 0x00225520
+ #define PHY_ID_AM79C874 0x0022561b
+
+ #define MII_AM79C_IR 17 /* Interrupt Status/Control Register */
+@@ -87,19 +88,31 @@ static irqreturn_t am79c_handle_interrup
+ return IRQ_HANDLED;
+ }
+
+-static struct phy_driver am79c_driver[] = { {
+- .phy_id = PHY_ID_AM79C874,
+- .name = "AM79C874",
+- .phy_id_mask = 0xfffffff0,
+- /* PHY_BASIC_FEATURES */
+- .config_init = am79c_config_init,
+- .config_intr = am79c_config_intr,
+- .handle_interrupt = am79c_handle_interrupt,
+-} };
++static struct phy_driver am79c_drivers[] = {
++ {
++ .phy_id = PHY_ID_AM79C874,
++ .name = "AM79C874",
++ .phy_id_mask = 0xfffffff0,
++ /* PHY_BASIC_FEATURES */
++ .config_init = am79c_config_init,
++ .config_intr = am79c_config_intr,
++ .handle_interrupt = am79c_handle_interrupt,
++ },
++ {
++ .phy_id = PHY_ID_AC101L,
++ .name = "AC101L",
++ .phy_id_mask = 0xfffffff0,
++ /* PHY_BASIC_FEATURES */
++ .config_init = am79c_config_init,
++ .config_intr = am79c_config_intr,
++ .handle_interrupt = am79c_handle_interrupt,
++ },
++};
+
+-module_phy_driver(am79c_driver);
++module_phy_driver(am79c_drivers);
+
+ static struct mdio_device_id __maybe_unused amd_tbl[] = {
++ { PHY_ID_AC101L, 0xfffffff0 },
+ { PHY_ID_AM79C874, 0xfffffff0 },
+ { }
+ };
diff --git a/target/linux/generic/backport-6.6/833-v6.8-leds-core-Add-more-colors-from-DT-bindings-to-led_co.patch.patch b/target/linux/generic/backport-6.6/833-v6.8-leds-core-Add-more-colors-from-DT-bindings-to-led_co.patch.patch
new file mode 100644
index 0000000000..b71df6fa57
--- /dev/null
+++ b/target/linux/generic/backport-6.6/833-v6.8-leds-core-Add-more-colors-from-DT-bindings-to-led_co.patch.patch
@@ -0,0 +1,29 @@
+From a067943129b4ec6b835e02cfd5fbef01093c1471 Mon Sep 17 00:00:00 2001
+From: Ondrej Jirman <megi@xff.cz>
+Date: Sun, 8 Oct 2023 16:40:13 +0200
+Subject: [PATCH] leds: core: Add more colors from DT bindings to led_colors
+
+The colors are already part of DT bindings. Make sure the kernel is
+able to convert them to strings.
+
+Signed-off-by: Ondrej Jirman <megi@xff.cz>
+Link: https://lore.kernel.org/r/20231008144014.1180334-1-megi@xff.cz
+Signed-off-by: Lee Jones <lee@kernel.org>
+---
+ drivers/leds/led-core.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+--- a/drivers/leds/led-core.c
++++ b/drivers/leds/led-core.c
+@@ -36,6 +36,11 @@ const char * const led_colors[LED_COLOR_
+ [LED_COLOR_ID_IR] = "ir",
+ [LED_COLOR_ID_MULTI] = "multicolor",
+ [LED_COLOR_ID_RGB] = "rgb",
++ [LED_COLOR_ID_PURPLE] = "purple",
++ [LED_COLOR_ID_ORANGE] = "orange",
++ [LED_COLOR_ID_PINK] = "pink",
++ [LED_COLOR_ID_CYAN] = "cyan",
++ [LED_COLOR_ID_LIME] = "lime",
+ };
+ EXPORT_SYMBOL_GPL(led_colors);
+
diff --git a/target/linux/generic/backport-6.6/834-v6.8-leds-trigger-netdev-Extend-speeds-up-to-10G.patch b/target/linux/generic/backport-6.6/834-v6.8-leds-trigger-netdev-Extend-speeds-up-to-10G.patch
new file mode 100644
index 0000000000..1c8e014a1a
--- /dev/null
+++ b/target/linux/generic/backport-6.6/834-v6.8-leds-trigger-netdev-Extend-speeds-up-to-10G.patch
@@ -0,0 +1,111 @@
+From bc8e1da69a68d9871773b657d18400a7941cbdef Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 28 Nov 2023 04:00:10 +0000
+Subject: [PATCH] leds: trigger: netdev: Extend speeds up to 10G
+
+Add 2.5G, 5G and 10G as available speeds to the netdev LED trigger.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/99e7d3304c6bba7f4863a4a80764a869855f2085.1701143925.git.daniel@makrotopia.org
+Signed-off-by: Lee Jones <lee@kernel.org>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 32 ++++++++++++++++++++++++++-
+ include/linux/leds.h | 3 +++
+ 2 files changed, 34 insertions(+), 1 deletion(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -99,6 +99,18 @@ static void set_baseline_state(struct le
+ trigger_data->link_speed == SPEED_1000)
+ blink_on = true;
+
++ if (test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) &&
++ trigger_data->link_speed == SPEED_2500)
++ blink_on = true;
++
++ if (test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) &&
++ trigger_data->link_speed == SPEED_5000)
++ blink_on = true;
++
++ if (test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) &&
++ trigger_data->link_speed == SPEED_10000)
++ blink_on = true;
++
+ if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) &&
+ trigger_data->duplex == DUPLEX_HALF)
+ blink_on = true;
+@@ -286,6 +298,9 @@ static ssize_t netdev_led_attr_show(stru
+ case TRIGGER_NETDEV_LINK_10:
+ case TRIGGER_NETDEV_LINK_100:
+ case TRIGGER_NETDEV_LINK_1000:
++ case TRIGGER_NETDEV_LINK_2500:
++ case TRIGGER_NETDEV_LINK_5000:
++ case TRIGGER_NETDEV_LINK_10000:
+ case TRIGGER_NETDEV_HALF_DUPLEX:
+ case TRIGGER_NETDEV_FULL_DUPLEX:
+ case TRIGGER_NETDEV_TX:
+@@ -316,6 +331,9 @@ static ssize_t netdev_led_attr_store(str
+ case TRIGGER_NETDEV_LINK_10:
+ case TRIGGER_NETDEV_LINK_100:
+ case TRIGGER_NETDEV_LINK_1000:
++ case TRIGGER_NETDEV_LINK_2500:
++ case TRIGGER_NETDEV_LINK_5000:
++ case TRIGGER_NETDEV_LINK_10000:
+ case TRIGGER_NETDEV_HALF_DUPLEX:
+ case TRIGGER_NETDEV_FULL_DUPLEX:
+ case TRIGGER_NETDEV_TX:
+@@ -334,7 +352,10 @@ static ssize_t netdev_led_attr_store(str
+ if (test_bit(TRIGGER_NETDEV_LINK, &mode) &&
+ (test_bit(TRIGGER_NETDEV_LINK_10, &mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_100, &mode) ||
+- test_bit(TRIGGER_NETDEV_LINK_1000, &mode)))
++ test_bit(TRIGGER_NETDEV_LINK_1000, &mode) ||
++ test_bit(TRIGGER_NETDEV_LINK_2500, &mode) ||
++ test_bit(TRIGGER_NETDEV_LINK_5000, &mode) ||
++ test_bit(TRIGGER_NETDEV_LINK_10000, &mode)))
+ return -EINVAL;
+
+ cancel_delayed_work_sync(&trigger_data->work);
+@@ -364,6 +385,9 @@ DEFINE_NETDEV_TRIGGER(link, TRIGGER_NETD
+ DEFINE_NETDEV_TRIGGER(link_10, TRIGGER_NETDEV_LINK_10);
+ DEFINE_NETDEV_TRIGGER(link_100, TRIGGER_NETDEV_LINK_100);
+ DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000);
++DEFINE_NETDEV_TRIGGER(link_2500, TRIGGER_NETDEV_LINK_2500);
++DEFINE_NETDEV_TRIGGER(link_5000, TRIGGER_NETDEV_LINK_5000);
++DEFINE_NETDEV_TRIGGER(link_10000, TRIGGER_NETDEV_LINK_10000);
+ DEFINE_NETDEV_TRIGGER(half_duplex, TRIGGER_NETDEV_HALF_DUPLEX);
+ DEFINE_NETDEV_TRIGGER(full_duplex, TRIGGER_NETDEV_FULL_DUPLEX);
+ DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX);
+@@ -422,6 +446,9 @@ static struct attribute *netdev_trig_att
+ &dev_attr_link_10.attr,
+ &dev_attr_link_100.attr,
+ &dev_attr_link_1000.attr,
++ &dev_attr_link_2500.attr,
++ &dev_attr_link_5000.attr,
++ &dev_attr_link_10000.attr,
+ &dev_attr_full_duplex.attr,
+ &dev_attr_half_duplex.attr,
+ &dev_attr_rx.attr,
+@@ -519,6 +546,9 @@ static void netdev_trig_work(struct work
+ test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) ||
++ test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) ||
++ test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) ||
++ test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode);
+ interval = jiffies_to_msecs(
+--- a/include/linux/leds.h
++++ b/include/linux/leds.h
+@@ -533,6 +533,9 @@ enum led_trigger_netdev_modes {
+ TRIGGER_NETDEV_LINK_10,
+ TRIGGER_NETDEV_LINK_100,
+ TRIGGER_NETDEV_LINK_1000,
++ TRIGGER_NETDEV_LINK_2500,
++ TRIGGER_NETDEV_LINK_5000,
++ TRIGGER_NETDEV_LINK_10000,
+ TRIGGER_NETDEV_HALF_DUPLEX,
+ TRIGGER_NETDEV_FULL_DUPLEX,
+ TRIGGER_NETDEV_TX,
diff --git a/target/linux/generic/backport-6.6/835-v6.9-net-phy-add-support-for-PHY-LEDs-polarity-modes.patch b/target/linux/generic/backport-6.6/835-v6.9-net-phy-add-support-for-PHY-LEDs-polarity-modes.patch
new file mode 100644
index 0000000000..0182e6d1a2
--- /dev/null
+++ b/target/linux/generic/backport-6.6/835-v6.9-net-phy-add-support-for-PHY-LEDs-polarity-modes.patch
@@ -0,0 +1,98 @@
+From 7ae215ee7bb855f13c80565470fc7f67db4ba82f Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 25 Jan 2024 21:36:59 +0100
+Subject: [PATCH 3/5] net: phy: add support for PHY LEDs polarity modes
+
+Add support for PHY LEDs polarity modes. Some PHY require LED to be set
+to active low to be turned ON. Adds support for this by declaring
+active-low property in DT.
+
+PHY driver needs to declare .led_polarity_set() to configure LED
+polarity modes. Function will pass the index with the LED index and a
+bitmap with all the required modes to set.
+
+Current supported modes are:
+- active-low with the flag PHY_LED_ACTIVE_LOW. LED is set to active-low
+ to turn it ON.
+- inactive-high-impedance with the flag PHY_LED_INACTIVE_HIGH_IMPEDANCE.
+ LED is set to high impedance to turn it OFF.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/20240125203702.4552-4-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phy_device.c | 16 ++++++++++++++++
+ include/linux/phy.h | 22 ++++++++++++++++++++++
+ 2 files changed, 38 insertions(+)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -3138,6 +3138,7 @@ static int of_phy_led(struct phy_device
+ struct device *dev = &phydev->mdio.dev;
+ struct led_init_data init_data = {};
+ struct led_classdev *cdev;
++ unsigned long modes = 0;
+ struct phy_led *phyled;
+ u32 index;
+ int err;
+@@ -3155,6 +3156,21 @@ static int of_phy_led(struct phy_device
+ if (index > U8_MAX)
+ return -EINVAL;
+
++ if (of_property_read_bool(led, "active-low"))
++ set_bit(PHY_LED_ACTIVE_LOW, &modes);
++ if (of_property_read_bool(led, "inactive-high-impedance"))
++ set_bit(PHY_LED_INACTIVE_HIGH_IMPEDANCE, &modes);
++
++ if (modes) {
++ /* Return error if asked to set polarity modes but not supported */
++ if (!phydev->drv->led_polarity_set)
++ return -EINVAL;
++
++ err = phydev->drv->led_polarity_set(phydev, index, modes);
++ if (err)
++ return err;
++ }
++
+ phyled->index = index;
+ if (phydev->drv->led_brightness_set)
+ cdev->brightness_set_blocking = phy_led_set_brightness;
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -791,6 +791,15 @@ struct phy_led {
+
+ #define to_phy_led(d) container_of(d, struct phy_led, led_cdev)
+
++/* Modes for PHY LED configuration */
++enum phy_led_modes {
++ PHY_LED_ACTIVE_LOW = 0,
++ PHY_LED_INACTIVE_HIGH_IMPEDANCE = 1,
++
++ /* keep it last */
++ __PHY_LED_MODES_NUM,
++};
++
+ /**
+ * struct phy_driver - Driver structure for a particular PHY type
+ *
+@@ -1059,6 +1068,19 @@ struct phy_driver {
+ int (*led_hw_control_get)(struct phy_device *dev, u8 index,
+ unsigned long *rules);
+
++ /**
++ * @led_polarity_set: Set the LED polarity modes
++ * @dev: PHY device which has the LED
++ * @index: Which LED of the PHY device
++ * @modes: bitmap of LED polarity modes
++ *
++ * Configure LED with all the required polarity modes in @modes
++ * to make it correctly turn ON or OFF.
++ *
++ * Returns 0, or an error code.
++ */
++ int (*led_polarity_set)(struct phy_device *dev, int index,
++ unsigned long modes);
+ };
+ #define to_phy_driver(d) container_of(to_mdio_common_driver(d), \
+ struct phy_driver, mdiodrv)
diff --git a/target/linux/generic/backport-6.6/836-v6.7-leds-trigger-netdev-fix-RTNL-handling-to-prevent-pot.patch b/target/linux/generic/backport-6.6/836-v6.7-leds-trigger-netdev-fix-RTNL-handling-to-prevent-pot.patch
new file mode 100644
index 0000000000..cbb9172032
--- /dev/null
+++ b/target/linux/generic/backport-6.6/836-v6.7-leds-trigger-netdev-fix-RTNL-handling-to-prevent-pot.patch
@@ -0,0 +1,170 @@
+From fe2b1226656afae56702d1d84c6900f6b67df297 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Fri, 1 Dec 2023 11:23:22 +0100
+Subject: [PATCH] leds: trigger: netdev: fix RTNL handling to prevent potential
+ deadlock
+
+When working on LED support for r8169 I got the following lockdep
+warning. Easiest way to prevent this scenario seems to be to take
+the RTNL lock before the trigger_data lock in set_device_name().
+
+======================================================
+WARNING: possible circular locking dependency detected
+6.7.0-rc2-next-20231124+ #2 Not tainted
+------------------------------------------------------
+bash/383 is trying to acquire lock:
+ffff888103aa1c68 (&trigger_data->lock){+.+.}-{3:3}, at: netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
+
+but task is already holding lock:
+ffffffff8cddf808 (rtnl_mutex){+.+.}-{3:3}, at: rtnl_lock+0x12/0x20
+
+which lock already depends on the new lock.
+
+the existing dependency chain (in reverse order) is:
+
+-> #1 (rtnl_mutex){+.+.}-{3:3}:
+ __mutex_lock+0x9b/0xb50
+ mutex_lock_nested+0x16/0x20
+ rtnl_lock+0x12/0x20
+ set_device_name+0xa9/0x120 [ledtrig_netdev]
+ netdev_trig_activate+0x1a1/0x230 [ledtrig_netdev]
+ led_trigger_set+0x172/0x2c0
+ led_trigger_write+0xf1/0x140
+ sysfs_kf_bin_write+0x5d/0x80
+ kernfs_fop_write_iter+0x15d/0x210
+ vfs_write+0x1f0/0x510
+ ksys_write+0x6c/0xf0
+ __x64_sys_write+0x14/0x20
+ do_syscall_64+0x3f/0xf0
+ entry_SYSCALL_64_after_hwframe+0x6c/0x74
+
+-> #0 (&trigger_data->lock){+.+.}-{3:3}:
+ __lock_acquire+0x1459/0x25a0
+ lock_acquire+0xc8/0x2d0
+ __mutex_lock+0x9b/0xb50
+ mutex_lock_nested+0x16/0x20
+ netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
+ call_netdevice_register_net_notifiers+0x5a/0x100
+ register_netdevice_notifier+0x85/0x120
+ netdev_trig_activate+0x1d4/0x230 [ledtrig_netdev]
+ led_trigger_set+0x172/0x2c0
+ led_trigger_write+0xf1/0x140
+ sysfs_kf_bin_write+0x5d/0x80
+ kernfs_fop_write_iter+0x15d/0x210
+ vfs_write+0x1f0/0x510
+ ksys_write+0x6c/0xf0
+ __x64_sys_write+0x14/0x20
+ do_syscall_64+0x3f/0xf0
+ entry_SYSCALL_64_after_hwframe+0x6c/0x74
+
+other info that might help us debug this:
+
+ Possible unsafe locking scenario:
+
+ CPU0 CPU1
+ ---- ----
+ lock(rtnl_mutex);
+ lock(&trigger_data->lock);
+ lock(rtnl_mutex);
+ lock(&trigger_data->lock);
+
+ *** DEADLOCK ***
+
+8 locks held by bash/383:
+ #0: ffff888103ff33f0 (sb_writers#3){.+.+}-{0:0}, at: ksys_write+0x6c/0xf0
+ #1: ffff888103aa1e88 (&of->mutex){+.+.}-{3:3}, at: kernfs_fop_write_iter+0x114/0x210
+ #2: ffff8881036f1890 (kn->active#82){.+.+}-{0:0}, at: kernfs_fop_write_iter+0x11d/0x210
+ #3: ffff888108e2c358 (&led_cdev->led_access){+.+.}-{3:3}, at: led_trigger_write+0x30/0x140
+ #4: ffffffff8cdd9e10 (triggers_list_lock){++++}-{3:3}, at: led_trigger_write+0x75/0x140
+ #5: ffff888108e2c270 (&led_cdev->trigger_lock){++++}-{3:3}, at: led_trigger_write+0xe3/0x140
+ #6: ffffffff8cdde3d0 (pernet_ops_rwsem){++++}-{3:3}, at: register_netdevice_notifier+0x1c/0x120
+ #7: ffffffff8cddf808 (rtnl_mutex){+.+.}-{3:3}, at: rtnl_lock+0x12/0x20
+
+stack backtrace:
+CPU: 0 PID: 383 Comm: bash Not tainted 6.7.0-rc2-next-20231124+ #2
+Hardware name: Default string Default string/Default string, BIOS ADLN.M6.SODIMM.ZB.CY.015 08/08/2023
+Call Trace:
+ <TASK>
+ dump_stack_lvl+0x5c/0xd0
+ dump_stack+0x10/0x20
+ print_circular_bug+0x2dd/0x410
+ check_noncircular+0x131/0x150
+ __lock_acquire+0x1459/0x25a0
+ lock_acquire+0xc8/0x2d0
+ ? netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
+ __mutex_lock+0x9b/0xb50
+ ? netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
+ ? __this_cpu_preempt_check+0x13/0x20
+ ? netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
+ ? __cancel_work_timer+0x11c/0x1b0
+ ? __mutex_lock+0x123/0xb50
+ mutex_lock_nested+0x16/0x20
+ ? mutex_lock_nested+0x16/0x20
+ netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
+ call_netdevice_register_net_notifiers+0x5a/0x100
+ register_netdevice_notifier+0x85/0x120
+ netdev_trig_activate+0x1d4/0x230 [ledtrig_netdev]
+ led_trigger_set+0x172/0x2c0
+ ? preempt_count_add+0x49/0xc0
+ led_trigger_write+0xf1/0x140
+ sysfs_kf_bin_write+0x5d/0x80
+ kernfs_fop_write_iter+0x15d/0x210
+ vfs_write+0x1f0/0x510
+ ksys_write+0x6c/0xf0
+ __x64_sys_write+0x14/0x20
+ do_syscall_64+0x3f/0xf0
+ entry_SYSCALL_64_after_hwframe+0x6c/0x74
+RIP: 0033:0x7f269055d034
+Code: c7 00 16 00 00 00 b8 ff ff ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 f3 0f 1e fa 80 3d 35 c3 0d 00 00 74 13 b8 01 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 54 c3 0f 1f 00 48 83 ec 28 48 89 54 24 18 48
+RSP: 002b:00007ffddb7ef748 EFLAGS: 00000202 ORIG_RAX: 0000000000000001
+RAX: ffffffffffffffda RBX: 0000000000000007 RCX: 00007f269055d034
+RDX: 0000000000000007 RSI: 000055bf5f4af3c0 RDI: 0000000000000001
+RBP: 000055bf5f4af3c0 R08: 0000000000000073 R09: 0000000000000001
+R10: 0000000000000000 R11: 0000000000000202 R12: 0000000000000007
+R13: 00007f26906325c0 R14: 00007f269062ff20 R15: 0000000000000000
+ </TASK>
+
+Fixes: d5e01266e7f5 ("leds: trigger: netdev: add additional specific link speed mode")
+Cc: stable@vger.kernel.org
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Acked-by: Lee Jones <lee@kernel.org>
+Link: https://lore.kernel.org/r/fb5c8294-2a10-4bf5-8f10-3d2b77d2757e@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 11 +++++++----
+ 1 file changed, 7 insertions(+), 4 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -235,6 +235,11 @@ static int set_device_name(struct led_ne
+ {
+ cancel_delayed_work_sync(&trigger_data->work);
+
++ /*
++ * Take RTNL lock before trigger_data lock to prevent potential
++ * deadlock with netdev notifier registration.
++ */
++ rtnl_lock();
+ mutex_lock(&trigger_data->lock);
+
+ if (trigger_data->net_dev) {
+@@ -254,16 +259,14 @@ static int set_device_name(struct led_ne
+ trigger_data->carrier_link_up = false;
+ trigger_data->link_speed = SPEED_UNKNOWN;
+ trigger_data->duplex = DUPLEX_UNKNOWN;
+- if (trigger_data->net_dev != NULL) {
+- rtnl_lock();
++ if (trigger_data->net_dev)
+ get_device_state(trigger_data);
+- rtnl_unlock();
+- }
+
+ trigger_data->last_activity = 0;
+
+ set_baseline_state(trigger_data);
+ mutex_unlock(&trigger_data->lock);
++ rtnl_unlock();
+
+ return 0;
+ }
diff --git a/target/linux/generic/backport-6.6/837-v6.4-net-phy-hide-the-PHYLIB_LEDS-knob.patch b/target/linux/generic/backport-6.6/837-v6.4-net-phy-hide-the-PHYLIB_LEDS-knob.patch
new file mode 100644
index 0000000000..51b4bbbfcb
--- /dev/null
+++ b/target/linux/generic/backport-6.6/837-v6.4-net-phy-hide-the-PHYLIB_LEDS-knob.patch
@@ -0,0 +1,43 @@
+From 9b78d919632b7149d311aaad5a977e4b48b10321 Mon Sep 17 00:00:00 2001
+From: Paolo Abeni <pabeni@redhat.com>
+Date: Wed, 26 Apr 2023 10:15:31 +0200
+Subject: [PATCH] net: phy: hide the PHYLIB_LEDS knob
+
+commit 4bb7aac70b5d ("net: phy: fix circular LEDS_CLASS dependencies")
+solved a build failure, but introduces a new config knob with a default
+'y' value: PHYLIB_LEDS.
+
+The latter is against the current new config policy. The exception
+was raised to allow the user to catch bad configurations without led
+support.
+
+Anyway the current definition of PHYLIB_LEDS does not fit the above
+goal: if LEDS_CLASS is disabled, the new config will be available
+only with PHYLIB disabled, too.
+
+Hide the mentioned config, to preserve the randconfig testing done so
+far, while respecting the mentioned policy.
+
+Suggested-by: Andrew Lunn <andrew@lunn.ch>
+Suggested-by: Arnd Bergmann <arnd@arndb.de>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Link: https://lore.kernel.org/r/d82489be8ed911c383c3447e9abf469995ccf39a.1682496488.git.pabeni@redhat.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/Kconfig | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -45,10 +45,8 @@ config LED_TRIGGER_PHY
+ for any speed known to the PHY.
+
+ config PHYLIB_LEDS
+- bool "Support probing LEDs from device tree"
++ def_bool OF
+ depends on LEDS_CLASS=y || LEDS_CLASS=PHYLIB
+- depends on OF
+- default y
+ help
+ When LED class support is enabled, phylib can automatically
+ probe LED setting from device tree.
diff --git a/target/linux/generic/backport-6.6/838-v6.9-leds-trigger-netdev-Fix-kernel-panic-on-interface-re.patch b/target/linux/generic/backport-6.6/838-v6.9-leds-trigger-netdev-Fix-kernel-panic-on-interface-re.patch
new file mode 100644
index 0000000000..8d391678ff
--- /dev/null
+++ b/target/linux/generic/backport-6.6/838-v6.9-leds-trigger-netdev-Fix-kernel-panic-on-interface-re.patch
@@ -0,0 +1,55 @@
+From 12ce20e02e532f101b725d71c52a36c5cc8ad1e6 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Sun, 4 Feb 2024 00:54:01 +0100
+Subject: [PATCH] leds: trigger: netdev: Fix kernel panic on interface rename
+ trig notify
+
+Commit d5e01266e7f5 ("leds: trigger: netdev: add additional specific link
+speed mode") in the various changes, reworked the way to set the LINKUP
+mode in commit cee4bd16c319 ("leds: trigger: netdev: Recheck
+NETDEV_LED_MODE_LINKUP on dev rename") and moved it to a generic function.
+
+This changed the logic where, in the previous implementation the dev
+from the trigger event was used to check if the carrier was ok, but in
+the new implementation with the generic function, the dev in
+trigger_data is used instead.
+
+This is problematic and cause a possible kernel panic due to the fact
+that the dev in the trigger_data still reference the old one as the
+new one (passed from the trigger event) still has to be hold and saved
+in the trigger_data struct (done in the NETDEV_REGISTER case).
+
+On calling of get_device_state(), an invalid net_dev is used and this
+cause a kernel panic.
+
+To handle this correctly, move the call to get_device_state() after the
+new net_dev is correctly set in trigger_data (in the NETDEV_REGISTER
+case) and correctly parse the new dev.
+
+Fixes: d5e01266e7f5 ("leds: trigger: netdev: add additional specific link speed mode")
+Cc: stable@vger.kernel.org
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/20240203235413.1146-1-ansuelsmth@gmail.com
+Signed-off-by: Lee Jones <lee@kernel.org>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -489,12 +489,12 @@ static int netdev_trig_notify(struct not
+ trigger_data->duplex = DUPLEX_UNKNOWN;
+ switch (evt) {
+ case NETDEV_CHANGENAME:
+- get_device_state(trigger_data);
+- fallthrough;
+ case NETDEV_REGISTER:
+ dev_put(trigger_data->net_dev);
+ dev_hold(dev);
+ trigger_data->net_dev = dev;
++ if (evt == NETDEV_CHANGENAME)
++ get_device_state(trigger_data);
+ break;
+ case NETDEV_UNREGISTER:
+ dev_put(trigger_data->net_dev);
diff --git a/target/linux/generic/backport-6.6/890-v6.2-mtd-spinand-winbond-fix-flash-detection.patch b/target/linux/generic/backport-6.6/890-v6.2-mtd-spinand-winbond-fix-flash-detection.patch
new file mode 100644
index 0000000000..38fbc3a3d7
--- /dev/null
+++ b/target/linux/generic/backport-6.6/890-v6.2-mtd-spinand-winbond-fix-flash-detection.patch
@@ -0,0 +1,40 @@
+From dbf70fc204d2fbb0d8ad8f42038a60846502efda Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Date: Mon, 10 Oct 2022 13:51:09 +0300
+Subject: [PATCH] mtd: spinand: winbond: fix flash identification
+
+Winbond uses 3 bytes to identify flash: vendor_id, dev_id_0, dev_id_1,
+but current driver uses only first 2 bytes of it for devices
+identification. As result Winbond W25N02KV flash (id_bytes: EF, AA, 22)
+is identified as W25N01GV (id_bytes: EF, AA, 21).
+
+Fix this by adding missed identification bytes.
+
+Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Reviewed-by: Frieder Schrempf <frieder.schrempf@kontron.de>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Link: https://lore.kernel.org/linux-mtd/20221010105110.446674-1-mikhail.kshevetskiy@iopsys.eu
+---
+ drivers/mtd/nand/spi/winbond.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/mtd/nand/spi/winbond.c
++++ b/drivers/mtd/nand/spi/winbond.c
+@@ -76,7 +76,7 @@ static int w25m02gv_select_target(struct
+
+ static const struct spinand_info winbond_spinand_table[] = {
+ SPINAND_INFO("W25M02GV",
+- SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xab),
++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xab, 0x21),
+ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 2),
+ NAND_ECCREQ(1, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+@@ -86,7 +86,7 @@ static const struct spinand_info winbond
+ SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL),
+ SPINAND_SELECT_TARGET(w25m02gv_select_target)),
+ SPINAND_INFO("W25N01GV",
+- SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa),
++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x21),
+ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
+ NAND_ECCREQ(1, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
diff --git a/target/linux/generic/backport-6.6/891-v6.2-mtd-spinand-winbond-add-W25N02KV.patch b/target/linux/generic/backport-6.6/891-v6.2-mtd-spinand-winbond-add-W25N02KV.patch
new file mode 100644
index 0000000000..d75a1acc57
--- /dev/null
+++ b/target/linux/generic/backport-6.6/891-v6.2-mtd-spinand-winbond-add-W25N02KV.patch
@@ -0,0 +1,106 @@
+From 6154c7a583483d7b69f53bea868efdc369edd563 Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Date: Mon, 10 Oct 2022 13:51:10 +0300
+Subject: [PATCH] mtd: spinand: winbond: add Winbond W25N02KV flash support
+
+Add support of Winbond W25N02KV flash
+
+Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Reviewed-by: Frieder Schrempf <frieder.schrempf@kontron.de>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Link: https://lore.kernel.org/linux-mtd/20221010105110.446674-2-mikhail.kshevetskiy@iopsys.eu
+---
+ drivers/mtd/nand/spi/winbond.c | 75 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 75 insertions(+)
+
+--- a/drivers/mtd/nand/spi/winbond.c
++++ b/drivers/mtd/nand/spi/winbond.c
+@@ -74,6 +74,72 @@ static int w25m02gv_select_target(struct
+ return spi_mem_exec_op(spinand->spimem, &op);
+ }
+
++static int w25n02kv_ooblayout_ecc(struct mtd_info *mtd, int section,
++ struct mtd_oob_region *region)
++{
++ if (section > 3)
++ return -ERANGE;
++
++ region->offset = 64 + (16 * section);
++ region->length = 13;
++
++ return 0;
++}
++
++static int w25n02kv_ooblayout_free(struct mtd_info *mtd, int section,
++ struct mtd_oob_region *region)
++{
++ if (section > 3)
++ return -ERANGE;
++
++ region->offset = (16 * section) + 2;
++ region->length = 14;
++
++ return 0;
++}
++
++static const struct mtd_ooblayout_ops w25n02kv_ooblayout = {
++ .ecc = w25n02kv_ooblayout_ecc,
++ .free = w25n02kv_ooblayout_free,
++};
++
++static int w25n02kv_ecc_get_status(struct spinand_device *spinand,
++ u8 status)
++{
++ struct nand_device *nand = spinand_to_nand(spinand);
++ u8 mbf = 0;
++ struct spi_mem_op op = SPINAND_GET_FEATURE_OP(0x30, &mbf);
++
++ switch (status & STATUS_ECC_MASK) {
++ case STATUS_ECC_NO_BITFLIPS:
++ return 0;
++
++ case STATUS_ECC_UNCOR_ERROR:
++ return -EBADMSG;
++
++ case STATUS_ECC_HAS_BITFLIPS:
++ /*
++ * Let's try to retrieve the real maximum number of bitflips
++ * in order to avoid forcing the wear-leveling layer to move
++ * data around if it's not necessary.
++ */
++ if (spi_mem_exec_op(spinand->spimem, &op))
++ return nanddev_get_ecc_conf(nand)->strength;
++
++ mbf >>= 4;
++
++ if (WARN_ON(mbf > nanddev_get_ecc_conf(nand)->strength || !mbf))
++ return nanddev_get_ecc_conf(nand)->strength;
++
++ return mbf;
++
++ default:
++ break;
++ }
++
++ return -EINVAL;
++}
++
+ static const struct spinand_info winbond_spinand_table[] = {
+ SPINAND_INFO("W25M02GV",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xab, 0x21),
+@@ -94,6 +160,15 @@ static const struct spinand_info winbond
+ &update_cache_variants),
+ 0,
+ SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL)),
++ SPINAND_INFO("W25N02KV",
++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x22),
++ 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),
++ 0,
++ SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
+ };
+
+ static int winbond_spinand_init(struct spinand_device *spinand)
diff --git a/target/linux/generic/backport-6.6/892-v6.5-mtd-spinand-winbond-Fix-ecc_get_status.patch b/target/linux/generic/backport-6.6/892-v6.5-mtd-spinand-winbond-Fix-ecc_get_status.patch
new file mode 100644
index 0000000000..2f408f5a30
--- /dev/null
+++ b/target/linux/generic/backport-6.6/892-v6.5-mtd-spinand-winbond-Fix-ecc_get_status.patch
@@ -0,0 +1,49 @@
+From f5a05060670a4d8d6523afc7963eb559c2e3615f Mon Sep 17 00:00:00 2001
+From: Olivier Maignial <olivier.maignial@hotmail.fr>
+Date: Fri, 23 Jun 2023 17:33:37 +0200
+Subject: [PATCH] mtd: spinand: winbond: Fix ecc_get_status
+
+Reading ECC status is failing.
+
+w25n02kv_ecc_get_status() is using on-stack buffer for
+SPINAND_GET_FEATURE_OP() output. It is not suitable for
+DMA needs of spi-mem.
+
+Fix this by using the spi-mem operations dedicated buffer
+spinand->scratchbuf.
+
+See
+spinand->scratchbuf:
+https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/mtd/spinand.h?h=v6.3#n418
+spi_mem_check_op():
+https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/spi/spi-mem.c?h=v6.3#n199
+
+Fixes: 6154c7a58348 ("mtd: spinand: winbond: add Winbond W25N02KV flash support")
+Cc: stable@vger.kernel.org
+Signed-off-by: Olivier Maignial <olivier.maignial@hotmail.fr>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Link: https://lore.kernel.org/linux-mtd/DB4P250MB1032EDB9E36B764A33769039FE23A@DB4P250MB1032.EURP250.PROD.OUTLOOK.COM
+---
+ drivers/mtd/nand/spi/winbond.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/mtd/nand/spi/winbond.c
++++ b/drivers/mtd/nand/spi/winbond.c
+@@ -108,7 +108,7 @@ static int w25n02kv_ecc_get_status(struc
+ {
+ struct nand_device *nand = spinand_to_nand(spinand);
+ u8 mbf = 0;
+- struct spi_mem_op op = SPINAND_GET_FEATURE_OP(0x30, &mbf);
++ struct spi_mem_op op = SPINAND_GET_FEATURE_OP(0x30, spinand->scratchbuf);
+
+ switch (status & STATUS_ECC_MASK) {
+ case STATUS_ECC_NO_BITFLIPS:
+@@ -126,7 +126,7 @@ static int w25n02kv_ecc_get_status(struc
+ if (spi_mem_exec_op(spinand->spimem, &op))
+ return nanddev_get_ecc_conf(nand)->strength;
+
+- mbf >>= 4;
++ mbf = *(spinand->scratchbuf) >> 4;
+
+ if (WARN_ON(mbf > nanddev_get_ecc_conf(nand)->strength || !mbf))
+ return nanddev_get_ecc_conf(nand)->strength;
diff --git a/target/linux/generic/backport-6.6/894-v6.8-net-ethtool-implement-ethtool_puts.patch b/target/linux/generic/backport-6.6/894-v6.8-net-ethtool-implement-ethtool_puts.patch
new file mode 100644
index 0000000000..5094a6d774
--- /dev/null
+++ b/target/linux/generic/backport-6.6/894-v6.8-net-ethtool-implement-ethtool_puts.patch
@@ -0,0 +1,139 @@
+From mboxrd@z Thu Jan 1 00:00:00 1970
+Authentication-Results: smtp.subspace.kernel.org;
+ dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="sMUeie/T"
+Received: from mail-yb1-xb49.google.com (mail-yb1-xb49.google.com [IPv6:2607:f8b0:4864:20::b49])
+ by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 84BB8D6D
+ for <bpf@vger.kernel.org>; Wed, 6 Dec 2023 15:16:16 -0800 (PST)
+Received: by mail-yb1-xb49.google.com with SMTP id 3f1490d57ef6-db5416d0fccso403298276.1
+ for <bpf@vger.kernel.org>; Wed, 06 Dec 2023 15:16:16 -0800 (PST)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+ d=google.com; s=20230601; t=1701904575; x=1702509375; darn=vger.kernel.org;
+ h=cc:to:from:subject:message-id:references:mime-version:in-reply-to
+ :date:from:to:cc:subject:date:message-id:reply-to;
+ bh=/7eYcPC4ZNNyPcPPs0B5tDplF0arxw3r0vINNNou0rY=;
+ b=sMUeie/TxdytzC0EyT11QWi1TqTtiv7KCTs1F2vLmUUvPKNA3+1MHFo8ECW+0gQuDE
+ FGrgdZKGK5mXQgkF0N3JiSLvKO8tpQOIB57JLCG5IVy5dr2vVv0ExU3Dag2Cc4oBIBIO
+ w/cH95O1oPlvluIpATmAsxenVr7mFomU63BqYiRGLaEhWeb2hJ636GO8lubtsDfdFFoi
+ GPOL2tQwV93VnqmywBBpFaNAULN0UoCFhfkKv5prvpkXq19sWI7zyorVZ+rdTYem5m4T
+ dXsDaLXPtC3Dh2JOad1duSQIah/wCHYYUcV3IoFhwj2y0Uk/TTCrnZPORweSADcEy6Ho
+ vDrA==
+X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+ d=1e100.net; s=20230601; t=1701904575; x=1702509375;
+ h=cc:to:from:subject:message-id:references:mime-version:in-reply-to
+ :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to;
+ bh=/7eYcPC4ZNNyPcPPs0B5tDplF0arxw3r0vINNNou0rY=;
+ b=Dmc6aSntxPlxAk72zVO1G9WoZnFtLolxENlLscYYAHG3VE+PQ8gGN2rPzcGoKb2Btb
+ 4b0PvjOzSlPQyghahdhdlz04RtAeeGG/MkfNiYjFql5OifIoovb51kroiPYrVsa7Ps7Y
+ +Pxug0+NPdTm5s9TNz940ZKl3GRME8UTmVxpWJRX03XMOqb6Wgsh2SK9ahXKc4yRsi62
+ 3a3J72WmmSgvimxwM/99fXwvoUQpiv2J1xCoqc1Ng4q4qSuZvzmHN7ZTGaUhLxOqLeLK
+ 3W4RKHW6rZ7UjppuB6I3NXW+D344By2rdKp1sRXpjdQ0GS3YUcvlRETcJBXJudHfQP5Y
+ CLOw==
+X-Gm-Message-State: AOJu0YzdCTLdwny+N99zeMgyKqFsEZhfIhL2cbgKA6zC1U/OLkxxRLoM
+ XrYVBC9DmxCGmP4o+M/Z/kHUew/9faHlCiLGxw==
+X-Google-Smtp-Source: AGHT+IFRXxBV6JuX5Cl/k2o1+WKkCwkR8j20MJSkmoGCedPAtqFttH8OVh1/6vdfnq8MPN++A2h89peZQhyG8OsJ8A==
+X-Received: from jstitt-linux1.c.googlers.com ([fda3:e722:ac3:cc00:2b:ff92:c0a8:23b5])
+ (user=justinstitt job=sendgmr) by 2002:a25:dac7:0:b0:da0:3117:f35 with SMTP
+ id n190-20020a25dac7000000b00da031170f35mr28652ybf.3.1701904575576; Wed, 06
+ Dec 2023 15:16:15 -0800 (PST)
+Date: Wed, 06 Dec 2023 23:16:10 +0000
+In-Reply-To: <20231206-ethtool_puts_impl-v5-0-5a2528e17bf8@google.com>
+Precedence: bulk
+X-Mailing-List: bpf@vger.kernel.org
+List-Id: <bpf.vger.kernel.org>
+List-Subscribe: <mailto:bpf+subscribe@vger.kernel.org>
+List-Unsubscribe: <mailto:bpf+unsubscribe@vger.kernel.org>
+Mime-Version: 1.0
+References: <20231206-ethtool_puts_impl-v5-0-5a2528e17bf8@google.com>
+X-Developer-Key: i=justinstitt@google.com; a=ed25519; pk=tC3hNkJQTpNX/gLKxTNQKDmiQl6QjBNCGKJINqAdJsE=
+X-Developer-Signature: v=1; a=ed25519-sha256; t=1701904573; l=1840;
+ i=justinstitt@google.com; s=20230717; h=from:subject:message-id;
+ bh=UMdetIL2ZsPIkSodqhw2fM21NHJVjCu0lRImFuNhVoM=; b=a8rMnXfVVQ5gsxHWG4WRMwOLxZgflqXZtNuKx26vv4DwYvvCtCiYjl3f1frOjV/Ul2kaxq5g/
+ b/UOv678JKCDASVokxG5GJifAnU7/kqRxdhcwfRkrD8RUfcsmiZOfyF
+X-Mailer: b4 0.12.3
+Message-ID: <20231206-ethtool_puts_impl-v5-1-5a2528e17bf8@google.com>
+Subject: [PATCH net-next v5 1/3] ethtool: Implement ethtool_puts()
+From: justinstitt@google.com
+To: "David S. Miller" <davem@davemloft.net>, Eric Dumazet <edumazet@google.com>,
+ Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>, Shay Agroskin <shayagr@amazon.com>,
+ Arthur Kiyanovski <akiyano@amazon.com>, David Arinzon <darinzon@amazon.com>, Noam Dagan <ndagan@amazon.com>,
+ Saeed Bishara <saeedb@amazon.com>, Rasesh Mody <rmody@marvell.com>,
+ Sudarsana Kalluru <skalluru@marvell.com>, GR-Linux-NIC-Dev@marvell.com,
+ Dimitris Michailidis <dmichail@fungible.com>, Yisen Zhuang <yisen.zhuang@huawei.com>,
+ Salil Mehta <salil.mehta@huawei.com>, Jesse Brandeburg <jesse.brandeburg@intel.com>,
+ Tony Nguyen <anthony.l.nguyen@intel.com>, Louis Peens <louis.peens@corigine.com>,
+ Shannon Nelson <shannon.nelson@amd.com>, Brett Creeley <brett.creeley@amd.com>, drivers@pensando.io,
+ "K. Y. Srinivasan" <kys@microsoft.com>, Haiyang Zhang <haiyangz@microsoft.com>, Wei Liu <wei.liu@kernel.org>,
+ Dexuan Cui <decui@microsoft.com>, Ronak Doshi <doshir@vmware.com>,
+ VMware PV-Drivers Reviewers <pv-drivers@vmware.com>, Andy Whitcroft <apw@canonical.com>, Joe Perches <joe@perches.com>,
+ Dwaipayan Ray <dwaipayanray1@gmail.com>, Lukas Bulwahn <lukas.bulwahn@gmail.com>,
+ Hauke Mehrtens <hauke@hauke-m.de>, Andrew Lunn <andrew@lunn.ch>,
+ Florian Fainelli <f.fainelli@gmail.com>, Vladimir Oltean <olteanv@gmail.com>,
+ "=?utf-8?q?Ar=C4=B1n=C3=A7_=C3=9CNAL?=" <arinc.unal@arinc9.com>, Daniel Golle <daniel@makrotopia.org>,
+ Landen Chao <Landen.Chao@mediatek.com>, DENG Qingfang <dqfext@gmail.com>,
+ Sean Wang <sean.wang@mediatek.com>, Matthias Brugger <matthias.bgg@gmail.com>,
+ AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>,
+ Linus Walleij <linus.walleij@linaro.org>,
+ "=?utf-8?q?Alvin_=C5=A0ipraga?=" <alsi@bang-olufsen.dk>, Wei Fang <wei.fang@nxp.com>,
+ Shenwei Wang <shenwei.wang@nxp.com>, Clark Wang <xiaoning.wang@nxp.com>,
+ NXP Linux Team <linux-imx@nxp.com>, Lars Povlsen <lars.povlsen@microchip.com>,
+ Steen Hegelund <Steen.Hegelund@microchip.com>, Daniel Machon <daniel.machon@microchip.com>,
+ UNGLinuxDriver@microchip.com, Jiawen Wu <jiawenwu@trustnetic.com>,
+ Mengyuan Lou <mengyuanlou@net-swift.com>, Heiner Kallweit <hkallweit1@gmail.com>,
+ Russell King <linux@armlinux.org.uk>, Alexei Starovoitov <ast@kernel.org>,
+ Daniel Borkmann <daniel@iogearbox.net>, Jesper Dangaard Brouer <hawk@kernel.org>,
+ John Fastabend <john.fastabend@gmail.com>
+Cc: linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
+ Nick Desaulniers <ndesaulniers@google.com>, Nathan Chancellor <nathan@kernel.org>,
+ Kees Cook <keescook@chromium.org>, intel-wired-lan@lists.osuosl.org,
+ oss-drivers@corigine.com, linux-hyperv@vger.kernel.org,
+ linux-arm-kernel@lists.infradead.org, linux-mediatek@lists.infradead.org,
+ bpf@vger.kernel.org, Justin Stitt <justinstitt@google.com>
+Content-Type: text/plain; charset="utf-8"
+
+Use strscpy() to implement ethtool_puts().
+
+Functionally the same as ethtool_sprintf() when it's used with two
+arguments or with just "%s" format specifier.
+
+Signed-off-by: Justin Stitt <justinstitt@google.com>
+---
+ include/linux/ethtool.h | 13 +++++++++++++
+ net/ethtool/ioctl.c | 7 +++++++
+ 2 files changed, 20 insertions(+)
+
+--- a/include/linux/ethtool.h
++++ b/include/linux/ethtool.h
+@@ -843,4 +843,17 @@ int ethtool_get_phc_vclocks(struct net_d
+ * next string.
+ */
+ extern __printf(2, 3) void ethtool_sprintf(u8 **data, const char *fmt, ...);
++
++/**
++ * ethtool_puts - Write string to ethtool string data
++ * @data: Pointer to a pointer to the start of string to update
++ * @str: String to write
++ *
++ * Write string to *data without a trailing newline. Update *data
++ * to point at start of next string.
++ *
++ * Prefer this function to ethtool_sprintf() when given only
++ * two arguments or if @fmt is just "%s".
++ */
++extern void ethtool_puts(u8 **data, const char *str);
+ #endif /* _LINUX_ETHTOOL_H */
+--- a/net/ethtool/ioctl.c
++++ b/net/ethtool/ioctl.c
+@@ -1974,6 +1974,13 @@ __printf(2, 3) void ethtool_sprintf(u8 *
+ }
+ EXPORT_SYMBOL(ethtool_sprintf);
+
++void ethtool_puts(u8 **data, const char *str)
++{
++ strscpy(*data, str, ETH_GSTRING_LEN);
++ *data += ETH_GSTRING_LEN;
++}
++EXPORT_SYMBOL(ethtool_puts);
++
+ static int ethtool_phys_id(struct net_device *dev, void __user *useraddr)
+ {
+ struct ethtool_value id;